diff --git a/index.html b/index.html
index d11a4211..ff28a04f 100644
--- a/index.html
+++ b/index.html
@@ -21,8 +21,13 @@
sprite.x = 50;
sprite.y = 50;
- // const textFiled = sprite.addChild(new TextField());
- // textFiled.text = "Hello Next2D";
+ const textFiled = sprite.addChild(new TextField());
+ textFiled.x = 150;
+ textFiled.y = 100;
+ textFiled.border = true;
+ textFiled.type = "input";
+ textFiled.multiline = true;
+ textFiled.text = "Hello Next2D\nTest Mode On\n\nText Field";
const shape = sprite.addChild(new Shape());
shape
diff --git a/packages/core/src/CoreUtil.ts b/packages/core/src/CoreUtil.ts
index 0b320ffd..d9556cde 100644
--- a/packages/core/src/CoreUtil.ts
+++ b/packages/core/src/CoreUtil.ts
@@ -77,12 +77,21 @@ export const $getMainElement = (): HTMLDivElement =>
return $mainElement as NonNullable;
};
+/**
+ * @description マウス、タップ時の画面のmatrix情報
+ * Screen matrix information when mouse or tap is pressed
+ *
+ * @type {Float32Array}
+ * @protected
+ */
+export const $hitMatrix: Float32Array = new Float32Array([1, 0, 0, 1, 0, 0]);
+
/**
* @description マウス、タップがヒットしたDisplayObjectを取得します。
* Get the DisplayObject that the mouse or tap hit.
*
* @type {IPlayerHitObject}
- * @private
+ * @protected
*/
export const $hitObject: IPlayerHitObject = {
"x": 0,
diff --git a/packages/core/src/Player/service/PlayerDoubleClickEventService.ts b/packages/core/src/Player/service/PlayerDoubleClickEventService.ts
new file mode 100644
index 00000000..5c43f654
--- /dev/null
+++ b/packages/core/src/Player/service/PlayerDoubleClickEventService.ts
@@ -0,0 +1,29 @@
+import type { DisplayObject } from "@next2d/display";
+import { $stage } from "@next2d/display";
+import { PointerEvent } from "@next2d/events";
+
+/**
+ * @description ポインターのダブルタップイベントを処理します。
+ * Processes the pointer double tap event.
+ *
+ * @param {D | null} display_object
+ * @return {void}
+ * @method
+ * @protected
+ */
+export const execute = (display_object: D | null = null): void =>
+{
+ if (display_object) {
+ if (display_object.willTrigger(PointerEvent.DOUBLE_CLICK)) {
+ display_object.dispatchEvent(
+ new PointerEvent(PointerEvent.DOUBLE_CLICK)
+ );
+ }
+ } else {
+ if ($stage.willTrigger(PointerEvent.DOUBLE_CLICK)) {
+ $stage.dispatchEvent(
+ new PointerEvent(PointerEvent.DOUBLE_CLICK)
+ );
+ }
+ }
+};
\ No newline at end of file
diff --git a/packages/core/src/Player/service/PlayerPointerDownEventService.ts b/packages/core/src/Player/service/PlayerPointerDownEventService.ts
new file mode 100644
index 00000000..7dde3232
--- /dev/null
+++ b/packages/core/src/Player/service/PlayerPointerDownEventService.ts
@@ -0,0 +1,80 @@
+import type { DisplayObject } from "@next2d/display";
+import type { TextField } from "@next2d/text";
+import { $stage } from "@next2d/display";
+import { PointerEvent } from "@next2d/events";
+import {
+ $setSelectedTextField,
+ $getSelectedTextField,
+ $textArea
+} from "@next2d/text";
+import {
+ $hitObject,
+ $hitMatrix
+} from "../../CoreUtil";
+
+/**
+ * @description ポインターダウンイベントを処理します。
+ * Processes the pointer down event.
+ *
+ * @param {D | null} display_object
+ * @return {void}
+ * @method
+ * @protected
+ */
+export const execute = (
+ display_object: D | null = null,
+ page_x: number = 0,
+ page_y: number = 0
+): void => {
+
+ if (display_object) {
+
+ if (display_object.isText) {
+
+ // 選択中のTextFieldがある場合はフォーカスを解除します。
+ const selectedTextField = $getSelectedTextField();
+ if (selectedTextField
+ && selectedTextField.instanceId !== display_object.instanceId
+ ) {
+ selectedTextField.focus = false;
+ }
+
+ if (!(display_object as unknown as TextField).focus) {
+ (display_object as unknown as TextField).focus = true;
+ $setSelectedTextField(display_object as unknown as TextField);
+ }
+
+ (display_object as unknown as TextField).setFocusIndex(
+ $hitObject.x - $hitMatrix[4],
+ $hitObject.y - $hitMatrix[5]
+ );
+
+ $textArea.style.top = `${page_x}px`;
+ $textArea.style.left = `${page_y}px`;
+
+ } else {
+ // ヒットしたDisplayObjectポインターダウンイベントを発火します。
+ if (display_object.willTrigger(PointerEvent.POINTER_DOWN)) {
+ display_object.dispatchEvent(
+ new PointerEvent(PointerEvent.POINTER_DOWN)
+ );
+ }
+ }
+
+ } else {
+
+ // 選択中のTextFieldがある場合はフォーカスを解除します。
+ const selectedTextField = $getSelectedTextField();
+ if (selectedTextField) {
+ selectedTextField.focus = false;
+ $setSelectedTextField(null);
+ }
+
+ // ステージ全体のポインターダウンイベントを発火します。
+ if ($stage.willTrigger(PointerEvent.POINTER_DOWN)) {
+ $stage.dispatchEvent(
+ new PointerEvent(PointerEvent.POINTER_DOWN)
+ );
+ }
+ }
+};
\ No newline at end of file
diff --git a/packages/core/src/Player/service/PlayerPointerMoveEventService.ts b/packages/core/src/Player/service/PlayerPointerMoveEventService.ts
new file mode 100644
index 00000000..adf3950c
--- /dev/null
+++ b/packages/core/src/Player/service/PlayerPointerMoveEventService.ts
@@ -0,0 +1,34 @@
+import type { DisplayObject } from "@next2d/display";
+import type { TextField } from "@next2d/text";
+import { $player } from "../../Player";
+import {
+ $hitObject,
+ $hitMatrix
+} from "../../CoreUtil";
+
+/**
+ * @description ポインタームーブイベントを処理します。
+ * Processes the pointer move event.
+ *
+ * @param {D | null} display_object
+ * @return {void}
+ * @method
+ * @protected
+ */
+export const execute = (
+ display_object: D | null = null,
+ page_x: number = 0,
+ page_y: number = 0
+): void => {
+
+ console.log(page_x, page_y);
+ if (display_object) {
+ if (display_object.isText && $player.mouseState === "down") {
+ (display_object as unknown as TextField).setFocusIndex(
+ $hitObject.x - $hitMatrix[4],
+ $hitObject.y - $hitMatrix[5],
+ true
+ );
+ }
+ }
+};
\ No newline at end of file
diff --git a/packages/core/src/Player/service/PlayerPointerUpEventService.ts b/packages/core/src/Player/service/PlayerPointerUpEventService.ts
new file mode 100644
index 00000000..f6b13d8f
--- /dev/null
+++ b/packages/core/src/Player/service/PlayerPointerUpEventService.ts
@@ -0,0 +1,29 @@
+import type { DisplayObject } from "@next2d/display";
+import { $stage } from "@next2d/display";
+import { PointerEvent } from "@next2d/events";
+
+/**
+ * @description ポインターアップイベントを処理します。
+ * Processes the pointer up event.
+ *
+ * @param {DisplayObject | null} display_object
+ * @return {void}
+ * @method
+ * @protected
+ */
+export const execute = (display_object: D | null = null): void =>
+{
+ if (display_object) {
+ if (display_object.willTrigger(PointerEvent.POINTER_UP)) {
+ display_object.dispatchEvent(
+ new PointerEvent(PointerEvent.POINTER_UP)
+ );
+ }
+ } else {
+ if ($stage.willTrigger(PointerEvent.POINTER_UP)) {
+ $stage.dispatchEvent(
+ new PointerEvent(PointerEvent.POINTER_UP)
+ );
+ }
+ }
+};
\ No newline at end of file
diff --git a/packages/core/src/Player/usecase/PlayerHitTestUseCase.ts b/packages/core/src/Player/usecase/PlayerHitTestUseCase.ts
index 94d48a08..929ebfed 100644
--- a/packages/core/src/Player/usecase/PlayerHitTestUseCase.ts
+++ b/packages/core/src/Player/usecase/PlayerHitTestUseCase.ts
@@ -1,19 +1,19 @@
+import type { DisplayObject } from "@next2d/display";
import { $player } from "../../Player";
import { $stage } from "@next2d/display";
import { PointerEvent as Next2D_PointerEvent } from "@next2d/events";
+import { execute as playerPointerDownEventService } from "../service/PlayerPointerDownEventService";
+import { execute as playerDoubleClickEventService } from "../service/PlayerDoubleClickEventService";
+import { execute as playerPointerUpEventService } from "../service/PlayerPointerUpEventService";
+import { execute as playerPointerMoveEventService } from "../service/PlayerPointerMoveEventService";
import {
$devicePixelRatio,
$hitContext,
$getMainElement,
- $hitObject
+ $hitObject,
+ $hitMatrix
} from "../../CoreUtil";
-/**
- * @type {Float32Array}
- * @private
- */
-const $matrix: Float32Array = new Float32Array([1, 0, 0, 1, 0, 0]);
-
/**
* @type {string}
* @private
@@ -68,15 +68,15 @@ export const execute = (event: PointerEvent, canvas: HTMLCanvasElement): void =>
$hitObject.hit = null;
// hit test
- $matrix[4] = ($player.rendererWidth - $stage.stageWidth * $player.rendererScale) / 2;
- $matrix[5] = ($player.rendererHeight - $stage.stageHeight * $player.rendererScale) / 2;
+ $hitMatrix[4] = ($player.rendererWidth - $stage.stageWidth * $player.rendererScale) / 2;
+ $hitMatrix[5] = ($player.rendererHeight - $stage.stageHeight * $player.rendererScale) / 2;
// reset
$hitContext.beginPath();
$hitContext.setTransform(1, 0, 0, 1, 0, 0);
// ヒット判定
- $stage.$mouseHit($hitContext, $matrix, $hitObject);
+ $stage.$mouseHit($hitContext, $hitMatrix, $hitObject);
// ヒットしたオブジェクトがある場合
if ($hitObject.hit) {
@@ -88,21 +88,19 @@ export const execute = (event: PointerEvent, canvas: HTMLCanvasElement): void =>
canvas.style.cursor = $currentCursor = $hitObject.pointer;
}
- switch (true) {
-
- // ヒットしたオブジェクトがある場合
- case $hitObject.hit === null:
- break;
+ const hitDisplayObject = $hitObject.hit as DisplayObject | null;
+ switch (event.type) {
- // ヒットしたオブジェクトがない場合
- default:
+ case Next2D_PointerEvent.POINTER_MOVE:
+ playerPointerMoveEventService(
+ hitDisplayObject, event.pageX, event.pageY
+ );
break;
- }
-
- switch (event.type) {
case Next2D_PointerEvent.POINTER_DOWN:
+
clearTimeout($timerId);
+
if (!$wait) {
// 初回のタップであればダブルタップを待機モードに変更
@@ -114,18 +112,24 @@ export const execute = (event: PointerEvent, canvas: HTMLCanvasElement): void =>
$wait = false;
}, 300);
+ playerPointerDownEventService(
+ hitDisplayObject,
+ event.pageX,
+ event.pageY
+ );
+
} else {
// ダブルタップを終了
$wait = false;
+ playerDoubleClickEventService(hitDisplayObject);
+
}
break;
case Next2D_PointerEvent.POINTER_UP:
- break;
-
- case Next2D_PointerEvent.POINTER_MOVE:
+ playerPointerUpEventService(hitDisplayObject);
break;
}
diff --git a/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts b/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts
index ca00d12a..db9d8760 100644
--- a/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts
+++ b/packages/display/src/TextField/usecase/TextFieldGenerateRenderQueueUseCase.ts
@@ -49,7 +49,7 @@ export const execute = (
point_y: number
): void => {
- if (!text_field.visible || !text_field.text) {
+ if (!text_field.visible) {
render_queue.push(0);
return ;
}
@@ -253,6 +253,7 @@ export const execute = (
render_queue.push(Math.abs(text_field.xMax - text_field.xMin));
render_queue.push(Math.abs(text_field.yMax - text_field.yMin));
render_queue.push(text_field.focusIndex);
+ render_queue.push(text_field.selectIndex);
render_queue.push(+text_field.focusVisible);
render_queue.push(text_field.thickness);
render_queue.push(text_field.thicknessColor);
diff --git a/packages/renderer/src/TextField/usecase/TextFieldDrawOffscreenCanvasUseCase.ts b/packages/renderer/src/TextField/usecase/TextFieldDrawOffscreenCanvasUseCase.ts
index 83ddaab7..2681ef9c 100644
--- a/packages/renderer/src/TextField/usecase/TextFieldDrawOffscreenCanvasUseCase.ts
+++ b/packages/renderer/src/TextField/usecase/TextFieldDrawOffscreenCanvasUseCase.ts
@@ -87,6 +87,76 @@ export const execute = (
context.setTransform(x_scale, 0, 0, y_scale, tx * x_scale, ty * y_scale);
context.beginPath();
+ if (text_setting.selectIndex > -1
+ && text_setting.focusIndex > -1
+ ) {
+ const range = text_data.textTable.length - 1;
+
+ let minIndex = 0;
+ let maxIndex = 0;
+ if (text_setting.focusIndex <= text_setting.selectIndex) {
+ minIndex = Math.min(text_setting.focusIndex, range);
+ maxIndex = Math.min(text_setting.selectIndex, range);
+ } else {
+ minIndex = Math.min(text_setting.selectIndex, range);
+ maxIndex = Math.min(text_setting.focusIndex - 1, range);
+ }
+
+ const textObject = text_data.textTable[minIndex];
+ const lineObject = text_data.lineTable[textObject.line];
+ const offsetAlign = textFiledGetAlignOffsetService(
+ text_data, lineObject, text_setting
+ );
+
+ let x = 0;
+ if (minIndex && textObject.mode === "text") {
+ let idx = minIndex;
+ while (idx) {
+ const textObject = text_data.textTable[--idx];
+ if (textObject.mode !== "text") {
+ break;
+ }
+ x += textObject.w;
+ }
+ }
+
+ context.fillStyle = "#b4d7ff";
+
+ let w = 0;
+ for (let idx = minIndex; idx <= maxIndex; ++idx) {
+
+ const textObject = text_data.textTable[idx];
+ if (textObject.mode === "text") {
+
+ w += textObject.w;
+
+ if (idx !== maxIndex) {
+ continue;
+ }
+ }
+
+ let y = 0;
+ const line = textObject.mode === "text"
+ ? textObject.line
+ : textObject.line - 1;
+
+ for (let idx = 0; idx < line; ++idx) {
+ y += text_data.heightTable[idx];
+ }
+
+ context.beginPath();
+ context.rect(
+ x, y,
+ w + offsetAlign,
+ text_data.heightTable[line]
+ );
+ context.fill();
+
+ x = 0;
+ w = 0;
+ }
+ }
+
const rawWidth = text_setting.rawWidth;
let scrollX = 0;
if (text_setting.scrollX > 0) {
@@ -103,22 +173,6 @@ export const execute = (
}
const limitHeight = rawHeight + scrollY;
- if (!text_data.textTable.length
- && text_setting.focusIndex > -1
- && text_setting.focusVisible
- ) {
-
- const color = $intToRGBA(text_setting.defaultColor);
-
- context.strokeStyle = `rgba(${color.R},${color.G},${color.B},${color.A})`;
- context.beginPath();
- context.moveTo(0, 0);
- context.lineTo(0, 0 + (text_setting.defaultSize || 12));
- context.stroke();
-
- return canvas;
- }
-
// setup
let offsetWidth = 0;
let offsetHeight = 0;
@@ -172,7 +226,9 @@ export const execute = (
context.fillStyle = `rgba(${color.R},${color.G},${color.B},${color.A})`;
// focus line
- if (text_setting.focusVisible && text_setting.focusIndex === idx) {
+ if (text_setting.focusVisible
+ && text_setting.focusIndex === idx
+ ) {
const x = offsetWidth + offsetAlign + 0.1;
let line = textObject.line;
@@ -185,12 +241,11 @@ export const execute = (
? textObject.h
: text_data.ascentTable[line - 1];
- if (line > 0 && !text_data.ascentTable[line - 1]) {
- line = textObject.line;
- y = text_data.ascentTable[line - 1];
- } else {
+ if (line > 0) {
line = textObject.line - 1;
y = text_data.ascentTable[line];
+ } else {
+ y = textObject.h;
}
}
@@ -274,6 +329,28 @@ export const execute = (
}
}
+ if (text_setting.focusVisible
+ && text_setting.focusIndex >= text_data.textTable.length
+ ) {
+ const textObject = text_data.textTable[text_setting.focusIndex - 1];
+ if (textObject) {
+ const color = $intToRGBA(textObject.textFormat.color || 0);
+ context.strokeStyle = `rgba(${color.R},${color.G},${color.B},${color.A})`;
+
+ const x = offsetWidth + offsetAlign + 0.1;
+ const y = offsetHeight + verticalAlign;
+
+ context.beginPath();
+ if (textObject.mode === "text") {
+ context.moveTo(x, y - textObject.y);
+ } else {
+ context.moveTo(x, y + textObject.h);
+ }
+ context.lineTo(x, y);
+ context.stroke();
+ }
+ }
+
context.restore();
return canvas;
diff --git a/packages/renderer/src/TextField/usecase/TextFieldRenderUseCase.ts b/packages/renderer/src/TextField/usecase/TextFieldRenderUseCase.ts
index 9e84219d..ad5f620a 100644
--- a/packages/renderer/src/TextField/usecase/TextFieldRenderUseCase.ts
+++ b/packages/renderer/src/TextField/usecase/TextFieldRenderUseCase.ts
@@ -125,6 +125,7 @@ export const execute = (render_queue: Float32Array, index: number): number =>
"rawWidth": render_queue[index++],
"rawHeight": render_queue[index++],
"focusIndex": render_queue[index++],
+ "selectIndex": render_queue[index++],
"focusVisible": Boolean(render_queue[index++]),
"thickness": render_queue[index++],
"thicknessColor": render_queue[index++],
diff --git a/packages/renderer/src/interface/ITextSetting.ts b/packages/renderer/src/interface/ITextSetting.ts
index 60e4b225..f2864db9 100644
--- a/packages/renderer/src/interface/ITextSetting.ts
+++ b/packages/renderer/src/interface/ITextSetting.ts
@@ -12,6 +12,7 @@ export interface ITextSetting {
rawHeight: number;
autoSize: ITextFieldAutoSize;
focusIndex: number;
+ selectIndex: number;
focusVisible: boolean;
thickness: number;
thicknessColor: number;
diff --git a/packages/text/src/TextField.ts b/packages/text/src/TextField.ts
index 8cf57d86..05a42992 100644
--- a/packages/text/src/TextField.ts
+++ b/packages/text/src/TextField.ts
@@ -19,6 +19,7 @@ import { execute as textFieldReplaceTextUseCase } from "./TextField/usecase/Text
import { execute as textFieldCopyUseCase } from "./TextField/usecase/TextFieldCopyUseCase";
import { execute as textFieldInsertTextUseCase } from "./TextField/usecase/TextFieldInsertTextUseCase";
import { execute as textFieldApplyChangesService } from "./TextField/service/TextFieldApplyChangesService";
+import { execute as textFieldSetFocusIndexUseCase } from "./TextField/usecase/TextFieldSetFocusIndexUseCase";
import {
$clamp,
$toColorInt
@@ -957,7 +958,7 @@ export class TextField extends InteractiveObject
set text (text: string)
{
text = `${text}`;
- if (text === this._$text) {
+ if (text !== "" && text === this._$text) {
return ;
}
@@ -1220,293 +1221,22 @@ export class TextField extends InteractiveObject
textFieldInsertTextUseCase(this, this._$copyText);
}
- // /**
- // * @param {number} stage_x
- // * @param {number} stage_y
- // * @return {void}
- // * @method
- // * @private
- // */
- // _$setIndex (stage_x: number, stage_y: number): void
- // {
- // if (this.type !== "input") {
- // return ;
- // }
-
- // const textData: TextData = textFieldGetTextDataUseCase(this);
- // if (!textData.textTable.length) {
- // this.focusIndex = 0;
- // this.selectIndex = -1;
- // this.setBlinkingTimer();
- // return ;
- // }
-
- // const width: number = this.width;
- // const height: number = this.height;
-
- // let tx: number = 0;
- // if (this._$scrollX > 0) {
- // tx += this._$scrollX * (this.textWidth - width) / width;
- // }
-
- // let ty: number = 0;
- // if (this._$scrollY) {
- // ty += this._$scrollY * (this.textHeight - height) / height;
- // }
-
- // const eventType: string = $getEventType();
- // const point: Point = this.globalToLocal(new Point(stage_x, stage_y));
- // const x: number = point.x + tx;
- // const y: number = point.y + ty;
-
- // let w: number = 2;
- // let yMin: number = 2;
- // let yMax: number = yMin + textData.heightTable[0];
- // for (let idx: number = 1; idx < textData.textTable.length; ++idx) {
-
- // const textObject: ITextObject = textData.textTable[idx];
-
- // switch (textObject.mode) {
-
- // case "break":
- // case "wrap":
- // if (x > w && y > yMin
- // && yMax > y
- // && width > x
- // ) {
- // const index: number = idx;
- // switch (eventType) {
-
- // case $TOUCH_MOVE:
- // case $MOUSE_MOVE:
- // if (this._$selectIndex !== index && this._$focusIndex === index) {
- // this._$selectIndex = index;
-
- // if (this._$focusIndex !== index) {
- // this._$focusVisible = false;
- // $clearTimeout(this._$timerId);
-
- // this._$doChanged();
- // $doUpdated();
- // }
- // }
- // break;
-
- // default:
- // if (this._$focusIndex !== index || this._$selectIndex > -1) {
- // this._$focusIndex = index;
- // this._$selectIndex = -1;
- // this.setBlinkingTimer();
- // }
- // break;
- // }
-
- // return ;
- // }
-
- // w = 2;
- // yMin += textData.heightTable[textObject.line - 1];
- // yMax = yMin + textData.heightTable[textObject.line];
- // break;
-
- // case "text":
- // if (idx === textData.textTable.length - 1
- // && x > w && y > yMin && yMax > y
- // && width > x
- // ) {
-
- // const index: number = textData.textTable.length;
- // switch (eventType) {
-
- // case $TOUCH_MOVE:
- // case $MOUSE_MOVE:
- // if (this._$selectIndex !== index) {
- // this._$selectIndex = index;
-
- // if (this._$focusIndex !== index) {
- // this._$focusVisible = false;
- // $clearTimeout(this._$timerId);
-
- // this._$doChanged();
- // $doUpdated();
- // }
- // }
- // break;
-
- // default:
- // if (this._$focusIndex !== index || this._$selectIndex > -1) {
- // this._$focusIndex = index;
- // this._$selectIndex = -1;
- // this.setBlinkingTimer();
- // }
- // break;
-
- // }
-
- // return ;
- // }
-
- // if (x > w && y > yMin
- // && yMax > y
- // && w + textObject.w > x
- // ) {
-
- // let index: number = idx;
- // switch (eventType) {
- // case $TOUCH_MOVE:
- // case $MOUSE_MOVE:
-
- // if (this._$focusIndex > index) { // left
- // if (this._$focusIndex === index + 1) {
- // if (w + textObject.w / 2 < x) {
- // index = -1;
- // }
- // } else {
- // if (w + textObject.w / 2 < x) {
- // index += 1;
- // }
- // }
- // } else { // right
- // if (this._$focusIndex === index) {
- // if (w + textObject.w / 2 > x) {
- // index = -1;
- // }
- // } else {
- // if (w + textObject.w / 2 > x) {
- // index -= 1;
- // }
- // }
- // }
-
- // if (this._$selectIndex !== index) {
- // this._$selectIndex = index;
-
- // if (this._$selectIndex > -1) {
- // this._$focusVisible = false;
- // $clearTimeout(this._$timerId);
- // }
-
- // this._$doChanged();
- // $doUpdated();
- // }
- // break;
-
- // default:
-
- // if (w + textObject.w / 2 < x) {
- // const textObject: ITextObject = textData.textTable[index + 1];
- // if (!textObject || textObject.mode === "text") {
- // index += 1;
- // }
- // }
-
- // if (this._$focusIndex !== index || this._$selectIndex > -1) {
- // this._$focusIndex = index;
- // this._$selectIndex = -1;
- // this.setBlinkingTimer();
- // }
- // break;
-
- // }
- // return ;
- // }
-
- // w += textObject.w;
- // break;
-
- // default:
- // break;
-
- // }
- // }
-
- // switch (eventType) {
-
- // case $TOUCH_MOVE:
- // case $MOUSE_MOVE:
- // // reset
- // this._$focusIndex = -1;
- // this._$selectIndex = -1;
- // break;
-
- // default:
- // this._$focusIndex = textData.textTable.length;
- // this._$selectIndex = -1;
- // this.setBlinkingTimer();
- // break;
-
- // }
- // }
-
- // /**
- // * @param {CanvasRenderingContext2D} context
- // * @param {Float32Array} matrix
- // * @param {object} options
- // * @return {boolean}
- // * @method
- // * @private
- // */
- // _$mouseHit (
- // context: CanvasRenderingContext2D,
- // matrix: Float32Array,
- // options: PlayerHitObjectImpl
- // ): boolean {
-
- // if (!this._$visible) {
- // return false;
- // }
-
- // return this._$hit(context, matrix, options);
- // }
-
- // /**
- // * @param {CanvasRenderingContext2D} context
- // * @param {Float32Array} matrix
- // * @param {object} options
- // * @return {boolean}
- // * @method
- // * @private
- // */
- // _$hit (
- // context: CanvasRenderingContext2D,
- // matrix: Float32Array,
- // options: PlayerHitObjectImpl
- // ): boolean {
-
- // let multiMatrix: Float32Array = matrix;
- // const rawMatrix: Float32Array = this._$transform._$rawMatrix();
- // if (rawMatrix[0] !== 1 || rawMatrix[1] !== 0
- // || rawMatrix[2] !== 0 || rawMatrix[3] !== 1
- // || rawMatrix[4] !== 0 || rawMatrix[5] !== 0
- // ) {
- // multiMatrix = $multiplicationMatrix(matrix, rawMatrix);
- // }
-
- // const baseBounds: IBounds = this._$getBounds(null);
-
- // const bounds: IBounds = $boundsMatrix(baseBounds, multiMatrix);
- // const xMax = +bounds.xMax;
- // const xMin = +bounds.xMin;
- // const yMax = +bounds.yMax;
- // const yMin = +bounds.yMin;
- // $poolBoundsObject(bounds);
- // $poolBoundsObject(baseBounds);
-
- // const width: number = Math.ceil(Math.abs(xMax - xMin));
- // const height: number = Math.ceil(Math.abs(yMax - yMin));
-
- // context.setTransform(1, 0, 0, 1, xMin, yMin);
- // context.beginPath();
- // context.moveTo(0, 0);
- // context.lineTo(width, 0);
- // context.lineTo(width, height);
- // context.lineTo(0, height);
- // context.lineTo(0, 0);
-
- // if (multiMatrix !== matrix) {
- // $poolFloat32Array6(multiMatrix);
- // }
-
- // return context.isPointInPath(options.x, options.y);
- // }
-}
+ /**
+ * @description テキストフィールドのフォーカス位置を設定します。
+ * Sets the focus position of the text field.
+ *
+ * @param {number} stage_x
+ * @param {number} stage_y
+ * @param {boolean} [selected=false]
+ * @return {void}
+ * @method
+ * @private
+ */
+ setFocusIndex (
+ stage_x: number,
+ stage_y: number,
+ selected: boolean = false
+ ): void {
+ textFieldSetFocusIndexUseCase(this, stage_x, stage_y, selected);
+ }
+}
\ No newline at end of file
diff --git a/packages/text/src/TextField/service/TextFieldBlinkingClearTimeoutService.ts b/packages/text/src/TextField/service/TextFieldBlinkingClearTimeoutService.ts
index e356df5d..58edd6c8 100644
--- a/packages/text/src/TextField/service/TextFieldBlinkingClearTimeoutService.ts
+++ b/packages/text/src/TextField/service/TextFieldBlinkingClearTimeoutService.ts
@@ -1,4 +1,7 @@
-import { $getBlinkingTimerId } from "../../TextUtil";
+import {
+ $getBlinkingTimerId,
+ $setBlinkingTimerId
+} from "../../TextUtil";
/**
* @description テキストの点滅のタイマーをクリアします。
@@ -10,5 +13,9 @@ import { $getBlinkingTimerId } from "../../TextUtil";
*/
export const execute = (): void =>
{
- clearTimeout($getBlinkingTimerId());
+ const timerId = $getBlinkingTimerId();
+ if (timerId !== undefined) {
+ clearTimeout(timerId);
+ }
+ $setBlinkingTimerId(void 0);
};
\ No newline at end of file
diff --git a/packages/text/src/TextField/usecase/TextFieldSetFocusIndexUseCase.ts b/packages/text/src/TextField/usecase/TextFieldSetFocusIndexUseCase.ts
new file mode 100644
index 00000000..8a7018c7
--- /dev/null
+++ b/packages/text/src/TextField/usecase/TextFieldSetFocusIndexUseCase.ts
@@ -0,0 +1,209 @@
+import type { TextField } from "../../TextField";
+import { Point } from "@next2d/geom";
+import { execute as textFieldGetTextDataUseCase } from "./TextFieldGetTextDataUseCase";
+import { execute as textFieldBlinkingUseCase } from "./TextFieldBlinkingUseCase";
+import { execute as textFieldApplyChangesService } from "../service/TextFieldApplyChangesService";
+import { execute as textFieldBlinkingClearTimeoutService } from "../service/TextFieldBlinkingClearTimeoutService";
+import { $getBlinkingTimerId } from "../../TextUtil";
+
+/**
+ * @description テキストフィールドのフォーカスしてるテキスト位置(インデックス)を設定します。
+ * Set the text position (index) that the text field is focusing on.
+ *
+ * @param {TextField} text_field
+ * @param {number} stage_x
+ * @param {number} stage_y
+ * @param {boolean} [selected=false]
+ * @return {void}
+ * @method
+ * @protected
+ */
+export const execute = (
+ text_field: TextField,
+ stage_x: number,
+ stage_y: number,
+ selected: boolean = false
+): void => {
+
+ if (text_field.type !== "input") {
+ return ;
+ }
+
+ const textData = textFieldGetTextDataUseCase(text_field);
+ if (2 > textData.textTable.length) {
+ text_field.focusIndex = 1;
+ text_field.selectIndex = -1;
+
+ if ($getBlinkingTimerId() === undefined) {
+ textFieldBlinkingUseCase(text_field);
+ }
+
+ return ;
+ }
+
+ const width = text_field.width;
+ const height = text_field.height;
+
+ let tx = 0;
+ if (text_field.scrollX > 0) {
+ tx += text_field.scrollX * (text_field.textWidth - width) / width;
+ }
+
+ let ty = 0;
+ if (text_field.scrollY > 0) {
+ ty += text_field.scrollY * (text_field.textHeight - height) / height;
+ }
+
+ const point = text_field.globalToLocal(new Point(stage_x, stage_y));
+ const x = point.x + tx;
+ const y = point.y + ty;
+
+ let w = 2;
+ let yMin = 2;
+ let yMax = yMin + textData.heightTable[0];
+ for (let idx = 1; idx < textData.textTable.length; ++idx) {
+
+ const textObject = textData.textTable[idx];
+ if (!textObject) {
+ continue;
+ }
+
+ switch (textObject.mode) {
+
+ case "break":
+ case "wrap":
+ if (x > w && y > yMin
+ && yMax > y
+ && width > x
+ ) {
+ const index = idx;
+ if (selected) {
+ if (text_field.selectIndex !== index
+ && text_field.focusIndex === index
+ ) {
+ text_field.selectIndex = index;
+
+ if (text_field.focusIndex !== index) {
+ text_field.focusVisible = false;
+ textFieldBlinkingClearTimeoutService();
+ textFieldApplyChangesService(text_field);
+ }
+ }
+ } else {
+ if (text_field.focusIndex !== index || text_field.selectIndex > -1) {
+ text_field.focusIndex = index;
+ text_field.selectIndex = -1;
+ textFieldApplyChangesService(text_field);
+ }
+ }
+
+ return ;
+ }
+
+ w = 2;
+ yMin += textData.heightTable[textObject.line - 1];
+ yMax = yMin + textData.heightTable[textObject.line];
+ break;
+
+ case "text":
+ if (x > w && y > yMin
+ && yMax > y
+ && w + textObject.w > x
+ ) {
+ let index = idx;
+ if (selected) {
+ if (text_field.focusIndex > index) {
+ // left
+ if (text_field.focusIndex === index + 1) {
+ if (w + textObject.w / 2 < x) {
+ index = -1;
+ }
+ } else {
+ if (w + textObject.w / 2 < x) {
+ index += 1;
+ }
+ }
+ } else {
+ // right
+ if (text_field.focusIndex === index) {
+ if (w + textObject.w / 2 > x) {
+ index = -1;
+ }
+ } else {
+ if (w + textObject.w / 2 > x) {
+ index -= 1;
+ }
+ }
+ }
+
+ if (text_field.selectIndex !== index) {
+ text_field.selectIndex = index;
+
+ if (text_field.selectIndex > -1) {
+ text_field.focusVisible = false;
+ textFieldBlinkingClearTimeoutService();
+ }
+
+ textFieldApplyChangesService(text_field);
+ }
+ } else {
+ if (w + textObject.w / 2 < x) {
+ const textObject = textData.textTable[index + 1];
+ if (!textObject || textObject.mode === "text") {
+ index += 1;
+ }
+ }
+
+ if (text_field.focusIndex !== index || text_field.selectIndex > -1) {
+ text_field.focusIndex = index;
+ text_field.selectIndex = -1;
+ textFieldApplyChangesService(text_field);
+ }
+ }
+ return ;
+ }
+
+ if (idx === textData.textTable.length - 1
+ && x > w && y > yMin && yMax > y
+ && width > x
+ ) {
+ const index = textData.textTable.length;
+ if (selected) {
+ if (text_field.selectIndex !== index) {
+ text_field.selectIndex = index;
+
+ if (text_field.focusIndex !== index) {
+ text_field.focusVisible = false;
+ textFieldBlinkingClearTimeoutService();
+ textFieldApplyChangesService(text_field);
+ }
+ }
+ } else {
+ if (text_field.focusIndex !== index || text_field.selectIndex > -1) {
+ text_field.focusIndex = index;
+ text_field.selectIndex = -1;
+ textFieldApplyChangesService(text_field);
+ }
+ }
+ return ;
+ }
+
+ w += textObject.w;
+ break;
+
+ default:
+ break;
+
+ }
+
+ if (!selected) {
+ text_field.focusIndex = textData.textTable.length;
+ text_field.selectIndex = -1;
+ if ($getBlinkingTimerId() === undefined) {
+ textFieldBlinkingUseCase(text_field);
+ } else {
+ textFieldApplyChangesService(text_field);
+ }
+ }
+ }
+};
\ No newline at end of file
diff --git a/packages/text/src/TextField/usecase/TextFieldSetFocusUseCase.ts b/packages/text/src/TextField/usecase/TextFieldSetFocusUseCase.ts
index 7dd90bb9..932161dd 100644
--- a/packages/text/src/TextField/usecase/TextFieldSetFocusUseCase.ts
+++ b/packages/text/src/TextField/usecase/TextFieldSetFocusUseCase.ts
@@ -1,8 +1,12 @@
import type { TextField } from "../../TextField";
import { FocusEvent } from "@next2d/events";
-import { $textArea } from "../../TextUtil";
import { execute as textFieldApplyChangesService } from "../service/TextFieldApplyChangesService";
import { execute as textFieldBlinkingClearTimeoutService } from "../service/TextFieldBlinkingClearTimeoutService";
+import { execute as textFieldBlinkingUseCase } from "./TextFieldBlinkingUseCase";
+import {
+ $textArea,
+ $getBlinkingTimerId
+} from "../../TextUtil";
/**
* @description フォーカス
@@ -23,7 +27,13 @@ export const execute = (text_field: TextField, name: string): void =>
if (text_field.focus) {
$textArea.focus();
- // todo set stage x,y
+ if ($getBlinkingTimerId() === undefined) {
+ if (text_field.focusIndex === -1) {
+ text_field.focusIndex = 1;
+ text_field.selectIndex = -1;
+ }
+ textFieldBlinkingUseCase(text_field);
+ }
} else {
// params reset
diff --git a/packages/text/src/TextUtil.ts b/packages/text/src/TextUtil.ts
index 4b09af4b..259b2215 100644
--- a/packages/text/src/TextUtil.ts
+++ b/packages/text/src/TextUtil.ts
@@ -183,7 +183,7 @@ export const $clamp = (
* @type {NodeJS.Timeout}
* @private
*/
-let $timerId: NodeJS.Timeout;
+let $timerId: NodeJS.Timeout | void;
/**
* @description テキスト点滅のタイマーIDを返却
@@ -192,7 +192,7 @@ let $timerId: NodeJS.Timeout;
* @return {NodeJS.Timeout}
* @protected
*/
-export const $getBlinkingTimerId = (): NodeJS.Timeout =>
+export const $getBlinkingTimerId = (): NodeJS.Timeout | void =>
{
return $timerId;
};
@@ -205,7 +205,7 @@ export const $getBlinkingTimerId = (): NodeJS.Timeout =>
* @return {void}
* @protected
*/
-export const $setBlinkingTimerId = (timer_id: NodeJS.Timeout): void =>
+export const $setBlinkingTimerId = (timer_id: NodeJS.Timeout | void): void =>
{
$timerId = timer_id;
};
\ No newline at end of file