Repository navigation
Fix FlxInputText cursor, selection and click position when scaled #3665
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -253,6 +253,22 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| */ | ||
| var _justGainedFocus:Bool = false; | ||
|
|
||
| /** | ||
| * Stores the origin that sprites were last positioned with, used for checking | ||
| * if they need to be updated. | ||
| */ | ||
| var _lastOrigin:FlxPoint = FlxPoint.get(); | ||
| /** | ||
| * Stores the offset that sprites were last positioned with, used for checking | ||
| * if they need to be updated. | ||
| */ | ||
| var _lastOffset:FlxPoint = FlxPoint.get(); | ||
| /** | ||
| * Stores the scale that sprites were last positioned with, used for checking | ||
| * if they need to be updated. | ||
| */ | ||
| var _lastScale:FlxPoint = FlxPoint.get(1, 1); | ||
|
|
||
| /** | ||
| * Internal variable that holds the camera that the text field is being pressed on. | ||
| */ | ||
|
|
@@ -466,6 +482,14 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| { | ||
| regenGraphic(); | ||
|
|
||
| if (!_lastScale.equals(scale) || !_lastOrigin.equals(origin) || !_lastOffset.equals(offset)) | ||
| { | ||
| _lastScale.copyFrom(scale); | ||
| _lastOrigin.copyFrom(origin); | ||
| _lastOffset.copyFrom(offset); | ||
| updateSpritePositions(); | ||
| } | ||
|
|
||
| drawSprite(_fieldBorderSprite); | ||
| drawSprite(_backgroundSprite); | ||
|
|
||
|
|
@@ -493,6 +517,9 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| _backgroundSprite = FlxDestroyUtil.destroy(_backgroundSprite); | ||
| _caret = FlxDestroyUtil.destroy(_caret); | ||
| _fieldBorderSprite = FlxDestroyUtil.destroy(_fieldBorderSprite); | ||
| _lastOffset = FlxDestroyUtil.put(_lastOffset); | ||
| _lastOrigin = FlxDestroyUtil.put(_lastOrigin); | ||
| _lastScale = FlxDestroyUtil.put(_lastScale); | ||
| _pointerCamera = null; | ||
| while (_selectionBoxes.length > 0) | ||
| FlxDestroyUtil.destroy(_selectionBoxes.pop()); | ||
|
|
@@ -631,7 +658,7 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| { | ||
| bounds = bounds.clipTo(clipRect); | ||
| } | ||
| bounds.offset(x - sprite.x, y - sprite.y); | ||
| bounds.offset(-worldToFrameX(sprite.x), -worldToFrameY(sprite.y)); | ||
|
|
||
| sprite.clipRect = rect.clipTo(bounds); | ||
|
|
||
|
|
@@ -646,11 +673,49 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| if (sprite != null && sprite.visible) | ||
| { | ||
| sprite.scrollFactor.copyFrom(scrollFactor); | ||
| sprite.scale.copyFrom(scale); | ||
| sprite.origin.set(0, 0); | ||
| sprite._cameras = _cameras; | ||
| sprite.draw(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gets the X position in the world of the specified X position in the text field, | ||
| * takes `scale`, `origin` and `offset` into account. | ||
| */ | ||
| inline function frameToWorldX(frameX:Float):Float | ||
| { | ||
| return x - offset.x + origin.x + (frameX - origin.x) * scale.x; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the Y position in the world of the specified Y position in the text field, | ||
| * takes `scale`, `origin` and `offset` into account. | ||
| */ | ||
| inline function frameToWorldY(frameY:Float):Float | ||
| { | ||
| return y - offset.y + origin.y + (frameY - origin.y) * scale.y; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the X position in the text field of the specified X position in the world, | ||
| * takes `scale`, `origin` and `offset` into account. | ||
| */ | ||
| inline function worldToFrameX(worldX:Float):Float | ||
| { | ||
| return (worldX - x + offset.x - origin.x) / scale.x + origin.x; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the Y position in the text field of the specified Y position in the world, | ||
| * takes `scale`, `origin` and `offset` into account. | ||
| */ | ||
| inline function worldToFrameY(worldY:Float):Float | ||
| { | ||
| return (worldY - y + offset.y - origin.y) / scale.y + origin.y; | ||
| } | ||
|
Comment on lines
+692
to
+717
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FlxSprite has a few helpers similar to this, but it doesn't have simple X/Y converters. it may be worth adding them For instance: All of this is outside the scope of this PR but I wanna note it, and make a new issue about it after this is merged |
||
|
|
||
| /** | ||
| * Returns the specified text filtered using `forceCase` and `filterMode`. | ||
| * @param newText The string to filter. | ||
|
|
@@ -1200,8 +1265,8 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| if (!background) | ||
| return; | ||
|
|
||
| _fieldBorderSprite.setPosition(x - fieldBorderThickness, y - fieldBorderThickness); | ||
| _backgroundSprite.setPosition(x, y); | ||
| _fieldBorderSprite.setPosition(frameToWorldX(-fieldBorderThickness), frameToWorldY(-fieldBorderThickness)); | ||
| _backgroundSprite.setPosition(frameToWorldX(0), frameToWorldY(0)); | ||
| clipSprite(_fieldBorderSprite, true); | ||
| clipSprite(_backgroundSprite); | ||
| } | ||
|
|
@@ -1216,25 +1281,25 @@ class FlxInputText extends FlxText implements IFlxInputText | |
|
|
||
| if (text.length == 0) | ||
| { | ||
| _caret.setPosition(x + getCaretOffsetX(), y + GUTTER); | ||
| _caret.setPosition(frameToWorldX(getCaretOffsetX()), frameToWorldY(GUTTER)); | ||
| } | ||
| else | ||
| { | ||
| var boundaries = getCharBoundaries(_caretIndex - 1); | ||
| if (boundaries != null) | ||
| { | ||
| _caret.setPosition(x + boundaries.right - scrollH, y + boundaries.y - getScrollVOffset()); | ||
| _caret.setPosition(frameToWorldX(boundaries.right - scrollH), frameToWorldY(boundaries.y - getScrollVOffset())); | ||
| } | ||
| else | ||
| { | ||
| boundaries = getCharBoundaries(_caretIndex); | ||
| if (boundaries != null) | ||
| { | ||
| _caret.setPosition(x + boundaries.x - scrollH, y + boundaries.y - getScrollVOffset()); | ||
| _caret.setPosition(frameToWorldX(boundaries.x - scrollH), frameToWorldY(boundaries.y - getScrollVOffset())); | ||
| } | ||
| else // end of line | ||
| { | ||
| _caret.setPosition(x + getCaretOffsetX(), y + GUTTER + getLineY(getLineIndexOfChar(_caretIndex)) - getScrollVOffset()); | ||
| _caret.setPosition(frameToWorldX(getCaretOffsetX()), frameToWorldY(GUTTER + getLineY(getLineIndexOfChar(_caretIndex)) - getScrollVOffset())); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1416,7 +1481,7 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| 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.setPosition(frameToWorldX(boxRect.x), frameToWorldY(boxRect.y)); | ||
| box.makeGraphic(Std.int(boxRect.width), Std.int(boxRect.height)); | ||
| clipSprite(box); | ||
| box.visible = true; | ||
|
|
@@ -1572,11 +1637,12 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| function checkPointerOverlap(pointer:FlxPointer):Bool | ||
| { | ||
| var overlap = false; | ||
| var pointerPos = FlxPoint.get(); | ||
| for (camera in getCameras()) | ||
| { | ||
| pointer.getWorldPosition(camera, pointerPos); | ||
| if (overlapsPoint(pointerPos, true, camera)) | ||
| var relativePos = getRelativePosition(pointer, camera); | ||
| var pointerOverlaps = relativePos.x >= 0 && relativePos.x < width && relativePos.y >= 0 && relativePos.y < height; | ||
| relativePos.put(); | ||
| if (pointerOverlaps) | ||
| { | ||
| if (_pointerCamera == null) | ||
| _pointerCamera = camera; | ||
|
|
@@ -1585,7 +1651,6 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| } | ||
| } | ||
|
|
||
| pointerPos.put(); | ||
| return overlap; | ||
| } | ||
|
|
||
|
|
@@ -1722,13 +1787,16 @@ class FlxInputText extends FlxText implements IFlxInputText | |
| } | ||
|
|
||
| /** | ||
| * Returns the position of the pointer relative to the text field. | ||
| * Returns the position of the pointer relative to the text field, using | ||
| * `_pointerCamera` if `camera` is `null`. | ||
| */ | ||
| function getRelativePosition(pointer:FlxPointer):FlxPoint | ||
| function getRelativePosition(pointer:FlxPointer, ?camera:FlxCamera):FlxPoint | ||
| { | ||
| 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); | ||
| if (camera == null) | ||
| camera = _pointerCamera; | ||
|
|
||
| var pointerPos = pointer.getWorldPosition(camera, FlxPoint.get()); | ||
| var result = transformWorldToPixels(pointerPos, camera); | ||
| pointerPos.put(); | ||
| return result; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should assume 0 origin, here. This change only works if the dev explicitly sets the origin to zero, otherwise the text is misaligned. while it may be common for devs to set the origin to zero, it's not the default.
I see 3 options:
sprite.origin.copyFrom(origin);and better account for non-zero origin in the text selection codeNumber 1 could break existing code (while unlikely, we would still need to wait for the next major release rather than a patch release.) Number 2 is the most robust and ideal, but I don't know the work involved. Number 3 is clearly the easiest
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have similar thoughts for offset, it looks like the offset is used to position the sub sprites rather than being copied to them. I wonder if that plays well with things like clipRect and getPixelAt (though I don't know if this is something we tested before). Worth noting that without testing the features mentioned above, offsetting the input text behaves exactly how I expect
I'm okay with also leaving this as is, for the scope of this, but I do find it odd that scale is copied to child sprites, offset is not and origin is assumed zero. Three different solutions for three the three properties.