From cce7231b11aef87bfd6b37b922eb459b4db85922 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Tue, 18 Jun 2024 21:59:41 -0400 Subject: [PATCH 01/48] Initial text input implementation --- flixel/FlxG.hx | 13 +- flixel/system/frontEnds/InputTextFrontEnd.hx | 204 +++++++ flixel/text/FlxInputText.hx | 579 +++++++++++++++++++ 3 files changed, 791 insertions(+), 5 deletions(-) create mode 100644 flixel/system/frontEnds/InputTextFrontEnd.hx create mode 100644 flixel/text/FlxInputText.hx diff --git a/flixel/FlxG.hx b/flixel/FlxG.hx index e855617a4c..22b8daf8a8 100644 --- a/flixel/FlxG.hx +++ b/flixel/FlxG.hx @@ -1,10 +1,5 @@ package flixel; -import openfl.Lib; -import openfl.display.DisplayObject; -import openfl.display.Stage; -import openfl.display.StageDisplayState; -import openfl.net.URLRequest; import flixel.effects.postprocess.PostProcess; import flixel.math.FlxMath; import flixel.math.FlxRandom; @@ -17,6 +12,7 @@ import flixel.system.frontEnds.CameraFrontEnd; import flixel.system.frontEnds.ConsoleFrontEnd; import flixel.system.frontEnds.DebuggerFrontEnd; import flixel.system.frontEnds.InputFrontEnd; +import flixel.system.frontEnds.InputTextFrontEnd; import flixel.system.frontEnds.LogFrontEnd; import flixel.system.frontEnds.PluginFrontEnd; import flixel.system.frontEnds.SignalFrontEnd; @@ -28,6 +24,11 @@ import flixel.system.scaleModes.RatioScaleMode; import flixel.util.FlxCollision; import flixel.util.FlxSave; import flixel.util.typeLimit.NextState; +import openfl.Lib; +import openfl.display.DisplayObject; +import openfl.display.Stage; +import openfl.display.StageDisplayState; +import openfl.net.URLRequest; #if FLX_TOUCH import flixel.input.touch.FlxTouchManager; #end @@ -320,6 +321,8 @@ class FlxG */ public static var plugins(default, null):PluginFrontEnd; + public static var inputText(default, null):InputTextFrontEnd = new InputTextFrontEnd(); + public static var initialWidth(default, null):Int = 0; public static var initialHeight(default, null):Int = 0; diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx new file mode 100644 index 0000000000..9d9b35a0f1 --- /dev/null +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -0,0 +1,204 @@ +package flixel.system.frontEnds; + +import lime.ui.KeyCode; +import lime.ui.KeyModifier; + +class InputTextFrontEnd +{ + public var focus(default, set):Null; + + var _registeredInputTexts:Array = []; + + public function new() {} + + public function registerInputText(input:IFlxInputText) + { + if (!_registeredInputTexts.contains(input)) + { + _registeredInputTexts.push(input); + + if (!FlxG.stage.window.onTextInput.has(onTextInput)) + { + FlxG.stage.window.onTextInput.add(onTextInput); + // Higher priority is needed here because FlxKeyboard will cancel + // the event for key codes in `preventDefaultKeys`. + FlxG.stage.window.onKeyDown.add(onKeyDown, false, 1000); + } + } + } + + public function unregisterInputText(input:IFlxInputText) + { + if (_registeredInputTexts.contains(input)) + { + _registeredInputTexts.remove(input); + + if (_registeredInputTexts.length == 0 && FlxG.stage.window.onTextInput.has(onTextInput)) + { + FlxG.stage.window.onTextInput.remove(onTextInput); + FlxG.stage.window.onKeyDown.remove(onKeyDown); + } + } + } + + function onTextInput(text:String) + { + if (focus != null) + { + focus.dispatchTypingAction(ADD_TEXT(text)); + } + } + + function onKeyDown(key:KeyCode, modifier:KeyModifier) + { + // Taken from OpenFL's `TextField` + var modifierPressed = #if mac modifier.metaKey #elseif js(modifier.metaKey || modifier.ctrlKey) #else (modifier.ctrlKey && !modifier.altKey) #end; + + switch (key) + { + case RETURN, NUMPAD_ENTER: + focus.dispatchTypingAction(COMMAND(NEW_LINE)); + case BACKSPACE: + focus.dispatchTypingAction(COMMAND(DELETE_LEFT)); + case DELETE: + focus.dispatchTypingAction(COMMAND(DELETE_RIGHT)); + case LEFT: + if (modifierPressed) + { + focus.dispatchTypingAction(MOVE_CURSOR(PREVIOUS_LINE, modifier.shiftKey)); + } + else + { + focus.dispatchTypingAction(MOVE_CURSOR(LEFT, modifier.shiftKey)); + } + case RIGHT: + if (modifierPressed) + { + focus.dispatchTypingAction(MOVE_CURSOR(NEXT_LINE, modifier.shiftKey)); + } + else + { + focus.dispatchTypingAction(MOVE_CURSOR(RIGHT, modifier.shiftKey)); + } + case UP: + if (modifierPressed) + { + focus.dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); + } + else + { + focus.dispatchTypingAction(MOVE_CURSOR(UP, modifier.shiftKey)); + } + case DOWN: + if (modifierPressed) + { + focus.dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); + } + else + { + focus.dispatchTypingAction(MOVE_CURSOR(DOWN, modifier.shiftKey)); + } + case HOME: + if (modifierPressed) + { + focus.dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); + } + else + { + focus.dispatchTypingAction(MOVE_CURSOR(LINE_BEGINNING, modifier.shiftKey)); + } + case END: + if (modifierPressed) + { + focus.dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); + } + else + { + focus.dispatchTypingAction(MOVE_CURSOR(LINE_END, modifier.shiftKey)); + } + case C: + if (modifierPressed) + { + focus.dispatchTypingAction(COMMAND(COPY)); + } + case X: + if (modifierPressed) + { + focus.dispatchTypingAction(COMMAND(CUT)); + } + #if !js + case V: + if (modifierPressed) + { + focus.dispatchTypingAction(COMMAND(PASTE)); + } + #end + case A: + if (modifierPressed) + { + focus.dispatchTypingAction(COMMAND(SELECT_ALL)); + } + default: + } + } + + function set_focus(value:IFlxInputText) + { + if (focus != value) + { + if (focus != null) + { + focus.hasFocus = false; + } + + focus = value; + + if (focus != null) + { + focus.hasFocus = true; + } + + FlxG.stage.window.textInputEnabled = (focus != null); + } + + return value; + } +} + +interface IFlxInputText +{ + public var hasFocus(default, set):Bool; + public function dispatchTypingAction(action:TypingAction):Void; +} + +enum TypingAction +{ + ADD_TEXT(text:String); + MOVE_CURSOR(type:MoveCursorAction, shiftKey:Bool); + COMMAND(cmd:TypingCommand); +} + +enum MoveCursorAction +{ + LEFT; + RIGHT; + UP; + DOWN; + HOME; + END; + LINE_BEGINNING; + LINE_END; + PREVIOUS_LINE; + NEXT_LINE; +} + +enum TypingCommand +{ + NEW_LINE; + DELETE_LEFT; + DELETE_RIGHT; + COPY; + CUT; + PASTE; + SELECT_ALL; +} \ No newline at end of file diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx new file mode 100644 index 0000000000..d15642a9ae --- /dev/null +++ b/flixel/text/FlxInputText.hx @@ -0,0 +1,579 @@ +package flixel.text; + +import flixel.math.FlxMath; +import flixel.system.frontEnds.InputTextFrontEnd; +import flixel.util.FlxColor; +import lime.system.Clipboard; +import openfl.utils.QName; + +class FlxInputText extends FlxText implements IFlxInputText +{ + static inline var GUTTER:Int = 2; + + public var caretColor(default, set):FlxColor = FlxColor.WHITE; + + public var caretIndex(default, set):Int = -1; + + public var caretWidth(default, set):Int = 1; + + public var hasFocus(default, set):Bool = false; + + public var maxLength(default, set):Int = 0; + + public var passwordMode(get, set):Bool; + + public var selectionBeginIndex(get, never):Int; + + public var selectionEndIndex(get, never):Int; + + var caret:FlxSprite; + + var selectionIndex:Int = -1; + + public function new(x:Float = 0, y:Float = 0, fieldWidth:Float = 0, ?text:String, size:Int = 8, embeddedFont:Bool = true) + { + super(x, y, fieldWidth, text, size, embeddedFont); + + // If the text field's type isn't INPUT and there's a new line at the end + // of the text, it won't be counted for in `numLines` + textField.type = INPUT; + + caret = new FlxSprite(); + caret.moves = caret.active = caret.visible = false; + regenCaret(); + updateCaretPosition(); + + FlxG.inputText.registerInputText(this); + } + + override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (visible) + { + if (FlxG.mouse.justPressed) + { + if (FlxG.mouse.overlaps(this)) + { + hasFocus = true; + } + else + { + hasFocus = false; + } + } + } + } + + override function draw():Void + { + super.draw(); + + drawSprite(caret); + } + + override function destroy():Void + { + FlxG.inputText.unregisterInputText(this); + + super.destroy(); + } + + public function dispatchTypingAction(action:TypingAction):Void + { + switch (action) + { + case ADD_TEXT(text): + replaceSelectedText(text); + case MOVE_CURSOR(type, shiftKey): + moveCursor(type, shiftKey); + case COMMAND(cmd): + runCommand(cmd); + } + } + + function drawSprite(sprite:FlxSprite):Void + { + if (sprite != null && sprite.visible) + { + sprite.scrollFactor.copyFrom(scrollFactor); + sprite._cameras = _cameras; + sprite.draw(); + } + } + + function getCharIndexOnDifferentLine(charIndex:Int, lineIndex:Int):Int + { + if (charIndex < 0 || charIndex > text.length) + return -1; + if (lineIndex < 0 || lineIndex > textField.numLines - 1) + return -1; + + var x = 0.0; + var charBoundaries = textField.getCharBoundaries(charIndex - 1); + if (charBoundaries != null) + { + x = charBoundaries.right; + } + else + { + x = GUTTER; + } + + var y:Float = GUTTER; + for (i in 0...lineIndex) + { + y += textField.getLineMetrics(i).height; + } + y += textField.getLineMetrics(lineIndex).height / 2; + + return getCharAtPosition(x, y); + } + + function getCharAtPosition(x:Float, y:Float):Int + { + var lineY:Float = GUTTER; + for (line in 0...textField.numLines) + { + var lineOffset = textField.getLineOffset(line); + var lineHeight = textField.getLineMetrics(line).height; + if (y >= lineY && y <= lineY + lineHeight) + { + // check for every character in the line + var lineLength = textField.getLineLength(line); + var lineEndIndex = lineOffset + lineLength; + for (char in 0...lineLength) + { + var boundaries = textField.getCharBoundaries(lineOffset + char); + // reached end of line, return this character + if (boundaries == null) + return lineOffset + char; + if (x <= boundaries.right) + { + if (x <= boundaries.x + (boundaries.width / 2)) + { + return lineOffset + char; + } + else + { + return (lineOffset + char < lineEndIndex) ? lineOffset + char + 1 : lineEndIndex; + } + } + } + + // a character wasn't found, return the last character of the line + return lineEndIndex; + } + + lineY += lineHeight; + } + + return -1; + } + + function moveCursor(type:MoveCursorAction, shiftKey:Bool):Void + { + switch (type) + { + case LEFT: + if (caretIndex > 0) + { + caretIndex--; + } + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case RIGHT: + if (caretIndex < text.length) + { + caretIndex++; + } + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case UP: + var lineIndex = textField.getLineIndexOfChar(caretIndex); + if (lineIndex > 0) + { + caretIndex = getCharIndexOnDifferentLine(caretIndex, lineIndex - 1); + } + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case DOWN: + var lineIndex = textField.getLineIndexOfChar(caretIndex); + if (lineIndex < textField.numLines - 1) + { + caretIndex = getCharIndexOnDifferentLine(caretIndex, lineIndex + 1); + } + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case HOME: + caretIndex = 0; + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case END: + caretIndex = text.length; + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case LINE_BEGINNING: + caretIndex = textField.getLineOffset(textField.getLineIndexOfChar(caretIndex)); + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case LINE_END: + var lineIndex = textField.getLineIndexOfChar(caretIndex); + if (lineIndex < textField.numLines - 1) + { + caretIndex = textField.getLineOffset(lineIndex + 1) - 1; + } + else + { + caretIndex = text.length; + } + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case PREVIOUS_LINE: + var lineIndex = textField.getLineIndexOfChar(caretIndex); + if (lineIndex > 0) + { + var index = textField.getLineOffset(lineIndex); + if (caretIndex == index) + { + caretIndex = textField.getLineOffset(lineIndex - 1); + } + else + { + caretIndex = index; + } + } + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + case NEXT_LINE: + var lineIndex = textField.getLineIndexOfChar(caretIndex); + if (lineIndex < textField.numLines - 1) + { + caretIndex = textField.getLineOffset(lineIndex + 1); + } + else + { + caretIndex = text.length; + } + + if (!shiftKey) + { + selectionIndex = caretIndex; + } + } + } + + function regenCaret():Void + { + caret.makeGraphic(caretWidth, Std.int(size + 2), caretColor); + } + + function replaceSelectedText(newText:String):Void + { + if (newText == null) + newText = ""; + if (newText == "" && selectionIndex == caretIndex) + return; + + var beginIndex = selectionBeginIndex; + var endIndex = selectionEndIndex; + + if (beginIndex == endIndex && maxLength > 0 && text.length == maxLength) + return; + + if (beginIndex > text.length) + { + beginIndex = text.length; + } + if (endIndex > text.length) + { + endIndex = text.length; + } + if (endIndex < beginIndex) + { + var cache = endIndex; + endIndex = beginIndex; + beginIndex = cache; + } + if (beginIndex < 0) + { + beginIndex = 0; + } + + replaceText(beginIndex, endIndex, newText); + } + + function replaceText(beginIndex:Int, endIndex:Int, newText:String):Void + { + if (endIndex < beginIndex || beginIndex < 0 || endIndex > text.length || newText == null) + return; + + if (maxLength > 0) + { + var removeLength = (endIndex - beginIndex); + var newMaxLength = maxLength - text.length + removeLength; + + if (newMaxLength <= 0) + { + newText = ""; + } + else if (newMaxLength < newText.length) + { + newText = newText.substr(0, newMaxLength); + } + } + + text = text.substring(0, beginIndex) + newText + text.substring(endIndex); + + selectionIndex = caretIndex = beginIndex + newText.length; + } + + function runCommand(cmd:TypingCommand):Void + { + switch (cmd) + { + case NEW_LINE: + if (textField.multiline) + { + replaceSelectedText("\n"); + } + case DELETE_LEFT: + if (selectionIndex == caretIndex && caretIndex > 0) + { + selectionIndex = caretIndex - 1; + } + + if (selectionIndex != caretIndex) + { + replaceSelectedText(""); + selectionIndex = caretIndex; + } + case DELETE_RIGHT: + if (selectionIndex == caretIndex && caretIndex < text.length) + { + selectionIndex = caretIndex + 1; + } + + if (selectionIndex != caretIndex) + { + replaceSelectedText(""); + selectionIndex = caretIndex; + } + case COPY: + if (caretIndex != selectionIndex && !passwordMode) + { + Clipboard.text = text.substring(caretIndex, selectionIndex); + } + case CUT: + if (caretIndex != selectionIndex && !passwordMode) + { + Clipboard.text = text.substring(caretIndex, selectionIndex); + + replaceSelectedText(""); + } + case PASTE: + if (Clipboard.text != null) + { + replaceSelectedText(Clipboard.text); + } + case SELECT_ALL: + selectionIndex = 0; + caretIndex = text.length; + } + } + + function updateCaretPosition():Void + { + if (textField == null) + return; + + if (text.length == 0) + { + caret.setPosition(x + GUTTER, y + GUTTER); + } + else + { + var lineIndex = textField.getLineIndexOfChar(caretIndex); + var boundaries = textField.getCharBoundaries(caretIndex - 1); + if (boundaries != null) + { + caret.setPosition(x + boundaries.right, y + boundaries.y); + } + else // end of line + { + var lineY:Float = GUTTER; + for (i in 0...lineIndex) + { + lineY += textField.getLineMetrics(i).height; + } + caret.setPosition(x + GUTTER, y + lineY); + } + } + } + + override function set_color(value:FlxColor):FlxColor + { + if (color != value) + { + super.set_color(value); + caretColor = value; + } + + return value; + } + + override function set_text(value:String):String + { + if (text != value) + { + super.set_text(value); + + if (hasFocus) + { + if (text.length < selectionIndex) + { + selectionIndex = text.length; + } + if (text.length < caretIndex) + { + caretIndex = text.length; + } + } + else + { + selectionIndex = 0; + caretIndex = 0; + } + } + + return value; + } + + function set_caretColor(value:FlxColor):FlxColor + { + if (caretColor != value) + { + caretColor = value; + regenCaret(); + } + return value; + } + + function set_caretIndex(value:Int):Int + { + if (caretIndex != value) + { + caretIndex = value; + if (caretIndex < 0) + caretIndex = 0; + if (caretIndex > text.length) + caretIndex = text.length; + updateCaretPosition(); + } + return value; + } + + function set_caretWidth(value:Int):Int + { + if (caretWidth != value) + { + caretWidth = value; + regenCaret(); + } + return value; + } + + function set_hasFocus(value:Bool):Bool + { + if (hasFocus != value) + { + hasFocus = value; + if (hasFocus) + { + FlxG.inputText.focus = this; + + if (caretIndex < 0) + { + caretIndex = text.length; + selectionIndex = caretIndex; + } + + caret.visible = true; + } + else if (FlxG.inputText.focus == this) + { + FlxG.inputText.focus = null; + + if (selectionIndex != caretIndex) + { + selectionIndex = caretIndex; + } + + caret.visible = false; + } + } + return value; + } + + function set_maxLength(value:Int):Int + { + if (maxLength != value) + { + maxLength = value; + if (maxLength > 0 && text.length > maxLength) + { + text = text.substr(0, maxLength); + } + } + + return value; + } + + function get_passwordMode():Bool + { + return textField.displayAsPassword; + } + + function set_passwordMode(value:Bool):Bool + { + return textField.displayAsPassword = value; + } + + function get_selectionBeginIndex():Int + { + return FlxMath.minInt(caretIndex, selectionIndex); + } + + function get_selectionEndIndex():Int + { + return FlxMath.maxInt(caretIndex, selectionIndex); + } +} \ No newline at end of file From 99087576094ad7394e846521761f7672b0686eb9 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Tue, 18 Jun 2024 22:48:28 -0400 Subject: [PATCH 02/48] Fix code climate? --- flixel/system/frontEnds/InputTextFrontEnd.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index 9d9b35a0f1..12ae5327a5 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -167,8 +167,8 @@ class InputTextFrontEnd interface IFlxInputText { - public var hasFocus(default, set):Bool; - public function dispatchTypingAction(action:TypingAction):Void; + var hasFocus(default, set):Bool; + function dispatchTypingAction(action:TypingAction):Void; } enum TypingAction From 292283892cc4077ce6971ea4d3835b68342d393f Mon Sep 17 00:00:00 2001 From: Starmapo Date: Tue, 18 Jun 2024 23:46:04 -0400 Subject: [PATCH 03/48] Add missing FLX_MOUSE check --- flixel/text/FlxInputText.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index d15642a9ae..f1a955e718 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -50,6 +50,7 @@ class FlxInputText extends FlxText implements IFlxInputText { super.update(elapsed); + #if FLX_MOUSE if (visible) { if (FlxG.mouse.justPressed) @@ -64,6 +65,7 @@ class FlxInputText extends FlxText implements IFlxInputText } } } + #end } override function draw():Void From dd2ff7ede990fdab33b32eab2ddb80544373a239 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Wed, 19 Jun 2024 09:27:30 -0400 Subject: [PATCH 04/48] Add multiline variable - Regenerate text graphic when `passwordMode` changes --- flixel/text/FlxInputText.hx | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index f1a955e718..4fdd7c2131 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -19,6 +19,8 @@ class FlxInputText extends FlxText implements IFlxInputText public var hasFocus(default, set):Bool = false; public var maxLength(default, set):Int = 0; + + public var multiline(get, set):Bool; public var passwordMode(get, set):Bool; @@ -363,7 +365,7 @@ class FlxInputText extends FlxText implements IFlxInputText switch (cmd) { case NEW_LINE: - if (textField.multiline) + if (multiline) { replaceSelectedText("\n"); } @@ -558,6 +560,23 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function get_multiline():Bool + { + return textField.multiline; + } + + function set_multiline(value:Bool):Bool + { + if (textField.multiline != value) + { + textField.multiline = value; + // `wordWrap` will still add new lines even if `multiline` is false, + // let's change it accordingly + wordWrap = value; + _regen = true; + } + return value; + } function get_passwordMode():Bool { @@ -566,7 +585,12 @@ class FlxInputText extends FlxText implements IFlxInputText function set_passwordMode(value:Bool):Bool { - return textField.displayAsPassword = value; + if (textField.displayAsPassword != value) + { + textField.displayAsPassword = value; + _regen = true; + } + return value; } function get_selectionBeginIndex():Int From 54a650c117911b8f58486dce2413f7b1f977f639 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Wed, 19 Jun 2024 11:56:33 -0400 Subject: [PATCH 05/48] Place caret at closest character to mouse --- flixel/text/FlxInputText.hx | 57 ++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 4fdd7c2131..29aaf20d06 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -1,6 +1,8 @@ package flixel.text; +import flixel.input.FlxPointer; import flixel.math.FlxMath; +import flixel.math.FlxPoint; import flixel.system.frontEnds.InputTextFrontEnd; import flixel.util.FlxColor; import lime.system.Clipboard; @@ -19,7 +21,7 @@ class FlxInputText extends FlxText implements IFlxInputText public var hasFocus(default, set):Bool = false; public var maxLength(default, set):Int = 0; - + public var multiline(get, set):Bool; public var passwordMode(get, set):Bool; @@ -55,17 +57,7 @@ class FlxInputText extends FlxText implements IFlxInputText #if FLX_MOUSE if (visible) { - if (FlxG.mouse.justPressed) - { - if (FlxG.mouse.overlaps(this)) - { - hasFocus = true; - } - else - { - hasFocus = false; - } - } + updateInput(); } #end } @@ -443,6 +435,44 @@ class FlxInputText extends FlxText implements IFlxInputText } } + #if FLX_MOUSE + function updateInput() + { + if (FlxG.mouse.justPressed) + { + updatePointerInput(FlxG.mouse); + } + } + + function updatePointerInput(pointer:FlxPointer) + { + var overlap = false; + var pointerPos = FlxPoint.get(); + for (camera in getCameras()) + { + pointer.getWorldPosition(camera, pointerPos); + if (overlapsPoint(pointerPos, true, camera)) + { + hasFocus = true; + + getScreenPosition(_point, camera); + caretIndex = getCharAtPosition(pointerPos.x - _point.x, pointerPos.y - _point.y); + selectionIndex = caretIndex; + + overlap = true; + break; + } + } + + if (!overlap) + { + hasFocus = false; + } + + pointerPos.put(); + } + #end + override function set_color(value:FlxColor):FlxColor { if (color != value) @@ -560,6 +590,7 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function get_multiline():Bool { return textField.multiline; @@ -602,4 +633,4 @@ class FlxInputText extends FlxText implements IFlxInputText { return FlxMath.maxInt(caretIndex, selectionIndex); } -} \ No newline at end of file +} From 73220a2a5371acda1e9762374aa6640fa0cc2c87 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Wed, 19 Jun 2024 16:09:26 -0400 Subject: [PATCH 06/48] Selection boxes + selected text color - Add `setSelection()` function - `FlxInputText` variables are now destroyed properly --- flixel/text/FlxInputText.hx | 331 ++++++++++++++++++++++++++---------- 1 file changed, 245 insertions(+), 86 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 29aaf20d06..1cecb385e8 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -5,7 +5,10 @@ import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.system.frontEnds.InputTextFrontEnd; import flixel.util.FlxColor; +import flixel.util.FlxDestroyUtil; import lime.system.Clipboard; +import openfl.geom.Rectangle; +import openfl.text.TextFormat; import openfl.utils.QName; class FlxInputText extends FlxText implements IFlxInputText @@ -14,7 +17,7 @@ class FlxInputText extends FlxText implements IFlxInputText public var caretColor(default, set):FlxColor = FlxColor.WHITE; - public var caretIndex(default, set):Int = -1; + public var caretIndex(get, set):Int; public var caretWidth(default, set):Int = 1; @@ -25,14 +28,20 @@ class FlxInputText extends FlxText implements IFlxInputText public var multiline(get, set):Bool; public var passwordMode(get, set):Bool; + + public var selectedTextColor(default, set):FlxColor = FlxColor.WHITE; public var selectionBeginIndex(get, never):Int; + + public var selectionColor(default, set):FlxColor = FlxColor.BLACK; public var selectionEndIndex(get, never):Int; - var caret:FlxSprite; - - var selectionIndex:Int = -1; + var _caret:FlxSprite; + var _caretIndex:Int = -1; + var _selectionBoxes:Array = []; + var _selectionFormat:TextFormat = new TextFormat(); + var _selectionIndex:Int = -1; public function new(x:Float = 0, y:Float = 0, fieldWidth:Float = 0, ?text:String, size:Int = 8, embeddedFont:Bool = true) { @@ -42,8 +51,10 @@ class FlxInputText extends FlxText implements IFlxInputText // of the text, it won't be counted for in `numLines` textField.type = INPUT; - caret = new FlxSprite(); - caret.moves = caret.active = caret.visible = false; + _selectionFormat.color = selectedTextColor; + + _caret = new FlxSprite(); + _caret.visible = false; regenCaret(); updateCaretPosition(); @@ -64,17 +75,33 @@ class FlxInputText extends FlxText implements IFlxInputText override function draw():Void { + for (box in _selectionBoxes) + drawSprite(box); + super.draw(); - drawSprite(caret); + drawSprite(_caret); } override function destroy():Void { FlxG.inputText.unregisterInputText(this); + + _caret = FlxDestroyUtil.destroy(_caret); + while (_selectionBoxes.length > 0) + FlxDestroyUtil.destroy(_selectionBoxes.pop()); + _selectionBoxes = null; + _selectionFormat = null; super.destroy(); } + + override function applyFormats(formatAdjusted:TextFormat, useBorderColor:Bool = false):Void + { + super.applyFormats(formatAdjusted, useBorderColor); + + textField.setTextFormat(_selectionFormat, selectionBeginIndex, selectionEndIndex); + } public function dispatchTypingAction(action:TypingAction):Void { @@ -88,6 +115,21 @@ class FlxInputText extends FlxText implements IFlxInputText runCommand(cmd); } } + + public function setSelection(beginIndex:Int, endIndex:Int):Void + { + _selectionIndex = beginIndex; + _caretIndex = endIndex; + _regen = true; // regenerate so the selected text format is applied + + if (textField == null) + return; + + _caret.alpha = (_selectionIndex == _caretIndex) ? 1 : 0; + + updateCaretPosition(); + regenSelectionBoxes(); + } function drawSprite(sprite:FlxSprite):Void { @@ -173,130 +215,205 @@ class FlxInputText extends FlxText implements IFlxInputText switch (type) { case LEFT: - if (caretIndex > 0) + if (_caretIndex > 0) { - caretIndex--; + _caretIndex--; } if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case RIGHT: - if (caretIndex < text.length) + if (_caretIndex < text.length) { - caretIndex++; + _caretIndex++; } if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case UP: - var lineIndex = textField.getLineIndexOfChar(caretIndex); + var lineIndex = textField.getLineIndexOfChar(_caretIndex); if (lineIndex > 0) { - caretIndex = getCharIndexOnDifferentLine(caretIndex, lineIndex - 1); + _caretIndex = getCharIndexOnDifferentLine(_caretIndex, lineIndex - 1); } if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case DOWN: - var lineIndex = textField.getLineIndexOfChar(caretIndex); + var lineIndex = textField.getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) { - caretIndex = getCharIndexOnDifferentLine(caretIndex, lineIndex + 1); + _caretIndex = getCharIndexOnDifferentLine(_caretIndex, lineIndex + 1); } if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case HOME: - caretIndex = 0; + _caretIndex = 0; if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case END: - caretIndex = text.length; + _caretIndex = text.length; if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case LINE_BEGINNING: - caretIndex = textField.getLineOffset(textField.getLineIndexOfChar(caretIndex)); + _caretIndex = textField.getLineOffset(textField.getLineIndexOfChar(_caretIndex)); if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case LINE_END: - var lineIndex = textField.getLineIndexOfChar(caretIndex); + var lineIndex = textField.getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) { - caretIndex = textField.getLineOffset(lineIndex + 1) - 1; + _caretIndex = textField.getLineOffset(lineIndex + 1) - 1; } else { - caretIndex = text.length; + _caretIndex = text.length; } if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case PREVIOUS_LINE: - var lineIndex = textField.getLineIndexOfChar(caretIndex); + var lineIndex = textField.getLineIndexOfChar(_caretIndex); if (lineIndex > 0) { var index = textField.getLineOffset(lineIndex); - if (caretIndex == index) + if (_caretIndex == index) { - caretIndex = textField.getLineOffset(lineIndex - 1); + _caretIndex = textField.getLineOffset(lineIndex - 1); } else { - caretIndex = index; + _caretIndex = index; } } if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); case NEXT_LINE: - var lineIndex = textField.getLineIndexOfChar(caretIndex); + var lineIndex = textField.getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) { - caretIndex = textField.getLineOffset(lineIndex + 1); + _caretIndex = textField.getLineOffset(lineIndex + 1); } else { - caretIndex = text.length; + _caretIndex = text.length; } if (!shiftKey) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } + setSelection(_selectionIndex, _caretIndex); } } function regenCaret():Void { - caret.makeGraphic(caretWidth, Std.int(size + 2), caretColor); + _caret.makeGraphic(caretWidth, Std.int(size + 2), caretColor); + } + + function regenSelectionBoxes():Void + { + if (textField == null) + return; + + while (_selectionBoxes.length > textField.numLines) + { + var box = _selectionBoxes.pop(); + if (box != null) + box.destroy(); + } + + if (_caretIndex == _selectionIndex) + { + for (box in _selectionBoxes) + { + if (box != null) + box.visible = false; + } + + return; + } + + var beginLine = textField.getLineIndexOfChar(selectionBeginIndex); + var endLine = textField.getLineIndexOfChar(selectionEndIndex); + + for (line in 0...textField.numLines) + { + if (line >= beginLine && line <= endLine) + { + var lineStartIndex = textField.getLineOffset(line); + var lineEndIndex = lineStartIndex + textField.getLineLength(line); + + var startIndex = FlxMath.maxInt(lineStartIndex, selectionBeginIndex); + var endIndex = FlxMath.minInt(lineEndIndex, selectionEndIndex); + + var startBoundaries = textField.getCharBoundaries(startIndex); + var endBoundaries = textField.getCharBoundaries(endIndex - 1); + if (endBoundaries == null && endIndex > startIndex) // end of line, try getting the previous character + { + endBoundaries = textField.getCharBoundaries(endIndex - 2); + } + + if (startBoundaries != null && endBoundaries != null) + { + if (_selectionBoxes[line] == null) + _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, selectionColor); + + _selectionBoxes[line].setPosition(x + startBoundaries.x, y + startBoundaries.y); + _selectionBoxes[line].setGraphicSize(endBoundaries.right - startBoundaries.x, startBoundaries.height); + _selectionBoxes[line].updateHitbox(); + _selectionBoxes[line].visible = true; + } + else if (_selectionBoxes[line] != null) + { + _selectionBoxes[line].visible = false; + } + } + else if (_selectionBoxes[line] != null) + { + _selectionBoxes[line].visible = false; + } + } } function replaceSelectedText(newText:String):Void { if (newText == null) newText = ""; - if (newText == "" && selectionIndex == caretIndex) + if (newText == "" && _selectionIndex == _caretIndex) return; var beginIndex = selectionBeginIndex; @@ -349,7 +466,8 @@ class FlxInputText extends FlxText implements IFlxInputText text = text.substring(0, beginIndex) + newText + text.substring(endIndex); - selectionIndex = caretIndex = beginIndex + newText.length; + _selectionIndex = _caretIndex = beginIndex + newText.length; + setSelection(_selectionIndex, _caretIndex); } function runCommand(cmd:TypingCommand):Void @@ -362,36 +480,36 @@ class FlxInputText extends FlxText implements IFlxInputText replaceSelectedText("\n"); } case DELETE_LEFT: - if (selectionIndex == caretIndex && caretIndex > 0) + if (_selectionIndex == _caretIndex && _caretIndex > 0) { - selectionIndex = caretIndex - 1; + _selectionIndex = _caretIndex - 1; } - if (selectionIndex != caretIndex) + if (_selectionIndex != _caretIndex) { replaceSelectedText(""); - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } case DELETE_RIGHT: - if (selectionIndex == caretIndex && caretIndex < text.length) + if (_selectionIndex == _caretIndex && _caretIndex < text.length) { - selectionIndex = caretIndex + 1; + _selectionIndex = _caretIndex + 1; } - if (selectionIndex != caretIndex) + if (_selectionIndex != _caretIndex) { replaceSelectedText(""); - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; } case COPY: - if (caretIndex != selectionIndex && !passwordMode) + if (_caretIndex != _selectionIndex && !passwordMode) { - Clipboard.text = text.substring(caretIndex, selectionIndex); + Clipboard.text = text.substring(_caretIndex, _selectionIndex); } case CUT: - if (caretIndex != selectionIndex && !passwordMode) + if (_caretIndex != _selectionIndex && !passwordMode) { - Clipboard.text = text.substring(caretIndex, selectionIndex); + Clipboard.text = text.substring(_caretIndex, _selectionIndex); replaceSelectedText(""); } @@ -401,8 +519,9 @@ class FlxInputText extends FlxText implements IFlxInputText replaceSelectedText(Clipboard.text); } case SELECT_ALL: - selectionIndex = 0; - caretIndex = text.length; + _selectionIndex = 0; + _caretIndex = text.length; + setSelection(_selectionIndex, _caretIndex); } } @@ -413,30 +532,30 @@ class FlxInputText extends FlxText implements IFlxInputText if (text.length == 0) { - caret.setPosition(x + GUTTER, y + GUTTER); + _caret.setPosition(x + GUTTER, y + GUTTER); } else { - var lineIndex = textField.getLineIndexOfChar(caretIndex); - var boundaries = textField.getCharBoundaries(caretIndex - 1); + var boundaries = textField.getCharBoundaries(_caretIndex - 1); if (boundaries != null) { - caret.setPosition(x + boundaries.right, y + boundaries.y); + _caret.setPosition(x + boundaries.right, y + boundaries.y); } else // end of line { var lineY:Float = GUTTER; + var lineIndex = textField.getLineIndexOfChar(_caretIndex); for (i in 0...lineIndex) { lineY += textField.getLineMetrics(i).height; } - caret.setPosition(x + GUTTER, y + lineY); + _caret.setPosition(x + GUTTER, y + lineY); } } } #if FLX_MOUSE - function updateInput() + function updateInput():Void { if (FlxG.mouse.justPressed) { @@ -444,7 +563,7 @@ class FlxInputText extends FlxText implements IFlxInputText } } - function updatePointerInput(pointer:FlxPointer) + function updatePointerInput(pointer:FlxPointer):Void { var overlap = false; var pointerPos = FlxPoint.get(); @@ -456,8 +575,9 @@ class FlxInputText extends FlxText implements IFlxInputText hasFocus = true; getScreenPosition(_point, camera); - caretIndex = getCharAtPosition(pointerPos.x - _point.x, pointerPos.y - _point.y); - selectionIndex = caretIndex; + _caretIndex = getCharAtPosition(pointerPos.x - _point.x, pointerPos.y - _point.y); + _selectionIndex = _caretIndex; + setSelection(_selectionIndex, _caretIndex); overlap = true; break; @@ -492,20 +612,21 @@ class FlxInputText extends FlxText implements IFlxInputText if (hasFocus) { - if (text.length < selectionIndex) + if (text.length < _selectionIndex) { - selectionIndex = text.length; + _selectionIndex = text.length; } - if (text.length < caretIndex) + if (text.length < _caretIndex) { - caretIndex = text.length; + _caretIndex = text.length; } } else { - selectionIndex = 0; - caretIndex = 0; + _selectionIndex = 0; + _caretIndex = 0; } + setSelection(_selectionIndex, _caretIndex); } return value; @@ -518,20 +639,26 @@ class FlxInputText extends FlxText implements IFlxInputText caretColor = value; regenCaret(); } + return value; } + function get_caretIndex():Int + { + return _caretIndex; + } function set_caretIndex(value:Int):Int { - if (caretIndex != value) + if (_caretIndex != value) { - caretIndex = value; - if (caretIndex < 0) - caretIndex = 0; - if (caretIndex > text.length) - caretIndex = text.length; - updateCaretPosition(); + _caretIndex = value; + if (_caretIndex < 0) + _caretIndex = 0; + if (_caretIndex > text.length) + _caretIndex = text.length; + setSelection(_caretIndex, _caretIndex); } + return value; } @@ -542,6 +669,7 @@ class FlxInputText extends FlxText implements IFlxInputText caretWidth = value; regenCaret(); } + return value; } @@ -554,26 +682,29 @@ class FlxInputText extends FlxText implements IFlxInputText { FlxG.inputText.focus = this; - if (caretIndex < 0) + if (_caretIndex < 0) { - caretIndex = text.length; - selectionIndex = caretIndex; + _caretIndex = text.length; + _selectionIndex = _caretIndex; + setSelection(_selectionIndex, _caretIndex); } - caret.visible = true; + _caret.visible = true; } else if (FlxG.inputText.focus == this) { FlxG.inputText.focus = null; - if (selectionIndex != caretIndex) + if (_selectionIndex != _caretIndex) { - selectionIndex = caretIndex; + _selectionIndex = _caretIndex; + setSelection(_selectionIndex, _caretIndex); } - caret.visible = false; + _caret.visible = false; } } + return value; } @@ -606,6 +737,7 @@ class FlxInputText extends FlxText implements IFlxInputText wordWrap = value; _regen = true; } + return value; } @@ -624,13 +756,40 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function set_selectedTextColor(value:FlxColor):FlxColor + { + if (selectedTextColor != value) + { + selectedTextColor = value; + _selectionFormat.color = selectedTextColor; + _regen = true; + } + + return value; + } + function get_selectionBeginIndex():Int { - return FlxMath.minInt(caretIndex, selectionIndex); + return FlxMath.minInt(_caretIndex, _selectionIndex); + } + + function set_selectionColor(value:FlxColor):FlxColor + { + if (selectionColor != value) + { + selectionColor = value; + for (box in _selectionBoxes) + { + if (box != null) + box.makeGraphic(1, 1, selectionColor); + } + } + + return value; } function get_selectionEndIndex():Int { - return FlxMath.maxInt(caretIndex, selectionIndex); + return FlxMath.maxInt(_caretIndex, _selectionIndex); } } From 99055c79ebbb6c0d1c8296df58fbb681e1ce17f3 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Wed, 19 Jun 2024 20:23:20 -0400 Subject: [PATCH 07/48] Implement text selection with mouse & text scrolling - Added `scrollH`, `scrollV`, `bottomScrollV`, `maxScrollH` & `maxScrollV` variables - Return end of text if character isn't found at position --- flixel/text/FlxInputText.hx | 315 ++++++++++++++++++++++++++---------- 1 file changed, 234 insertions(+), 81 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 1cecb385e8..5b14d6d66e 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -7,6 +7,7 @@ import flixel.system.frontEnds.InputTextFrontEnd; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; import lime.system.Clipboard; +import openfl.display.BitmapData; import openfl.geom.Rectangle; import openfl.text.TextFormat; import openfl.utils.QName; @@ -15,6 +16,8 @@ class FlxInputText extends FlxText implements IFlxInputText { static inline var GUTTER:Int = 2; + public var bottomScrollV(get, never):Int; + public var caretColor(default, set):FlxColor = FlxColor.WHITE; public var caretIndex(get, set):Int; @@ -24,11 +27,19 @@ class FlxInputText extends FlxText implements IFlxInputText public var hasFocus(default, set):Bool = false; public var maxLength(default, set):Int = 0; + + public var maxScrollH(get, never):Int; + + public var maxScrollV(get, never):Int; public var multiline(get, set):Bool; public var passwordMode(get, set):Bool; + public var scrollH(get, set):Int; + + public var scrollV(get, set):Int; + public var selectedTextColor(default, set):FlxColor = FlxColor.WHITE; public var selectionBeginIndex(get, never):Int; @@ -39,6 +50,8 @@ class FlxInputText extends FlxText implements IFlxInputText var _caret:FlxSprite; var _caretIndex:Int = -1; + var _mouseDown:Bool; + var _pointerCamera:FlxCamera; var _selectionBoxes:Array = []; var _selectionFormat:TextFormat = new TextFormat(); var _selectionIndex:Int = -1; @@ -88,6 +101,7 @@ class FlxInputText extends FlxText implements IFlxInputText FlxG.inputText.unregisterInputText(this); _caret = FlxDestroyUtil.destroy(_caret); + _pointerCamera = null; while (_selectionBoxes.length > 0) FlxDestroyUtil.destroy(_selectionBoxes.pop()); _selectionBoxes = null; @@ -120,15 +134,11 @@ class FlxInputText extends FlxText implements IFlxInputText { _selectionIndex = beginIndex; _caretIndex = endIndex; - _regen = true; // regenerate so the selected text format is applied if (textField == null) return; - - _caret.alpha = (_selectionIndex == _caretIndex) ? 1 : 0; - - updateCaretPosition(); - regenSelectionBoxes(); + + updateSelection(); } function drawSprite(sprite:FlxSprite):Void @@ -160,9 +170,13 @@ class FlxInputText extends FlxText implements IFlxInputText } var y:Float = GUTTER; - for (i in 0...lineIndex) + for (i in 0...FlxMath.maxInt(scrollV - 1, lineIndex)) { - y += textField.getLineMetrics(i).height; + var lineHeight = textField.getLineMetrics(i).height; + if (i < scrollV - 1) + y -= lineHeight; + if (i < lineIndex) + y += lineHeight; } y += textField.getLineMetrics(lineIndex).height / 2; @@ -171,6 +185,14 @@ class FlxInputText extends FlxText implements IFlxInputText function getCharAtPosition(x:Float, y:Float):Int { + x += scrollH; + for (i in 0...scrollV - 1) + { + y += textField.getLineMetrics(i).height; + } + if (y > textField.textHeight) + y = textField.textHeight; + var lineY:Float = GUTTER; for (line in 0...textField.numLines) { @@ -207,7 +229,7 @@ class FlxInputText extends FlxText implements IFlxInputText lineY += lineHeight; } - return -1; + return text.length; } function moveCursor(type:MoveCursorAction, shiftKey:Bool):Void @@ -343,72 +365,7 @@ class FlxInputText extends FlxText implements IFlxInputText { _caret.makeGraphic(caretWidth, Std.int(size + 2), caretColor); } - - function regenSelectionBoxes():Void - { - if (textField == null) - return; - - while (_selectionBoxes.length > textField.numLines) - { - var box = _selectionBoxes.pop(); - if (box != null) - box.destroy(); - } - - if (_caretIndex == _selectionIndex) - { - for (box in _selectionBoxes) - { - if (box != null) - box.visible = false; - } - - return; - } - - var beginLine = textField.getLineIndexOfChar(selectionBeginIndex); - var endLine = textField.getLineIndexOfChar(selectionEndIndex); - - for (line in 0...textField.numLines) - { - if (line >= beginLine && line <= endLine) - { - var lineStartIndex = textField.getLineOffset(line); - var lineEndIndex = lineStartIndex + textField.getLineLength(line); - - var startIndex = FlxMath.maxInt(lineStartIndex, selectionBeginIndex); - var endIndex = FlxMath.minInt(lineEndIndex, selectionEndIndex); - - var startBoundaries = textField.getCharBoundaries(startIndex); - var endBoundaries = textField.getCharBoundaries(endIndex - 1); - if (endBoundaries == null && endIndex > startIndex) // end of line, try getting the previous character - { - endBoundaries = textField.getCharBoundaries(endIndex - 2); - } - - if (startBoundaries != null && endBoundaries != null) - { - if (_selectionBoxes[line] == null) - _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, selectionColor); - - _selectionBoxes[line].setPosition(x + startBoundaries.x, y + startBoundaries.y); - _selectionBoxes[line].setGraphicSize(endBoundaries.right - startBoundaries.x, startBoundaries.height); - _selectionBoxes[line].updateHitbox(); - _selectionBoxes[line].visible = true; - } - else if (_selectionBoxes[line] != null) - { - _selectionBoxes[line].visible = false; - } - } - else if (_selectionBoxes[line] != null) - { - _selectionBoxes[line].visible = false; - } - } - } - + function replaceSelectedText(newText:String):Void { if (newText == null) @@ -536,10 +493,15 @@ class FlxInputText extends FlxText implements IFlxInputText } else { + var scrollY = 0.0; + for (i in 0...scrollV - 1) + { + scrollY += textField.getLineMetrics(i).height; + } var boundaries = textField.getCharBoundaries(_caretIndex - 1); if (boundaries != null) { - _caret.setPosition(x + boundaries.right, y + boundaries.y); + _caret.setPosition(x + boundaries.right - scrollH, y + boundaries.y - scrollY); } else // end of line { @@ -549,7 +511,88 @@ class FlxInputText extends FlxText implements IFlxInputText { lineY += textField.getLineMetrics(i).height; } - _caret.setPosition(x + GUTTER, y + lineY); + _caret.setPosition(x + GUTTER, y + lineY - scrollY); + } + } + } + + function updateSelection():Void + { + textField.setSelection(_selectionIndex, _caretIndex); + _caret.alpha = (_selectionIndex == _caretIndex) ? 1 : 0; + updateCaretPosition(); + updateSelectionBoxes(); + + _regen = true; + } + + function updateSelectionBoxes():Void + { + if (textField == null) + return; + + while (_selectionBoxes.length > textField.numLines) + { + var box = _selectionBoxes.pop(); + if (box != null) + box.destroy(); + } + + if (_caretIndex == _selectionIndex) + { + for (box in _selectionBoxes) + { + if (box != null) + box.visible = false; + } + + return; + } + + var beginLine = textField.getLineIndexOfChar(selectionBeginIndex); + var endLine = textField.getLineIndexOfChar(selectionEndIndex); + + var scrollY = 0.0; + for (i in 0...scrollV - 1) + { + scrollY += textField.getLineMetrics(i).height; + } + + for (line in 0...textField.numLines) + { + if ((line >= scrollV - 1 && line <= bottomScrollV - 1) && (line >= beginLine && line <= endLine)) + { + var lineStartIndex = textField.getLineOffset(line); + var lineEndIndex = lineStartIndex + textField.getLineLength(line); + + var startIndex = FlxMath.maxInt(lineStartIndex, selectionBeginIndex); + var endIndex = FlxMath.minInt(lineEndIndex, selectionEndIndex); + + var startBoundaries = textField.getCharBoundaries(startIndex); + var endBoundaries = textField.getCharBoundaries(endIndex - 1); + if (endBoundaries == null && endIndex > startIndex) // end of line, try getting the previous character + { + endBoundaries = textField.getCharBoundaries(endIndex - 2); + } + + if (startBoundaries != null && endBoundaries != null) + { + if (_selectionBoxes[line] == null) + _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, selectionColor); + + _selectionBoxes[line].setPosition(x + startBoundaries.x - scrollH, y + startBoundaries.y - scrollY); + _selectionBoxes[line].setGraphicSize(endBoundaries.right - startBoundaries.x, startBoundaries.height); + _selectionBoxes[line].updateHitbox(); + _selectionBoxes[line].visible = true; + } + else if (_selectionBoxes[line] != null) + { + _selectionBoxes[line].visible = false; + } + } + else if (_selectionBoxes[line] != null) + { + _selectionBoxes[line].visible = false; } } } @@ -557,13 +600,26 @@ class FlxInputText extends FlxText implements IFlxInputText #if FLX_MOUSE function updateInput():Void { + if (_mouseDown) + { + if (FlxG.mouse.justMoved) + { + updatePointerDrag(FlxG.mouse); + } + + if (FlxG.mouse.released) + { + _mouseDown = false; + updatePointerRelease(FlxG.mouse); + } + } if (FlxG.mouse.justPressed) { - updatePointerInput(FlxG.mouse); + _mouseDown = checkPointerOverlap(FlxG.mouse); } } - function updatePointerInput(pointer:FlxPointer):Void + function checkPointerOverlap(pointer:FlxPointer):Bool { var overlap = false; var pointerPos = FlxPoint.get(); @@ -573,13 +629,15 @@ class FlxInputText extends FlxText implements IFlxInputText if (overlapsPoint(pointerPos, true, camera)) { hasFocus = true; + _pointerCamera = camera; - getScreenPosition(_point, camera); - _caretIndex = getCharAtPosition(pointerPos.x - _point.x, pointerPos.y - _point.y); + var relativePos = getRelativePosition(pointerPos); + _caretIndex = getCharAtPosition(relativePos.x, relativePos.y); _selectionIndex = _caretIndex; setSelection(_selectionIndex, _caretIndex); overlap = true; + relativePos.put(); break; } } @@ -590,6 +648,51 @@ class FlxInputText extends FlxText implements IFlxInputText } pointerPos.put(); + return overlap; + } + + function updatePointerDrag(pointer:FlxPointer):Void + { + if (_selectionIndex < 0) + return; + + var pointerPos = pointer.getWorldPosition(_pointerCamera); + var relativePos = getRelativePosition(pointerPos); + + var char = getCharAtPosition(relativePos.x, relativePos.y); + if (char != _caretIndex) + { + _caretIndex = char; + updateSelection(); + } + + pointerPos.put(); + relativePos.put(); + } + + function updatePointerRelease(pointer:FlxPointer):Void + { + if (!hasFocus) + return; + + var pointerPos = pointer.getWorldPosition(_pointerCamera); + var relativePos = getRelativePosition(pointerPos); + + var upPos = getCharAtPosition(relativePos.x, relativePos.y); + var leftPos = FlxMath.minInt(_selectionIndex, upPos); + var rightPos = FlxMath.maxInt(_selectionIndex, upPos); + + _selectionIndex = leftPos; + _caretIndex = rightPos; + + pointerPos.put(); + relativePos.put(); + } + + function getRelativePosition(point:FlxPoint) + { + getScreenPosition(_point, _pointerCamera); + return FlxPoint.get(point.x - _point.x, point.y - _point.y); } #end @@ -631,6 +734,10 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function get_bottomScrollV():Int + { + return textField.bottomScrollV; + } function set_caretColor(value:FlxColor):FlxColor { @@ -721,6 +828,15 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function get_maxScrollH():Int + { + return textField.maxScrollH; + } + + function get_maxScrollV():Int + { + return textField.maxScrollV; + } function get_multiline():Bool { @@ -755,6 +871,43 @@ class FlxInputText extends FlxText implements IFlxInputText } return value; } + function get_scrollH():Int + { + return textField.scrollH; + } + + function set_scrollH(value:Int):Int + { + if (value > maxScrollH) + value = maxScrollH; + if (value < 0) + value = 0; + if (textField.scrollH != value) + { + textField.scrollH = value; + updateSelection(); + } + return value; + } + + function get_scrollV():Int + { + return textField.scrollV; + } + + function set_scrollV(value:Int):Int + { + if (value > maxScrollV) + value = maxScrollV; + if (value < 1) + value = 1; + if (textField.scrollV != value || textField.scrollV == 0) + { + textField.scrollV = value; + updateSelection(); + } + return value; + } function set_selectedTextColor(value:FlxColor):FlxColor { From 8ea7c586964cc9618d207b38d4fa1484f9f1f313 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Thu, 20 Jun 2024 20:46:43 -0400 Subject: [PATCH 08/48] Fix selection not working correctly when mouse is out of bounds - Selection boxes are now clipped inside the text bounds - Simplified getting the Y offset of a line --- flixel/text/FlxInputText.hx | 83 +++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 5b14d6d66e..ae9daef3ec 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -3,6 +3,7 @@ package flixel.text; import flixel.input.FlxPointer; import flixel.math.FlxMath; import flixel.math.FlxPoint; +import flixel.math.FlxRect; import flixel.system.frontEnds.InputTextFrontEnd; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; @@ -169,16 +170,7 @@ class FlxInputText extends FlxText implements IFlxInputText x = GUTTER; } - var y:Float = GUTTER; - for (i in 0...FlxMath.maxInt(scrollV - 1, lineIndex)) - { - var lineHeight = textField.getLineMetrics(i).height; - if (i < scrollV - 1) - y -= lineHeight; - if (i < lineIndex) - y += lineHeight; - } - y += textField.getLineMetrics(lineIndex).height / 2; + var y = GUTTER + getLineY(lineIndex) + textField.getLineMetrics(lineIndex).height / 2 - getLineY(scrollV - 1); return getCharAtPosition(x, y); } @@ -186,16 +178,19 @@ class FlxInputText extends FlxText implements IFlxInputText function getCharAtPosition(x:Float, y:Float):Int { x += scrollH; - for (i in 0...scrollV - 1) - { - y += textField.getLineMetrics(i).height; - } + y += getLineY(scrollV - 1); + + if (x < GUTTER) + x = GUTTER; + if (y > textField.textHeight) y = textField.textHeight; + if (y < GUTTER) + y = GUTTER; - var lineY:Float = GUTTER; for (line in 0...textField.numLines) { + var lineY = GUTTER + getLineY(line); var lineOffset = textField.getLineOffset(line); var lineHeight = textField.getLineMetrics(line).height; if (y >= lineY && y <= lineY + lineHeight) @@ -225,12 +220,19 @@ class FlxInputText extends FlxText implements IFlxInputText // a character wasn't found, return the last character of the line return lineEndIndex; } - - lineY += lineHeight; } return text.length; } + function getLineY(line:Int):Float + { + var scrollY = 0.0; + for (i in 0...line) + { + scrollY += textField.getLineMetrics(i).height; + } + return scrollY; + } function moveCursor(type:MoveCursorAction, shiftKey:Bool):Void { @@ -493,25 +495,15 @@ class FlxInputText extends FlxText implements IFlxInputText } else { - var scrollY = 0.0; - for (i in 0...scrollV - 1) - { - scrollY += textField.getLineMetrics(i).height; - } var boundaries = textField.getCharBoundaries(_caretIndex - 1); if (boundaries != null) { - _caret.setPosition(x + boundaries.right - scrollH, y + boundaries.y - scrollY); + _caret.setPosition(x + boundaries.right - scrollH, y + boundaries.y - getLineY(scrollV - 1)); } else // end of line { - var lineY:Float = GUTTER; var lineIndex = textField.getLineIndexOfChar(_caretIndex); - for (i in 0...lineIndex) - { - lineY += textField.getLineMetrics(i).height; - } - _caret.setPosition(x + GUTTER, y + lineY - scrollY); + _caret.setPosition(x + GUTTER, y + GUTTER + getLineY(lineIndex) - getLineY(scrollV - 1)); } } } @@ -552,14 +544,11 @@ class FlxInputText extends FlxText implements IFlxInputText var beginLine = textField.getLineIndexOfChar(selectionBeginIndex); var endLine = textField.getLineIndexOfChar(selectionEndIndex); - var scrollY = 0.0; - for (i in 0...scrollV - 1) - { - scrollY += textField.getLineMetrics(i).height; - } + var scrollY = getLineY(scrollV - 1); for (line in 0...textField.numLines) { + var box = _selectionBoxes[line]; if ((line >= scrollV - 1 && line <= bottomScrollV - 1) && (line >= beginLine && line <= endLine)) { var lineStartIndex = textField.getLineOffset(line); @@ -577,22 +566,28 @@ class FlxInputText extends FlxText implements IFlxInputText if (startBoundaries != null && endBoundaries != null) { - if (_selectionBoxes[line] == null) - _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, selectionColor); + if (box == null) + box = _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, selectionColor); - _selectionBoxes[line].setPosition(x + startBoundaries.x - scrollH, y + startBoundaries.y - scrollY); - _selectionBoxes[line].setGraphicSize(endBoundaries.right - startBoundaries.x, startBoundaries.height); - _selectionBoxes[line].updateHitbox(); - _selectionBoxes[line].visible = true; + var boxRect = FlxRect.get(startBoundaries.x - scrollH, startBoundaries.y - scrollY, endBoundaries.right - startBoundaries.x, + startBoundaries.height); + boxRect.clipTo(FlxRect.weak(0, 0, width, height)); // clip the selection box inside the text sprite + + box.setPosition(x + boxRect.x, y + boxRect.y); + box.setGraphicSize(boxRect.width, boxRect.height); + box.updateHitbox(); + box.visible = true; + + boxRect.put(); } - else if (_selectionBoxes[line] != null) + else if (box != null) { - _selectionBoxes[line].visible = false; + box.visible = false; } } - else if (_selectionBoxes[line] != null) + else if (box != null) { - _selectionBoxes[line].visible = false; + box.visible = false; } } } From 090642b6bc0b3fcb7a395a7f2cd9ebc497bec679 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Thu, 20 Jun 2024 22:58:03 -0400 Subject: [PATCH 09/48] Mouse wheel scrolling - Fix scrollV not being able to be modified directly --- flixel/text/FlxInputText.hx | 97 +++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index ae9daef3ec..39a905d06a 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -113,9 +113,12 @@ class FlxInputText extends FlxText implements IFlxInputText override function applyFormats(formatAdjusted:TextFormat, useBorderColor:Bool = false):Void { + var cache = scrollV; + super.applyFormats(formatAdjusted, useBorderColor); textField.setTextFormat(_selectionFormat, selectionBeginIndex, selectionEndIndex); + scrollV = cache; } public function dispatchTypingAction(action:TypingAction):Void @@ -224,6 +227,7 @@ class FlxInputText extends FlxText implements IFlxInputText return text.length; } + function getLineY(line:Int):Float { var scrollY = 0.0; @@ -233,6 +237,12 @@ class FlxInputText extends FlxText implements IFlxInputText } return scrollY; } + + function isCaretLineVisible():Bool + { + var line = textField.getLineIndexOfChar(_caretIndex); + return line >= scrollV - 1 && line <= bottomScrollV - 1; + } function moveCursor(type:MoveCursorAction, shiftKey:Bool):Void { @@ -511,10 +521,7 @@ class FlxInputText extends FlxText implements IFlxInputText function updateSelection():Void { textField.setSelection(_selectionIndex, _caretIndex); - _caret.alpha = (_selectionIndex == _caretIndex) ? 1 : 0; - updateCaretPosition(); - updateSelectionBoxes(); - + updateSelectionSprites(); _regen = true; } @@ -592,6 +599,13 @@ class FlxInputText extends FlxText implements IFlxInputText } } + function updateSelectionSprites():Void + { + _caret.alpha = (_selectionIndex == _caretIndex && isCaretLineVisible()) ? 1 : 0; + updateCaretPosition(); + updateSelectionBoxes(); + } + #if FLX_MOUSE function updateInput():Void { @@ -608,9 +622,22 @@ class FlxInputText extends FlxText implements IFlxInputText updatePointerRelease(FlxG.mouse); } } - if (FlxG.mouse.justPressed) + if (checkPointerOverlap(FlxG.mouse)) { - _mouseDown = checkPointerOverlap(FlxG.mouse); + if (FlxG.mouse.justPressed) + { + _mouseDown = true; + updatePointerPress(FlxG.mouse); + } + + if (FlxG.mouse.wheel != 0) + { + scrollV = FlxMath.minInt(scrollV - FlxG.mouse.wheel, maxScrollV); + } + } + else if (FlxG.mouse.justPressed) + { + hasFocus = false; } } @@ -623,36 +650,35 @@ class FlxInputText extends FlxText implements IFlxInputText pointer.getWorldPosition(camera, pointerPos); if (overlapsPoint(pointerPos, true, camera)) { - hasFocus = true; - _pointerCamera = camera; - - var relativePos = getRelativePosition(pointerPos); - _caretIndex = getCharAtPosition(relativePos.x, relativePos.y); - _selectionIndex = _caretIndex; - setSelection(_selectionIndex, _caretIndex); - + if (_pointerCamera == null) + _pointerCamera = camera; overlap = true; - relativePos.put(); break; } } - - if (!overlap) - { - hasFocus = false; - } pointerPos.put(); return overlap; } + function updatePointerPress(pointer:FlxPointer):Void + { + hasFocus = true; + + var relativePos = getRelativePosition(pointer); + _caretIndex = getCharAtPosition(relativePos.x, relativePos.y); + _selectionIndex = _caretIndex; + setSelection(_selectionIndex, _caretIndex); + + relativePos.put(); + } + function updatePointerDrag(pointer:FlxPointer):Void { if (_selectionIndex < 0) return; - var pointerPos = pointer.getWorldPosition(_pointerCamera); - var relativePos = getRelativePosition(pointerPos); + var relativePos = getRelativePosition(pointer); var char = getCharAtPosition(relativePos.x, relativePos.y); if (char != _caretIndex) @@ -660,8 +686,7 @@ class FlxInputText extends FlxText implements IFlxInputText _caretIndex = char; updateSelection(); } - - pointerPos.put(); + relativePos.put(); } @@ -670,8 +695,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (!hasFocus) return; - var pointerPos = pointer.getWorldPosition(_pointerCamera); - var relativePos = getRelativePosition(pointerPos); + var relativePos = getRelativePosition(pointer); var upPos = getCharAtPosition(relativePos.x, relativePos.y); var leftPos = FlxMath.minInt(_selectionIndex, upPos); @@ -679,15 +703,18 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = leftPos; _caretIndex = rightPos; - - pointerPos.put(); + relativePos.put(); + _pointerCamera = null; } - function getRelativePosition(point:FlxPoint) + function getRelativePosition(pointer:FlxPointer):FlxPoint { + var pointerPos = pointer.getWorldPosition(_pointerCamera); getScreenPosition(_point, _pointerCamera); - return FlxPoint.get(point.x - _point.x, point.y - _point.y); + var result = FlxPoint.get(pointerPos.x - _point.x, pointerPos.y - _point.y); + pointerPos.put(); + return result; } #end @@ -729,6 +756,7 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function get_bottomScrollV():Int { return textField.bottomScrollV; @@ -749,6 +777,7 @@ class FlxInputText extends FlxText implements IFlxInputText { return _caretIndex; } + function set_caretIndex(value:Int):Int { if (_caretIndex != value) @@ -823,6 +852,7 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function get_maxScrollH():Int { return textField.maxScrollH; @@ -866,6 +896,7 @@ class FlxInputText extends FlxText implements IFlxInputText } return value; } + function get_scrollH():Int { return textField.scrollH; @@ -880,7 +911,8 @@ class FlxInputText extends FlxText implements IFlxInputText if (textField.scrollH != value) { textField.scrollH = value; - updateSelection(); + _regen = true; + updateSelectionSprites(); } return value; } @@ -899,7 +931,8 @@ class FlxInputText extends FlxText implements IFlxInputText if (textField.scrollV != value || textField.scrollV == 0) { textField.scrollV = value; - updateSelection(); + _regen = true; + updateSelectionSprites(); } return value; } From 20afded4d51ddb23c9428dcab58232f056c82b15 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Fri, 21 Jun 2024 17:18:31 -0400 Subject: [PATCH 10/48] Implemented double press and dragging - Selection sprites now just change their color instead of making new graphics - scrollH can now be modified properly as well - Word wrap no longer changes with multiline (multiline only affects adding new lines) --- flixel/text/FlxInputText.hx | 120 ++++++++++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 19 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 39a905d06a..4cf87d5024 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -17,6 +17,8 @@ class FlxInputText extends FlxText implements IFlxInputText { static inline var GUTTER:Int = 2; + static final DELIMITERS:Array = ['\n', '.', '!', '?', ',', ' ', ';', ':', '(', ')', '-', '_', '/']; + public var bottomScrollV(get, never):Int; public var caretColor(default, set):FlxColor = FlxColor.WHITE; @@ -51,8 +53,10 @@ class FlxInputText extends FlxText implements IFlxInputText var _caret:FlxSprite; var _caretIndex:Int = -1; - var _mouseDown:Bool; + var _lastClickTime:Int = 0; + var _mouseDown:Bool = false; var _pointerCamera:FlxCamera; + var _scrollVCounter:Float = 0; var _selectionBoxes:Array = []; var _selectionFormat:TextFormat = new TextFormat(); var _selectionIndex:Int = -1; @@ -82,7 +86,7 @@ class FlxInputText extends FlxText implements IFlxInputText #if FLX_MOUSE if (visible) { - updateInput(); + updateInput(elapsed); } #end } @@ -113,12 +117,27 @@ class FlxInputText extends FlxText implements IFlxInputText override function applyFormats(formatAdjusted:TextFormat, useBorderColor:Bool = false):Void { - var cache = scrollV; - + // scroll variables will be reset when `textField.setTextFormat()` is called, + // cache the current ones first + var cacheScrollH = scrollH; + var cacheScrollV = scrollV; + super.applyFormats(formatAdjusted, useBorderColor); textField.setTextFormat(_selectionFormat, selectionBeginIndex, selectionEndIndex); - scrollV = cache; + // set the scroll back to how it was + scrollH = cacheScrollH; + scrollV = cacheScrollV; + } + + override function regenGraphic():Void + { + var regenSelection = _regen; + + super.regenGraphic(); + + if (_caret != null && regenSelection) + updateSelectionSprites(); } public function dispatchTypingAction(action:TypingAction):Void @@ -375,7 +394,7 @@ class FlxInputText extends FlxText implements IFlxInputText function regenCaret():Void { - _caret.makeGraphic(caretWidth, Std.int(size + 2), caretColor); + _caret.makeGraphic(caretWidth, Std.int(size + 2), FlxColor.WHITE); } function replaceSelectedText(newText:String):Void @@ -521,7 +540,6 @@ class FlxInputText extends FlxText implements IFlxInputText function updateSelection():Void { textField.setSelection(_selectionIndex, _caretIndex); - updateSelectionSprites(); _regen = true; } @@ -574,7 +592,10 @@ class FlxInputText extends FlxText implements IFlxInputText if (startBoundaries != null && endBoundaries != null) { if (box == null) - box = _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, selectionColor); + { + box = _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); + box.color = selectionColor; + } var boxRect = FlxRect.get(startBoundaries.x - scrollH, startBoundaries.y - scrollY, endBoundaries.right - startBoundaries.x, startBoundaries.height); @@ -607,13 +628,15 @@ class FlxInputText extends FlxText implements IFlxInputText } #if FLX_MOUSE - function updateInput():Void + function updateInput(elapsed:Float):Void { if (_mouseDown) { + updatePointerDrag(FlxG.mouse, elapsed); + if (FlxG.mouse.justMoved) { - updatePointerDrag(FlxG.mouse); + updatePointerMove(FlxG.mouse); } if (FlxG.mouse.released) @@ -628,6 +651,16 @@ class FlxInputText extends FlxText implements IFlxInputText { _mouseDown = true; updatePointerPress(FlxG.mouse); + var currentTime = FlxG.game.ticks; + if (currentTime - _lastClickTime < 500) + { + updatePointerDoublePress(FlxG.mouse); + _lastClickTime = 0; + } + else + { + _lastClickTime = currentTime; + } } if (FlxG.mouse.wheel != 0) @@ -672,8 +705,36 @@ class FlxInputText extends FlxText implements IFlxInputText relativePos.put(); } + function updatePointerDrag(pointer:FlxPointer, elapsed:Float) + { + var relativePos = getRelativePosition(pointer); + + if (relativePos.x > width - 1) + { + scrollH += Std.int(Math.max(Math.min((relativePos.x - width) * .1, 10), 1)); + } + else if (relativePos.x < 1) + { + scrollH -= Std.int(Math.max(Math.min(relativePos.x * -.1, 10), 1)); + } + + _scrollVCounter += elapsed; + + if (_scrollVCounter > 0.1) + { + if (relativePos.y > height - 2) + { + scrollV = Std.int(Math.min(scrollV + Math.max(Math.min((relativePos.y - height) * .03, 5), 1), maxScrollV)); + } + else if (relativePos.y < 2) + { + scrollV -= Std.int(Math.max(Math.min(relativePos.y * -.03, 5), 1)); + } + _scrollVCounter = 0; + } + } - function updatePointerDrag(pointer:FlxPointer):Void + function updatePointerMove(pointer:FlxPointer):Void { if (_selectionIndex < 0) return; @@ -708,6 +769,33 @@ class FlxInputText extends FlxText implements IFlxInputText _pointerCamera = null; } + function updatePointerDoublePress(pointer:FlxPointer):Void + { + var rightPos = text.length; + if (text.length > 0 && _caretIndex >= 0 && rightPos >= _caretIndex) + { + var leftPos = -1; + var pos = 0; + var startPos = FlxMath.maxInt(_caretIndex, 1); + + for (c in DELIMITERS) + { + pos = text.lastIndexOf(c, startPos - 1); + if (pos > leftPos) + leftPos = pos + 1; + + pos = text.indexOf(c, startPos); + if (pos < rightPos && pos != -1) + rightPos = pos; + } + + if (leftPos != rightPos) + { + setSelection(leftPos, rightPos); + } + } + } + function getRelativePosition(pointer:FlxPointer):FlxPoint { var pointerPos = pointer.getWorldPosition(_pointerCamera); @@ -767,7 +855,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (caretColor != value) { caretColor = value; - regenCaret(); + _caret.color = caretColor; } return value; @@ -873,10 +961,6 @@ class FlxInputText extends FlxText implements IFlxInputText if (textField.multiline != value) { textField.multiline = value; - // `wordWrap` will still add new lines even if `multiline` is false, - // let's change it accordingly - wordWrap = value; - _regen = true; } return value; @@ -912,7 +996,6 @@ class FlxInputText extends FlxText implements IFlxInputText { textField.scrollH = value; _regen = true; - updateSelectionSprites(); } return value; } @@ -932,7 +1015,6 @@ class FlxInputText extends FlxText implements IFlxInputText { textField.scrollV = value; _regen = true; - updateSelectionSprites(); } return value; } @@ -962,7 +1044,7 @@ class FlxInputText extends FlxText implements IFlxInputText for (box in _selectionBoxes) { if (box != null) - box.makeGraphic(1, 1, selectionColor); + box.color = selectionColor; } } From 90646809548d15e71630bcb16fddcf75adb98ffb Mon Sep 17 00:00:00 2001 From: Starmapo Date: Fri, 21 Jun 2024 17:53:47 -0400 Subject: [PATCH 11/48] Action callbacks --- flixel/text/FlxInputText.hx | 87 ++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 4cf87d5024..c04d3f34a5 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -15,12 +15,22 @@ import openfl.utils.QName; class FlxInputText extends FlxText implements IFlxInputText { + public static inline var BACKSPACE_ACTION:String = "backspace"; + + public static inline var DELETE_ACTION:String = "delete"; + + public static inline var ENTER_ACTION:String = "enter"; + + public static inline var INPUT_ACTION:String = "input"; + static inline var GUTTER:Int = 2; static final DELIMITERS:Array = ['\n', '.', '!', '?', ',', ' ', ';', ':', '(', ')', '-', '_', '/']; public var bottomScrollV(get, never):Int; + public var callback:String->String->Void; + public var caretColor(default, set):FlxColor = FlxColor.WHITE; public var caretIndex(get, set):Int; @@ -144,8 +154,8 @@ class FlxInputText extends FlxText implements IFlxInputText { switch (action) { - case ADD_TEXT(text): - replaceSelectedText(text); + case ADD_TEXT(newText): + addText(newText); case MOVE_CURSOR(type, shiftKey): moveCursor(type, shiftKey); case COMMAND(cmd): @@ -163,6 +173,15 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelection(); } + function addText(newText:String):Void + { + newText = filterText(newText); + if (newText.length > 0) + { + replaceSelectedText(newText); + onChange(INPUT_ACTION); + } + } function drawSprite(sprite:FlxSprite):Void { @@ -173,6 +192,25 @@ class FlxInputText extends FlxText implements IFlxInputText sprite.draw(); } } + function filterText(newText:String):String + { + if (maxLength > 0) + { + var removeLength = (selectionEndIndex - selectionBeginIndex); + var newMaxLength = maxLength - text.length + removeLength; + + if (newMaxLength <= 0) + { + newText = ""; + } + else if (newMaxLength < newText.length) + { + newText = newText.substr(0, newMaxLength); + } + } + + return newText; + } function getCharIndexOnDifferentLine(charIndex:Int, lineIndex:Int):Int { @@ -391,6 +429,11 @@ class FlxInputText extends FlxText implements IFlxInputText setSelection(_selectionIndex, _caretIndex); } } + function onChange(action:String):Void + { + if (callback != null) + callback(text, action); + } function regenCaret():Void { @@ -409,21 +452,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (beginIndex == endIndex && maxLength > 0 && text.length == maxLength) return; - - if (beginIndex > text.length) - { - beginIndex = text.length; - } - if (endIndex > text.length) - { - endIndex = text.length; - } - if (endIndex < beginIndex) - { - var cache = endIndex; - endIndex = beginIndex; - beginIndex = cache; - } + if (beginIndex < 0) { beginIndex = 0; @@ -436,22 +465,7 @@ class FlxInputText extends FlxText implements IFlxInputText { if (endIndex < beginIndex || beginIndex < 0 || endIndex > text.length || newText == null) return; - - if (maxLength > 0) - { - var removeLength = (endIndex - beginIndex); - var newMaxLength = maxLength - text.length + removeLength; - - if (newMaxLength <= 0) - { - newText = ""; - } - else if (newMaxLength < newText.length) - { - newText = newText.substr(0, newMaxLength); - } - } - + text = text.substring(0, beginIndex) + newText + text.substring(endIndex); _selectionIndex = _caretIndex = beginIndex + newText.length; @@ -465,8 +479,9 @@ class FlxInputText extends FlxText implements IFlxInputText case NEW_LINE: if (multiline) { - replaceSelectedText("\n"); + addText("\n"); } + onChange(ENTER_ACTION); case DELETE_LEFT: if (_selectionIndex == _caretIndex && _caretIndex > 0) { @@ -477,6 +492,7 @@ class FlxInputText extends FlxText implements IFlxInputText { replaceSelectedText(""); _selectionIndex = _caretIndex; + onChange(BACKSPACE_ACTION); } case DELETE_RIGHT: if (_selectionIndex == _caretIndex && _caretIndex < text.length) @@ -488,6 +504,7 @@ class FlxInputText extends FlxText implements IFlxInputText { replaceSelectedText(""); _selectionIndex = _caretIndex; + onChange(DELETE_ACTION); } case COPY: if (_caretIndex != _selectionIndex && !passwordMode) @@ -504,7 +521,7 @@ class FlxInputText extends FlxText implements IFlxInputText case PASTE: if (Clipboard.text != null) { - replaceSelectedText(Clipboard.text); + addText(Clipboard.text); } case SELECT_ALL: _selectionIndex = 0; From 2257a368c52e0bfe0bc3a3cc310b1998668fa977 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Fri, 21 Jun 2024 18:19:58 -0400 Subject: [PATCH 12/48] Fix "final" keyword screwing up code climate --- checkstyle.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/checkstyle.json b/checkstyle.json index b603612dab..60a10dcb3d 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -35,7 +35,8 @@ "STATIC", "MACRO", "INLINE", - "DYNAMIC" + "DYNAMIC", + "FINAL" ] } }, From 2e043ab72c4806c4e5489f6fb85ffb1b7b52ac0d Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sat, 22 Jun 2024 12:32:39 -0400 Subject: [PATCH 13/48] Various fixes & improvements - Caret is now positioned properly with different alignments - Caret is now clipped inside the text bounds - Caret is now automatically resized when changing `bold`, `font`, `italic`, `size` or `systemFont` variables - Fixed crash when pressing down a key while there isn't a focused input text - Fixed selected text format overwriting the border color - Fixed caret not being visible when text is empty - Fixed selection boxes sometimes not being updated immediately - Added `useSelectedTextFormat` variable - Double press check is now when the mouse is released (same as OpenFL) --- flixel/system/frontEnds/InputTextFrontEnd.hx | 3 + flixel/text/FlxInputText.hx | 151 ++++++++++++++++--- 2 files changed, 133 insertions(+), 21 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index 12ae5327a5..b8530263b3 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -51,6 +51,9 @@ class InputTextFrontEnd function onKeyDown(key:KeyCode, modifier:KeyModifier) { + if (focus == null) + return; + // Taken from OpenFL's `TextField` var modifierPressed = #if mac modifier.metaKey #elseif js(modifier.metaKey || modifier.ctrlKey) #else (modifier.ctrlKey && !modifier.altKey) #end; diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index c04d3f34a5..3aac6dfddf 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -60,6 +60,14 @@ class FlxInputText extends FlxText implements IFlxInputText public var selectionColor(default, set):FlxColor = FlxColor.BLACK; public var selectionEndIndex(get, never):Int; + + /** + * If `false`, no extra format will be applied for selected text. + * + * Useful if you are using `addFormat()`, as the selected text format might + * overwrite some of their properties. + */ + public var useSelectedTextFormat(default, set):Bool = true; var _caret:FlxSprite; var _caretIndex:Int = -1; @@ -81,9 +89,9 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionFormat.color = selectedTextColor; - _caret = new FlxSprite(); + _caret = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); _caret.visible = false; - regenCaret(); + updateCaretSize(); updateCaretPosition(); FlxG.inputText.registerInputText(this); @@ -103,6 +111,8 @@ class FlxInputText extends FlxText implements IFlxInputText override function draw():Void { + regenGraphic(); + for (box in _selectionBoxes) drawSprite(box); @@ -134,7 +144,9 @@ class FlxInputText extends FlxText implements IFlxInputText super.applyFormats(formatAdjusted, useBorderColor); - textField.setTextFormat(_selectionFormat, selectionBeginIndex, selectionEndIndex); + if (!useBorderColor && useSelectedTextFormat) + textField.setTextFormat(_selectionFormat, selectionBeginIndex, selectionEndIndex); + // set the scroll back to how it was scrollH = cacheScrollH; scrollV = cacheScrollV; @@ -173,6 +185,7 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelection(); } + function addText(newText:String):Void { newText = filterText(newText); @@ -192,6 +205,7 @@ class FlxInputText extends FlxText implements IFlxInputText sprite.draw(); } } + function filterText(newText:String):String { if (maxLength > 0) @@ -211,6 +225,16 @@ class FlxInputText extends FlxText implements IFlxInputText return newText; } + + function getCaretOffsetX():Float + { + return switch (alignment) + { + case CENTER: (width / 2); + case RIGHT: width - GUTTER; + default: GUTTER; + } + } function getCharIndexOnDifferentLine(charIndex:Int, lineIndex:Int):Int { @@ -297,6 +321,10 @@ class FlxInputText extends FlxText implements IFlxInputText function isCaretLineVisible():Bool { + // `getLineIndexOfChar()` will return -1 if text is empty, but we still want the caret to show up + if (text.length == 0) + return true; + var line = textField.getLineIndexOfChar(_caretIndex); return line >= scrollV - 1 && line <= bottomScrollV - 1; } @@ -429,16 +457,12 @@ class FlxInputText extends FlxText implements IFlxInputText setSelection(_selectionIndex, _caretIndex); } } + function onChange(action:String):Void { if (callback != null) callback(text, action); } - - function regenCaret():Void - { - _caret.makeGraphic(caretWidth, Std.int(size + 2), FlxColor.WHITE); - } function replaceSelectedText(newText:String):Void { @@ -537,7 +561,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (text.length == 0) { - _caret.setPosition(x + GUTTER, y + GUTTER); + _caret.setPosition(x + getCaretOffsetX(), y + GUTTER); } else { @@ -546,12 +570,31 @@ class FlxInputText extends FlxText implements IFlxInputText { _caret.setPosition(x + boundaries.right - scrollH, y + boundaries.y - getLineY(scrollV - 1)); } - else // end of line + else { - var lineIndex = textField.getLineIndexOfChar(_caretIndex); - _caret.setPosition(x + GUTTER, y + GUTTER + getLineY(lineIndex) - getLineY(scrollV - 1)); + boundaries = textField.getCharBoundaries(_caretIndex); + if (boundaries != null) + { + _caret.setPosition(x + boundaries.x - scrollH, y + boundaries.y - getLineY(scrollV - 1)); + } + else // end of line + { + var lineIndex = textField.getLineIndexOfChar(_caretIndex); + _caret.setPosition(x + getCaretOffsetX(), y + GUTTER + getLineY(lineIndex) - getLineY(scrollV - 1)); + } } } + + _caret.clipRect = _caret.getHitbox(_caret.clipRect).clipTo(FlxRect.weak(x, y, width, height)).offset(-_caret.x, -_caret.y); + } + + function updateCaretSize():Void + { + if (_caret == null) + return; + + _caret.setGraphicSize(caretWidth, textField.getLineMetrics(0).height); + _caret.updateHitbox(); } function updateSelection():Void @@ -660,14 +703,7 @@ class FlxInputText extends FlxText implements IFlxInputText { _mouseDown = false; updatePointerRelease(FlxG.mouse); - } - } - if (checkPointerOverlap(FlxG.mouse)) - { - if (FlxG.mouse.justPressed) - { - _mouseDown = true; - updatePointerPress(FlxG.mouse); + var currentTime = FlxG.game.ticks; if (currentTime - _lastClickTime < 500) { @@ -679,6 +715,14 @@ class FlxInputText extends FlxText implements IFlxInputText _lastClickTime = currentTime; } } + } + if (checkPointerOverlap(FlxG.mouse)) + { + if (FlxG.mouse.justPressed) + { + _mouseDown = true; + updatePointerPress(FlxG.mouse); + } if (FlxG.mouse.wheel != 0) { @@ -722,6 +766,7 @@ class FlxInputText extends FlxText implements IFlxInputText relativePos.put(); } + function updatePointerDrag(pointer:FlxPointer, elapsed:Float) { var relativePos = getRelativePosition(pointer); @@ -823,6 +868,17 @@ class FlxInputText extends FlxText implements IFlxInputText } #end + override function set_bold(value:Bool):Bool + { + if (bold != value) + { + super.set_bold(value); + updateCaretSize(); + } + + return value; + } + override function set_color(value:FlxColor):FlxColor { if (color != value) @@ -833,6 +889,49 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + override function set_font(value:String):String + { + if (font != value) + { + super.set_font(value); + updateCaretSize(); + } + + return value; + } + + override function set_italic(value:Bool):Bool + { + if (italic != value) + { + super.set_italic(value); + updateCaretSize(); + } + + return value; + } + + override function set_size(value:Int):Int + { + if (size != value) + { + super.set_size(value); + updateCaretSize(); + } + + return value; + } + + override function set_systemFont(value:String):String + { + if (systemFont != value) + { + super.set_systemFont(value); + updateCaretSize(); + } + + return value; + } override function set_text(value:String):String { @@ -903,7 +1002,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (caretWidth != value) { caretWidth = value; - regenCaret(); + updateCaretSize(); } return value; @@ -1072,4 +1171,14 @@ class FlxInputText extends FlxText implements IFlxInputText { return FlxMath.maxInt(_caretIndex, _selectionIndex); } + function set_useSelectedTextFormat(value:Bool):Bool + { + if (useSelectedTextFormat != value) + { + useSelectedTextFormat = value; + _regen = true; + } + + return value; + } } From febc1f395743222f5554de28b24422794fce8d23 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sat, 22 Jun 2024 15:24:34 -0400 Subject: [PATCH 14/48] Add `forceCase` and filterMode` - Moved action callback types to an enum abstract --- flixel/text/FlxInputText.hx | 117 ++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 3aac6dfddf..b05473d7d0 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -15,21 +15,13 @@ import openfl.utils.QName; class FlxInputText extends FlxText implements IFlxInputText { - public static inline var BACKSPACE_ACTION:String = "backspace"; - - public static inline var DELETE_ACTION:String = "delete"; - - public static inline var ENTER_ACTION:String = "enter"; - - public static inline var INPUT_ACTION:String = "input"; - static inline var GUTTER:Int = 2; static final DELIMITERS:Array = ['\n', '.', '!', '?', ',', ' ', ';', ':', '(', ')', '-', '_', '/']; public var bottomScrollV(get, never):Int; - public var callback:String->String->Void; + public var callback:String->FlxInputTextAction->Void; public var caretColor(default, set):FlxColor = FlxColor.WHITE; @@ -37,6 +29,12 @@ class FlxInputText extends FlxText implements IFlxInputText public var caretWidth(default, set):Int = 1; + public var customFilterPattern(default, set):EReg; + + public var filterMode(default, set):FlxInputTextFilterMode = NO_FILTER; + + public var forceCase(default, set):FlxInputTextCase = ALL_CASES; + public var hasFocus(default, set):Bool = false; public var maxLength(default, set):Int = 0; @@ -188,7 +186,7 @@ class FlxInputText extends FlxText implements IFlxInputText function addText(newText:String):Void { - newText = filterText(newText); + newText = filterText(newText, true); if (newText.length > 0) { replaceSelectedText(newText); @@ -206,11 +204,11 @@ class FlxInputText extends FlxText implements IFlxInputText } } - function filterText(newText:String):String + function filterText(newText:String, selection:Bool = false):String { if (maxLength > 0) { - var removeLength = (selectionEndIndex - selectionBeginIndex); + var removeLength = selection ? (selectionEndIndex - selectionBeginIndex) : text.length; var newMaxLength = maxLength - text.length + removeLength; if (newMaxLength <= 0) @@ -223,6 +221,34 @@ class FlxInputText extends FlxText implements IFlxInputText } } + if (forceCase == UPPER_CASE) + { + newText = newText.toUpperCase(); + } + else if (forceCase == LOWER_CASE) + { + newText = newText.toLowerCase(); + } + + if (filterMode != NO_FILTER) + { + var pattern = switch (filterMode) + { + case ONLY_ALPHA: + ~/[^a-zA-Z]*/g; + case ONLY_NUMERIC: + ~/[^0-9]*/g; + case ONLY_ALPHANUMERIC: + ~/[^a-zA-Z0-9]*/g; + case CUSTOM_FILTER: + customFilterPattern; + default: + throw "Unknown filterMode (" + filterMode + ")"; + } + if (pattern != null) + newText = pattern.replace(newText, ""); + } + return newText; } @@ -458,7 +484,7 @@ class FlxInputText extends FlxText implements IFlxInputText } } - function onChange(action:String):Void + function onChange(action:FlxInputTextAction):Void { if (callback != null) callback(text, action); @@ -1008,6 +1034,42 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function set_customFilterPattern(value:EReg):EReg + { + if (customFilterPattern != value) + { + customFilterPattern = value; + if (filterMode == CUSTOM_FILTER) + { + text = filterText(text); + } + } + + return value; + } + + function set_filterMode(value:FlxInputTextFilterMode):FlxInputTextFilterMode + { + if (filterMode != value) + { + filterMode = value; + text = filterText(text); + } + + return value; + } + + function set_forceCase(value:FlxInputTextCase):FlxInputTextCase + { + if (forceCase != value) + { + forceCase = value; + text = filterText(text); + } + + return value; + } + function set_hasFocus(value:Bool):Bool { if (hasFocus != value) @@ -1048,10 +1110,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (maxLength != value) { maxLength = value; - if (maxLength > 0 && text.length > maxLength) - { - text = text.substr(0, maxLength); - } + text = filterText(text); } return value; @@ -1182,3 +1241,27 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } } + +enum abstract FlxInputTextAction(String) from String to String +{ + var INPUT_ACTION = "input"; + var BACKSPACE_ACTION = "backspace"; + var DELETE_ACTION = "delete"; + var ENTER_ACTION = "enter"; +} + +enum abstract FlxInputTextCase(Int) from Int to Int +{ + var ALL_CASES = 0; + var UPPER_CASE = 1; + var LOWER_CASE = 2; +} + +enum abstract FlxInputTextFilterMode(Int) from Int to Int +{ + var NO_FILTER = 0; + var ONLY_ALPHA = 1; + var ONLY_NUMERIC = 2; + var ONLY_ALPHANUMERIC = 3; + var CUSTOM_FILTER = 4; +} \ No newline at end of file From abbc89b2ee81fa46b2a5e1306b8363170fbe3fde Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sun, 23 Jun 2024 19:55:10 -0400 Subject: [PATCH 15/48] Added background for text input - Added `focusGained` and `focusLost` callbacks - Fixed selection boxes not being clipped properly when they're compeletely out of bounds --- flixel/text/FlxInputText.hx | 169 +++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 3 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index b05473d7d0..348a8d6643 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -7,6 +7,7 @@ import flixel.math.FlxRect; import flixel.system.frontEnds.InputTextFrontEnd; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; +import flixel.util.FlxSpriteUtil; import lime.system.Clipboard; import openfl.display.BitmapData; import openfl.geom.Rectangle; @@ -19,6 +20,10 @@ class FlxInputText extends FlxText implements IFlxInputText static final DELIMITERS:Array = ['\n', '.', '!', '?', ',', ' ', ';', ':', '(', ')', '-', '_', '/']; + public var background(default, set):Bool = false; + + public var backgroundColor(default, set):FlxColor = FlxColor.TRANSPARENT; + public var bottomScrollV(get, never):Int; public var callback:String->FlxInputTextAction->Void; @@ -31,8 +36,16 @@ class FlxInputText extends FlxText implements IFlxInputText public var customFilterPattern(default, set):EReg; + public var fieldBorderColor(default, set):FlxColor = FlxColor.BLACK; + + public var fieldBorderThickness(default, set):Int = 1; + public var filterMode(default, set):FlxInputTextFilterMode = NO_FILTER; + public var focusGained:Void->Void; + + public var focusLost:Void->Void; + public var forceCase(default, set):FlxInputTextCase = ALL_CASES; public var hasFocus(default, set):Bool = false; @@ -67,8 +80,10 @@ class FlxInputText extends FlxText implements IFlxInputText */ public var useSelectedTextFormat(default, set):Bool = true; + var _backgroundSprite:FlxSprite; var _caret:FlxSprite; var _caretIndex:Int = -1; + var _fieldBorderSprite:FlxSprite; var _lastClickTime:Int = 0; var _mouseDown:Bool = false; var _pointerCamera:FlxCamera; @@ -77,9 +92,11 @@ class FlxInputText extends FlxText implements IFlxInputText var _selectionFormat:TextFormat = new TextFormat(); var _selectionIndex:Int = -1; - public function new(x:Float = 0, y:Float = 0, fieldWidth:Float = 0, ?text:String, size:Int = 8, embeddedFont:Bool = true) + public function new(x:Float = 0, y:Float = 0, fieldWidth:Float = 0, ?text:String, size:Int = 8, textColor:FlxColor = FlxColor.BLACK, + backgroundColor:FlxColor = FlxColor.WHITE, embeddedFont:Bool = true) { super(x, y, fieldWidth, text, size, embeddedFont); + this.backgroundColor = backgroundColor; // If the text field's type isn't INPUT and there's a new line at the end // of the text, it won't be counted for in `numLines` @@ -92,6 +109,13 @@ class FlxInputText extends FlxText implements IFlxInputText updateCaretSize(); updateCaretPosition(); + color = textColor; + + if (backgroundColor != FlxColor.TRANSPARENT) + { + background = true; + } + FlxG.inputText.registerInputText(this); } @@ -111,6 +135,9 @@ class FlxInputText extends FlxText implements IFlxInputText { regenGraphic(); + drawSprite(_fieldBorderSprite); + drawSprite(_backgroundSprite); + for (box in _selectionBoxes) drawSprite(box); @@ -123,7 +150,9 @@ class FlxInputText extends FlxText implements IFlxInputText { FlxG.inputText.unregisterInputText(this); + _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); _caret = FlxDestroyUtil.destroy(_caret); + _fieldBorderSprite = FlxDestroyUtil.destroy(_fieldBorderSprite); _pointerCamera = null; while (_selectionBoxes.length > 0) FlxDestroyUtil.destroy(_selectionBoxes.pop()); @@ -490,6 +519,17 @@ class FlxInputText extends FlxText implements IFlxInputText callback(text, action); } + function regenBackground():Void + { + if (!background) + return; + + _fieldBorderSprite.makeGraphic(Std.int(fieldWidth) + (fieldBorderThickness * 2), Std.int(fieldHeight) + (fieldBorderThickness * 2), fieldBorderColor); + _backgroundSprite.makeGraphic(Std.int(fieldWidth), Std.int(fieldHeight), backgroundColor); + + updateBackgroundPosition(); + } + function replaceSelectedText(newText:String):Void { if (newText == null) @@ -579,6 +619,15 @@ class FlxInputText extends FlxText implements IFlxInputText setSelection(_selectionIndex, _caretIndex); } } + + function updateBackgroundPosition():Void + { + if (!background) + return; + + _fieldBorderSprite.setPosition(x - fieldBorderThickness, y - fieldBorderThickness); + _backgroundSprite.setPosition(x, y); + } function updateCaretPosition():Void { @@ -682,13 +731,16 @@ class FlxInputText extends FlxText implements IFlxInputText box = _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); box.color = selectionColor; } - + var boxRect = FlxRect.get(startBoundaries.x - scrollH, startBoundaries.y - scrollY, endBoundaries.right - startBoundaries.x, startBoundaries.height); boxRect.clipTo(FlxRect.weak(0, 0, width, height)); // clip the selection box inside the text sprite box.setPosition(x + boxRect.x, y + boxRect.y); - box.setGraphicSize(boxRect.width, boxRect.height); + if (boxRect.width > 0 && boxRect.height > 0) + box.setGraphicSize(boxRect.width, boxRect.height); + else + box.scale.set(); box.updateHitbox(); box.visible = true; @@ -712,6 +764,12 @@ class FlxInputText extends FlxText implements IFlxInputText updateCaretPosition(); updateSelectionBoxes(); } + function updateSpritePositions():Void + { + updateBackgroundPosition(); + updateCaretPosition(); + updateSelectionBoxes(); + } #if FLX_MOUSE function updateInput(elapsed:Float):Void @@ -915,6 +973,28 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + override function set_fieldHeight(value:Float):Float + { + if (fieldHeight != value) + { + super.set_fieldHeight(value); + regenBackground(); + } + + return value; + } + + override function set_fieldWidth(value:Float):Float + { + if (fieldWidth != value) + { + super.set_fieldWidth(value); + regenBackground(); + } + + return value; + } + override function set_font(value:String):String { if (font != value) @@ -987,6 +1067,64 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + override function set_x(value:Float) + { + if (x != value) + { + super.set_x(value); + updateSpritePositions(); + } + + return value; + } + + override function set_y(value:Float) + { + if (y != value) + { + super.set_y(value); + updateSpritePositions(); + } + + return value; + } + + function set_background(value:Bool):Bool + { + if (background != value) + { + background = value; + + if (background) + { + if (_backgroundSprite == null) + _backgroundSprite = new FlxSprite(); + if (_fieldBorderSprite == null) + _fieldBorderSprite = new FlxSprite(); + + regenBackground(); + } + else + { + _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); + _fieldBorderSprite = FlxDestroyUtil.destroy(_fieldBorderSprite); + } + } + + return value; + } + + function set_backgroundColor(value:FlxColor):FlxColor + { + if (backgroundColor != value) + { + backgroundColor = value; + regenBackground(); + } + + return value; + } + function get_bottomScrollV():Int { return textField.bottomScrollV; @@ -1047,6 +1185,27 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function set_fieldBorderColor(value:FlxColor):FlxColor + { + if (fieldBorderColor != value) + { + fieldBorderColor = value; + regenBackground(); + } + + return value; + } + + function set_fieldBorderThickness(value:Int):Int + { + if (fieldBorderThickness != value) + { + fieldBorderThickness = value; + regenBackground(); + } + + return value; + } function set_filterMode(value:FlxInputTextFilterMode):FlxInputTextFilterMode { @@ -1087,6 +1246,8 @@ class FlxInputText extends FlxText implements IFlxInputText } _caret.visible = true; + if (focusGained != null) + focusGained(); } else if (FlxG.inputText.focus == this) { @@ -1099,6 +1260,8 @@ class FlxInputText extends FlxText implements IFlxInputText } _caret.visible = false; + if (focusLost != null) + focusLost(); } } From 9e14f2789048a7f7d9d233f01fbfbd97579dccae Mon Sep 17 00:00:00 2001 From: Starmapo Date: Mon, 24 Jun 2024 10:18:18 -0400 Subject: [PATCH 16/48] Some improvements - Added bounds check while changing `caretIndex`, `caretWidth`, `fieldBorderThickness` and `maxLength` - FlxInputText is now single-line by default - Fixed text scroll being reset while moving selection with mouse - Caret index now starts at the end of the text if focus is enabled through code - Background now gets regenerated in `regenGraphic` instead of instantly after changing a related variable --- flixel/text/FlxInputText.hx | 101 +++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 348a8d6643..af4644741b 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -87,6 +87,7 @@ class FlxInputText extends FlxText implements IFlxInputText var _lastClickTime:Int = 0; var _mouseDown:Bool = false; var _pointerCamera:FlxCamera; + var _regenBackground:Bool = false; var _scrollVCounter:Float = 0; var _selectionBoxes:Array = []; var _selectionFormat:TextFormat = new TextFormat(); @@ -98,6 +99,7 @@ class FlxInputText extends FlxText implements IFlxInputText super(x, y, fieldWidth, text, size, embeddedFont); this.backgroundColor = backgroundColor; + wordWrap = multiline = false; // If the text field's type isn't INPUT and there's a new line at the end // of the text, it won't be counted for in `numLines` textField.type = INPUT; @@ -171,7 +173,7 @@ class FlxInputText extends FlxText implements IFlxInputText super.applyFormats(formatAdjusted, useBorderColor); - if (!useBorderColor && useSelectedTextFormat) + if (!useBorderColor && useSelectedTextFormat && selectionEndIndex > selectionBeginIndex) textField.setTextFormat(_selectionFormat, selectionBeginIndex, selectionEndIndex); // set the scroll back to how it was @@ -187,6 +189,8 @@ class FlxInputText extends FlxText implements IFlxInputText if (_caret != null && regenSelection) updateSelectionSprites(); + if (_regenBackground) + regenBackground(); } public function dispatchTypingAction(action:TypingAction):Void @@ -524,10 +528,29 @@ class FlxInputText extends FlxText implements IFlxInputText if (!background) return; - _fieldBorderSprite.makeGraphic(Std.int(fieldWidth) + (fieldBorderThickness * 2), Std.int(fieldHeight) + (fieldBorderThickness * 2), fieldBorderColor); - _backgroundSprite.makeGraphic(Std.int(fieldWidth), Std.int(fieldHeight), backgroundColor); + if (fieldBorderThickness > 0) + { + _fieldBorderSprite.makeGraphic(Std.int(fieldWidth) + (fieldBorderThickness * 2), Std.int(fieldHeight) + (fieldBorderThickness * 2), + fieldBorderColor); + _fieldBorderSprite.visible = true; + } + else + { + _fieldBorderSprite.visible = false; + } + + if (backgroundColor.alpha > 0) + { + _backgroundSprite.makeGraphic(Std.int(fieldWidth), Std.int(fieldHeight), backgroundColor); + _backgroundSprite.visible = true; + } + else + { + _backgroundSprite.visible = false; + } updateBackgroundPosition(); + _regenBackground = false; } function replaceSelectedText(newText:String):Void @@ -672,10 +695,18 @@ class FlxInputText extends FlxText implements IFlxInputText _caret.updateHitbox(); } - function updateSelection():Void + function updateSelection(keepScroll:Bool = false):Void { + var cacheScrollH = scrollH; + var cacheScrollV = scrollV; + textField.setSelection(_selectionIndex, _caretIndex); _regen = true; + if (keepScroll) + { + scrollH = cacheScrollH; + scrollV = cacheScrollV; + } } function updateSelectionBoxes():Void @@ -846,7 +877,7 @@ class FlxInputText extends FlxText implements IFlxInputText var relativePos = getRelativePosition(pointer); _caretIndex = getCharAtPosition(relativePos.x, relativePos.y); _selectionIndex = _caretIndex; - setSelection(_selectionIndex, _caretIndex); + updateSelection(true); relativePos.put(); } @@ -891,7 +922,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (char != _caretIndex) { _caretIndex = char; - updateSelection(); + updateSelection(true); } relativePos.put(); @@ -910,6 +941,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = leftPos; _caretIndex = rightPos; + updateSelection(true); relativePos.put(); _pointerCamera = null; @@ -978,7 +1010,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (fieldHeight != value) { super.set_fieldHeight(value); - regenBackground(); + _regenBackground = true; } return value; @@ -989,7 +1021,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (fieldWidth != value) { super.set_fieldWidth(value); - regenBackground(); + _regenBackground = true; } return value; @@ -1045,23 +1077,27 @@ class FlxInputText extends FlxText implements IFlxInputText { super.set_text(value); - if (hasFocus) + if (textField != null) { - if (text.length < _selectionIndex) + if (hasFocus) { - _selectionIndex = text.length; + if (text.length < _selectionIndex) + { + _selectionIndex = text.length; + } + if (text.length < _caretIndex) + { + _caretIndex = text.length; + } } - if (text.length < _caretIndex) + else { - _caretIndex = text.length; + _selectionIndex = 0; + _caretIndex = 0; } + + setSelection(_selectionIndex, _caretIndex); } - else - { - _selectionIndex = 0; - _caretIndex = 0; - } - setSelection(_selectionIndex, _caretIndex); } return value; @@ -1102,7 +1138,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (_fieldBorderSprite == null) _fieldBorderSprite = new FlxSprite(); - regenBackground(); + _regenBackground = true; } else { @@ -1119,7 +1155,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (backgroundColor != value) { backgroundColor = value; - regenBackground(); + _regenBackground = true; } return value; @@ -1148,13 +1184,13 @@ class FlxInputText extends FlxText implements IFlxInputText function set_caretIndex(value:Int):Int { + if (value < 0) + value = 0; + if (value > text.length) + value = text.length; if (_caretIndex != value) { _caretIndex = value; - if (_caretIndex < 0) - _caretIndex = 0; - if (_caretIndex > text.length) - _caretIndex = text.length; setSelection(_caretIndex, _caretIndex); } @@ -1163,6 +1199,8 @@ class FlxInputText extends FlxText implements IFlxInputText function set_caretWidth(value:Int):Int { + if (value < 1) + value = 1; if (caretWidth != value) { caretWidth = value; @@ -1185,12 +1223,13 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + function set_fieldBorderColor(value:FlxColor):FlxColor { if (fieldBorderColor != value) { fieldBorderColor = value; - regenBackground(); + _regenBackground = true; } return value; @@ -1198,10 +1237,12 @@ class FlxInputText extends FlxText implements IFlxInputText function set_fieldBorderThickness(value:Int):Int { + if (value < 0) + value = 0; if (fieldBorderThickness != value) { fieldBorderThickness = value; - regenBackground(); + _regenBackground = true; } return value; @@ -1242,7 +1283,7 @@ class FlxInputText extends FlxText implements IFlxInputText { _caretIndex = text.length; _selectionIndex = _caretIndex; - setSelection(_selectionIndex, _caretIndex); + updateSelection(true); } _caret.visible = true; @@ -1256,7 +1297,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (_selectionIndex != _caretIndex) { _selectionIndex = _caretIndex; - setSelection(_selectionIndex, _caretIndex); + updateSelection(true); } _caret.visible = false; @@ -1270,6 +1311,8 @@ class FlxInputText extends FlxText implements IFlxInputText function set_maxLength(value:Int):Int { + if (value < 0) + value = 0; if (maxLength != value) { maxLength = value; From 8010c88a67dbb39f3740177f964ae92b8dfa149d Mon Sep 17 00:00:00 2001 From: Starmapo Date: Mon, 24 Jun 2024 12:12:31 -0400 Subject: [PATCH 17/48] Added `editable` and selectable` variables - Added change and scroll action callbacks - Made `replaceSelectedText()` public --- flixel/text/FlxInputText.hx | 90 ++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index af4644741b..a4a4a48be3 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -35,6 +35,8 @@ class FlxInputText extends FlxText implements IFlxInputText public var caretWidth(default, set):Int = 1; public var customFilterPattern(default, set):EReg; + + public var editable:Bool = true; public var fieldBorderColor(default, set):FlxColor = FlxColor.BLACK; @@ -64,6 +66,8 @@ class FlxInputText extends FlxText implements IFlxInputText public var scrollV(get, set):Int; + public var selectable:Bool = true; + public var selectedTextColor(default, set):FlxColor = FlxColor.WHITE; public var selectionBeginIndex(get, never):Int; @@ -198,7 +202,10 @@ class FlxInputText extends FlxText implements IFlxInputText switch (action) { case ADD_TEXT(newText): - addText(newText); + if (editable) + { + addText(newText); + } case MOVE_CURSOR(type, shiftKey): moveCursor(type, shiftKey); case COMMAND(cmd): @@ -206,6 +213,27 @@ class FlxInputText extends FlxText implements IFlxInputText } } + public function replaceSelectedText(newText:String):Void + { + if (newText == null) + newText = ""; + if (newText == "" && _selectionIndex == _caretIndex) + return; + + var beginIndex = selectionBeginIndex; + var endIndex = selectionEndIndex; + + if (beginIndex == endIndex && maxLength > 0 && text.length == maxLength) + return; + + if (beginIndex < 0) + { + beginIndex = 0; + } + + replaceText(beginIndex, endIndex, newText); + } + public function setSelection(beginIndex:Int, endIndex:Int):Void { _selectionIndex = beginIndex; @@ -520,7 +548,13 @@ class FlxInputText extends FlxText implements IFlxInputText function onChange(action:FlxInputTextAction):Void { if (callback != null) + { callback(text, action); + if (action == INPUT_ACTION || action == BACKSPACE_ACTION || action == DELETE_ACTION) + { + callback(text, CHANGE_ACTION); + } + } } function regenBackground():Void @@ -553,27 +587,6 @@ class FlxInputText extends FlxText implements IFlxInputText _regenBackground = false; } - function replaceSelectedText(newText:String):Void - { - if (newText == null) - newText = ""; - if (newText == "" && _selectionIndex == _caretIndex) - return; - - var beginIndex = selectionBeginIndex; - var endIndex = selectionEndIndex; - - if (beginIndex == endIndex && maxLength > 0 && text.length == maxLength) - return; - - if (beginIndex < 0) - { - beginIndex = 0; - } - - replaceText(beginIndex, endIndex, newText); - } - function replaceText(beginIndex:Int, endIndex:Int, newText:String):Void { if (endIndex < beginIndex || beginIndex < 0 || endIndex > text.length || newText == null) @@ -590,12 +603,15 @@ class FlxInputText extends FlxText implements IFlxInputText switch (cmd) { case NEW_LINE: - if (multiline) + if (editable && multiline) { addText("\n"); } onChange(ENTER_ACTION); case DELETE_LEFT: + if (!editable) + return; + if (_selectionIndex == _caretIndex && _caretIndex > 0) { _selectionIndex = _caretIndex - 1; @@ -608,6 +624,9 @@ class FlxInputText extends FlxText implements IFlxInputText onChange(BACKSPACE_ACTION); } case DELETE_RIGHT: + if (!editable) + return; + if (_selectionIndex == _caretIndex && _caretIndex < text.length) { _selectionIndex = _caretIndex + 1; @@ -625,14 +644,14 @@ class FlxInputText extends FlxText implements IFlxInputText Clipboard.text = text.substring(_caretIndex, _selectionIndex); } case CUT: - if (_caretIndex != _selectionIndex && !passwordMode) + if (editable && _caretIndex != _selectionIndex && !passwordMode) { Clipboard.text = text.substring(_caretIndex, _selectionIndex); replaceSelectedText(""); } case PASTE: - if (Clipboard.text != null) + if (editable && Clipboard.text != null) { addText(Clipboard.text); } @@ -702,11 +721,16 @@ class FlxInputText extends FlxText implements IFlxInputText textField.setSelection(_selectionIndex, _caretIndex); _regen = true; + if (keepScroll) { scrollH = cacheScrollH; scrollV = cacheScrollV; } + else if (scrollH != cacheScrollH || scrollV != cacheScrollV) + { + onChange(SCROLL_ACTION); + } } function updateSelectionBoxes():Void @@ -833,7 +857,7 @@ class FlxInputText extends FlxText implements IFlxInputText } if (checkPointerOverlap(FlxG.mouse)) { - if (FlxG.mouse.justPressed) + if (FlxG.mouse.justPressed && selectable) { _mouseDown = true; updatePointerPress(FlxG.mouse); @@ -841,7 +865,12 @@ class FlxInputText extends FlxText implements IFlxInputText if (FlxG.mouse.wheel != 0) { + var cacheScrollV = scrollV; scrollV = FlxMath.minInt(scrollV - FlxG.mouse.wheel, maxScrollV); + if (scrollV != cacheScrollV) + { + onChange(SCROLL_ACTION); + } } } else if (FlxG.mouse.justPressed) @@ -885,6 +914,8 @@ class FlxInputText extends FlxText implements IFlxInputText function updatePointerDrag(pointer:FlxPointer, elapsed:Float) { var relativePos = getRelativePosition(pointer); + var cacheScrollH = scrollH; + var cacheScrollV = scrollV; if (relativePos.x > width - 1) { @@ -909,6 +940,10 @@ class FlxInputText extends FlxText implements IFlxInputText } _scrollVCounter = 0; } + if (scrollH != cacheScrollH || scrollV != cacheScrollV) + { + onChange(SCROLL_ACTION); + } } function updatePointerMove(pointer:FlxPointer):Void @@ -1436,6 +1471,7 @@ class FlxInputText extends FlxText implements IFlxInputText { return FlxMath.maxInt(_caretIndex, _selectionIndex); } + function set_useSelectedTextFormat(value:Bool):Bool { if (useSelectedTextFormat != value) @@ -1450,10 +1486,12 @@ class FlxInputText extends FlxText implements IFlxInputText enum abstract FlxInputTextAction(String) from String to String { + var CHANGE_ACTION = "change"; var INPUT_ACTION = "input"; var BACKSPACE_ACTION = "backspace"; var DELETE_ACTION = "delete"; var ENTER_ACTION = "enter"; + var SCROLL_ACTION = "scroll"; } enum abstract FlxInputTextCase(Int) from Int to Int From 678c12993445cda7050c445164074a6bac4e6689 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Tue, 25 Jun 2024 10:52:09 -0400 Subject: [PATCH 18/48] Flixel hotkeys (volume & debugger) are now disabled while inputting text - Fixed space not being inputted on HTML5 --- flixel/input/keyboard/FlxKeyboard.hx | 7 +++-- flixel/system/frontEnds/InputTextFrontEnd.hx | 27 +++++++++++++++----- flixel/system/frontEnds/SoundFrontEnd.hx | 15 ++++++----- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/flixel/input/keyboard/FlxKeyboard.hx b/flixel/input/keyboard/FlxKeyboard.hx index a21a40fe73..02fcb532c3 100644 --- a/flixel/input/keyboard/FlxKeyboard.hx +++ b/flixel/input/keyboard/FlxKeyboard.hx @@ -101,7 +101,7 @@ class FlxKeyboard extends FlxKeyManager // Debugger toggle #if FLX_DEBUG - if (FlxG.game.debugger != null && inKeyArray(FlxG.debugger.toggleKeys, event)) + if (FlxG.game.debugger != null && inKeyArray(FlxG.debugger.toggleKeys, event) && !FlxG.inputText.isTyping) { FlxG.debugger.visible = !FlxG.debugger.visible; } @@ -114,7 +114,10 @@ class FlxKeyboard extends FlxKeyManager // Attempted to cancel the replay? #if FLX_RECORD - if (FlxG.game.replaying && !inKeyArray(FlxG.debugger.toggleKeys, event) && inKeyArray(FlxG.vcr.cancelKeys, event)) + if (FlxG.game.replaying + && !inKeyArray(FlxG.debugger.toggleKeys, event) + && inKeyArray(FlxG.vcr.cancelKeys, event) + && !FlxG.inputText.isTyping) { FlxG.vcr.cancelReplay(); } diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index b8530263b3..a52dd774dc 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -5,13 +5,15 @@ import lime.ui.KeyModifier; class InputTextFrontEnd { - public var focus(default, set):Null; + public var focus(default, set):IFlxInputText; + + public var isTyping(get, never):Bool; var _registeredInputTexts:Array = []; public function new() {} - public function registerInputText(input:IFlxInputText) + public function registerInputText(input:IFlxInputText):Void { if (!_registeredInputTexts.contains(input)) { @@ -27,7 +29,7 @@ class InputTextFrontEnd } } - public function unregisterInputText(input:IFlxInputText) + public function unregisterInputText(input:IFlxInputText):Void { if (_registeredInputTexts.contains(input)) { @@ -41,7 +43,7 @@ class InputTextFrontEnd } } - function onTextInput(text:String) + function onTextInput(text:String):Void { if (focus != null) { @@ -49,7 +51,7 @@ class InputTextFrontEnd } } - function onKeyDown(key:KeyCode, modifier:KeyModifier) + function onKeyDown(key:KeyCode, modifier:KeyModifier):Void { if (focus == null) return; @@ -143,9 +145,17 @@ class InputTextFrontEnd } default: } + #if html5 + // On HTML5, the SPACE key gets added to `FlxG.keys.preventDefaultKeys` by default, which also + // stops it from dispatching a text input event. We need to call `onTextInput()` manually + if (key == SPACE && FlxG.keys.preventDefaultKeys != null && FlxG.keys.preventDefaultKeys.contains(SPACE)) + { + onTextInput(" "); + } + #end } - function set_focus(value:IFlxInputText) + function set_focus(value:IFlxInputText):IFlxInputText { if (focus != value) { @@ -166,10 +176,15 @@ class InputTextFrontEnd return value; } + function get_isTyping():Bool + { + return focus != null && focus.editable; + } } interface IFlxInputText { + var editable:Bool; var hasFocus(default, set):Bool; function dispatchTypingAction(action:TypingAction):Void; } diff --git a/flixel/system/frontEnds/SoundFrontEnd.hx b/flixel/system/frontEnds/SoundFrontEnd.hx index 0a5e6bb07f..605cae1a02 100644 --- a/flixel/system/frontEnds/SoundFrontEnd.hx +++ b/flixel/system/frontEnds/SoundFrontEnd.hx @@ -394,12 +394,15 @@ class SoundFrontEnd list.update(elapsed); #if FLX_KEYBOARD - if (FlxG.keys.anyJustReleased(muteKeys)) - toggleMuted(); - else if (FlxG.keys.anyJustReleased(volumeUpKeys)) - changeVolume(0.1); - else if (FlxG.keys.anyJustReleased(volumeDownKeys)) - changeVolume(-0.1); + if (!FlxG.inputText.isTyping) + { + if (FlxG.keys.anyJustReleased(muteKeys)) + toggleMuted(); + else if (FlxG.keys.anyJustReleased(volumeUpKeys)) + changeVolume(0.1); + else if (FlxG.keys.anyJustReleased(volumeDownKeys)) + changeVolume(-0.1); + } #end } From 21d996f05e2d084ac9f1b80c0ea0cef9b2a55477 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Tue, 25 Jun 2024 11:58:00 -0400 Subject: [PATCH 19/48] Caret flashing timer - Fixed text going out of bounds when enabling multiline without a field height set - Last click time for double click now resets if the mouse clicked on something else --- flixel/system/frontEnds/InputTextFrontEnd.hx | 1 + flixel/text/FlxInputText.hx | 78 +++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index a52dd774dc..c39859cdf0 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -176,6 +176,7 @@ class InputTextFrontEnd return value; } + function get_isTyping():Bool { return focus != null && focus.editable; diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index a4a4a48be3..92491d89ac 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -8,6 +8,7 @@ import flixel.system.frontEnds.InputTextFrontEnd; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; import flixel.util.FlxSpriteUtil; +import flixel.util.FlxTimer; import lime.system.Clipboard; import openfl.display.BitmapData; import openfl.geom.Rectangle; @@ -86,7 +87,9 @@ class FlxInputText extends FlxText implements IFlxInputText var _backgroundSprite:FlxSprite; var _caret:FlxSprite; + var _caretFlash:Bool = false; var _caretIndex:Int = -1; + var _caretTimer:FlxTimer = new FlxTimer(); var _fieldBorderSprite:FlxSprite; var _lastClickTime:Int = 0; var _mouseDown:Bool = false; @@ -158,6 +161,11 @@ class FlxInputText extends FlxText implements IFlxInputText _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); _caret = FlxDestroyUtil.destroy(_caret); + if (_caretTimer != null) + { + _caretTimer.cancel(); + _caretTimer = FlxDestroyUtil.destroy(_caretTimer); + } _fieldBorderSprite = FlxDestroyUtil.destroy(_fieldBorderSprite); _pointerCamera = null; while (_selectionBoxes.length > 0) @@ -607,6 +615,11 @@ class FlxInputText extends FlxText implements IFlxInputText { addText("\n"); } + else + { + stopCaretTimer(); + startCaretTimer(); + } onChange(ENTER_ACTION); case DELETE_LEFT: if (!editable) @@ -623,6 +636,11 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; onChange(BACKSPACE_ACTION); } + else + { + stopCaretTimer(); + startCaretTimer(); + } case DELETE_RIGHT: if (!editable) return; @@ -638,6 +656,11 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; onChange(DELETE_ACTION); } + else + { + stopCaretTimer(); + startCaretTimer(); + } case COPY: if (_caretIndex != _selectionIndex && !passwordMode) { @@ -662,6 +685,29 @@ class FlxInputText extends FlxText implements IFlxInputText } } + function startCaretTimer():Void + { + _caretTimer.cancel(); + + _caretFlash = !_caretFlash; + updateCaretVisibility(); + _caretTimer.start(0.6, function(tmr) + { + startCaretTimer(); + }); + } + + function stopCaretTimer():Void + { + _caretTimer.cancel(); + + if (_caretFlash) + { + _caretFlash = false; + updateCaretVisibility(); + } + } + function updateBackgroundPosition():Void { if (!background) @@ -714,12 +760,19 @@ class FlxInputText extends FlxText implements IFlxInputText _caret.updateHitbox(); } + function updateCaretVisibility():Void + { + _caret.visible = (_caretFlash && _selectionIndex == _caretIndex && isCaretLineVisible()); + } + function updateSelection(keepScroll:Bool = false):Void { var cacheScrollH = scrollH; var cacheScrollV = scrollV; textField.setSelection(_selectionIndex, _caretIndex); + stopCaretTimer(); + startCaretTimer(); _regen = true; if (keepScroll) @@ -815,10 +868,11 @@ class FlxInputText extends FlxText implements IFlxInputText function updateSelectionSprites():Void { - _caret.alpha = (_selectionIndex == _caretIndex && isCaretLineVisible()) ? 1 : 0; + updateCaretVisibility(); updateCaretPosition(); updateSelectionBoxes(); } + function updateSpritePositions():Void { updateBackgroundPosition(); @@ -855,6 +909,11 @@ class FlxInputText extends FlxText implements IFlxInputText } } } + else if (FlxG.mouse.justReleased) + { + _lastClickTime = 0; + } + if (checkPointerOverlap(FlxG.mouse)) { if (FlxG.mouse.justPressed && selectable) @@ -978,6 +1037,12 @@ class FlxInputText extends FlxText implements IFlxInputText _caretIndex = rightPos; updateSelection(true); + if (hasFocus) + { + stopCaretTimer(); + startCaretTimer(); + } + relativePos.put(); _pointerCamera = null; } @@ -1321,7 +1386,9 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelection(true); } - _caret.visible = true; + stopCaretTimer(); + startCaretTimer(); + if (focusGained != null) focusGained(); } @@ -1335,7 +1402,8 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelection(true); } - _caret.visible = false; + stopCaretTimer(); + if (focusLost != null) focusLost(); } @@ -1377,6 +1445,10 @@ class FlxInputText extends FlxText implements IFlxInputText if (textField.multiline != value) { textField.multiline = value; + if (multiline) + { + _autoHeight = false; + } } return value; From 87da6768187b6a9243fce0e21d67d15a7df22c25 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Wed, 26 Jun 2024 23:28:16 -0400 Subject: [PATCH 20/48] Optimized selection box sprites (only visible lines are accounted for now) - Fixed untypeable characters being added to text input on Flash --- flixel/system/frontEnds/InputTextFrontEnd.hx | 13 +++++++++++++ flixel/text/FlxInputText.hx | 17 ++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index c39859cdf0..dd28273a6e 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -5,6 +5,10 @@ import lime.ui.KeyModifier; class InputTextFrontEnd { + #if flash + static final IGNORED_CHARACTERS:Array = [BACKSPACE, TAB, RETURN, ESCAPE, DELETE]; + #end + public var focus(default, set):IFlxInputText; public var isTyping(get, never):Bool; @@ -45,6 +49,15 @@ class InputTextFrontEnd function onTextInput(text:String):Void { + #if flash + // On Flash, any key press will get dispatched for `onTextInput`, including untypeable characters, + // which messes up the text. Let's catch and ignore them. + // "RETURN" is ignored as well, since we already handle creating new lines inside the text object. + var code = text.charCodeAt(0); + if (code == 0 || IGNORED_CHARACTERS.contains(code)) + return; + #end + if (focus != null) { focus.dispatchTypingAction(ADD_TEXT(text)); diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 92491d89ac..5b03ffbad3 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -790,8 +790,9 @@ class FlxInputText extends FlxText implements IFlxInputText { if (textField == null) return; - - while (_selectionBoxes.length > textField.numLines) + + var visibleLines = bottomScrollV - scrollV + 1; + while (_selectionBoxes.length > visibleLines) { var box = _selectionBoxes.pop(); if (box != null) @@ -812,12 +813,14 @@ class FlxInputText extends FlxText implements IFlxInputText var beginLine = textField.getLineIndexOfChar(selectionBeginIndex); var endLine = textField.getLineIndexOfChar(selectionEndIndex); - var scrollY = getLineY(scrollV - 1); + var beginV = scrollV - 1; + var scrollY = getLineY(beginV); - for (line in 0...textField.numLines) + for (line in beginV...bottomScrollV) { - var box = _selectionBoxes[line]; - if ((line >= scrollV - 1 && line <= bottomScrollV - 1) && (line >= beginLine && line <= endLine)) + var i = line - beginV; + var box = _selectionBoxes[i]; + if (line >= beginLine && line <= endLine) { var lineStartIndex = textField.getLineOffset(line); var lineEndIndex = lineStartIndex + textField.getLineLength(line); @@ -836,7 +839,7 @@ class FlxInputText extends FlxText implements IFlxInputText { if (box == null) { - box = _selectionBoxes[line] = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); + box = _selectionBoxes[i] = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); box.color = selectionColor; } From d286bc3c1680eadeb376db2c5c1f7bc134d7b9f4 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Thu, 27 Jun 2024 19:46:16 -0400 Subject: [PATCH 21/48] Various fixes for Flash - Fixed text selection and caret positioning on Flash - Copy, cut, paste and select all commands now work on Flash - Fixed horizontal scroll not being set automatically on Flash - Moved to using Flash's `TextEvent.TEXT_INPUT` event (does not dispatch with invalid characters) --- flixel/system/frontEnds/InputTextFrontEnd.hx | 91 ++++++-- flixel/text/FlxInputText.hx | 222 ++++++++++++++----- 2 files changed, 243 insertions(+), 70 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index dd28273a6e..4220763ae7 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -2,13 +2,11 @@ package flixel.system.frontEnds; import lime.ui.KeyCode; import lime.ui.KeyModifier; +import openfl.events.Event; +import openfl.events.TextEvent; class InputTextFrontEnd { - #if flash - static final IGNORED_CHARACTERS:Array = [BACKSPACE, TAB, RETURN, ESCAPE, DELETE]; - #end - public var focus(default, set):IFlxInputText; public var isTyping(get, never):Bool; @@ -23,12 +21,19 @@ class InputTextFrontEnd { _registeredInputTexts.push(input); - if (!FlxG.stage.window.onTextInput.has(onTextInput)) + if (!FlxG.stage.window.onKeyDown.has(onKeyDown)) { - FlxG.stage.window.onTextInput.add(onTextInput); + FlxG.stage.addEventListener(TextEvent.TEXT_INPUT, onTextInput); // Higher priority is needed here because FlxKeyboard will cancel // the event for key codes in `preventDefaultKeys`. FlxG.stage.window.onKeyDown.add(onKeyDown, false, 1000); + #if flash + FlxG.stage.addEventListener(Event.COPY, onCopy); + FlxG.stage.addEventListener(Event.CUT, onCut); + FlxG.stage.addEventListener(Event.PASTE, onPaste); + FlxG.stage.addEventListener(Event.SELECT_ALL, onSelectAll); + FlxG.stage.window.onKeyUp.add(onKeyUp, false, 1000); + #end } } } @@ -39,28 +44,30 @@ class InputTextFrontEnd { _registeredInputTexts.remove(input); - if (_registeredInputTexts.length == 0 && FlxG.stage.window.onTextInput.has(onTextInput)) + if (_registeredInputTexts.length == 0 && FlxG.stage.window.onKeyDown.has(onKeyDown)) { - FlxG.stage.window.onTextInput.remove(onTextInput); + FlxG.stage.removeEventListener(TextEvent.TEXT_INPUT, onTextInput); FlxG.stage.window.onKeyDown.remove(onKeyDown); + #if flash + FlxG.stage.removeEventListener(Event.COPY, onCopy); + FlxG.stage.removeEventListener(Event.CUT, onCut); + FlxG.stage.removeEventListener(Event.PASTE, onPaste); + FlxG.stage.removeEventListener(Event.SELECT_ALL, onSelectAll); + FlxG.stage.window.onKeyUp.remove(onKeyUp); + #end } } } - function onTextInput(text:String):Void + function onTextInput(event:TextEvent):Void { - #if flash - // On Flash, any key press will get dispatched for `onTextInput`, including untypeable characters, - // which messes up the text. Let's catch and ignore them. - // "RETURN" is ignored as well, since we already handle creating new lines inside the text object. - var code = text.charCodeAt(0); - if (code == 0 || IGNORED_CHARACTERS.contains(code)) + // Adding new lines is handled inside FlxInputText + if (event.text.length == 1 && event.text.charCodeAt(0) == KeyCode.RETURN) return; - #end if (focus != null) { - focus.dispatchTypingAction(ADD_TEXT(text)); + focus.dispatchTypingAction(ADD_TEXT(event.text)); } } @@ -69,6 +76,12 @@ class InputTextFrontEnd if (focus == null) return; + #if flash + // COPY, CUT, PASTE and SELECT_ALL events will only be dispatched if the stage has a focus. + // Let's set one manually (just the stage itself) + FlxG.stage.focus = FlxG.stage; + #end + // Taken from OpenFL's `TextField` var modifierPressed = #if mac modifier.metaKey #elseif js(modifier.metaKey || modifier.ctrlKey) #else (modifier.ctrlKey && !modifier.altKey) #end; @@ -158,15 +171,57 @@ class InputTextFrontEnd } default: } + #if html5 // On HTML5, the SPACE key gets added to `FlxG.keys.preventDefaultKeys` by default, which also // stops it from dispatching a text input event. We need to call `onTextInput()` manually if (key == SPACE && FlxG.keys.preventDefaultKeys != null && FlxG.keys.preventDefaultKeys.contains(SPACE)) { - onTextInput(" "); + onTextInput(new TextEvent(TextEvent.TEXT_INPUT, false, false, " ")); } #end } + #if flash + function onKeyUp(key:KeyCode, modifier:KeyModifier):Void + { + if (FlxG.stage.focus == FlxG.stage) + { + FlxG.stage.focus = null; + } + } + + function onCopy(e:Event):Void + { + if (focus != null) + { + focus.dispatchTypingAction(COMMAND(COPY)); + } + } + + function onCut(e:Event):Void + { + if (focus != null) + { + focus.dispatchTypingAction(COMMAND(CUT)); + } + } + + function onPaste(e:Event):Void + { + if (focus != null) + { + focus.dispatchTypingAction(COMMAND(PASTE)); + } + } + + function onSelectAll(e:Event):Void + { + if (focus != null) + { + focus.dispatchTypingAction(COMMAND(SELECT_ALL)); + } + } + #end function set_focus(value:IFlxInputText):IFlxInputText { diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 5b03ffbad3..243fd629b8 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -330,34 +330,13 @@ class FlxInputText extends FlxText implements IFlxInputText default: GUTTER; } } - - function getCharIndexOnDifferentLine(charIndex:Int, lineIndex:Int):Int - { - if (charIndex < 0 || charIndex > text.length) - return -1; - if (lineIndex < 0 || lineIndex > textField.numLines - 1) - return -1; - - var x = 0.0; - var charBoundaries = textField.getCharBoundaries(charIndex - 1); - if (charBoundaries != null) - { - x = charBoundaries.right; - } - else - { - x = GUTTER; - } - - var y = GUTTER + getLineY(lineIndex) + textField.getLineMetrics(lineIndex).height / 2 - getLineY(scrollV - 1); - - return getCharAtPosition(x, y); - } - + function getCharAtPosition(x:Float, y:Float):Int { + #if !flash x += scrollH; - y += getLineY(scrollV - 1); + y += getScrollVOffset(); + #end if (x < GUTTER) x = GUTTER; @@ -379,7 +358,7 @@ class FlxInputText extends FlxText implements IFlxInputText var lineEndIndex = lineOffset + lineLength; for (char in 0...lineLength) { - var boundaries = textField.getCharBoundaries(lineOffset + char); + var boundaries = getCharBoundaries(lineOffset + char); // reached end of line, return this character if (boundaries == null) return lineOffset + char; @@ -395,7 +374,7 @@ class FlxInputText extends FlxText implements IFlxInputText } } } - + // a character wasn't found, return the last character of the line return lineEndIndex; } @@ -404,6 +383,79 @@ class FlxInputText extends FlxText implements IFlxInputText return text.length; } + /** + * NOTE: On Flash, this will not give the correct Y for characters that are out-of-view, + * due to needing to internally change the vertical scroll to get their boundaries. + * You should use `getLineY()` and `getScrollVOffset()` instead for getting the proper Y position. + */ + function getCharBoundaries(char:Int):Rectangle + { + #if flash + // On Flash, `getCharBoundaries()` always returns null if the character is before + // the current vertical scroll. Let's just set the scroll directly at the line + // and change it back later + var cacheScrollV = scrollV; + // Change the internal text field's property instead to not cause a loop due to `_regen` + // always being set back to true + textField.scrollV = getLineIndexOfChar(char) + 1; + var prevRegen = _regen; + #end + + var boundaries = textField.getCharBoundaries(char); + if (boundaries == null) + { + #if flash + textField.scrollV = cacheScrollV; + _regen = prevRegen; + #end + return null; + } + + // Scrolling is already accounted for in `getCharBoundaries()` on Flash + #if !flash + boundaries.x -= scrollH; + boundaries.y -= getScrollVOffset(); + #else + textField.scrollV = cacheScrollV; + _regen = prevRegen; + #end + + return boundaries; + } + + function getCharIndexOnDifferentLine(charIndex:Int, lineIndex:Int):Int + { + if (charIndex < 0 || charIndex > text.length) + return -1; + if (lineIndex < 0 || lineIndex > textField.numLines - 1) + return -1; + + var x = 0.0; + var charBoundaries = getCharBoundaries(charIndex - 1); + if (charBoundaries != null) + { + x = charBoundaries.right; + } + else + { + x = GUTTER; + } + + var y = GUTTER + getLineY(lineIndex) + textField.getLineMetrics(lineIndex).height / 2; + + return getCharAtPosition(x, y); + } + + function getLineIndexOfChar(char:Int):Int + { + // On Flash, if the character is equal to the end of the text, it returns -1 as the line. + // We have to fix it manually. + return (char == text.length) ? textField.numLines - 1 : textField.getLineIndexOfChar(char); + } + + /** + * NOTE: This does not include the vertical gutter on top of the text field. + */ function getLineY(line:Int):Float { var scrollY = 0.0; @@ -414,13 +466,18 @@ class FlxInputText extends FlxText implements IFlxInputText return scrollY; } + function getScrollVOffset():Float + { + return getLineY(scrollV - 1); + } + function isCaretLineVisible():Bool { // `getLineIndexOfChar()` will return -1 if text is empty, but we still want the caret to show up if (text.length == 0) return true; - var line = textField.getLineIndexOfChar(_caretIndex); + var line = getLineIndexOfChar(_caretIndex); return line >= scrollV - 1 && line <= bottomScrollV - 1; } @@ -451,7 +508,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); case UP: - var lineIndex = textField.getLineIndexOfChar(_caretIndex); + var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex > 0) { _caretIndex = getCharIndexOnDifferentLine(_caretIndex, lineIndex - 1); @@ -463,7 +520,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); case DOWN: - var lineIndex = textField.getLineIndexOfChar(_caretIndex); + var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) { _caretIndex = getCharIndexOnDifferentLine(_caretIndex, lineIndex + 1); @@ -491,7 +548,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); case LINE_BEGINNING: - _caretIndex = textField.getLineOffset(textField.getLineIndexOfChar(_caretIndex)); + _caretIndex = textField.getLineOffset(getLineIndexOfChar(_caretIndex)); if (!shiftKey) { @@ -499,7 +556,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); case LINE_END: - var lineIndex = textField.getLineIndexOfChar(_caretIndex); + var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) { _caretIndex = textField.getLineOffset(lineIndex + 1) - 1; @@ -515,7 +572,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); case PREVIOUS_LINE: - var lineIndex = textField.getLineIndexOfChar(_caretIndex); + var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex > 0) { var index = textField.getLineOffset(lineIndex); @@ -535,7 +592,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); case NEXT_LINE: - var lineIndex = textField.getLineIndexOfChar(_caretIndex); + var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) { _caretIndex = textField.getLineOffset(lineIndex + 1); @@ -728,22 +785,22 @@ class FlxInputText extends FlxText implements IFlxInputText } else { - var boundaries = textField.getCharBoundaries(_caretIndex - 1); + var lineY = GUTTER + getLineY(getLineIndexOfChar(_caretIndex)); + var boundaries = getCharBoundaries(_caretIndex - 1); if (boundaries != null) { - _caret.setPosition(x + boundaries.right - scrollH, y + boundaries.y - getLineY(scrollV - 1)); + _caret.setPosition(x + boundaries.right - scrollH, y + lineY - getScrollVOffset()); } else { - boundaries = textField.getCharBoundaries(_caretIndex); + boundaries = getCharBoundaries(_caretIndex); if (boundaries != null) { - _caret.setPosition(x + boundaries.x - scrollH, y + boundaries.y - getLineY(scrollV - 1)); + _caret.setPosition(x + boundaries.x - scrollH, y + lineY - getScrollVOffset()); } else // end of line { - var lineIndex = textField.getLineIndexOfChar(_caretIndex); - _caret.setPosition(x + getCaretOffsetX(), y + GUTTER + getLineY(lineIndex) - getLineY(scrollV - 1)); + _caret.setPosition(x + getCaretOffsetX(), y + GUTTER + lineY - getScrollVOffset()); } } } @@ -765,6 +822,58 @@ class FlxInputText extends FlxText implements IFlxInputText _caret.visible = (_caretFlash && _selectionIndex == _caretIndex && isCaretLineVisible()); } + #if flash + function updateScrollH():Void + { + if (textField.textWidth <= width - (GUTTER * 2)) + { + scrollH = 0; + return; + } + + var tempScrollH = scrollH; + if (_caretIndex == 0 || textField.getLineOffset(getLineIndexOfChar(_caretIndex)) == _caretIndex) + { + tempScrollH = 0; + } + else + { + var caret:Rectangle = null; + if (_caretIndex < text.length) + { + caret = getCharBoundaries(_caretIndex); + } + if (caret == null) + { + caret = getCharBoundaries(_caretIndex - 1); + caret.x += caret.width; + } + + while (caret.x < tempScrollH && tempScrollH > 0) + { + tempScrollH -= 24; + } + while (caret.x > tempScrollH + width - (GUTTER * 2)) + { + tempScrollH += 24; + } + } + + if (tempScrollH < 0) + { + scrollH = 0; + } + else if (tempScrollH > maxScrollH) + { + scrollH = maxScrollH; + } + else + { + scrollH = tempScrollH; + } + } + #end + function updateSelection(keepScroll:Bool = false):Void { var cacheScrollH = scrollH; @@ -780,9 +889,17 @@ class FlxInputText extends FlxText implements IFlxInputText scrollH = cacheScrollH; scrollV = cacheScrollV; } - else if (scrollH != cacheScrollH || scrollV != cacheScrollV) + else { - onChange(SCROLL_ACTION); + #if flash + // Horizontal scroll is not automatically set on Flash + updateScrollH(); + #end + + if (scrollH != cacheScrollH || scrollV != cacheScrollV) + { + onChange(SCROLL_ACTION); + } } } @@ -810,11 +927,10 @@ class FlxInputText extends FlxText implements IFlxInputText return; } - var beginLine = textField.getLineIndexOfChar(selectionBeginIndex); - var endLine = textField.getLineIndexOfChar(selectionEndIndex); + var beginLine = getLineIndexOfChar(selectionBeginIndex); + var endLine = getLineIndexOfChar(selectionEndIndex); var beginV = scrollV - 1; - var scrollY = getLineY(beginV); for (line in beginV...bottomScrollV) { @@ -828,11 +944,11 @@ class FlxInputText extends FlxText implements IFlxInputText var startIndex = FlxMath.maxInt(lineStartIndex, selectionBeginIndex); var endIndex = FlxMath.minInt(lineEndIndex, selectionEndIndex); - var startBoundaries = textField.getCharBoundaries(startIndex); - var endBoundaries = textField.getCharBoundaries(endIndex - 1); + var startBoundaries = getCharBoundaries(startIndex); + var endBoundaries = getCharBoundaries(endIndex - 1); if (endBoundaries == null && endIndex > startIndex) // end of line, try getting the previous character { - endBoundaries = textField.getCharBoundaries(endIndex - 2); + endBoundaries = getCharBoundaries(endIndex - 2); } if (startBoundaries != null && endBoundaries != null) @@ -843,7 +959,8 @@ class FlxInputText extends FlxText implements IFlxInputText box.color = selectionColor; } - var boxRect = FlxRect.get(startBoundaries.x - scrollH, startBoundaries.y - scrollY, endBoundaries.right - startBoundaries.x, + var boxRect = FlxRect.get(startBoundaries.x - scrollH, GUTTER + getLineY(line) - getScrollVOffset(), + endBoundaries.right - startBoundaries.x, startBoundaries.height); boxRect.clipTo(FlxRect.weak(0, 0, width, height)); // clip the selection box inside the text sprite @@ -966,7 +1083,7 @@ class FlxInputText extends FlxText implements IFlxInputText hasFocus = true; var relativePos = getRelativePosition(pointer); - _caretIndex = getCharAtPosition(relativePos.x, relativePos.y); + _caretIndex = getCharAtPosition(relativePos.x + scrollH, relativePos.y + getScrollVOffset()); _selectionIndex = _caretIndex; updateSelection(true); @@ -1002,6 +1119,7 @@ class FlxInputText extends FlxText implements IFlxInputText } _scrollVCounter = 0; } + if (scrollH != cacheScrollH || scrollV != cacheScrollV) { onChange(SCROLL_ACTION); @@ -1015,7 +1133,7 @@ class FlxInputText extends FlxText implements IFlxInputText var relativePos = getRelativePosition(pointer); - var char = getCharAtPosition(relativePos.x, relativePos.y); + var char = getCharAtPosition(relativePos.x + scrollH, relativePos.y + getScrollVOffset()); if (char != _caretIndex) { _caretIndex = char; @@ -1032,7 +1150,7 @@ class FlxInputText extends FlxText implements IFlxInputText var relativePos = getRelativePosition(pointer); - var upPos = getCharAtPosition(relativePos.x, relativePos.y); + var upPos = getCharAtPosition(relativePos.x + scrollH, relativePos.y + getScrollVOffset()); var leftPos = FlxMath.minInt(_selectionIndex, upPos); var rightPos = FlxMath.maxInt(_selectionIndex, upPos); From 2dfba26ad87426adc3be81889d4893aae569efa7 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Thu, 27 Jun 2024 21:45:49 -0400 Subject: [PATCH 22/48] Fixed text selection and scrolling on other platforms --- flixel/text/FlxInputText.hx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 243fd629b8..0344015566 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -333,11 +333,6 @@ class FlxInputText extends FlxText implements IFlxInputText function getCharAtPosition(x:Float, y:Float):Int { - #if !flash - x += scrollH; - y += getScrollVOffset(); - #end - if (x < GUTTER) x = GUTTER; @@ -411,11 +406,7 @@ class FlxInputText extends FlxText implements IFlxInputText return null; } - // Scrolling is already accounted for in `getCharBoundaries()` on Flash - #if !flash - boundaries.x -= scrollH; - boundaries.y -= getScrollVOffset(); - #else + #if flash textField.scrollV = cacheScrollV; _regen = prevRegen; #end From d53767f009a60118904ec38c0528ff969c391995 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Fri, 28 Jun 2024 17:00:22 -0400 Subject: [PATCH 23/48] Fixed text selection with word wrapping enabled --- flixel/text/FlxInputText.hx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 0344015566..58bf88e906 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -942,7 +942,10 @@ class FlxInputText extends FlxText implements IFlxInputText endBoundaries = getCharBoundaries(endIndex - 2); } - if (startBoundaries != null && endBoundaries != null) + // If word wrapping is enabled, the start boundary might actually be at the end of + // the previous line, which causes some visual bugs. Let's check to make sure the + // boundaries are in the same line + if (startBoundaries != null && endBoundaries != null && FlxMath.equal(startBoundaries.y, endBoundaries.y)) { if (box == null) { From 262a7a0f22e4ac3eef28818ea876c7dcf9be4ebc Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sun, 30 Jun 2024 13:00:57 -0400 Subject: [PATCH 24/48] Added touch support - Now uses `window.setTextInputRect()` to prevent keyboard overlay from blocking the text field - Fixed pointer position being inaccurate with camera scrolling - Fixed `getCharBoundaries()` not giving the correct Y position --- flixel/system/frontEnds/InputTextFrontEnd.hx | 1 + flixel/text/FlxInputText.hx | 166 +++++++++++++++---- 2 files changed, 133 insertions(+), 34 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index 4220763ae7..0fd30c90b9 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -181,6 +181,7 @@ class InputTextFrontEnd } #end } + #if flash function onKeyUp(key:KeyCode, modifier:KeyModifier):Void { diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 58bf88e906..1471fd0d4a 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -1,5 +1,6 @@ package flixel.text; +import flixel.input.touch.FlxTouch; import flixel.input.FlxPointer; import flixel.math.FlxMath; import flixel.math.FlxPoint; @@ -91,14 +92,23 @@ class FlxInputText extends FlxText implements IFlxInputText var _caretIndex:Int = -1; var _caretTimer:FlxTimer = new FlxTimer(); var _fieldBorderSprite:FlxSprite; - var _lastClickTime:Int = 0; - var _mouseDown:Bool = false; var _pointerCamera:FlxCamera; var _regenBackground:Bool = false; - var _scrollVCounter:Float = 0; var _selectionBoxes:Array = []; var _selectionFormat:TextFormat = new TextFormat(); var _selectionIndex:Int = -1; + #if FLX_POINTER_INPUT + var _lastClickTime:Int = 0; + var _scrollVCounter:Float = 0; + #if FLX_MOUSE + var _mouseDown:Bool = false; + #end + #if FLX_TOUCH + var _currentTouch:FlxTouch; + var _lastTouchX:Null; + var _lastTouchY:Null; + #end + #end public function new(x:Float = 0, y:Float = 0, fieldWidth:Float = 0, ?text:String, size:Int = 8, textColor:FlxColor = FlxColor.BLACK, backgroundColor:FlxColor = FlxColor.WHITE, embeddedFont:Bool = true) @@ -132,10 +142,11 @@ class FlxInputText extends FlxText implements IFlxInputText { super.update(elapsed); - #if FLX_MOUSE + #if FLX_POINTER_INPUT if (visible) { - updateInput(elapsed); + if (!updateMouseInput(elapsed)) + updateTouchInput(elapsed); } #end } @@ -172,6 +183,9 @@ class FlxInputText extends FlxText implements IFlxInputText FlxDestroyUtil.destroy(_selectionBoxes.pop()); _selectionBoxes = null; _selectionFormat = null; + #if FLX_TOUCH + _currentTouch = null; + #end super.destroy(); } @@ -378,11 +392,6 @@ class FlxInputText extends FlxText implements IFlxInputText return text.length; } - /** - * NOTE: On Flash, this will not give the correct Y for characters that are out-of-view, - * due to needing to internally change the vertical scroll to get their boundaries. - * You should use `getLineY()` and `getScrollVOffset()` instead for getting the proper Y position. - */ function getCharBoundaries(char:Int):Rectangle { #if flash @@ -390,9 +399,10 @@ class FlxInputText extends FlxText implements IFlxInputText // the current vertical scroll. Let's just set the scroll directly at the line // and change it back later var cacheScrollV = scrollV; + var lineIndex = getLineIndexOfChar(char); // Change the internal text field's property instead to not cause a loop due to `_regen` // always being set back to true - textField.scrollV = getLineIndexOfChar(char) + 1; + textField.scrollV = lineIndex + 1; var prevRegen = _regen; #end @@ -409,6 +419,8 @@ class FlxInputText extends FlxText implements IFlxInputText #if flash textField.scrollV = cacheScrollV; _regen = prevRegen; + // Set the Y to the correct position + boundaries.y = GUTTER + getLineY(lineIndex); #end return boundaries; @@ -457,6 +469,33 @@ class FlxInputText extends FlxText implements IFlxInputText return scrollY; } + function getLimeBounds(camera:FlxCamera):lime.math.Rectangle + { + if (camera == null) + camera = FlxG.camera; + + var rect = getScreenBounds(camera); + + // transform bounds inside camera & stage + rect.x = (rect.x * camera.totalScaleX) - (0.5 * camera.width * (camera.scaleX - camera.initialZoom) * FlxG.scaleMode.scale.x) + FlxG.game.x; + rect.y = (rect.y * camera.totalScaleY) - (0.5 * camera.height * (camera.scaleY - camera.initialZoom) * FlxG.scaleMode.scale.y) + FlxG.game.y; + rect.width *= camera.totalScaleX; + rect.height *= camera.totalScaleY; + + #if openfl_dpi_aware + var scale = FlxG.stage.window.scale; + if (scale != 1.0) + { + rect.x /= scale; + rect.y /= scale; + rect.width /= scale; + rect.height /= scale; + } + #end + + return new lime.math.Rectangle(rect.x, rect.y, rect.width, rect.height); + } + function getScrollVOffset():Float { return getLineY(scrollV - 1); @@ -776,22 +815,21 @@ class FlxInputText extends FlxText implements IFlxInputText } else { - var lineY = GUTTER + getLineY(getLineIndexOfChar(_caretIndex)); var boundaries = getCharBoundaries(_caretIndex - 1); if (boundaries != null) { - _caret.setPosition(x + boundaries.right - scrollH, y + lineY - getScrollVOffset()); + _caret.setPosition(x + boundaries.right - scrollH, y + boundaries.y - getScrollVOffset()); } else { boundaries = getCharBoundaries(_caretIndex); if (boundaries != null) { - _caret.setPosition(x + boundaries.x - scrollH, y + lineY - getScrollVOffset()); + _caret.setPosition(x + boundaries.x - scrollH, y + boundaries.y - getScrollVOffset()); } else // end of line { - _caret.setPosition(x + getCaretOffsetX(), y + GUTTER + lineY - getScrollVOffset()); + _caret.setPosition(x + getCaretOffsetX(), y + GUTTER + getLineY(getLineIndexOfChar(_caretIndex)) - getScrollVOffset()); } } } @@ -922,7 +960,8 @@ class FlxInputText extends FlxText implements IFlxInputText var endLine = getLineIndexOfChar(selectionEndIndex); var beginV = scrollV - 1; - + var scrollVOffset = getScrollVOffset(); + for (line in beginV...bottomScrollV) { var i = line - beginV; @@ -953,7 +992,7 @@ class FlxInputText extends FlxText implements IFlxInputText box.color = selectionColor; } - var boxRect = FlxRect.get(startBoundaries.x - scrollH, GUTTER + getLineY(line) - getScrollVOffset(), + var boxRect = FlxRect.get(startBoundaries.x - scrollH, startBoundaries.y - scrollVOffset, endBoundaries.right - startBoundaries.x, startBoundaries.height); boxRect.clipTo(FlxRect.weak(0, 0, width, height)); // clip the selection box inside the text sprite @@ -994,9 +1033,11 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelectionBoxes(); } - #if FLX_MOUSE - function updateInput(elapsed:Float):Void + #if FLX_POINTER_INPUT + function updateMouseInput(elapsed:Float):Bool { + var overlap = false; + #if FLX_MOUSE if (_mouseDown) { updatePointerDrag(FlxG.mouse, elapsed); @@ -1008,19 +1049,8 @@ class FlxInputText extends FlxText implements IFlxInputText if (FlxG.mouse.released) { - _mouseDown = false; updatePointerRelease(FlxG.mouse); - - var currentTime = FlxG.game.ticks; - if (currentTime - _lastClickTime < 500) - { - updatePointerDoublePress(FlxG.mouse); - _lastClickTime = 0; - } - else - { - _lastClickTime = currentTime; - } + _mouseDown = false; } } else if (FlxG.mouse.justReleased) @@ -1030,12 +1060,12 @@ class FlxInputText extends FlxText implements IFlxInputText if (checkPointerOverlap(FlxG.mouse)) { + overlap = true; if (FlxG.mouse.justPressed && selectable) { _mouseDown = true; updatePointerPress(FlxG.mouse); } - if (FlxG.mouse.wheel != 0) { var cacheScrollV = scrollV; @@ -1050,6 +1080,60 @@ class FlxInputText extends FlxText implements IFlxInputText { hasFocus = false; } + #end + return overlap; + } + + function updateTouchInput(elapsed:Float):Bool + { + var overlap = false; + #if FLX_TOUCH + if (_currentTouch != null) + { + updatePointerDrag(_currentTouch, elapsed); + + if (_lastTouchX != _currentTouch.x || _lastTouchY != _currentTouch.y) + { + updatePointerMove(_currentTouch); + _lastTouchX = _currentTouch.x; + _lastTouchY = _currentTouch.y; + } + + if (_currentTouch.released) + { + updatePointerRelease(_currentTouch); + _currentTouch = null; + _lastTouchY = _lastTouchX = null; + } + } + + var pressedElsewhere = false; + for (touch in FlxG.touches.list) + { + if (checkPointerOverlap(touch)) + { + overlap = true; + if (touch.justPressed && selectable) + { + _currentTouch = touch; + _lastTouchX = touch.x; + _lastTouchY = touch.y; + updatePointerPress(touch); + } + break; + } + else if (touch.justPressed) + { + pressedElsewhere = true; + _lastClickTime = 0; + } + } + if (pressedElsewhere && _currentTouch == null) + { + hasFocus = false; + } + #end + return overlap; } function checkPointerOverlap(pointer:FlxPointer):Bool @@ -1084,7 +1168,7 @@ class FlxInputText extends FlxText implements IFlxInputText relativePos.put(); } - function updatePointerDrag(pointer:FlxPointer, elapsed:Float) + function updatePointerDrag(pointer:FlxPointer, elapsed:Float):Void { var relativePos = getRelativePosition(pointer); var cacheScrollH = scrollH; @@ -1160,6 +1244,16 @@ class FlxInputText extends FlxText implements IFlxInputText relativePos.put(); _pointerCamera = null; + var currentTime = FlxG.game.ticks; + if (currentTime - _lastClickTime < 500) + { + updatePointerDoublePress(pointer); + _lastClickTime = 0; + } + else + { + _lastClickTime = currentTime; + } } function updatePointerDoublePress(pointer:FlxPointer):Void @@ -1193,7 +1287,7 @@ class FlxInputText extends FlxText implements IFlxInputText { var pointerPos = pointer.getWorldPosition(_pointerCamera); getScreenPosition(_point, _pointerCamera); - var result = FlxPoint.get(pointerPos.x - _point.x, pointerPos.y - _point.y); + var result = FlxPoint.get((pointerPos.x - _pointerCamera.scroll.x) - _point.x, (pointerPos.y - _pointerCamera.scroll.y) - _point.y); pointerPos.put(); return result; } @@ -1492,6 +1586,10 @@ class FlxInputText extends FlxText implements IFlxInputText hasFocus = value; if (hasFocus) { + // Ensure that the text field isn't hidden by a keyboard overlay + var bounds = getLimeBounds(_pointerCamera); + FlxG.stage.window.setTextInputRect(bounds); + FlxG.inputText.focus = this; if (_caretIndex < 0) From 2aa01763e0d4533e22f46e3d914801c02fa84f10 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Thu, 4 Jul 2024 22:51:13 -0400 Subject: [PATCH 25/48] Added documentation - Fixed not being able to add text if the field starts out empty - Fixed the caret being the wrong size if the text field is empty - Fixed the background not being resized when auto size is enabled - Changing `customFilterPattern` now automatically sets `filterMode` to `CUSTOM_FILTER` - Renamed `_lastClickTime` to `_lastPressTime` --- flixel/FlxG.hx | 3 + flixel/system/frontEnds/InputTextFrontEnd.hx | 98 +++++ flixel/text/FlxInputText.hx | 419 ++++++++++++++++++- 3 files changed, 509 insertions(+), 11 deletions(-) diff --git a/flixel/FlxG.hx b/flixel/FlxG.hx index 22b8daf8a8..a619971681 100644 --- a/flixel/FlxG.hx +++ b/flixel/FlxG.hx @@ -321,6 +321,9 @@ class FlxG */ public static var plugins(default, null):PluginFrontEnd; + /** + * Used for detecting text input and dispatching commands on input text fields. + */ public static var inputText(default, null):InputTextFrontEnd = new InputTextFrontEnd(); public static var initialWidth(default, null):Int = 0; diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index 0fd30c90b9..4bbb746600 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -5,16 +5,32 @@ import lime.ui.KeyModifier; import openfl.events.Event; import openfl.events.TextEvent; +/** + * Accessed via `FlxG.inputText`. + */ class InputTextFrontEnd { + /** + * The input text object that's currently in focus, or `null` if there isn't any. + */ public var focus(default, set):IFlxInputText; + /** + * Returns whether or not there's currently an editable input text in focus. + */ public var isTyping(get, never):Bool; + /** + * Contains all of the currently registered input text objects. + */ var _registeredInputTexts:Array = []; public function new() {} + /** + * Registers an input text object, and initiates the event listeners if it's + * the first one to be added. + */ public function registerInputText(input:IFlxInputText):Void { if (!_registeredInputTexts.contains(input)) @@ -38,6 +54,10 @@ class InputTextFrontEnd } } + /** + * Unregisters an input text object, and removes the event listeners if there + * aren't any more left. + */ public function unregisterInputText(input:IFlxInputText):Void { if (_registeredInputTexts.contains(input)) @@ -59,6 +79,9 @@ class InputTextFrontEnd } } + /** + * Called when a `TEXT_INPUT` event is received. + */ function onTextInput(event:TextEvent):Void { // Adding new lines is handled inside FlxInputText @@ -71,6 +94,9 @@ class InputTextFrontEnd } } + /** + * Called when an `onKeyDown` event is recieved. + */ function onKeyDown(key:KeyCode, modifier:KeyModifier):Void { if (focus == null) @@ -183,6 +209,10 @@ class InputTextFrontEnd } #if flash + /** + * Called when an `onKeyUp` event is recieved. This is used to reset the stage's focus + * back to null. + */ function onKeyUp(key:KeyCode, modifier:KeyModifier):Void { if (FlxG.stage.focus == FlxG.stage) @@ -191,6 +221,9 @@ class InputTextFrontEnd } } + /** + * Called when a `COPY` event is received. + */ function onCopy(e:Event):Void { if (focus != null) @@ -199,6 +232,9 @@ class InputTextFrontEnd } } + /** + * Called when a `CUT` event is received. + */ function onCut(e:Event):Void { if (focus != null) @@ -207,6 +243,9 @@ class InputTextFrontEnd } } + /** + * Called when a `PASTE` event is received. + */ function onPaste(e:Event):Void { if (focus != null) @@ -215,6 +254,9 @@ class InputTextFrontEnd } } + /** + * Called when a `SELECT_ALL` event is received. + */ function onSelectAll(e:Event):Void { if (focus != null) @@ -268,25 +310,81 @@ enum TypingAction enum MoveCursorAction { + /** + * Moves the cursor one character to the left. + */ LEFT; + /** + * Moves the cursor one character to the right. + */ RIGHT; + /** + * Moves the cursor up to the previous line. + */ UP; + /** + * Moves the cursor down to the next line. + */ DOWN; + /** + * Moves the cursor to the beginning of the text. + */ HOME; + /** + * Moves the cursor to the end of the text. + */ END; + /** + * Moves the cursor to the beginning of the current line. + */ LINE_BEGINNING; + /** + * Moves the cursor to the end of the current line. + */ LINE_END; + /** + * Moves the cursor to the beginning of the current line, or the previous line + * if it's already there. + */ PREVIOUS_LINE; + /** + * Moves the cursor to the beginning of the next line, or the end of the text + * if it's at the last line. + */ NEXT_LINE; } enum TypingCommand { + /** + * Enters a new line into the text. + */ NEW_LINE; + /** + * Deletes the character to the left of the cursor, or the selection if + * there's already one. + */ DELETE_LEFT; + /** + * Deletes the character to the right of the cursor, or the selection if + * there's already one. + */ DELETE_RIGHT; + /** + * Copies the current selection into the clipboard. + */ COPY; + /** + * Copies the current selection into the clipboard and then removes it + * from the text field. + */ CUT; + /** + * Pastes the clipboard's text into the field. + */ PASTE; + /** + * Selects all of the text in the field. + */ SELECT_ALL; } \ No newline at end of file diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 1471fd0d4a..aa964ad2fa 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -16,66 +16,180 @@ import openfl.geom.Rectangle; import openfl.text.TextFormat; import openfl.utils.QName; +/** + * An `FlxText` object that can be selected and edited by the user. + */ class FlxInputText extends FlxText implements IFlxInputText { + /** + * The gaps at the sides of the text field (2px). + */ static inline var GUTTER:Int = 2; + /** + * Characters that break up the words to select when double-pressing. + */ static final DELIMITERS:Array = ['\n', '.', '!', '?', ',', ' ', ';', ':', '(', ')', '-', '_', '/']; + /** + * Whether or not the text field has a background. + */ public var background(default, set):Bool = false; - public var backgroundColor(default, set):FlxColor = FlxColor.TRANSPARENT; + /** + * The color of the background of the text field, if it's enabled. + */ + public var backgroundColor(default, set):FlxColor = FlxColor.WHITE; + /** + * Indicates the bottommost line (1-based index) that is currently + * visible in the text field. + */ public var bottomScrollV(get, never):Int; + /** + * A function called when an action occurs in the text field. The first parameter + * is the current text, and the second parameter indicates what kind of action + * it was. + */ public var callback:String->FlxInputTextAction->Void; - public var caretColor(default, set):FlxColor = FlxColor.WHITE; + /** + * The selection cursor's color. Has the same color as the text field by default, and + * it's automatically set whenever it changes. + */ + public var caretColor(default, set):FlxColor; + /** + * The position of the selection cursor. An index of 0 means the caret is before the + * character at position 0. + * + * Modifying this will reset the current selection (no text will be selected). + */ public var caretIndex(get, set):Int; + /** + * The selection cursor's width. + */ public var caretWidth(default, set):Int = 1; + /** + * This regular expression will filter out (remove) everything that matches. + * + * Changing this will automatically set `filterMode` to `CUSTOM_FILTER`. + */ public var customFilterPattern(default, set):EReg; + /** + * Whether or not the text field can be edited by the user. + */ public var editable:Bool = true; + /** + * The color of the border for the text field, if it has a background. + */ public var fieldBorderColor(default, set):FlxColor = FlxColor.BLACK; + /** + * The thickness of the border for the text field, if it has a background. + * + * Setting this to 0 will remove the border entirely. + */ public var fieldBorderThickness(default, set):Int = 1; + /** + * Defines how to filter the text (no filter, only letters, only numbers, + * only letters & numbers, or a custom filter). + */ public var filterMode(default, set):FlxInputTextFilterMode = NO_FILTER; + /** + * Callback that is triggered when this text field gains focus. + */ public var focusGained:Void->Void; + /** + * Callback that is triggered when this text field loses focus. + */ public var focusLost:Void->Void; + /** + * Defines whether a letter case is enforced on the text. + */ public var forceCase(default, set):FlxInputTextCase = ALL_CASES; + /** + * Whether or not the text field is the current active one on the screen. + */ public var hasFocus(default, set):Bool = false; + /** + * Set the maximum length for the text field. 0 means unlimited. + */ public var maxLength(default, set):Int = 0; + /** + * The maximum value of `scrollH`. + */ public var maxScrollH(get, never):Int; + /** + * The maximum value of `scrollV`. + */ public var maxScrollV(get, never):Int; public var multiline(get, set):Bool; + /** + * Whether or not the text field is a password text field. This will + * hide all characters behind asterisks (*), and prevent any text + * from being copied. + */ public var passwordMode(get, set):Bool; + /** + * The current horizontal scrolling position, in pixels. Defaults to + * 0, which means the text is not horizontally scrolled. + */ public var scrollH(get, set):Int; + /** + * The current vertical scrolling position, by line number. If the first + * line displayed is the first line in the text field, `scrollV` + * is set to 1 (not 0). + */ public var scrollV(get, set):Int; + /** + * Whether or not the text can be selected by the user. If set to false, + * the text field will technically also become uneditable, since the user + * can't select it first. + */ public var selectable:Bool = true; + /** + * The color that the text inside the selection will change into, if + * `useSelectedTextFormat` is enabled. + */ public var selectedTextColor(default, set):FlxColor = FlxColor.WHITE; + /** + * The beginning index of the current selection. + * + * **Warning:** Will be -1 if the text hasn't been selected yet! + */ public var selectionBeginIndex(get, never):Int; + /** + * The color of the selection, shown behind the currently selected text. + */ public var selectionColor(default, set):FlxColor = FlxColor.BLACK; + /** + * The ending index of the current selection. + * + * **Warning:** Will be -1 if the text hasn't been selected yet! + */ public var selectionEndIndex(get, never):Int; /** @@ -86,36 +200,110 @@ class FlxInputText extends FlxText implements IFlxInputText */ public var useSelectedTextFormat(default, set):Bool = true; + /** + * An FlxSprite representing the background of the text field. + */ var _backgroundSprite:FlxSprite; + /** + * An FlxSprite representing the selection cursor. + */ var _caret:FlxSprite; + /** + * This variable is used for the caret flash timer to indicate whether it + * is currently visible or not. + */ var _caretFlash:Bool = false; + /** + * Internal variable for the current index of the selection cursor. + */ var _caretIndex:Int = -1; + /** + * The timer used to flash the caret while the text field has focus. + */ var _caretTimer:FlxTimer = new FlxTimer(); + /** + * An FlxSprite representing the border of the text field. + */ var _fieldBorderSprite:FlxSprite; + /** + * Internal variable that holds the camera that the text field is being pressed on. + */ var _pointerCamera:FlxCamera; + /** + * Indicates whether or not the background sprites need to be regenerated due to a + * change. + */ var _regenBackground:Bool = false; + /** + * An array holding the selection box sprites for the text field. It will only be as + * long as the amount of lines that are currently visible. Some items may be null if + * the respective line hasn't been selected yet. + */ var _selectionBoxes:Array = []; + /** + * The format that will be used for text inside the current selection. + */ var _selectionFormat:TextFormat = new TextFormat(); + /** + * The current index of the selection from the caret. + */ var _selectionIndex:Int = -1; #if FLX_POINTER_INPUT - var _lastClickTime:Int = 0; + /** + * Stores the last time that this text field was pressed on, which helps to check for double-presses. + */ + var _lastPressTime:Int = 0; + + /** + * Timer for the text field to scroll vertically when dragging over it. + */ var _scrollVCounter:Float = 0; #if FLX_MOUSE + /** + * Indicates whether the mouse is pressing down on this text field. + */ var _mouseDown:Bool = false; #end #if FLX_TOUCH + /** + * Stores the FlxTouch that is pressing down on this text field, if there is one. + */ var _currentTouch:FlxTouch; + /** + * Used for checking if the current touch has just moved on the X axis. + */ var _lastTouchX:Null; + /** + * Used for checking if the current touch has just moved on the Y axis. + */ var _lastTouchY:Null; #end #end + /** + * Creates a new `FlxInputText` object at the specified position. + * @param x The X position of the text. + * @param y The Y position of the text. + * @param fieldWidth The `width` of the text object. Enables `autoSize` if `<= 0`. + * (`height` is determined automatically). + * @param text The actual text you would like to display initially. + * @param size The font size for this text object. + * @param textColor The color of the text + * @param backgroundColor The color of the background (`FlxColor.TRANSPARENT` for no background color) + * @param embeddedFont Whether this text field uses embedded fonts or not. + */ public function new(x:Float = 0, y:Float = 0, fieldWidth:Float = 0, ?text:String, size:Int = 8, textColor:FlxColor = FlxColor.BLACK, backgroundColor:FlxColor = FlxColor.WHITE, embeddedFont:Bool = true) { super(x, y, fieldWidth, text, size, embeddedFont); + if (text == null || text == "") + { + textField.text = ""; + _regen = true; + } this.backgroundColor = backgroundColor; + // Default to a single-line text field wordWrap = multiline = false; // If the text field's type isn't INPUT and there's a new line at the end // of the text, it won't be counted for in `numLines` @@ -166,6 +354,9 @@ class FlxInputText extends FlxText implements IFlxInputText drawSprite(_caret); } + /** + * Clean up memory. + */ override function destroy():Void { FlxG.inputText.unregisterInputText(this); @@ -235,6 +426,10 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Replaces the currently selected text with `newText`, or just inserts it at + * the selection cursor if there isn't any text selected. + */ public function replaceSelectedText(newText:String):Void { if (newText == null) @@ -256,6 +451,10 @@ class FlxInputText extends FlxText implements IFlxInputText replaceText(beginIndex, endIndex, newText); } + /** + * Sets the selection to span from `beginIndex` to `endIndex`. The selection cursor + * will end up at `endIndex`. + */ public function setSelection(beginIndex:Int, endIndex:Int):Void { _selectionIndex = beginIndex; @@ -267,6 +466,9 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelection(); } + /** + * Filters the specified text and adds it to the field at the current selection. + */ function addText(newText:String):Void { newText = filterText(newText, true); @@ -277,6 +479,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Helper function to draw sprites with the correct cameras and scroll factor. + */ function drawSprite(sprite:FlxSprite):Void { if (sprite != null && sprite.visible) @@ -287,6 +492,13 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Returns the specified text filtered using `maxLength`, `forceCase` and `filterMode`. + * @param newText The string to filter. + * @param selection Whether or not this string is meant to be added at the selection or if we're + * replacing the entire text. This is used for cutting the string appropiately + * when `maxLength` is set. + */ function filterText(newText:String, selection:Bool = false):String { if (maxLength > 0) @@ -335,6 +547,11 @@ class FlxInputText extends FlxText implements IFlxInputText return newText; } + /** + * Returns the X offset of the selection cursor based on the current alignment. + * + * Used for positioning the cursor when there isn't any text at the current line. + */ function getCaretOffsetX():Float { return switch (alignment) @@ -345,6 +562,13 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Gets the character index at a specific point on the text field. + * + * If the point is over a line but not over a character inside it, it will return + * the last character in the line. If no line is found at the point, the length + * of the text is returned. + */ function getCharAtPosition(x:Float, y:Float):Int { if (x < GUTTER) @@ -392,6 +616,12 @@ class FlxInputText extends FlxText implements IFlxInputText return text.length; } + /** + * Gets the boundaries of the character at the specified index in the text field. + * + * This handles `textField.getCharBoundaries()` not being able to return boundaries + * of a character that isn't currently visible on Flash. + */ function getCharBoundaries(char:Int):Rectangle { #if flash @@ -426,6 +656,10 @@ class FlxInputText extends FlxText implements IFlxInputText return boundaries; } + /** + * Gets the index of the character horizontally closest to `charIndex` at the + * specified line. + */ function getCharIndexOnDifferentLine(charIndex:Int, lineIndex:Int):Int { if (charIndex < 0 || charIndex > text.length) @@ -449,6 +683,12 @@ class FlxInputText extends FlxText implements IFlxInputText return getCharAtPosition(x, y); } + /** + * Gets the line index of the specified character. + * + * This handles `textField.getLineIndexOfChar()` not returning a valid index for the + * text's length on Flash. + */ function getLineIndexOfChar(char:Int):Int { // On Flash, if the character is equal to the end of the text, it returns -1 as the line. @@ -457,7 +697,9 @@ class FlxInputText extends FlxText implements IFlxInputText } /** - * NOTE: This does not include the vertical gutter on top of the text field. + * Gets the Y position of the specified line in the text field. + * + * **NOTE:** This does not include the vertical gutter on top of the text field. */ function getLineY(line:Int):Float { @@ -469,6 +711,11 @@ class FlxInputText extends FlxText implements IFlxInputText return scrollY; } + /** + * Calculates the bounds of the text field on the stage, which is used for setting the + * text input rect for the Lime window. + * @param camera The camera to use to get the bounds of the text field. + */ function getLimeBounds(camera:FlxCamera):lime.math.Rectangle { if (camera == null) @@ -496,11 +743,17 @@ class FlxInputText extends FlxText implements IFlxInputText return new lime.math.Rectangle(rect.x, rect.y, rect.width, rect.height); } + /** + * Gets the Y offset of the current vertical scroll based on `scrollV`. + */ function getScrollVOffset():Float { return getLineY(scrollV - 1); } + /** + * Checks if the line the selection cursor is at is currently visible. + */ function isCaretLineVisible():Bool { // `getLineIndexOfChar()` will return -1 if text is empty, but we still want the caret to show up @@ -511,6 +764,11 @@ class FlxInputText extends FlxText implements IFlxInputText return line >= scrollV - 1 && line <= bottomScrollV - 1; } + /** + * Dispatches an action to move the selection cursor. + * @param type The type of action to dispatch. + * @param shiftKey Whether or not the shift key is currently pressed. + */ function moveCursor(type:MoveCursorAction, shiftKey:Bool):Void { switch (type) @@ -640,6 +898,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Dispatches `callback` with the appropiate text action, if there is one set. + */ function onChange(action:FlxInputTextAction):Void { if (callback != null) @@ -652,6 +913,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Regenerates the background sprites if they're enabled. + */ function regenBackground():Void { if (!background) @@ -682,6 +946,10 @@ class FlxInputText extends FlxText implements IFlxInputText _regenBackground = false; } + /** + * Replaces the text at the specified range with `newText`, or just inserts it if + * `beginIndex` and `endIndex` are the same. + */ function replaceText(beginIndex:Int, endIndex:Int, newText:String):Void { if (endIndex < beginIndex || beginIndex < 0 || endIndex > text.length || newText == null) @@ -693,6 +961,9 @@ class FlxInputText extends FlxText implements IFlxInputText setSelection(_selectionIndex, _caretIndex); } + /** + * Runs the specified typing command. + */ function runCommand(cmd:TypingCommand):Void { switch (cmd) @@ -772,6 +1043,11 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Starts the timer for the caret to flash. + * + * Call this right after `stopCaretTimer()` to show the caret immediately. + */ function startCaretTimer():Void { _caretTimer.cancel(); @@ -784,6 +1060,9 @@ class FlxInputText extends FlxText implements IFlxInputText }); } + /** + * Stops the timer for the caret to flash and hides it. + */ function stopCaretTimer():Void { _caretTimer.cancel(); @@ -795,6 +1074,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Updates the position of the background sprites, if they're enabled. + */ function updateBackgroundPosition():Void { if (!background) @@ -804,6 +1086,9 @@ class FlxInputText extends FlxText implements IFlxInputText _backgroundSprite.setPosition(x, y); } + /** + * Updates the position of the selection cursor. + */ function updateCaretPosition():Void { if (textField == null) @@ -837,21 +1122,35 @@ class FlxInputText extends FlxText implements IFlxInputText _caret.clipRect = _caret.getHitbox(_caret.clipRect).clipTo(FlxRect.weak(x, y, width, height)).offset(-_caret.x, -_caret.y); } + /** + * Updates the size of the selection cursor. + */ function updateCaretSize():Void { if (_caret == null) return; + var lineHeight = height - (GUTTER * 2); + if (text.length > 0) + { + lineHeight = textField.getLineMetrics(0).height; + } - _caret.setGraphicSize(caretWidth, textField.getLineMetrics(0).height); + _caret.setGraphicSize(caretWidth, lineHeight); _caret.updateHitbox(); } + /** + * Updates the visibility of the selection cursor. + */ function updateCaretVisibility():Void { _caret.visible = (_caretFlash && _selectionIndex == _caretIndex && isCaretLineVisible()); } #if flash + /** + * Used in Flash to automatically update the horizontal scroll after setting the selection. + */ function updateScrollH():Void { if (textField.textWidth <= width - (GUTTER * 2)) @@ -903,6 +1202,10 @@ class FlxInputText extends FlxText implements IFlxInputText } #end + /** + * Updates the selection with the current `_selectionIndex` and `_caretIndex`. + * @param keepScroll Whether or not to keep the current horizontal and vertical scroll. + */ function updateSelection(keepScroll:Bool = false):Void { var cacheScrollH = scrollH; @@ -932,6 +1235,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Updates the selection boxes according to the current selection. + */ function updateSelectionBoxes():Void { if (textField == null) @@ -1019,6 +1325,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Updates both the selection cursor and the selection boxes. + */ function updateSelectionSprites():Void { updateCaretVisibility(); @@ -1026,6 +1335,9 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelectionBoxes(); } + /** + * Updates all of the sprites' positions. + */ function updateSpritePositions():Void { updateBackgroundPosition(); @@ -1034,6 +1346,10 @@ class FlxInputText extends FlxText implements IFlxInputText } #if FLX_POINTER_INPUT + /** + * Checks for mouse input on the text field. + * @return Whether or not mouse overlap was detected. + */ function updateMouseInput(elapsed:Float):Bool { var overlap = false; @@ -1055,7 +1371,7 @@ class FlxInputText extends FlxText implements IFlxInputText } else if (FlxG.mouse.justReleased) { - _lastClickTime = 0; + _lastPressTime = 0; } if (checkPointerOverlap(FlxG.mouse)) @@ -1084,6 +1400,10 @@ class FlxInputText extends FlxText implements IFlxInputText return overlap; } + /** + * Checks for touch input on the text field. + * @return Whether or not touch overlap was detected. + */ function updateTouchInput(elapsed:Float):Bool { var overlap = false; @@ -1125,7 +1445,7 @@ class FlxInputText extends FlxText implements IFlxInputText else if (touch.justPressed) { pressedElsewhere = true; - _lastClickTime = 0; + _lastPressTime = 0; } } if (pressedElsewhere && _currentTouch == null) @@ -1136,6 +1456,10 @@ class FlxInputText extends FlxText implements IFlxInputText return overlap; } + /** + * Checks if the pointer is overlapping the text field. This will also set + * `_pointerCamera` accordingly if it detects overlap. + */ function checkPointerOverlap(pointer:FlxPointer):Bool { var overlap = false; @@ -1156,6 +1480,9 @@ class FlxInputText extends FlxText implements IFlxInputText return overlap; } + /** + * Called when a pointer presses on this text field. + */ function updatePointerPress(pointer:FlxPointer):Void { hasFocus = true; @@ -1168,6 +1495,9 @@ class FlxInputText extends FlxText implements IFlxInputText relativePos.put(); } + /** + * Updates the text field's dragging while a pointer has pressed down on it. + */ function updatePointerDrag(pointer:FlxPointer, elapsed:Float):Void { var relativePos = getRelativePosition(pointer); @@ -1204,6 +1534,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Called when a pointer moves while its pressed down on the text field. + */ function updatePointerMove(pointer:FlxPointer):Void { if (_selectionIndex < 0) @@ -1221,6 +1554,9 @@ class FlxInputText extends FlxText implements IFlxInputText relativePos.put(); } + /** + * Called when a pointer is released after pressing down on the text field. + */ function updatePointerRelease(pointer:FlxPointer):Void { if (!hasFocus) @@ -1245,17 +1581,20 @@ class FlxInputText extends FlxText implements IFlxInputText relativePos.put(); _pointerCamera = null; var currentTime = FlxG.game.ticks; - if (currentTime - _lastClickTime < 500) + if (currentTime - _lastPressTime < 500) { updatePointerDoublePress(pointer); - _lastClickTime = 0; + _lastPressTime = 0; } else { - _lastClickTime = currentTime; + _lastPressTime = currentTime; } } + /** + * Called when a pointer double-presses the text field. + */ function updatePointerDoublePress(pointer:FlxPointer):Void { var rightPos = text.length; @@ -1283,6 +1622,9 @@ class FlxInputText extends FlxText implements IFlxInputText } } + /** + * Returns the position of the pointer relative to the text field. + */ function getRelativePosition(pointer:FlxPointer):FlxPoint { var pointerPos = pointer.getWorldPosition(_pointerCamera); @@ -1407,6 +1749,10 @@ class FlxInputText extends FlxText implements IFlxInputText setSelection(_selectionIndex, _caretIndex); } + if (autoSize || _autoHeight) + { + _regenBackground = true; + } } return value; @@ -1524,7 +1870,11 @@ class FlxInputText extends FlxText implements IFlxInputText if (customFilterPattern != value) { customFilterPattern = value; - if (filterMode == CUSTOM_FILTER) + if (filterMode != CUSTOM_FILTER) + { + filterMode = CUSTOM_FILTER; + } + else { text = filterText(text); } @@ -1771,26 +2121,73 @@ class FlxInputText extends FlxText implements IFlxInputText enum abstract FlxInputTextAction(String) from String to String { + /** + * Dispatched whenever the text is changed by the user. It's always + * dispatched after `INPUT_ACTION`, `BACKSPACE_ACTION`, and + * `DELETE_ACTION`. + */ var CHANGE_ACTION = "change"; + /** + * Dispatched whenever new text is added by the user. + */ var INPUT_ACTION = "input"; + /** + * Dispatched whenever text to the left is removed by the user (pressing + * backspace). + */ var BACKSPACE_ACTION = "backspace"; + /** + * Dispatched whenever text to the right is removed by the user (pressing + * delete). + */ var DELETE_ACTION = "delete"; + /** + * Dispatched whenever enter is pressed by the user while the text field + * is focused. + */ var ENTER_ACTION = "enter"; + /** + * Dispatched whenever the text field is scrolled in some way. + */ var SCROLL_ACTION = "scroll"; } enum abstract FlxInputTextCase(Int) from Int to Int { + /** + * Allows both lowercase and uppercase letters. + */ var ALL_CASES = 0; + /** + * Changes all text to be uppercase. + */ var UPPER_CASE = 1; + /** + * Changes all text to be lowercase. + */ var LOWER_CASE = 2; } enum abstract FlxInputTextFilterMode(Int) from Int to Int { + /** + * Does not filter the text at all. + */ var NO_FILTER = 0; + /** + * Only allows letters (a-z & A-Z) to be added to the text. + */ var ONLY_ALPHA = 1; + /** + * Only allows numbers (0-9) to be added to the text. + */ var ONLY_NUMERIC = 2; + /** + * Only allows letters (a-z & A-Z) and numbers (0-9) to be added to the text. + */ var ONLY_ALPHANUMERIC = 3; + /** + * Lets you use a custom filter with `customFilterPattern`. + */ var CUSTOM_FILTER = 4; } \ No newline at end of file From 9a8d3cc3a57ab97eba9abd5655cf36f682781a35 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sat, 13 Jul 2024 17:32:23 -0400 Subject: [PATCH 26/48] Add documentation to `multiline` variable - Remove setting `_autoHeight` to false after setting multiline to true as its no longer needed --- flixel/text/FlxInputText.hx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index aa964ad2fa..ccdc4ed95f 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -138,6 +138,10 @@ class FlxInputText extends FlxText implements IFlxInputText */ public var maxScrollV(get, never):Int; + /** + * Whether or not the user can create a new line in the text field + * with the enter key. + */ public var multiline(get, set):Bool; /** @@ -2008,10 +2012,6 @@ class FlxInputText extends FlxText implements IFlxInputText if (textField.multiline != value) { textField.multiline = value; - if (multiline) - { - _autoHeight = false; - } } return value; From 0d35d342f30096cb33ff4afc11843bfeee8b8367 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sat, 13 Jul 2024 18:24:08 -0400 Subject: [PATCH 27/48] Add `mouseWheelEnabled` variable - Remove unneeded `selectable` comment --- flixel/text/FlxInputText.hx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index ccdc4ed95f..0a961d80b5 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -137,6 +137,12 @@ class FlxInputText extends FlxText implements IFlxInputText * The maximum value of `scrollV`. */ public var maxScrollV(get, never):Int; + + /** + * Whether or not the text field will automatically be scrolled + * when the user rolls the mouse wheel on the text field. + */ + public var mouseWheelEnabled:Bool = true; /** * Whether or not the user can create a new line in the text field @@ -165,9 +171,7 @@ class FlxInputText extends FlxText implements IFlxInputText public var scrollV(get, set):Int; /** - * Whether or not the text can be selected by the user. If set to false, - * the text field will technically also become uneditable, since the user - * can't select it first. + * Whether or not the text can be selected by the user. */ public var selectable:Bool = true; @@ -1386,7 +1390,7 @@ class FlxInputText extends FlxText implements IFlxInputText _mouseDown = true; updatePointerPress(FlxG.mouse); } - if (FlxG.mouse.wheel != 0) + if (FlxG.mouse.wheel != 0 && mouseWheelEnabled) { var cacheScrollV = scrollV; scrollV = FlxMath.minInt(scrollV - FlxG.mouse.wheel, maxScrollV); From a0042fc9e999fa4ce8af0427ddff87864e063ec3 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sat, 13 Jul 2024 20:27:16 -0400 Subject: [PATCH 28/48] Replaced `PREVIOUS_LINE` and `NEXT_LINE` with `WORD_LEFT` and `WORD_RIGHT` - Ctrl + Up/Down now dispatches `LINE_LEFT` or `LINE_RIGHT` instead of `HOME` or `END` - Renamed `LINE_BEGINNING` and `LINE_END` to `LINE_LEFT` and `LINE_RIGHT` --- flixel/system/frontEnds/InputTextFrontEnd.hx | 28 +++++++-------- flixel/text/FlxInputText.hx | 37 +++++++++----------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index 4bbb746600..13b35102f5 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -122,7 +122,7 @@ class InputTextFrontEnd case LEFT: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(PREVIOUS_LINE, modifier.shiftKey)); + focus.dispatchTypingAction(MOVE_CURSOR(WORD_LEFT, modifier.shiftKey)); } else { @@ -131,7 +131,7 @@ class InputTextFrontEnd case RIGHT: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(NEXT_LINE, modifier.shiftKey)); + focus.dispatchTypingAction(MOVE_CURSOR(WORD_RIGHT, modifier.shiftKey)); } else { @@ -140,7 +140,7 @@ class InputTextFrontEnd case UP: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); + focus.dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); } else { @@ -149,7 +149,7 @@ class InputTextFrontEnd case DOWN: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); + focus.dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); } else { @@ -162,7 +162,7 @@ class InputTextFrontEnd } else { - focus.dispatchTypingAction(MOVE_CURSOR(LINE_BEGINNING, modifier.shiftKey)); + focus.dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); } case END: if (modifierPressed) @@ -171,7 +171,7 @@ class InputTextFrontEnd } else { - focus.dispatchTypingAction(MOVE_CURSOR(LINE_END, modifier.shiftKey)); + focus.dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); } case C: if (modifierPressed) @@ -337,21 +337,21 @@ enum MoveCursorAction /** * Moves the cursor to the beginning of the current line. */ - LINE_BEGINNING; + LINE_LEFT; /** * Moves the cursor to the end of the current line. */ - LINE_END; + LINE_RIGHT; /** - * Moves the cursor to the beginning of the current line, or the previous line - * if it's already there. + * Moves the cursor to the beginning of the previous word, or the + * start of the text if there aren't any more words. */ - PREVIOUS_LINE; + WORD_LEFT; /** - * Moves the cursor to the beginning of the next line, or the end of the text - * if it's at the last line. + * Moves the cursor to the beginning of the next word, or the end + * of the text if there aren't any more words. */ - NEXT_LINE; + WORD_RIGHT; } enum TypingCommand diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 0a961d80b5..f9099c2d8c 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -1,7 +1,7 @@ package flixel.text; -import flixel.input.touch.FlxTouch; import flixel.input.FlxPointer; +import flixel.input.touch.FlxTouch; import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; @@ -27,7 +27,7 @@ class FlxInputText extends FlxText implements IFlxInputText static inline var GUTTER:Int = 2; /** - * Characters that break up the words to select when double-pressing. + * Characters that break up the words to select. */ static final DELIMITERS:Array = ['\n', '.', '!', '?', ',', ' ', ';', ':', '(', ')', '-', '_', '/']; @@ -843,7 +843,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); - case LINE_BEGINNING: + case LINE_LEFT: _caretIndex = textField.getLineOffset(getLineIndexOfChar(_caretIndex)); if (!shiftKey) @@ -851,7 +851,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); - case LINE_END: + case LINE_RIGHT: var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) { @@ -867,18 +867,17 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); - case PREVIOUS_LINE: - var lineIndex = getLineIndexOfChar(_caretIndex); - if (lineIndex > 0) + case WORD_LEFT: + if (_caretIndex > 0) { - var index = textField.getLineOffset(lineIndex); - if (_caretIndex == index) + _caretIndex--; + while (_caretIndex > 0 && DELIMITERS.contains(text.charAt(_caretIndex))) { - _caretIndex = textField.getLineOffset(lineIndex - 1); + _caretIndex--; } - else + while (_caretIndex > 0 && !DELIMITERS.contains(text.charAt(_caretIndex - 1))) { - _caretIndex = index; + _caretIndex--; } } @@ -887,15 +886,14 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); - case NEXT_LINE: - var lineIndex = getLineIndexOfChar(_caretIndex); - if (lineIndex < textField.numLines - 1) + case WORD_RIGHT: + while (_caretIndex < text.length && !DELIMITERS.contains(text.charAt(_caretIndex))) { - _caretIndex = textField.getLineOffset(lineIndex + 1); + _caretIndex++; } - else + while (_caretIndex < text.length && DELIMITERS.contains(text.charAt(_caretIndex))) { - _caretIndex = text.length; + _caretIndex++; } if (!shiftKey) @@ -1609,12 +1607,11 @@ class FlxInputText extends FlxText implements IFlxInputText if (text.length > 0 && _caretIndex >= 0 && rightPos >= _caretIndex) { var leftPos = -1; - var pos = 0; var startPos = FlxMath.maxInt(_caretIndex, 1); for (c in DELIMITERS) { - pos = text.lastIndexOf(c, startPos - 1); + var pos = text.lastIndexOf(c, startPos - 1); if (pos > leftPos) leftPos = pos + 1; From 6eafd06f717e47a95b4c21ad5bf5b1cf72aa48d8 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Sun, 14 Jul 2024 00:47:16 -0400 Subject: [PATCH 29/48] Various fixes - Clip rect should now work properly - Fixed caret showing up after changing `text` through code - Focus will not be removed due to clicking outside of the text field if it has been granted via code in the same frame - Caret will no longer be visible if the text field isn't editable - Fixed some html5 tests not compiling on CI --- flixel/system/frontEnds/InputTextFrontEnd.hx | 2 +- flixel/text/FlxInputText.hx | 119 +++++++++++++++---- 2 files changed, 95 insertions(+), 26 deletions(-) diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/system/frontEnds/InputTextFrontEnd.hx index 13b35102f5..21a11ba8f0 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/system/frontEnds/InputTextFrontEnd.hx @@ -198,7 +198,7 @@ class InputTextFrontEnd default: } - #if html5 + #if (html5 && FLX_KEYBOARD) // On HTML5, the SPACE key gets added to `FlxG.keys.preventDefaultKeys` by default, which also // stops it from dispatching a text input event. We need to call `onTextInput()` manually if (key == SPACE && FlxG.keys.preventDefaultKeys != null && FlxG.keys.preventDefaultKeys.contains(SPACE)) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index f9099c2d8c..5e3f4257c6 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -233,6 +233,13 @@ class FlxInputText extends FlxText implements IFlxInputText * An FlxSprite representing the border of the text field. */ var _fieldBorderSprite:FlxSprite; + /** + * Helper variable to prevent the text field from being unfocused from + * clicking outside of the fieldif the focus has just been granted + * through code (e.g. a separate focusing system). + */ + var _justGainedFocus:Bool = false; + /** * Internal variable that holds the camera that the text field is being pressed on. */ @@ -319,7 +326,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionFormat.color = selectedTextColor; - _caret = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); + _caret = new FlxSprite().makeGraphic(1, 1); _caret.visible = false; updateCaretSize(); updateCaretPosition(); @@ -345,6 +352,10 @@ class FlxInputText extends FlxText implements IFlxInputText updateTouchInput(elapsed); } #end + if (_justGainedFocus) + { + _justGainedFocus = false; + } } override function draw():Void @@ -486,6 +497,32 @@ class FlxInputText extends FlxText implements IFlxInputText onChange(INPUT_ACTION); } } + + /** + * Clips the sprite inside the bounds of the text field, taking + * `clipRect` into account. + */ + function clipSprite(sprite:FlxSprite) + { + if (sprite == null) + return; + + var rect = sprite.clipRect; + if (rect == null) + rect = FlxRect.get(); + rect.set(0, 0, sprite.width, sprite.height); + + var bounds = FlxRect.get(0, 0, width, height); + if (clipRect != null) + { + bounds = bounds.clipTo(clipRect); + } + bounds.offset(x - sprite.x, y - sprite.y); + + sprite.clipRect = rect.clipTo(bounds); + + bounds.put(); + } /** * Helper function to draw sprites with the correct cameras and scroll factor. @@ -792,6 +829,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case RIGHT: if (_caretIndex < text.length) { @@ -803,6 +841,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case UP: var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex > 0) @@ -815,6 +854,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case DOWN: var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) @@ -827,6 +867,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case HOME: _caretIndex = 0; @@ -835,6 +876,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case END: _caretIndex = text.length; @@ -843,6 +885,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case LINE_LEFT: _caretIndex = textField.getLineOffset(getLineIndexOfChar(_caretIndex)); @@ -851,6 +894,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case LINE_RIGHT: var lineIndex = getLineIndexOfChar(_caretIndex); if (lineIndex < textField.numLines - 1) @@ -867,6 +911,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case WORD_LEFT: if (_caretIndex > 0) { @@ -886,6 +931,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); case WORD_RIGHT: while (_caretIndex < text.length && !DELIMITERS.contains(text.charAt(_caretIndex))) { @@ -901,6 +947,7 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex; } setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); } } @@ -965,6 +1012,16 @@ class FlxInputText extends FlxText implements IFlxInputText _selectionIndex = _caretIndex = beginIndex + newText.length; setSelection(_selectionIndex, _caretIndex); + restartCaretTimer(); + } + + /** + * Helper function to stop and then start the caret flashing timer. + */ + function restartCaretTimer():Void + { + stopCaretTimer(); + startCaretTimer(); } /** @@ -981,8 +1038,7 @@ class FlxInputText extends FlxText implements IFlxInputText } else { - stopCaretTimer(); - startCaretTimer(); + restartCaretTimer(); } onChange(ENTER_ACTION); case DELETE_LEFT: @@ -1002,8 +1058,7 @@ class FlxInputText extends FlxText implements IFlxInputText } else { - stopCaretTimer(); - startCaretTimer(); + restartCaretTimer(); } case DELETE_RIGHT: if (!editable) @@ -1022,8 +1077,7 @@ class FlxInputText extends FlxText implements IFlxInputText } else { - stopCaretTimer(); - startCaretTimer(); + restartCaretTimer(); } case COPY: if (_caretIndex != _selectionIndex && !passwordMode) @@ -1090,6 +1144,8 @@ class FlxInputText extends FlxText implements IFlxInputText _fieldBorderSprite.setPosition(x - fieldBorderThickness, y - fieldBorderThickness); _backgroundSprite.setPosition(x, y); + clipSprite(_fieldBorderSprite); + clipSprite(_backgroundSprite); } /** @@ -1125,7 +1181,7 @@ class FlxInputText extends FlxText implements IFlxInputText } } - _caret.clipRect = _caret.getHitbox(_caret.clipRect).clipTo(FlxRect.weak(x, y, width, height)).offset(-_caret.x, -_caret.y); + clipSprite(_caret); } /** @@ -1141,8 +1197,8 @@ class FlxInputText extends FlxText implements IFlxInputText lineHeight = textField.getLineMetrics(0).height; } - _caret.setGraphicSize(caretWidth, lineHeight); - _caret.updateHitbox(); + _caret.makeGraphic(caretWidth, Std.int(lineHeight)); + clipSprite(_caret); } /** @@ -1150,7 +1206,7 @@ class FlxInputText extends FlxText implements IFlxInputText */ function updateCaretVisibility():Void { - _caret.visible = (_caretFlash && _selectionIndex == _caretIndex && isCaretLineVisible()); + _caret.visible = (_caretFlash && editable && _selectionIndex == _caretIndex && isCaretLineVisible()); } #if flash @@ -1218,8 +1274,6 @@ class FlxInputText extends FlxText implements IFlxInputText var cacheScrollV = scrollV; textField.setSelection(_selectionIndex, _caretIndex); - stopCaretTimer(); - startCaretTimer(); _regen = true; if (keepScroll) @@ -1300,7 +1354,7 @@ class FlxInputText extends FlxText implements IFlxInputText { if (box == null) { - box = _selectionBoxes[i] = new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE); + box = _selectionBoxes[i] = new FlxSprite(); box.color = selectionColor; } @@ -1310,11 +1364,8 @@ class FlxInputText extends FlxText implements IFlxInputText boxRect.clipTo(FlxRect.weak(0, 0, width, height)); // clip the selection box inside the text sprite box.setPosition(x + boxRect.x, y + boxRect.y); - if (boxRect.width > 0 && boxRect.height > 0) - box.setGraphicSize(boxRect.width, boxRect.height); - else - box.scale.set(); - box.updateHitbox(); + box.makeGraphic(Std.int(boxRect.width), Std.int(boxRect.height)); + clipSprite(box); box.visible = true; boxRect.put(); @@ -1398,7 +1449,7 @@ class FlxInputText extends FlxText implements IFlxInputText } } } - else if (FlxG.mouse.justPressed) + else if (FlxG.mouse.justPressed && !_justGainedFocus) { hasFocus = false; } @@ -1454,7 +1505,7 @@ class FlxInputText extends FlxText implements IFlxInputText _lastPressTime = 0; } } - if (pressedElsewhere && _currentTouch == null) + if (pressedElsewhere && _currentTouch == null && !_justGainedFocus) { hasFocus = false; } @@ -1497,6 +1548,7 @@ class FlxInputText extends FlxText implements IFlxInputText _caretIndex = getCharAtPosition(relativePos.x + scrollH, relativePos.y + getScrollVOffset()); _selectionIndex = _caretIndex; updateSelection(true); + restartCaretTimer(); relativePos.put(); } @@ -1555,6 +1607,7 @@ class FlxInputText extends FlxText implements IFlxInputText { _caretIndex = char; updateSelection(true); + restartCaretTimer(); } relativePos.put(); @@ -1580,8 +1633,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (hasFocus) { - stopCaretTimer(); - startCaretTimer(); + restartCaretTimer(); } relativePos.put(); @@ -1650,6 +1702,18 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } + override function set_clipRect(value:FlxRect):FlxRect + { + super.set_clipRect(value); + + clipSprite(_backgroundSprite); + clipSprite(_fieldBorderSprite); + clipSprite(_caret); + for (box in _selectionBoxes) + clipSprite(box); + + return value; + } override function set_color(value:FlxColor):FlxColor { @@ -1753,6 +1817,10 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); + if (hasFocus) + { + restartCaretTimer(); + } } if (autoSize || _autoHeight) { @@ -1852,6 +1920,7 @@ class FlxInputText extends FlxText implements IFlxInputText { _caretIndex = value; setSelection(_caretIndex, _caretIndex); + restartCaretTimer(); } return value; @@ -1954,11 +2023,11 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelection(true); } - stopCaretTimer(); - startCaretTimer(); + restartCaretTimer(); if (focusGained != null) focusGained(); + _justGainedFocus = true; } else if (FlxG.inputText.focus == this) { From e9fc095a91574a294677ed93b6d9a2801658de4c Mon Sep 17 00:00:00 2001 From: Starmapo Date: Mon, 15 Jul 2024 09:54:33 -0400 Subject: [PATCH 30/48] Various more fixes - Fixed crash from recursive calls to `regenBackground()` due to `clipSprite()` - Fixed crash from `pointer.getWorldPosition()` - Fixed background not being updated after changing text format - Moved caret sprite regeneration to `regenGraphic()` --- flixel/text/FlxInputText.hx | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 5e3f4257c6..588da92c43 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -249,6 +249,12 @@ class FlxInputText extends FlxText implements IFlxInputText * change. */ var _regenBackground:Bool = false; + /** + * Indicates whether or not the selection cursor's size needs to be regenerated due + * to a change. + */ + var _regenCaretSize:Bool = false; + /** * An array holding the selection box sprites for the text field. It will only be as * long as the amount of lines that are currently visible. Some items may be null if @@ -423,7 +429,9 @@ class FlxInputText extends FlxText implements IFlxInputText super.regenGraphic(); - if (_caret != null && regenSelection) + if (_regenCaretSize) + updateCaretSize(); + if (regenSelection) updateSelectionSprites(); if (_regenBackground) regenBackground(); @@ -973,6 +981,8 @@ class FlxInputText extends FlxText implements IFlxInputText { if (!background) return; + + _regenBackground = false; if (fieldBorderThickness > 0) { @@ -996,7 +1006,6 @@ class FlxInputText extends FlxText implements IFlxInputText } updateBackgroundPosition(); - _regenBackground = false; } /** @@ -1153,7 +1162,7 @@ class FlxInputText extends FlxText implements IFlxInputText */ function updateCaretPosition():Void { - if (textField == null) + if (textField == null || _caret == null) return; if (text.length == 0) @@ -1191,6 +1200,8 @@ class FlxInputText extends FlxText implements IFlxInputText { if (_caret == null) return; + _regenCaretSize = false; + var lineHeight = height - (GUTTER * 2); if (text.length > 0) { @@ -1206,6 +1217,9 @@ class FlxInputText extends FlxText implements IFlxInputText */ function updateCaretVisibility():Void { + if (_caret == null) + return; + _caret.visible = (_caretFlash && editable && _selectionIndex == _caretIndex && isCaretLineVisible()); } @@ -1300,7 +1314,7 @@ class FlxInputText extends FlxText implements IFlxInputText */ function updateSelectionBoxes():Void { - if (textField == null) + if (textField == null || _selectionBoxes == null) return; var visibleLines = bottomScrollV - scrollV + 1; @@ -1684,7 +1698,7 @@ class FlxInputText extends FlxText implements IFlxInputText */ function getRelativePosition(pointer:FlxPointer):FlxPoint { - var pointerPos = pointer.getWorldPosition(_pointerCamera); + var pointerPos = pointer.getWorldPosition(_pointerCamera, FlxPoint.get()); getScreenPosition(_point, _pointerCamera); var result = FlxPoint.get((pointerPos.x - _pointerCamera.scroll.x) - _point.x, (pointerPos.y - _pointerCamera.scroll.y) - _point.y); pointerPos.put(); @@ -1697,7 +1711,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (bold != value) { super.set_bold(value); - updateCaretSize(); + _regenCaretSize = _regenBackground = true; } return value; @@ -1752,7 +1766,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (font != value) { super.set_font(value); - updateCaretSize(); + _regenCaretSize = _regenBackground = true; } return value; @@ -1763,7 +1777,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (italic != value) { super.set_italic(value); - updateCaretSize(); + _regenCaretSize = _regenBackground = true; } return value; @@ -1774,7 +1788,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (size != value) { super.set_size(value); - updateCaretSize(); + _regenCaretSize = _regenBackground = true; } return value; @@ -1785,7 +1799,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (systemFont != value) { super.set_systemFont(value); - updateCaretSize(); + _regenCaretSize = _regenBackground = true; } return value; @@ -1933,7 +1947,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (caretWidth != value) { caretWidth = value; - updateCaretSize(); + _regenCaretSize = true; } return value; From a8d5277bf194b030ba538084b77585addb9bf0a2 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Mon, 15 Jul 2024 17:09:06 -0400 Subject: [PATCH 31/48] Move input text "frontend" to a manager plugin --- flixel/FlxG.hx | 6 ----- flixel/input/keyboard/FlxKeyboard.hx | 5 ++-- flixel/system/frontEnds/PluginFrontEnd.hx | 3 +++ flixel/system/frontEnds/SoundFrontEnd.hx | 5 ++-- flixel/text/FlxInputText.hx | 17 +++++++----- .../FlxInputTextManager.hx} | 26 ++++++++++++++----- 6 files changed, 40 insertions(+), 22 deletions(-) rename flixel/{system/frontEnds/InputTextFrontEnd.hx => text/FlxInputTextManager.hx} (97%) diff --git a/flixel/FlxG.hx b/flixel/FlxG.hx index a619971681..a7dd006d2a 100644 --- a/flixel/FlxG.hx +++ b/flixel/FlxG.hx @@ -12,7 +12,6 @@ import flixel.system.frontEnds.CameraFrontEnd; import flixel.system.frontEnds.ConsoleFrontEnd; import flixel.system.frontEnds.DebuggerFrontEnd; import flixel.system.frontEnds.InputFrontEnd; -import flixel.system.frontEnds.InputTextFrontEnd; import flixel.system.frontEnds.LogFrontEnd; import flixel.system.frontEnds.PluginFrontEnd; import flixel.system.frontEnds.SignalFrontEnd; @@ -321,11 +320,6 @@ class FlxG */ public static var plugins(default, null):PluginFrontEnd; - /** - * Used for detecting text input and dispatching commands on input text fields. - */ - public static var inputText(default, null):InputTextFrontEnd = new InputTextFrontEnd(); - public static var initialWidth(default, null):Int = 0; public static var initialHeight(default, null):Int = 0; diff --git a/flixel/input/keyboard/FlxKeyboard.hx b/flixel/input/keyboard/FlxKeyboard.hx index 02fcb532c3..4f386fb461 100644 --- a/flixel/input/keyboard/FlxKeyboard.hx +++ b/flixel/input/keyboard/FlxKeyboard.hx @@ -1,10 +1,11 @@ package flixel.input.keyboard; #if FLX_KEYBOARD -import openfl.events.KeyboardEvent; import flixel.FlxG; import flixel.input.FlxInput; import flixel.system.replay.CodeValuePair; +import flixel.text.FlxInputText; +import openfl.events.KeyboardEvent; /** * Keeps track of what keys are pressed and how with handy Bools or strings. @@ -101,7 +102,7 @@ class FlxKeyboard extends FlxKeyManager // Debugger toggle #if FLX_DEBUG - if (FlxG.game.debugger != null && inKeyArray(FlxG.debugger.toggleKeys, event) && !FlxG.inputText.isTyping) + if (FlxG.game.debugger != null && inKeyArray(FlxG.debugger.toggleKeys, event) && !FlxInputText.globalManager.isTyping) { FlxG.debugger.visible = !FlxG.debugger.visible; } diff --git a/flixel/system/frontEnds/PluginFrontEnd.hx b/flixel/system/frontEnds/PluginFrontEnd.hx index 5195b50478..e34aefef17 100644 --- a/flixel/system/frontEnds/PluginFrontEnd.hx +++ b/flixel/system/frontEnds/PluginFrontEnd.hx @@ -2,6 +2,8 @@ package flixel.system.frontEnds; import flixel.input.mouse.FlxMouseEvent; import flixel.input.mouse.FlxMouseEventManager; +import flixel.text.FlxInputText; +import flixel.text.FlxInputTextManager; import flixel.tweens.FlxTween; import flixel.util.FlxStringUtil; import flixel.util.FlxTimer; @@ -138,6 +140,7 @@ class PluginFrontEnd addPlugin(FlxTimer.globalManager = new FlxTimerManager()); addPlugin(FlxTween.globalManager = new FlxTweenManager()); addPlugin(FlxMouseEvent.globalManager = new FlxMouseEventManager()); + addPlugin(FlxInputText.globalManager = new FlxInputTextManager()); } /** diff --git a/flixel/system/frontEnds/SoundFrontEnd.hx b/flixel/system/frontEnds/SoundFrontEnd.hx index 605cae1a02..9784d759bb 100644 --- a/flixel/system/frontEnds/SoundFrontEnd.hx +++ b/flixel/system/frontEnds/SoundFrontEnd.hx @@ -5,10 +5,11 @@ import flixel.FlxG; import flixel.group.FlxGroup; import flixel.input.keyboard.FlxKey; import flixel.math.FlxMath; -import flixel.system.FlxAssets; import flixel.sound.FlxSound; import flixel.sound.FlxSoundGroup; +import flixel.system.FlxAssets; import flixel.system.ui.FlxSoundTray; +import flixel.text.FlxInputText; import flixel.util.FlxSignal; import openfl.Assets; import openfl.media.Sound; @@ -394,7 +395,7 @@ class SoundFrontEnd list.update(elapsed); #if FLX_KEYBOARD - if (!FlxG.inputText.isTyping) + if (!FlxInputText.globalManager.isTyping) { if (FlxG.keys.anyJustReleased(muteKeys)) toggleMuted(); diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 588da92c43..ae2e332f44 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -5,7 +5,7 @@ import flixel.input.touch.FlxTouch; import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; -import flixel.system.frontEnds.InputTextFrontEnd; +import flixel.text.FlxInputTextManager; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; import flixel.util.FlxSpriteUtil; @@ -21,6 +21,11 @@ import openfl.utils.QName; */ class FlxInputText extends FlxText implements IFlxInputText { + /** + * The global manager that handles input text objects. + */ + public static var globalManager:FlxInputTextManager; + /** * The gaps at the sides of the text field (2px). */ @@ -344,7 +349,7 @@ class FlxInputText extends FlxText implements IFlxInputText background = true; } - FlxG.inputText.registerInputText(this); + FlxInputText.globalManager.registerInputText(this); } override function update(elapsed:Float):Void @@ -384,7 +389,7 @@ class FlxInputText extends FlxText implements IFlxInputText */ override function destroy():Void { - FlxG.inputText.unregisterInputText(this); + FlxInputText.globalManager.unregisterInputText(this); _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); _caret = FlxDestroyUtil.destroy(_caret); @@ -2028,7 +2033,7 @@ class FlxInputText extends FlxText implements IFlxInputText var bounds = getLimeBounds(_pointerCamera); FlxG.stage.window.setTextInputRect(bounds); - FlxG.inputText.focus = this; + FlxInputText.globalManager.focus = this; if (_caretIndex < 0) { @@ -2043,9 +2048,9 @@ class FlxInputText extends FlxText implements IFlxInputText focusGained(); _justGainedFocus = true; } - else if (FlxG.inputText.focus == this) + else if (FlxInputText.globalManager.focus == this) { - FlxG.inputText.focus = null; + FlxInputText.globalManager.focus = null; if (_selectionIndex != _caretIndex) { diff --git a/flixel/system/frontEnds/InputTextFrontEnd.hx b/flixel/text/FlxInputTextManager.hx similarity index 97% rename from flixel/system/frontEnds/InputTextFrontEnd.hx rename to flixel/text/FlxInputTextManager.hx index 21a11ba8f0..91ae867a47 100644 --- a/flixel/system/frontEnds/InputTextFrontEnd.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -1,4 +1,4 @@ -package flixel.system.frontEnds; +package flixel.text; import lime.ui.KeyCode; import lime.ui.KeyModifier; @@ -6,9 +6,10 @@ import openfl.events.Event; import openfl.events.TextEvent; /** - * Accessed via `FlxG.inputText`. + * A manager for tracking and dispatching events for input text objects. + * Normally accessed via the static `FlxInputText.globalManager` rather than being created separately. */ -class InputTextFrontEnd +class FlxInputTextManager extends FlxBasic { /** * The input text object that's currently in focus, or `null` if there isn't any. @@ -24,9 +25,7 @@ class InputTextFrontEnd * Contains all of the currently registered input text objects. */ var _registeredInputTexts:Array = []; - - public function new() {} - + /** * Registers an input text object, and initiates the event listeners if it's * the first one to be added. @@ -314,39 +313,48 @@ enum MoveCursorAction * Moves the cursor one character to the left. */ LEFT; + /** * Moves the cursor one character to the right. */ RIGHT; + /** * Moves the cursor up to the previous line. */ UP; + /** * Moves the cursor down to the next line. */ DOWN; + /** * Moves the cursor to the beginning of the text. */ HOME; + /** * Moves the cursor to the end of the text. */ END; + /** * Moves the cursor to the beginning of the current line. */ LINE_LEFT; + /** * Moves the cursor to the end of the current line. */ LINE_RIGHT; + /** * Moves the cursor to the beginning of the previous word, or the * start of the text if there aren't any more words. */ WORD_LEFT; + /** * Moves the cursor to the beginning of the next word, or the end * of the text if there aren't any more words. @@ -360,29 +368,35 @@ enum TypingCommand * Enters a new line into the text. */ NEW_LINE; + /** * Deletes the character to the left of the cursor, or the selection if * there's already one. */ DELETE_LEFT; + /** * Deletes the character to the right of the cursor, or the selection if * there's already one. */ DELETE_RIGHT; + /** * Copies the current selection into the clipboard. */ COPY; + /** * Copies the current selection into the clipboard and then removes it * from the text field. */ CUT; + /** * Pastes the clipboard's text into the field. */ PASTE; + /** * Selects all of the text in the field. */ From 14f9731573f50463df5791ad2ff74f9ad1442399 Mon Sep 17 00:00:00 2001 From: Starmapo Date: Mon, 15 Jul 2024 18:06:54 -0400 Subject: [PATCH 32/48] Fixed missing rename --- flixel/input/keyboard/FlxKeyboard.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flixel/input/keyboard/FlxKeyboard.hx b/flixel/input/keyboard/FlxKeyboard.hx index 4f386fb461..14f8684765 100644 --- a/flixel/input/keyboard/FlxKeyboard.hx +++ b/flixel/input/keyboard/FlxKeyboard.hx @@ -118,7 +118,7 @@ class FlxKeyboard extends FlxKeyManager if (FlxG.game.replaying && !inKeyArray(FlxG.debugger.toggleKeys, event) && inKeyArray(FlxG.vcr.cancelKeys, event) - && !FlxG.inputText.isTyping) + && !FlxInputText.globalManager.isTyping) { FlxG.vcr.cancelReplay(); } From 4ae5c246898f8ec222c2776a0d626015cf51383a Mon Sep 17 00:00:00 2001 From: Starmapo Date: Tue, 16 Jul 2024 14:49:34 -0400 Subject: [PATCH 33/48] Replace `callback` with `onTextChange` and `onScrollChange` signals - Replace `focusGained` and `focusLost` with `onFocusChange` signal - Renamed filter mode options and added `CHARS` option - Removed `customFilterPattern` as its now defined in the enum itself --- flixel/text/FlxInputText.hx | 171 +++++++++++++++--------------------- 1 file changed, 73 insertions(+), 98 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index ae2e332f44..0b6faa3c16 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -8,6 +8,7 @@ import flixel.math.FlxRect; import flixel.text.FlxInputTextManager; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; +import flixel.util.FlxSignal; import flixel.util.FlxSpriteUtil; import flixel.util.FlxTimer; import lime.system.Clipboard; @@ -16,6 +17,8 @@ import openfl.geom.Rectangle; import openfl.text.TextFormat; import openfl.utils.QName; +using StringTools; + /** * An `FlxText` object that can be selected and edited by the user. */ @@ -51,14 +54,7 @@ class FlxInputText extends FlxText implements IFlxInputText * visible in the text field. */ public var bottomScrollV(get, never):Int; - - /** - * A function called when an action occurs in the text field. The first parameter - * is the current text, and the second parameter indicates what kind of action - * it was. - */ - public var callback:String->FlxInputTextAction->Void; - + /** * The selection cursor's color. Has the same color as the text field by default, and * it's automatically set whenever it changes. @@ -77,13 +73,6 @@ class FlxInputText extends FlxText implements IFlxInputText * The selection cursor's width. */ public var caretWidth(default, set):Int = 1; - - /** - * This regular expression will filter out (remove) everything that matches. - * - * Changing this will automatically set `filterMode` to `CUSTOM_FILTER`. - */ - public var customFilterPattern(default, set):EReg; /** * Whether or not the text field can be edited by the user. @@ -103,21 +92,10 @@ class FlxInputText extends FlxText implements IFlxInputText public var fieldBorderThickness(default, set):Int = 1; /** - * Defines how to filter the text (no filter, only letters, only numbers, - * only letters & numbers, or a custom filter). - */ - public var filterMode(default, set):FlxInputTextFilterMode = NO_FILTER; - - /** - * Callback that is triggered when this text field gains focus. - */ - public var focusGained:Void->Void; - - /** - * Callback that is triggered when this text field loses focus. + * Defines how to filter the text (remove unwanted characters). */ - public var focusLost:Void->Void; - + public var filterMode(default, set):FlxInputTextFilterMode = NONE; + /** * Defines whether a letter case is enforced on the text. */ @@ -162,6 +140,25 @@ class FlxInputText extends FlxText implements IFlxInputText */ public var passwordMode(get, set):Bool; + /** + * Gets dispatched whenever this text field gains/loses focus, indicated by + * the `Bool` parameter (`true` if it has focus). + */ + public var onFocusChange(default, null):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + + /** + * Gets dispatched whenever the horizontal and/or vertical scroll is changed. + * The two parameters indicate the current `scrollH` and `scrollV` respectively. + */ + public var onScrollChange(default, null):FlxTypedSignalInt->Void> = new FlxTypedSignalInt->Void>(); + + /** + * Gets dispatched whenever the text is changed by the user. The `String` + * parameter is the current text, while the `FlxInputTextChange` parameter + * indicates what type of change occurred. + */ + public var onTextChange(default, null):FlxTypedSignalFlxInputTextChange->Void> = new FlxTypedSignalFlxInputTextChange->Void>(); + /** * The current horizontal scrolling position, in pixels. Defaults to * 0, which means the text is not horizontally scrolled. @@ -391,6 +388,21 @@ class FlxInputText extends FlxText implements IFlxInputText { FlxInputText.globalManager.unregisterInputText(this); + if (onFocusChange != null) + { + onFocusChange.destroy(); + onFocusChange = null; + } + if (onScrollChange != null) + { + onScrollChange.destroy(); + onScrollChange = null; + } + if (onTextChange != null) + { + onTextChange.destroy(); + onTextChange = null; + } _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); _caret = FlxDestroyUtil.destroy(_caret); if (_caretTimer != null) @@ -507,7 +519,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (newText.length > 0) { replaceSelectedText(newText); - onChange(INPUT_ACTION); + onTextChange.dispatch(text, INPUT_ACTION); } } @@ -583,18 +595,22 @@ class FlxInputText extends FlxText implements IFlxInputText newText = newText.toLowerCase(); } - if (filterMode != NO_FILTER) + if (filterMode != NONE) { var pattern = switch (filterMode) { - case ONLY_ALPHA: + case ALPHABET: ~/[^a-zA-Z]*/g; - case ONLY_NUMERIC: + case NUMERIC: ~/[^0-9]*/g; - case ONLY_ALPHANUMERIC: + case ALPHANUMERIC: ~/[^a-zA-Z0-9]*/g; - case CUSTOM_FILTER: - customFilterPattern; + case REG(reg): + reg; + case CHARS(chars): + // In a character set, only \, - and ] need to be escaped + chars = chars.replace('\\', "\\\\").replace('-', "\\-").replace(']', "\\]"); + new EReg("[^" + chars + "]*", "g"); default: throw "Unknown filterMode (" + filterMode + ")"; } @@ -964,21 +980,6 @@ class FlxInputText extends FlxText implements IFlxInputText } } - /** - * Dispatches `callback` with the appropiate text action, if there is one set. - */ - function onChange(action:FlxInputTextAction):Void - { - if (callback != null) - { - callback(text, action); - if (action == INPUT_ACTION || action == BACKSPACE_ACTION || action == DELETE_ACTION) - { - callback(text, CHANGE_ACTION); - } - } - } - /** * Regenerates the background sprites if they're enabled. */ @@ -1054,7 +1055,7 @@ class FlxInputText extends FlxText implements IFlxInputText { restartCaretTimer(); } - onChange(ENTER_ACTION); + onTextChange.dispatch(text, ENTER_ACTION); case DELETE_LEFT: if (!editable) return; @@ -1068,7 +1069,7 @@ class FlxInputText extends FlxText implements IFlxInputText { replaceSelectedText(""); _selectionIndex = _caretIndex; - onChange(BACKSPACE_ACTION); + onTextChange.dispatch(text, BACKSPACE_ACTION); } else { @@ -1087,7 +1088,7 @@ class FlxInputText extends FlxText implements IFlxInputText { replaceSelectedText(""); _selectionIndex = _caretIndex; - onChange(DELETE_ACTION); + onTextChange.dispatch(text, DELETE_ACTION); } else { @@ -1309,7 +1310,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (scrollH != cacheScrollH || scrollV != cacheScrollV) { - onChange(SCROLL_ACTION); + onScrollChange.dispatch(scrollH, scrollV); } } } @@ -1464,7 +1465,7 @@ class FlxInputText extends FlxText implements IFlxInputText scrollV = FlxMath.minInt(scrollV - FlxG.mouse.wheel, maxScrollV); if (scrollV != cacheScrollV) { - onChange(SCROLL_ACTION); + onScrollChange.dispatch(scrollH, scrollV); } } } @@ -1607,7 +1608,7 @@ class FlxInputText extends FlxText implements IFlxInputText if (scrollH != cacheScrollH || scrollV != cacheScrollV) { - onChange(SCROLL_ACTION); + onScrollChange.dispatch(scrollH, scrollV); } } @@ -1957,24 +1958,6 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - - function set_customFilterPattern(value:EReg):EReg - { - if (customFilterPattern != value) - { - customFilterPattern = value; - if (filterMode != CUSTOM_FILTER) - { - filterMode = CUSTOM_FILTER; - } - else - { - text = filterText(text); - } - } - - return value; - } function set_fieldBorderColor(value:FlxColor):FlxColor { @@ -2044,8 +2027,6 @@ class FlxInputText extends FlxText implements IFlxInputText restartCaretTimer(); - if (focusGained != null) - focusGained(); _justGainedFocus = true; } else if (FlxInputText.globalManager.focus == this) @@ -2059,10 +2040,8 @@ class FlxInputText extends FlxText implements IFlxInputText } stopCaretTimer(); - - if (focusLost != null) - focusLost(); } + onFocusChange.dispatch(hasFocus); } return value; @@ -2208,14 +2187,8 @@ class FlxInputText extends FlxText implements IFlxInputText } } -enum abstract FlxInputTextAction(String) from String to String +enum abstract FlxInputTextChange(String) from String to String { - /** - * Dispatched whenever the text is changed by the user. It's always - * dispatched after `INPUT_ACTION`, `BACKSPACE_ACTION`, and - * `DELETE_ACTION`. - */ - var CHANGE_ACTION = "change"; /** * Dispatched whenever new text is added by the user. */ @@ -2235,10 +2208,6 @@ enum abstract FlxInputTextAction(String) from String to String * is focused. */ var ENTER_ACTION = "enter"; - /** - * Dispatched whenever the text field is scrolled in some way. - */ - var SCROLL_ACTION = "scroll"; } enum abstract FlxInputTextCase(Int) from Int to Int @@ -2257,26 +2226,32 @@ enum abstract FlxInputTextCase(Int) from Int to Int var LOWER_CASE = 2; } -enum abstract FlxInputTextFilterMode(Int) from Int to Int +enum FlxInputTextFilterMode { /** * Does not filter the text at all. */ - var NO_FILTER = 0; + NONE; /** * Only allows letters (a-z & A-Z) to be added to the text. */ - var ONLY_ALPHA = 1; + ALPHABET; /** * Only allows numbers (0-9) to be added to the text. */ - var ONLY_NUMERIC = 2; + NUMERIC; /** * Only allows letters (a-z & A-Z) and numbers (0-9) to be added to the text. */ - var ONLY_ALPHANUMERIC = 3; + ALPHANUMERIC; + /** + * Uses a regular expression to filter the text. Characters that are matched + * will be removed. + */ + REG(reg:EReg); + /** - * Lets you use a custom filter with `customFilterPattern`. + * Only allows the characters present in the string to be added to the text. */ - var CUSTOM_FILTER = 4; + CHARS(chars:String); } \ No newline at end of file From 47c19669ac0508edbdd38d08d7664cc39b8aa81d Mon Sep 17 00:00:00 2001 From: Starmapo Date: Tue, 16 Jul 2024 15:05:53 -0400 Subject: [PATCH 34/48] Move `ENTER_ACTION` over to `onEnter` --- flixel/text/FlxInputText.hx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 0b6faa3c16..687c8f81a2 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -140,6 +140,12 @@ class FlxInputText extends FlxText implements IFlxInputText */ public var passwordMode(get, set):Bool; + /** + * Gets dispatched whenever the enter key is pressed on the text field. + * The `String` parameter is the current text. + */ + public var onEnter(default, null):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + /** * Gets dispatched whenever this text field gains/loses focus, indicated by * the `Bool` parameter (`true` if it has focus). @@ -1055,7 +1061,7 @@ class FlxInputText extends FlxText implements IFlxInputText { restartCaretTimer(); } - onTextChange.dispatch(text, ENTER_ACTION); + onEnter.dispatch(text); case DELETE_LEFT: if (!editable) return; @@ -2203,11 +2209,6 @@ enum abstract FlxInputTextChange(String) from String to String * delete). */ var DELETE_ACTION = "delete"; - /** - * Dispatched whenever enter is pressed by the user while the text field - * is focused. - */ - var ENTER_ACTION = "enter"; } enum abstract FlxInputTextCase(Int) from Int to Int From 070c7f0203a013b688a947c933f210915b509f1b Mon Sep 17 00:00:00 2001 From: Starmapo Date: Fri, 19 Jul 2024 12:41:39 -0400 Subject: [PATCH 35/48] Implement `destroy()` for FlxInputTextManager - Add `unregisterAll()` to FlxInputTextManager --- flixel/text/FlxInputTextManager.hx | 31 +++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/flixel/text/FlxInputTextManager.hx b/flixel/text/FlxInputTextManager.hx index 91ae867a47..6bc25c7fbd 100644 --- a/flixel/text/FlxInputTextManager.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -24,7 +24,17 @@ class FlxInputTextManager extends FlxBasic /** * Contains all of the currently registered input text objects. */ - var _registeredInputTexts:Array = []; + var _registeredInputTexts:Array; + + /** + * Clean up memory. + */ + override public function destroy():Void + { + focus = null; + unregisterAll(); + super.destroy(); + } /** * Registers an input text object, and initiates the event listeners if it's @@ -32,6 +42,9 @@ class FlxInputTextManager extends FlxBasic */ public function registerInputText(input:IFlxInputText):Void { + if (_registeredInputTexts == null) + _registeredInputTexts = []; + if (!_registeredInputTexts.contains(input)) { _registeredInputTexts.push(input); @@ -52,6 +65,18 @@ class FlxInputTextManager extends FlxBasic } } } + + /** + * Unregisters all the input texts from the manager. + */ + public function unregisterAll():Void + { + if (_registeredInputTexts == null) + return; + + for (input in _registeredInputTexts) + unregisterInputText(input); + } /** * Unregisters an input text object, and removes the event listeners if there @@ -59,6 +84,9 @@ class FlxInputTextManager extends FlxBasic */ public function unregisterInputText(input:IFlxInputText):Void { + if (_registeredInputTexts == null) + return; + if (_registeredInputTexts.contains(input)) { _registeredInputTexts.remove(input); @@ -74,6 +102,7 @@ class FlxInputTextManager extends FlxBasic FlxG.stage.removeEventListener(Event.SELECT_ALL, onSelectAll); FlxG.stage.window.onKeyUp.remove(onKeyUp); #end + _registeredInputTexts = null; } } } From bb7f7002cb1768a046b0752e3c8439e22a5bcb30 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Tue, 23 Jul 2024 11:53:01 -0500 Subject: [PATCH 36/48] add onTypingAction --- flixel/text/FlxInputTextManager.hx | 60 ++++++++++++++++++------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/flixel/text/FlxInputTextManager.hx b/flixel/text/FlxInputTextManager.hx index 6bc25c7fbd..ace46a3e5c 100644 --- a/flixel/text/FlxInputTextManager.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -1,5 +1,6 @@ package flixel.text; +import flixel.util.FlxSignal; import lime.ui.KeyCode; import lime.ui.KeyModifier; import openfl.events.Event; @@ -21,6 +22,11 @@ class FlxInputTextManager extends FlxBasic */ public var isTyping(get, never):Bool; + /** + * + */ + public final onTypingAction = new FlxTypedSignal<(action:TypingAction)->Void>(); + /** * Contains all of the currently registered input text objects. */ @@ -118,10 +124,16 @@ class FlxInputTextManager extends FlxBasic if (focus != null) { - focus.dispatchTypingAction(ADD_TEXT(event.text)); + dispatchTypingAction(ADD_TEXT(event.text)); } } + function dispatchTypingAction(action:TypingAction) + { + focus.dispatchTypingAction(action); + onTypingAction.dispatch(action); + } + /** * Called when an `onKeyDown` event is recieved. */ @@ -142,86 +154,86 @@ class FlxInputTextManager extends FlxBasic switch (key) { case RETURN, NUMPAD_ENTER: - focus.dispatchTypingAction(COMMAND(NEW_LINE)); + dispatchTypingAction(COMMAND(NEW_LINE)); case BACKSPACE: - focus.dispatchTypingAction(COMMAND(DELETE_LEFT)); + dispatchTypingAction(COMMAND(DELETE_LEFT)); case DELETE: - focus.dispatchTypingAction(COMMAND(DELETE_RIGHT)); + dispatchTypingAction(COMMAND(DELETE_RIGHT)); case LEFT: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(WORD_LEFT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(WORD_LEFT, modifier.shiftKey)); } else { - focus.dispatchTypingAction(MOVE_CURSOR(LEFT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(LEFT, modifier.shiftKey)); } case RIGHT: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(WORD_RIGHT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(WORD_RIGHT, modifier.shiftKey)); } else { - focus.dispatchTypingAction(MOVE_CURSOR(RIGHT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(RIGHT, modifier.shiftKey)); } case UP: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); } else { - focus.dispatchTypingAction(MOVE_CURSOR(UP, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(UP, modifier.shiftKey)); } case DOWN: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); } else { - focus.dispatchTypingAction(MOVE_CURSOR(DOWN, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(DOWN, modifier.shiftKey)); } case HOME: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); } else { - focus.dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); } case END: if (modifierPressed) { - focus.dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); } else { - focus.dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); + dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); } case C: if (modifierPressed) { - focus.dispatchTypingAction(COMMAND(COPY)); + dispatchTypingAction(COMMAND(COPY)); } case X: if (modifierPressed) { - focus.dispatchTypingAction(COMMAND(CUT)); + dispatchTypingAction(COMMAND(CUT)); } #if !js case V: if (modifierPressed) { - focus.dispatchTypingAction(COMMAND(PASTE)); + dispatchTypingAction(COMMAND(PASTE)); } #end case A: if (modifierPressed) { - focus.dispatchTypingAction(COMMAND(SELECT_ALL)); + dispatchTypingAction(COMMAND(SELECT_ALL)); } default: } @@ -256,7 +268,7 @@ class FlxInputTextManager extends FlxBasic { if (focus != null) { - focus.dispatchTypingAction(COMMAND(COPY)); + dispatchTypingAction(COMMAND(COPY)); } } @@ -267,7 +279,7 @@ class FlxInputTextManager extends FlxBasic { if (focus != null) { - focus.dispatchTypingAction(COMMAND(CUT)); + dispatchTypingAction(COMMAND(CUT)); } } @@ -278,7 +290,7 @@ class FlxInputTextManager extends FlxBasic { if (focus != null) { - focus.dispatchTypingAction(COMMAND(PASTE)); + dispatchTypingAction(COMMAND(PASTE)); } } @@ -289,7 +301,7 @@ class FlxInputTextManager extends FlxBasic { if (focus != null) { - focus.dispatchTypingAction(COMMAND(SELECT_ALL)); + dispatchTypingAction(COMMAND(SELECT_ALL)); } } #end From 8c059336fc20049abb8bfc79b5e9f883ef7cec35 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Tue, 23 Jul 2024 12:16:31 -0500 Subject: [PATCH 37/48] allow custom manager --- flixel/text/FlxInputText.hx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 687c8f81a2..69fae4d36f 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -216,6 +216,11 @@ class FlxInputText extends FlxText implements IFlxInputText */ public var useSelectedTextFormat(default, set):Bool = true; + /** + * The input text manager powering this instance + */ + public var manager(default, null):FlxInputTextManager; + /** * An FlxSprite representing the background of the text field. */ @@ -320,9 +325,11 @@ class FlxInputText extends FlxText implements IFlxInputText * @param textColor The color of the text * @param backgroundColor The color of the background (`FlxColor.TRANSPARENT` for no background color) * @param embeddedFont Whether this text field uses embedded fonts or not. + * @param manager Optional input text manager that will power this input text. + * If `null`, `globalManager` is used */ public function new(x:Float = 0, y:Float = 0, fieldWidth:Float = 0, ?text:String, size:Int = 8, textColor:FlxColor = FlxColor.BLACK, - backgroundColor:FlxColor = FlxColor.WHITE, embeddedFont:Bool = true) + backgroundColor:FlxColor = FlxColor.WHITE, embeddedFont:Bool = true, ?manager:FlxInputTextManager) { super(x, y, fieldWidth, text, size, embeddedFont); if (text == null || text == "") @@ -352,7 +359,13 @@ class FlxInputText extends FlxText implements IFlxInputText background = true; } - FlxInputText.globalManager.registerInputText(this); + if (manager == null) + { + manager = FlxInputText.globalManager; + } + + this.manager = manager; + manager.registerInputText(this); } override function update(elapsed:Float):Void @@ -392,7 +405,7 @@ class FlxInputText extends FlxText implements IFlxInputText */ override function destroy():Void { - FlxInputText.globalManager.unregisterInputText(this); + manager.unregisterInputText(this); if (onFocusChange != null) { @@ -2022,7 +2035,7 @@ class FlxInputText extends FlxText implements IFlxInputText var bounds = getLimeBounds(_pointerCamera); FlxG.stage.window.setTextInputRect(bounds); - FlxInputText.globalManager.focus = this; + manager.focus = this; if (_caretIndex < 0) { @@ -2035,9 +2048,9 @@ class FlxInputText extends FlxText implements IFlxInputText _justGainedFocus = true; } - else if (FlxInputText.globalManager.focus == this) + else if (manager.focus == this) { - FlxInputText.globalManager.focus = null; + manager.focus = null; if (_selectionIndex != _caretIndex) { From ae1644b3c761da21e51f032d663807e07cd2d131 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Tue, 23 Jul 2024 12:35:34 -0500 Subject: [PATCH 38/48] add setManager --- flixel/text/FlxInputText.hx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 69fae4d36f..bf3100aef5 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -368,6 +368,29 @@ class FlxInputText extends FlxText implements IFlxInputText manager.registerInputText(this); } + public function setManager(manager:FlxInputTextManager) + { + if (this.manager == null) + { + FlxG.log.error("Cannot set manager once destroyed"); + return; + } + + if (manager == this.manager) + return; + + final hasFocus = this.manager.focus == this; + this.manager.unregisterInputText(this); + + manager.registerInputText(this); + if (hasFocus) + { + manager.focus = this; + } + + this.manager = manager; + } + override function update(elapsed:Float):Void { super.update(elapsed); From c3b7622bab4922b48ba0f8a70026aa511cf3bbbe Mon Sep 17 00:00:00 2001 From: George FunBook Date: Wed, 24 Jul 2024 07:48:15 -0500 Subject: [PATCH 39/48] remove focus setter for setFocus --- flixel/text/FlxInputText.hx | 6 ++-- flixel/text/FlxInputTextManager.hx | 44 ++++++++++++++---------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index bf3100aef5..a2dd900cff 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -385,7 +385,7 @@ class FlxInputText extends FlxText implements IFlxInputText manager.registerInputText(this); if (hasFocus) { - manager.focus = this; + manager.setFocus(this); } this.manager = manager; @@ -2058,7 +2058,7 @@ class FlxInputText extends FlxText implements IFlxInputText var bounds = getLimeBounds(_pointerCamera); FlxG.stage.window.setTextInputRect(bounds); - manager.focus = this; + manager.setFocus(this); if (_caretIndex < 0) { @@ -2073,7 +2073,7 @@ class FlxInputText extends FlxText implements IFlxInputText } else if (manager.focus == this) { - manager.focus = null; + manager.setFocus(null); if (_selectionIndex != _caretIndex) { diff --git a/flixel/text/FlxInputTextManager.hx b/flixel/text/FlxInputTextManager.hx index ace46a3e5c..fb0eda5f20 100644 --- a/flixel/text/FlxInputTextManager.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -15,7 +15,7 @@ class FlxInputTextManager extends FlxBasic /** * The input text object that's currently in focus, or `null` if there isn't any. */ - public var focus(default, set):IFlxInputText; + public var focus(default, null):IFlxInputText; /** * Returns whether or not there's currently an editable input text in focus. @@ -113,6 +113,26 @@ class FlxInputTextManager extends FlxBasic } } + public function setFocus(value:IFlxInputText) + { + if (focus != value) + { + if (focus != null) + { + focus.hasFocus = false; + } + + focus = value; + + if (focus != null) + { + focus.hasFocus = true; + } + + FlxG.stage.window.textInputEnabled = (focus != null); + } + } + /** * Called when a `TEXT_INPUT` event is received. */ @@ -306,28 +326,6 @@ class FlxInputTextManager extends FlxBasic } #end - function set_focus(value:IFlxInputText):IFlxInputText - { - if (focus != value) - { - if (focus != null) - { - focus.hasFocus = false; - } - - focus = value; - - if (focus != null) - { - focus.hasFocus = true; - } - - FlxG.stage.window.textInputEnabled = (focus != null); - } - - return value; - } - function get_isTyping():Bool { return focus != null && focus.editable; From 2046850c6466dd68d90109029023b0ac09ea817d Mon Sep 17 00:00:00 2001 From: George FunBook Date: Wed, 24 Jul 2024 08:08:49 -0500 Subject: [PATCH 40/48] better destroy/init --- flixel/text/FlxInputTextManager.hx | 78 ++++++++++++++---------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/flixel/text/FlxInputTextManager.hx b/flixel/text/FlxInputTextManager.hx index fb0eda5f20..389dc91ab2 100644 --- a/flixel/text/FlxInputTextManager.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -30,16 +30,47 @@ class FlxInputTextManager extends FlxBasic /** * Contains all of the currently registered input text objects. */ - var _registeredInputTexts:Array; + final _registeredInputTexts = new Array(); /** * Clean up memory. */ override public function destroy():Void { - focus = null; - unregisterAll(); super.destroy(); + + focus = null; + _registeredInputTexts.resize(0); + removeEvents(); + } + + function addEvents() + { + + FlxG.stage.addEventListener(TextEvent.TEXT_INPUT, onTextInput); + // Higher priority is needed here because FlxKeyboard will cancel + // the event for key codes in `preventDefaultKeys`. + FlxG.stage.window.onKeyDown.add(onKeyDown, false, 1000); + #if flash + FlxG.stage.addEventListener(Event.COPY, onCopy); + FlxG.stage.addEventListener(Event.CUT, onCut); + FlxG.stage.addEventListener(Event.PASTE, onPaste); + FlxG.stage.addEventListener(Event.SELECT_ALL, onSelectAll); + FlxG.stage.window.onKeyUp.add(onKeyUp, false, 1000); + #end + } + + function removeEvents() + { + FlxG.stage.removeEventListener(TextEvent.TEXT_INPUT, onTextInput); + FlxG.stage.window.onKeyDown.remove(onKeyDown); + #if flash + FlxG.stage.removeEventListener(Event.COPY, onCopy); + FlxG.stage.removeEventListener(Event.CUT, onCut); + FlxG.stage.removeEventListener(Event.PASTE, onPaste); + FlxG.stage.removeEventListener(Event.SELECT_ALL, onSelectAll); + FlxG.stage.window.onKeyUp.remove(onKeyUp); + #end } /** @@ -48,41 +79,16 @@ class FlxInputTextManager extends FlxBasic */ public function registerInputText(input:IFlxInputText):Void { - if (_registeredInputTexts == null) - _registeredInputTexts = []; - if (!_registeredInputTexts.contains(input)) { _registeredInputTexts.push(input); if (!FlxG.stage.window.onKeyDown.has(onKeyDown)) { - FlxG.stage.addEventListener(TextEvent.TEXT_INPUT, onTextInput); - // Higher priority is needed here because FlxKeyboard will cancel - // the event for key codes in `preventDefaultKeys`. - FlxG.stage.window.onKeyDown.add(onKeyDown, false, 1000); - #if flash - FlxG.stage.addEventListener(Event.COPY, onCopy); - FlxG.stage.addEventListener(Event.CUT, onCut); - FlxG.stage.addEventListener(Event.PASTE, onPaste); - FlxG.stage.addEventListener(Event.SELECT_ALL, onSelectAll); - FlxG.stage.window.onKeyUp.add(onKeyUp, false, 1000); - #end + addEvents(); } } } - - /** - * Unregisters all the input texts from the manager. - */ - public function unregisterAll():Void - { - if (_registeredInputTexts == null) - return; - - for (input in _registeredInputTexts) - unregisterInputText(input); - } /** * Unregisters an input text object, and removes the event listeners if there @@ -90,25 +96,13 @@ class FlxInputTextManager extends FlxBasic */ public function unregisterInputText(input:IFlxInputText):Void { - if (_registeredInputTexts == null) - return; - if (_registeredInputTexts.contains(input)) { _registeredInputTexts.remove(input); if (_registeredInputTexts.length == 0 && FlxG.stage.window.onKeyDown.has(onKeyDown)) { - FlxG.stage.removeEventListener(TextEvent.TEXT_INPUT, onTextInput); - FlxG.stage.window.onKeyDown.remove(onKeyDown); - #if flash - FlxG.stage.removeEventListener(Event.COPY, onCopy); - FlxG.stage.removeEventListener(Event.CUT, onCut); - FlxG.stage.removeEventListener(Event.PASTE, onPaste); - FlxG.stage.removeEventListener(Event.SELECT_ALL, onSelectAll); - FlxG.stage.window.onKeyUp.remove(onKeyUp); - #end - _registeredInputTexts = null; + removeEvents(); } } } From f7895dcef0a8d600b9749d8a6ca95cfe1564bccb Mon Sep 17 00:00:00 2001 From: George FunBook Date: Wed, 24 Jul 2024 08:10:15 -0500 Subject: [PATCH 41/48] use case ifs --- flixel/text/FlxInputTextManager.hx | 94 +++++++++--------------------- 1 file changed, 26 insertions(+), 68 deletions(-) diff --git a/flixel/text/FlxInputTextManager.hx b/flixel/text/FlxInputTextManager.hx index 389dc91ab2..852e7a010b 100644 --- a/flixel/text/FlxInputTextManager.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -173,82 +173,40 @@ class FlxInputTextManager extends FlxBasic dispatchTypingAction(COMMAND(DELETE_LEFT)); case DELETE: dispatchTypingAction(COMMAND(DELETE_RIGHT)); + case LEFT if (modifierPressed): + dispatchTypingAction(MOVE_CURSOR(WORD_LEFT, modifier.shiftKey)); case LEFT: - if (modifierPressed) - { - dispatchTypingAction(MOVE_CURSOR(WORD_LEFT, modifier.shiftKey)); - } - else - { - dispatchTypingAction(MOVE_CURSOR(LEFT, modifier.shiftKey)); - } + dispatchTypingAction(MOVE_CURSOR(LEFT, modifier.shiftKey)); + case RIGHT if (modifierPressed): + dispatchTypingAction(MOVE_CURSOR(WORD_RIGHT, modifier.shiftKey)); case RIGHT: - if (modifierPressed) - { - dispatchTypingAction(MOVE_CURSOR(WORD_RIGHT, modifier.shiftKey)); - } - else - { - dispatchTypingAction(MOVE_CURSOR(RIGHT, modifier.shiftKey)); - } + dispatchTypingAction(MOVE_CURSOR(RIGHT, modifier.shiftKey)); + case UP if (modifierPressed): + dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); case UP: - if (modifierPressed) - { - dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); - } - else - { - dispatchTypingAction(MOVE_CURSOR(UP, modifier.shiftKey)); - } + dispatchTypingAction(MOVE_CURSOR(UP, modifier.shiftKey)); + case DOWN if (modifierPressed): + dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); case DOWN: - if (modifierPressed) - { - dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); - } - else - { - dispatchTypingAction(MOVE_CURSOR(DOWN, modifier.shiftKey)); - } + dispatchTypingAction(MOVE_CURSOR(DOWN, modifier.shiftKey)); + case HOME if (modifierPressed): + dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); case HOME: - if (modifierPressed) - { - dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); - } - else - { - dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); - } + dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); + case END if (modifierPressed): + dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); case END: - if (modifierPressed) - { - dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); - } - else - { - dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); - } - case C: - if (modifierPressed) - { - dispatchTypingAction(COMMAND(COPY)); - } - case X: - if (modifierPressed) - { - dispatchTypingAction(COMMAND(CUT)); - } + dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); + case C if (modifierPressed): + dispatchTypingAction(COMMAND(COPY)); + case X if (modifierPressed): + dispatchTypingAction(COMMAND(CUT)); #if !js - case V: - if (modifierPressed) - { - dispatchTypingAction(COMMAND(PASTE)); - } + case V if (modifierPressed): + dispatchTypingAction(COMMAND(PASTE)); #end - case A: - if (modifierPressed) - { - dispatchTypingAction(COMMAND(SELECT_ALL)); - } + case A if (modifierPressed): + dispatchTypingAction(COMMAND(SELECT_ALL)); default: } From 6664a933ae1553e74527a294002c576bd7682b12 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Wed, 24 Jul 2024 12:05:11 -0500 Subject: [PATCH 42/48] improve mac key behavior --- flixel/text/FlxInputText.hx | 4 +- flixel/text/FlxInputTextManager.hx | 66 +++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index a2dd900cff..0979ccc163 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -939,7 +939,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); restartCaretTimer(); - case HOME: + case TOP: _caretIndex = 0; if (!shiftKey) @@ -948,7 +948,7 @@ class FlxInputText extends FlxText implements IFlxInputText } setSelection(_selectionIndex, _caretIndex); restartCaretTimer(); - case END: + case BOTTOM: _caretIndex = text.length; if (!shiftKey) diff --git a/flixel/text/FlxInputTextManager.hx b/flixel/text/FlxInputTextManager.hx index 852e7a010b..e621d39c3e 100644 --- a/flixel/text/FlxInputTextManager.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -32,6 +32,24 @@ class FlxInputTextManager extends FlxBasic */ final _registeredInputTexts = new Array(); + /** + * Whether we should use mac modifer keys or not. Behavior in linux is currently unknown + */ + final _mac:Bool = false; + + public function new () + { + #if mac + _mac = true; + #elseif (js && html5) + final userAgent = js.Browser.navigator.userAgent.toUpperCase(); + final platform = js.Browser.navigator.platform.toUpperCase(); + _mac = userAgent.indexOf("APPLEWEBKIT") != -1 || platform.indexOf("MAC") != -1; + #end + + super(); + } + /** * Clean up memory. */ @@ -161,9 +179,15 @@ class FlxInputTextManager extends FlxBasic // Let's set one manually (just the stage itself) FlxG.stage.focus = FlxG.stage; #end - - // Taken from OpenFL's `TextField` - var modifierPressed = #if mac modifier.metaKey #elseif js(modifier.metaKey || modifier.ctrlKey) #else (modifier.ctrlKey && !modifier.altKey) #end; + + // Modifier used for commands like cut, copy and paste + final commandPressed = _mac ? modifier.metaKey : modifier.ctrlKey; + + // Modifier used to move one word over + final wordModPressed = modifier.altKey; + + // Modifier used to move one line over + final lineModPressed = commandPressed; switch (key) { @@ -173,39 +197,43 @@ class FlxInputTextManager extends FlxBasic dispatchTypingAction(COMMAND(DELETE_LEFT)); case DELETE: dispatchTypingAction(COMMAND(DELETE_RIGHT)); - case LEFT if (modifierPressed): + case LEFT if (lineModPressed): + dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); + case LEFT if (wordModPressed): dispatchTypingAction(MOVE_CURSOR(WORD_LEFT, modifier.shiftKey)); case LEFT: dispatchTypingAction(MOVE_CURSOR(LEFT, modifier.shiftKey)); - case RIGHT if (modifierPressed): + case RIGHT if (lineModPressed): + dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); + case RIGHT if (wordModPressed): dispatchTypingAction(MOVE_CURSOR(WORD_RIGHT, modifier.shiftKey)); case RIGHT: dispatchTypingAction(MOVE_CURSOR(RIGHT, modifier.shiftKey)); - case UP if (modifierPressed): - dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); + case UP if (_mac && commandPressed): + dispatchTypingAction(MOVE_CURSOR(TOP, modifier.shiftKey)); case UP: dispatchTypingAction(MOVE_CURSOR(UP, modifier.shiftKey)); - case DOWN if (modifierPressed): - dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); + case DOWN if (_mac && commandPressed): + dispatchTypingAction(MOVE_CURSOR(BOTTOM, modifier.shiftKey)); case DOWN: dispatchTypingAction(MOVE_CURSOR(DOWN, modifier.shiftKey)); - case HOME if (modifierPressed): - dispatchTypingAction(MOVE_CURSOR(HOME, modifier.shiftKey)); + case HOME if (!_mac && commandPressed): + dispatchTypingAction(MOVE_CURSOR(TOP, modifier.shiftKey)); case HOME: dispatchTypingAction(MOVE_CURSOR(LINE_LEFT, modifier.shiftKey)); - case END if (modifierPressed): - dispatchTypingAction(MOVE_CURSOR(END, modifier.shiftKey)); + case END if (!_mac && commandPressed): + dispatchTypingAction(MOVE_CURSOR(BOTTOM, modifier.shiftKey)); case END: dispatchTypingAction(MOVE_CURSOR(LINE_RIGHT, modifier.shiftKey)); - case C if (modifierPressed): + case C if (commandPressed): dispatchTypingAction(COMMAND(COPY)); - case X if (modifierPressed): + case X if (commandPressed): dispatchTypingAction(COMMAND(CUT)); #if !js - case V if (modifierPressed): + case V if (commandPressed): dispatchTypingAction(COMMAND(PASTE)); #end - case A if (modifierPressed): + case A if (commandPressed): dispatchTypingAction(COMMAND(SELECT_ALL)); default: } @@ -323,12 +351,12 @@ enum MoveCursorAction /** * Moves the cursor to the beginning of the text. */ - HOME; + TOP; /** * Moves the cursor to the end of the text. */ - END; + BOTTOM; /** * Moves the cursor to the beginning of the current line. From f3d2597c1fa770374565a0f8786c783dc52a2810 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Wed, 24 Jul 2024 12:19:06 -0500 Subject: [PATCH 43/48] fix selection start/end issue --- flixel/text/FlxInputText.hx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 0979ccc163..9207c1bac2 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -1682,23 +1682,13 @@ class FlxInputText extends FlxText implements IFlxInputText { if (!hasFocus) return; - - var relativePos = getRelativePosition(pointer); - - var upPos = getCharAtPosition(relativePos.x + scrollH, relativePos.y + getScrollVOffset()); - var leftPos = FlxMath.minInt(_selectionIndex, upPos); - var rightPos = FlxMath.maxInt(_selectionIndex, upPos); - _selectionIndex = leftPos; - _caretIndex = rightPos; - updateSelection(true); if (hasFocus) { restartCaretTimer(); } - relativePos.put(); _pointerCamera = null; var currentTime = FlxG.game.ticks; if (currentTime - _lastPressTime < 500) From 2a69a6e8184595649973bca989aa90da1470207b Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 26 Jul 2024 09:55:55 -0500 Subject: [PATCH 44/48] remove set_hasFocus for startFocus and endFocus --- flixel/text/FlxInputText.hx | 103 ++++++++++++++++------------- flixel/text/FlxInputTextManager.hx | 7 +- 2 files changed, 60 insertions(+), 50 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 9207c1bac2..6cdcc6bcf1 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -104,7 +104,7 @@ class FlxInputText extends FlxText implements IFlxInputText /** * Whether or not the text field is the current active one on the screen. */ - public var hasFocus(default, set):Bool = false; + public var hasFocus(default, null):Bool = false; /** * Set the maximum length for the text field. 0 means unlimited. @@ -391,6 +391,57 @@ class FlxInputText extends FlxText implements IFlxInputText this.manager = manager; } + public function startFocus() + { + if (!hasFocus) + { + // set first to avoid infinite loop + hasFocus = true; + + // Ensure that the text field isn't hidden by a keyboard overlay + final bounds = getLimeBounds(_pointerCamera); + FlxG.stage.window.setTextInputRect(bounds); + + manager.setFocus(this); + + if (_caretIndex < 0) + { + _caretIndex = text.length; + _selectionIndex = _caretIndex; + updateSelection(true); + } + + restartCaretTimer(); + + _justGainedFocus = true; + onFocusChange.dispatch(hasFocus); + } + } + + public function endFocus() + { + if (hasFocus) + { + // set first to avoid infinite loop + hasFocus = false; + + // make sure we have not already switched to a new focus (probably not needed, but may in the future) + if (manager.focus == this) + { + manager.setFocus(null); + } + + if (_selectionIndex != _caretIndex) + { + _selectionIndex = _caretIndex; + updateSelection(true); + } + + stopCaretTimer(); + onFocusChange.dispatch(hasFocus); + } + } + override function update(elapsed:Float):Void { super.update(elapsed); @@ -1513,7 +1564,7 @@ class FlxInputText extends FlxText implements IFlxInputText } else if (FlxG.mouse.justPressed && !_justGainedFocus) { - hasFocus = false; + endFocus(); } #end return overlap; @@ -1569,7 +1620,7 @@ class FlxInputText extends FlxText implements IFlxInputText } if (pressedElsewhere && _currentTouch == null && !_justGainedFocus) { - hasFocus = false; + endFocus(); } #end return overlap; @@ -1604,7 +1655,7 @@ class FlxInputText extends FlxText implements IFlxInputText */ function updatePointerPress(pointer:FlxPointer):Void { - hasFocus = true; + startFocus(); var relativePos = getRelativePosition(pointer); _caretIndex = getCharAtPosition(relativePos.x + scrollH, relativePos.y + getScrollVOffset()); @@ -2037,48 +2088,6 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - function set_hasFocus(value:Bool):Bool - { - if (hasFocus != value) - { - hasFocus = value; - if (hasFocus) - { - // Ensure that the text field isn't hidden by a keyboard overlay - var bounds = getLimeBounds(_pointerCamera); - FlxG.stage.window.setTextInputRect(bounds); - - manager.setFocus(this); - - if (_caretIndex < 0) - { - _caretIndex = text.length; - _selectionIndex = _caretIndex; - updateSelection(true); - } - - restartCaretTimer(); - - _justGainedFocus = true; - } - else if (manager.focus == this) - { - manager.setFocus(null); - - if (_selectionIndex != _caretIndex) - { - _selectionIndex = _caretIndex; - updateSelection(true); - } - - stopCaretTimer(); - } - onFocusChange.dispatch(hasFocus); - } - - return value; - } - function set_maxLength(value:Int):Int { if (value < 0) @@ -2091,7 +2100,7 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - + function get_maxScrollH():Int { return textField.maxScrollH; diff --git a/flixel/text/FlxInputTextManager.hx b/flixel/text/FlxInputTextManager.hx index e621d39c3e..f941292366 100644 --- a/flixel/text/FlxInputTextManager.hx +++ b/flixel/text/FlxInputTextManager.hx @@ -131,14 +131,14 @@ class FlxInputTextManager extends FlxBasic { if (focus != null) { - focus.hasFocus = false; + focus.endFocus(); } focus = value; if (focus != null) { - focus.hasFocus = true; + focus.startFocus(); } FlxG.stage.window.textInputEnabled = (focus != null); @@ -315,7 +315,8 @@ class FlxInputTextManager extends FlxBasic interface IFlxInputText { var editable:Bool; - var hasFocus(default, set):Bool; + function startFocus():Void; + function endFocus():Void; function dispatchTypingAction(action:TypingAction):Void; } From af4de4ab68910af2f03c8633f69af7f36df84005 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 26 Jul 2024 16:50:14 -0500 Subject: [PATCH 45/48] use final signals --- flixel/text/FlxInputText.hx | 51 +++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index 6cdcc6bcf1..cc34987e62 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -141,29 +141,34 @@ class FlxInputText extends FlxText implements IFlxInputText public var passwordMode(get, set):Bool; /** - * Gets dispatched whenever the enter key is pressed on the text field. - * The `String` parameter is the current text. + * Gets dispatched whenever the enter key is pressed on the text field + * + * @param text The current text */ - public var onEnter(default, null):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + public final onEnter = new FlxTypedSignal<(text:String)->Void>(); /** - * Gets dispatched whenever this text field gains/loses focus, indicated by - * the `Bool` parameter (`true` if it has focus). + * Gets dispatched whenever this text field gains/loses focus + * + * @param focused Whether the text is focused */ - public var onFocusChange(default, null):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + public final onFocusChange = new FlxTypedSignal<(focused:Bool)->Void>(); /** - * Gets dispatched whenever the horizontal and/or vertical scroll is changed. - * The two parameters indicate the current `scrollH` and `scrollV` respectively. + * Gets dispatched whenever the horizontal and/or vertical scroll is changed + * + * @param scrollH The current horizontal scroll + * @param scrollV The current vertical scroll */ - public var onScrollChange(default, null):FlxTypedSignalInt->Void> = new FlxTypedSignalInt->Void>(); + public final onScrollChange = new FlxTypedSignal<(scrollH:Int, scrollV:Int)->Void>(); /** - * Gets dispatched whenever the text is changed by the user. The `String` - * parameter is the current text, while the `FlxInputTextChange` parameter - * indicates what type of change occurred. + * Gets dispatched whenever the text is changed by the user + * + * @param text The current text + * @param text What type of change occurred */ - public var onTextChange(default, null):FlxTypedSignalFlxInputTextChange->Void> = new FlxTypedSignalFlxInputTextChange->Void>(); + public final onTextChange = new FlxTypedSignal<(text:String, change:FlxInputTextChange)->Void>(); /** * The current horizontal scrolling position, in pixels. Defaults to @@ -481,21 +486,11 @@ class FlxInputText extends FlxText implements IFlxInputText { manager.unregisterInputText(this); - if (onFocusChange != null) - { - onFocusChange.destroy(); - onFocusChange = null; - } - if (onScrollChange != null) - { - onScrollChange.destroy(); - onScrollChange = null; - } - if (onTextChange != null) - { - onTextChange.destroy(); - onTextChange = null; - } + FlxDestroyUtil.destroy(onEnter); + FlxDestroyUtil.destroy(onFocusChange); + FlxDestroyUtil.destroy(onScrollChange); + FlxDestroyUtil.destroy(onTextChange); + _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); _caret = FlxDestroyUtil.destroy(_caret); if (_caretTimer != null) From ef09deb2bd159e5fd7c64b2c4a0d7f74f7056004 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 26 Jul 2024 16:51:18 -0500 Subject: [PATCH 46/48] let openfl handle maxChars --- flixel/text/FlxInputText.hx | 39 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index cc34987e62..f571f9cf96 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -109,7 +109,7 @@ class FlxInputText extends FlxText implements IFlxInputText /** * Set the maximum length for the text field. 0 means unlimited. */ - public var maxLength(default, set):Int = 0; + public var maxChars(get, set):Int; /** * The maximum value of `scrollH`. @@ -572,7 +572,7 @@ class FlxInputText extends FlxText implements IFlxInputText var beginIndex = selectionBeginIndex; var endIndex = selectionEndIndex; - if (beginIndex == endIndex && maxLength > 0 && text.length == maxLength) + if (beginIndex == endIndex && maxChars > 0 && text.length == maxChars) return; if (beginIndex < 0) @@ -651,29 +651,13 @@ class FlxInputText extends FlxText implements IFlxInputText } /** - * Returns the specified text filtered using `maxLength`, `forceCase` and `filterMode`. + * Returns the specified text filtered using `forceCase` and `filterMode`. * @param newText The string to filter. * @param selection Whether or not this string is meant to be added at the selection or if we're - * replacing the entire text. This is used for cutting the string appropiately - * when `maxLength` is set. + * replacing the entire text. */ function filterText(newText:String, selection:Bool = false):String { - if (maxLength > 0) - { - var removeLength = selection ? (selectionEndIndex - selectionBeginIndex) : text.length; - var newMaxLength = maxLength - text.length + removeLength; - - if (newMaxLength <= 0) - { - newText = ""; - } - else if (newMaxLength < newText.length) - { - newText = newText.substr(0, newMaxLength); - } - } - if (forceCase == UPPER_CASE) { newText = newText.toUpperCase(); @@ -2083,14 +2067,17 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - function set_maxLength(value:Int):Int + inline function get_maxChars():Int { - if (value < 0) - value = 0; - if (maxLength != value) + return textField.maxChars; + } + + function set_maxChars(value:Int):Int + { + if (textField.maxChars != value) { - maxLength = value; - text = filterText(text); + textField.maxChars = value; + _regen = true; } return value; From fdf3f1dd3dcb5e34e0e74ba3affaaa0a707d71ac Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 26 Jul 2024 16:51:27 -0500 Subject: [PATCH 47/48] inline setters remove redundancies --- flixel/text/FlxInputText.hx | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index f571f9cf96..dbbb14b667 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -1971,7 +1971,7 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - function get_bottomScrollV():Int + inline function get_bottomScrollV():Int { return textField.bottomScrollV; } @@ -1987,7 +1987,7 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - function get_caretIndex():Int + inline function get_caretIndex():Int { return _caretIndex; } @@ -2083,22 +2083,22 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - function get_maxScrollH():Int + inline function get_maxScrollH():Int { return textField.maxScrollH; } - function get_maxScrollV():Int + inline function get_maxScrollV():Int { return textField.maxScrollV; } - function get_multiline():Bool + inline function get_multiline():Bool { return textField.multiline; } - function set_multiline(value:Bool):Bool + inline function set_multiline(value:Bool):Bool { if (textField.multiline != value) { @@ -2108,7 +2108,7 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - function get_passwordMode():Bool + inline function get_passwordMode():Bool { return textField.displayAsPassword; } @@ -2122,18 +2122,14 @@ class FlxInputText extends FlxText implements IFlxInputText } return value; } - - function get_scrollH():Int + + inline function get_scrollH():Int { return textField.scrollH; } function set_scrollH(value:Int):Int { - if (value > maxScrollH) - value = maxScrollH; - if (value < 0) - value = 0; if (textField.scrollH != value) { textField.scrollH = value; @@ -2142,17 +2138,13 @@ class FlxInputText extends FlxText implements IFlxInputText return value; } - function get_scrollV():Int + inline function get_scrollV():Int { return textField.scrollV; } function set_scrollV(value:Int):Int { - if (value > maxScrollV) - value = maxScrollV; - if (value < 1) - value = 1; if (textField.scrollV != value || textField.scrollV == 0) { textField.scrollV = value; From 949f3bdd850acfd9a504157129f031eb7bd3ebb4 Mon Sep 17 00:00:00 2001 From: George FunBook Date: Fri, 26 Jul 2024 17:02:12 -0500 Subject: [PATCH 48/48] simplify caret blink --- flixel/text/FlxInputText.hx | 65 +++++-------------------------------- 1 file changed, 8 insertions(+), 57 deletions(-) diff --git a/flixel/text/FlxInputText.hx b/flixel/text/FlxInputText.hx index dbbb14b667..86180d4dd7 100644 --- a/flixel/text/FlxInputText.hx +++ b/flixel/text/FlxInputText.hx @@ -234,11 +234,6 @@ class FlxInputText extends FlxText implements IFlxInputText * An FlxSprite representing the selection cursor. */ var _caret:FlxSprite; - /** - * This variable is used for the caret flash timer to indicate whether it - * is currently visible or not. - */ - var _caretFlash:Bool = false; /** * Internal variable for the current index of the selection cursor. */ @@ -246,7 +241,7 @@ class FlxInputText extends FlxText implements IFlxInputText /** * The timer used to flash the caret while the text field has focus. */ - var _caretTimer:FlxTimer = new FlxTimer(); + var _caretTimer:Float = 0; /** * An FlxSprite representing the border of the text field. */ @@ -442,7 +437,6 @@ class FlxInputText extends FlxText implements IFlxInputText updateSelection(true); } - stopCaretTimer(); onFocusChange.dispatch(hasFocus); } } @@ -451,6 +445,10 @@ class FlxInputText extends FlxText implements IFlxInputText { super.update(elapsed); + _caretTimer += elapsed; + final showCaret = (_caretTimer % 1.2) < 0.6; + _caret.visible = showCaret && hasFocus && editable && _selectionIndex == _caretIndex && isCaretLineVisible(); + #if FLX_POINTER_INPUT if (visible) { @@ -493,11 +491,6 @@ class FlxInputText extends FlxText implements IFlxInputText _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); _caret = FlxDestroyUtil.destroy(_caret); - if (_caretTimer != null) - { - _caretTimer.cancel(); - _caretTimer = FlxDestroyUtil.destroy(_caretTimer); - } _fieldBorderSprite = FlxDestroyUtil.destroy(_fieldBorderSprite); _pointerCamera = null; while (_selectionBoxes.length > 0) @@ -1102,15 +1095,6 @@ class FlxInputText extends FlxText implements IFlxInputText restartCaretTimer(); } - /** - * Helper function to stop and then start the caret flashing timer. - */ - function restartCaretTimer():Void - { - stopCaretTimer(); - startCaretTimer(); - } - /** * Runs the specified typing command. */ @@ -1189,38 +1173,17 @@ class FlxInputText extends FlxText implements IFlxInputText setSelection(_selectionIndex, _caretIndex); } } - + /** * Starts the timer for the caret to flash. * * Call this right after `stopCaretTimer()` to show the caret immediately. */ - function startCaretTimer():Void + function restartCaretTimer():Void { - _caretTimer.cancel(); - - _caretFlash = !_caretFlash; - updateCaretVisibility(); - _caretTimer.start(0.6, function(tmr) - { - startCaretTimer(); - }); + _caretTimer = 0; } - /** - * Stops the timer for the caret to flash and hides it. - */ - function stopCaretTimer():Void - { - _caretTimer.cancel(); - - if (_caretFlash) - { - _caretFlash = false; - updateCaretVisibility(); - } - } - /** * Updates the position of the background sprites, if they're enabled. */ @@ -1290,17 +1253,6 @@ class FlxInputText extends FlxText implements IFlxInputText clipSprite(_caret); } - /** - * Updates the visibility of the selection cursor. - */ - function updateCaretVisibility():Void - { - if (_caret == null) - return; - - _caret.visible = (_caretFlash && editable && _selectionIndex == _caretIndex && isCaretLineVisible()); - } - #if flash /** * Used in Flash to automatically update the horizontal scroll after setting the selection. @@ -1479,7 +1431,6 @@ class FlxInputText extends FlxText implements IFlxInputText */ function updateSelectionSprites():Void { - updateCaretVisibility(); updateCaretPosition(); updateSelectionBoxes(); }