Repository navigation
Add double click and double click + drag gestures to SelectionArea - #124817
Conversation
justinmc
left a comment
There was a problem hiding this comment.
LGTM with nits 👍
I'm always bummed that we don't get this kind of behavior for free shared between RenderEditable and RenderParagraph. But given that we can't do that yet, this is pretty clean, and I think TextGranularity makes it straightforward.
You have some test failures FYI.
There was a problem hiding this comment.
Should these two names be the same? textGranularity and granularity.
There was a problem hiding this comment.
I think we have a method with the same name in text_selection.dart. Is it reasonable to share code here?
There was a problem hiding this comment.
I think I can re-visit this once I implement triple-click gestures. Right now there are some small nuances between how this effective consecutive tap count works on static vs editable text.
There was a problem hiding this comment.
No apostrophe here: "platform's" => "platforms"
There was a problem hiding this comment.
return isPositionAtOrBelowStartingBaseline;
There was a problem hiding this comment.
Should there be spaces between the brackets and the text? I forget. Here and below, if I'm right.
There was a problem hiding this comment.
Looks like the other methods in this file with named parameters do not, so I just followed that. But I am also unsure what the preferred styling here is.
There was a problem hiding this comment.
Sounds like it's not a big deal then, keeping it like this is fine 👍
There was a problem hiding this comment.
It was probably my oversight.
There was a problem hiding this comment.
Should textGranularity actually receive a default value here?
There was a problem hiding this comment.
Nit: Maybe specify that the default is [TextGranularity.character] instead of saying "character by character".
There was a problem hiding this comment.
Why is this needed now? (and in a few other places below)
There was a problem hiding this comment.
This is needed because of the switch to PanGestureRecognizer -> TapAndPanGestureRecognizer. PanGestureRecognizer calls onDown earlier than TapAndPanGestureRecognizer. PanGestureRecognizer calls it in addAllowedPointer, and TapAndPanGestureRecognizer waits for the recognizer to declare victory.
There was a problem hiding this comment.
Thanks for the explanation!
There was a problem hiding this comment.
Super nit: Maybe assign this offset to a variable so that it's clear where it's coming from?
|
Looks like the failing tests are because Offset textOffsetToPosition(RenderParagraph paragraph, int offset) {
const Rect caret = Rect.fromLTWH(0.0, 0.0, 2.0, 20.0);
final Offset localOffset = paragraph.getOffsetForCaret(TextPosition(offset: offset), caret);
return paragraph.localToGlobal(localOffset);
}
test('getWordBoundary control test failing', () {
final RenderParagraph paragraph = RenderParagraph(
const TextSpan(text: 'How are you?'),
textDirection: TextDirection.ltr,
);
layout(paragraph);
final Offset target4 = textOffsetToPosition(paragraph, 4);
final TextPosition position4 = paragraph.getPositionForOffset(target4);
final TextRange range4 = paragraph.getWordBoundary(position4);
expect(range4.textInside('How are you?'), equals('are'));
});This fails with Looks like this test passes using Opened #125582 for further investigation. |
|
Thanks for opening that issue! |
|
|
||
| final (TextPosition start, TextPosition end) wordBoundary = _getWordBoundaryAtPosition(position); | ||
| TextPosition targetPosition = isEnd ? wordBoundary.$2 : wordBoundary.$1; | ||
| if (!_rect.contains(localPosition)) { |
There was a problem hiding this comment.
I am not entirely sure what this is for can you explain?
There was a problem hiding this comment.
I found that since SelectionUtils.adjustDragOffset returns the top left of the rect for ltr if the drag is in Area1 of a Selectable it will return TextPosition(0). When using _getWordBoundaryAtPosition(position) that ends up selecting the first word of the selectable.
This is not desired since say we have a list of Selectables and our drag starts at the first one, dragging down to the second one moves the selection to the word on the second selectable, but then when dragging back up to the first selectable the second selectables first word is still selected because _getWordBoundaryAtPosition(TextPosition(0)) = first word.
This same logic is in _updateSelectionEdge but it works there because the edge will moved to TextPosition(0) and not the word at TextPosition(0).
This was my workaround for this issue.
There was a problem hiding this comment.
Thanks for explanation. If this is the case, I think you can move this before the whole _getWordBoundaryAtPosition and short circuit it. Also would be good to add some comments about this
| localPosition, | ||
| direction: paragraph.textDirection, | ||
| ); | ||
| final TextPosition position = _clampTextPosition(paragraph.getPositionForOffset(adjustedOffset)); |
There was a problem hiding this comment.
This probably should instead clamp after _getWordBoundaryAtPosition
it is fine if the position is outside of the text range of this selectablefragment, as long as the targetPosition is in the text range.
| return SelectionResult.previous; | ||
| } | ||
|
|
||
| return SelectionUtils.getResultBasedOnRect(_rect, localPosition); |
There was a problem hiding this comment.
Can you also copy the todo over?
| /// The [globalPosition] contains the new offset of the edge. | ||
| /// | ||
| /// This event is dispatched when the framework detects [DragStartDetails] in | ||
| /// The [granularity] contains the granularity that the selection edge should move by. |
There was a problem hiding this comment.
should we document that only character and word is supported?
There was a problem hiding this comment.
why do you need to cache this value? is it possible to figure out whether the you need to move the start or end based on the current selection and the newly updated selection? for example, if current selection is (start: 3, end: 6) and you receive a end update, you will know you need to swap the start if new end position is < 3
There was a problem hiding this comment.
It is hard to read the logic in these block. Can you add check isEnd in the condition? I think it would be noop in the set selection anyway
There was a problem hiding this comment.
I think you don't need to cache the _originWordSelectionStart, you just need to find the wordboundary of _textSelectionEnd/Start and move them to the other side of the word.
Also the current logc assume _originWordSelectionStart and _originWordSelectionEnd are enclosing a word. is it possible it is not the case?
|
This is ready for another review. I'm still running into the issue where the scrollable synthesizes an event for the opposite edge sometimes losing the origin word boundary, but this happens a lot less if we track the synthesized event 9239d36. |
There was a problem hiding this comment.
should the rect check here also applies to handle select word? if so we could consider put all these tweak into _getWordBoundaryAtPosition
There was a problem hiding this comment.
This rect check is to handle the results of SelectionUtils.adjustDragOffset which isn't used in handle select word.
There was a problem hiding this comment.
This seems to be inverse of shouldSwapEdgesWhenSelectionIsNormalized except for the equal case? can the equal case be handle the same way as either one of shouldSwapEdgesWhenSelectionIsNormalized or shouldSwapEdgesWhenSelectionNotNormalized
There was a problem hiding this comment.
it is also hard to interpret the name. is it swaping the existingSelectionStart and existingSelectionEnd or is it swaping the targetPosition to be wordBoundary.wordStart or wordBoundary.wordEnd? base on the code, it seems to be both.
Maybe call this willNewSelectionBeNormalized
There was a problem hiding this comment.
The equal case should actually not swap, I have updated the implementation to reflect this.
There was a problem hiding this comment.
Which class is synthesizing the start event to selectable?
There was a problem hiding this comment.
_startNewMouseSelectionGesture is the one setting the start edge with _selectWordAt, or were you referring to something else?
There was a problem hiding this comment.
I am referring to cases where the _selectEndTo reach a new selectable other than the _selectWordAt originally sent to
There was a problem hiding this comment.
_SelectableRegionContainerDelegate is synthesizing the first start event on ensureChildUpdated when _selectEndTo reaches a new selectable.
There was a problem hiding this comment.
I see, so it would be a selectStart with granularity character. As long as renderParagraph can handle that, that sounds fine.
There was a problem hiding this comment.
watch out for ellipsized case where the targetPosition may not be able to reach range.end. basically what to todo is describing below.
The return value should always be determine by SelectionUtils.getResultBasedOnRect(_rect, localPosition); until we have a way to know what text are ellipsized
아직 더블클릭시 단어 선택 기능은 없음 아래 링크 merge 되면 될듯? flutter/flutter#124817 파비콘 변경해보고 배경색 정도 변경해봄 근데 자체 tag가 생성되서 이게 SEO가 잘되나? 표준 태그들 안쓰는거 같은데
justinmc
left a comment
There was a problem hiding this comment.
Mostly just questions about code organization.
There was a problem hiding this comment.
Nit: Is this any simpler?
final (wordStart: TextPosition wordStart, wordEnd: TextPosition wordEnd) = _getWordBoundaryAtPosition(edgeToKeepInPlace);So you don't have to refer to localWordBoundary. Here and other places where you receive a record.
There was a problem hiding this comment.
I mentioned this before in my old review but just to double check, it's not possible to deduplicate this code with what we have in EditableText is it? Assuming that EditableText also has similar code to extend selection by word.
There was a problem hiding this comment.
The code for extending selection by word is in RenderEditable here
flutter/packages/flutter/lib/src/rendering/editable.dart
Lines 2066 to 2090 in de00429
SelectionArea can span multiple Selectables while one EditableText contains the entire selection so the implementation details are a little different.
There was a problem hiding this comment.
It's really hard to follow all of the if/else blocks below here. Would it be possible to split any of this up into smaller methods or anything like that?
I do appreciate all of the comments though!
There was a problem hiding this comment.
I tried simplifying some of the cases a bit more. Hope that clears it up a bit.
There was a problem hiding this comment.
I am also agree, I would suggest separating out two branches (isEnd = true/false) into two separate function. It seems like this code try too hard to deduplicate these two branchs but now it is hard to figure out which edge a variable holds
There was a problem hiding this comment.
I've separated the logic into two methods, one for updating the end edge and one for updating the start edge. I think it is clearer just more LOC.
There was a problem hiding this comment.
Looks like an improvement to me despite more lines 👍
There was a problem hiding this comment.
I see, so it would be a selectStart with granularity character. As long as renderParagraph can handle that, that sounds fine.
There was a problem hiding this comment.
It was probably my oversight.
There was a problem hiding this comment.
I am also agree, I would suggest separating out two branches (isEnd = true/false) into two separate function. It seems like this code try too hard to deduplicate these two branchs but now it is hard to figure out which edge a variable holds
There was a problem hiding this comment.
| final TextPosition? possibleExistingEdge = existingSelectionStart ?? existingSelectionEnd; | |
| if (possibleExistingEdge != null && (possibleExistingEdge == existingSelectionStart && isEnd || possibleExistingEdge == existingSelectionEnd && !isEnd)) { | |
| final TextPosition? possibleExistingEdge = isEnd ? existingSelectionStart : existingSelectionEnd; | |
| if (possibleExistingEdge != null) { |
There was a problem hiding this comment.
As long as the non-moving edge is null you have to go to the else case no?
There was a problem hiding this comment.
also this may be easier if we separate out the isEnd = true/false into two separate functions
Adds double click to select a word.
Adds double click + drag to select word by word.
Screen.Recording.2023-04-25.at.11.03.35.AM.mov
Partially fixes #104552
Pre-launch Checklist
///).