-
Notifications
You must be signed in to change notification settings - Fork 244
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
[SuperTextField][Mobile] Fix scrolling when changing the selection to a visible position (Resolves #1096) #1101
[SuperTextField][Mobile] Fix scrolling when changing the selection to a visible position (Resolves #1096) #1101
Conversation
…isible position (Resolves superlistapp#1096)
@@ -995,7 +995,7 @@ class TextScrollController with ChangeNotifier { | |||
// Scroll the content down. | |||
_scrollOffset = rect.top; | |||
_log.finer(' - updated _scrollOffset to $_scrollOffset'); | |||
} else if (rect.bottom > _delegate!.viewportHeight!) { | |||
} else if ((rect.bottom - _scrollOffset) > _delegate!.viewportHeight!) { |
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.
We should rename this rect
and the incoming extentCharacterRect
to reflect the space that the rectangle sits in.
rectInContentSpace
, extentCharacterRectInContentSpace
This way we hopefully won't overlook details like this in the future.
Also, can you please confirm in the _delegate
that this rect definitely is in content space, and not viewport space? Related to that, if the rect is in content space, then how is rect.top < 0
working? If rect
is in content space, then rect.top
would never be less than zero. So something seems inconsistent here.
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.
The rect is in content space, we get it from:
final extentCharacterRectInContentSpace = _delegate!.getCharacterRectAtPosition(TextPosition(offset: characterIndex));
_ensureRectIsVisible(extentCharacterRectInContentSpace);
getCharacterRectAtPosition
just calls _textLayout.getCharacterBox
:
@override
Rect getCharacterRectAtPosition(TextPosition position) {
return _textLayout.getCharacterBox(position)?.toRect() ?? Rect.fromLTRB(0, 0, 0, _textLayout.estimatedLineHeight);
}
Related to that, if the rect is in content space, then how is rect.top < 0 working?
Indeed, this doesn't seem to be working.
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.
getCharacterRectAtPosition
just calls_textLayout.getCharacterBox
What if there's padding in the text field? Does everything still work?
When saying that something doesn't work, please describe exactly what isn't working. What did you try? What was expected? What was the actual result? I can't review a decision to replicate behavior if I can't see exactly what issue you're sidestepping. |
if (!textFieldBox.size.contains(adjustedOffset)) {
print("Couldn't tap at $adjustedOffset in text field with size ${textFieldBox.size}");
return false;
} This code doesn't take into account the cases where the content height is bigger than the textfield height. |
@angelosilvestre that explanation still requires me to go read some source code to contextualize what you're saying. How are you trying to use the tool, what's the expected output, what's the actual output? |
I'm trying to place the selection at the end of the text. This offset isn't visible, because the content height is bigger than the viewport height. I call Expected result: the selection is placed at Actual result: An exception is thrown with the message The exception also happens if we already scrolled to the end. |
Ok, that's a much more helpful description. Thanks. I'll think about that. |
Do we have an |
super_editor/test/super_textfield/super_textfield_scroll_test.dart
Outdated
Show resolved
Hide resolved
super_editor/test/super_textfield/super_textfield_scroll_test.dart
Outdated
Show resolved
Hide resolved
super_editor/lib/src/infrastructure/super_textfield/infrastructure/text_scrollview.dart
Outdated
Show resolved
Hide resolved
final extentCharacterRect = _delegate!.getCharacterRectAtPosition(TextPosition(offset: characterIndex)); | ||
_ensureRectIsVisible(extentCharacterRect); | ||
|
||
final padding = _delegate!.padding ?? const EdgeInsets.all(0.0); |
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.
There's a widget.padding
, which you also exposed as padding
, and now there's _delegate!.padding
? Which padding is the real padding? Or am I looking at two different classes? I can't tell with the way that GitHub is showing the code.
Also, my initial reaction is that I don't think we want to expose arbitrary layout details like padding. What happens if there's also a border, or margin, or anything else that impacts position? It seems to me that the getCharacterRectAtPosition
should just return the actual character rect. To me, this looks like pieces of the layout implementation are being pulled into the scrolling system.
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.
Or am I looking at two different classes?
There are different classes
Updated getCharacterRectAtPosition
to return the adjusted rect and removed the padding from the delegate.
expect( | ||
SuperTextFieldInspector.findScrollOffset(), | ||
10.0, | ||
); |
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 see anything in this test that shows what it has to do with padding. If the test of padding is incidental, then please update the comments to focus on what we're proving with this behavior.
@@ -1185,15 +1185,29 @@ class SuperTextFieldScrollviewState extends State<SuperTextFieldScrollview> with | |||
const gutterExtent = 0; // _dragGutterExtent | |||
final extentLineIndex = (extentOffset.dy / widget.estimatedLineHeight).round(); | |||
|
|||
final firstCharY = _textLayout.getCharacterBox(const TextPosition(offset: 0))?.top ?? 0.0; | |||
final lastCharY = | |||
_textLayout.getCharacterBox(TextPosition(offset: widget.textController.text.text.length - 1))?.top ?? |
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.
Shouldn't we use the bottom
of the last character, not the top
?
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 think we should use the top
, because the extentOffset
refers to the top-left. I made an adjustment to use zero when _textLayout.getCharacterBox
returns null
.
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 didn't understand that explanation. We're trying to determine the bottom most y-value that should be visible, right? I mean, imagine that only the top pixel of a line were visible - that means that nearly the entire line is below the bottom of the viewport, right?
Correct me if I'm wrong, but for the top line we essentially do "topCharacter.top - halfLineHeight", right?
I would expect for the bottom would do "bottomCharacter.bottom + halfLineHeight".
Am I getting something wrong?
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.
But we only use lastCharY
to determine if we are at the last line of the textfield, not to determine the rect that should be visible.
super_editor/lib/src/infrastructure/super_textfield/desktop/desktop_textfield.dart
Outdated
Show resolved
Hide resolved
super_editor/lib/src/infrastructure/super_textfield/desktop/desktop_textfield.dart
Outdated
Show resolved
Hide resolved
super_editor/lib/src/infrastructure/super_textfield/desktop/desktop_textfield.dart
Show resolved
Hide resolved
super_editor/lib/src/infrastructure/super_textfield/infrastructure/text_scrollview.dart
Outdated
Show resolved
Hide resolved
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.
It's not obvious what details we care about in these goldens. At a minimum, can you make the text field not as wide as the parent, center it, add a border around the viewport? Also, can you take a few minutes to see if it's easy for us to use the golden toolkit to render multiple of these images, together, along with a brief description of each one?
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.
Updated to use the golden toolkit.
super_editor/lib/src/infrastructure/super_textfield/desktop/desktop_textfield.dart
Outdated
Show resolved
Hide resolved
super_editor/lib/src/infrastructure/super_textfield/infrastructure/text_scrollview.dart
Show resolved
Hide resolved
super_editor/lib/src/infrastructure/super_textfield/infrastructure/text_scrollview.dart
Outdated
Show resolved
Hide resolved
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.
These descriptions state what's painted, but not what we're testing. Why do we care that we're at the end? If a developer eventually got a different golden result, and had to re-evaluate it, what should they look for?
Also, should there a caret visible in here?
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.
Updated.
The caret isn't visible because we are not focusing the textfield. Also, using the robot to place the selection is throwing an exception when the textfield is scrollable, as we mentioned before.
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.
Remind me how this situation is happening to users, if we can't simulate it with user interaction in the test?
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.
Well, this test was added to ensure the textfield is being displayed correctly when we scroll it to make the selected line visible. This also happens if the selection is changed without direct interaction with the textfield...
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.
What if you alter the test to place the caret where visible, and then press the down arrow enough times to get to the end, and then press up enough times to get back to the start? Then we'd test real user behavior, and we'd see the caret...
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.
As now we are using the three text fields in the same golden test, when we focus one textfield to press down it will unfocus the others. When SuperTextField
loses focus it clears the selection...
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.
LGTM
… a visible position (Resolves superlistapp#1096) (superlistapp#1101)
[SuperTextField][Mobile] Fix scrolling when changing ths selection to a visible position. Resolves #1096
When changing the selection in a multi-line textfield, if the selected position offset is greater than the text field height the text field is being scrolled, even if this offset is already visible on screen:
Screen.Recording.2023-04-24.at.16.16.40.mov
The issue is that we are only comparing the offset with the viewport height, ignoring the fact that we might already have scrolled to a position that makes the selection visible.
This PR changes the comparison to consider the current scroll offset.
I tried using the robot to change the selection, but it doesn't seem to work when the textfield scrolls.
I copied
_findInnerPlatformTextField
fromSuperTextFieldRobot
. Is there any shared file where we could de-dup this?