Repository navigation
[flutter] hacked together global selection example - #81849
jonahwilliams wants to merge 16 commits into
Conversation
chunhtai
left a comment
There was a problem hiding this comment.
I think this is going in a good direction, although we may need to figure out how this can integrate with our current textediting logic. We are facing an issue that we can't separate out text selection from text editing in the textfield because they are tightly bound together, and here is an on going refactoring to make that easier cc @LongCatIsLooong .
| locale: appLocale, | ||
| delegates: _localizationsDelegates.toList(), | ||
| child: title, | ||
| child: SelectionArea(child: title), |
There was a problem hiding this comment.
I wonder whether we should make this to be per modalRoute thing
There was a problem hiding this comment.
Ahh, that would probably solve the problem of being able to select text on different routes...
There was a problem hiding this comment.
Where would you put this in the router/routing/navigator to essentially wrap each route in its own?
There was a problem hiding this comment.
I was imagine we can probably put this in _ModalScope, but I am also worry about whether there is a use case when user want to select across different routes?
There was a problem hiding this comment.
To construct one: One bigger desktop screens you may have a two column layout where the left and right side show different Navigator stacks. Do we need to support selecting across both?
There was a problem hiding this comment.
Ahh, this works great actually, solves the problem of highlighting old routes. I'm not sure what the use case would be for straddling different modal routes TBH
|
|
||
| @override | ||
| void update(RenderParagraph renderBox, Rect rect) { | ||
| var tl = renderBox.localToGlobal(rect.topLeft); |
There was a problem hiding this comment.
Maybe there can be an API to let SelectionRegistrant to decide selection rect given the pointer start and end position?
| @override | ||
| void paint(PaintingContext context, Offset offset) { | ||
| super.paint(context, offset); | ||
| for (var entry in _state._selectionStates.entries) { |
There was a problem hiding this comment.
I wonder whether we need to worry about something been draw on top of the selection, we can also make this an overlay entry that either been part of the modalRouter overlayEntries or a CompositedTransformFollower.
There was a problem hiding this comment.
Yes, definitely took some short cuts with this one 😅
Definitely - one way to think about unification would be for each editable text to introduce its own SelectableArea. That could allow us to bind the selection region to that specific field if you're editing. |
goderbauer
left a comment
There was a problem hiding this comment.
Wondering: Instead of drawing the selection in a global place high up the tree could we turn this upside down, i.e. interested Widgets can register their interest in being selectable as part of global select. When the SelectionArea then detects a global select gestures, it tells all interested widgets that fall in the user-selected area that they have been selected (giving them a rect to indicate the area inside their own bonds that's considered "selected"). The individual widgets can then decide how they want to render this selection state. This would help with drawing the selections in the right order and non-text widgets could also participate with custom "selected" states/
| _SelectionAreaState createState() => _SelectionAreaState(); | ||
|
|
||
| static SelectionRegistrant? of(BuildContext context) { | ||
| return context.findAncestorStateOfType<_SelectionAreaState>(); |
There was a problem hiding this comment.
Could be an InheritedWidget for faster lookups?
| gestures: _gestureRecognizers, | ||
| behavior: HitTestBehavior.translucent, | ||
| excludeFromSemantics: true, | ||
| child: RenderHackyTextSelectionWidget(this, widget.child), |
There was a problem hiding this comment.
Could maybe be replaced by a CustomPaint?
| } | ||
| } | ||
|
|
||
| class _SelectionState { |
There was a problem hiding this comment.
nit: *State is a not an ideal name for this given that State objects usually belong to StatefulWidgets.
|
Presumably, we also need a way to opt out? For example, on the web it seems that buttons are not participating in global selection. In Flutter, they'd be using Text widgets. |
|
Yeah - for the opt out we could provide some sort of flag / setting that ends up with text not registering itself with the selection area. That would cause it to be otherwise ignored. We would need to update the framework defaults |
I can see that working - with the caveat that I don't think we necessarily want to expand It would also be odd if different text selection runs had, say, different colors for example |
|
Gold has detected about 1 untriaged digest(s) on patchset 3. |
I think that's allowed, though: https://www.w3schools.com/howto/howto_css_text_selection.asp (select across the table under "Select the following text:") |
|
(Just saw the video added to the PR description): The current implementation selects too much too eagerly right? "Animation" and "2D Transformation" shouldn't be selected until the mouse has moved below it, I think? |
Yes the logic on what to select is totally bonkers right now, but getting there ... |
|
I will update the example to push the selection painting down to the paragraph layer. |
|
Ahh, and of course drawing the selection overlays "on top" does not work when you scroll |
|
Moving selection box drawing into the paragraph objects themselves is much simpler. Still need to handle the correct order for assembling the text elements into a reasonable copy paste output. You could sort of concatenate it all together based on top to bottom, left to right? Need to think about it some more. |
| void _onCopy() { | ||
| if (_selectionStates.isEmpty) | ||
| return; | ||
| // The order in which these should be concatenated is ???. For now this |
There was a problem hiding this comment.
I actually came here to ask the same question. How do we determine the order the content of the "selected" widgets should appear in in the copy buffer?
There was a problem hiding this comment.
Yes, this one is a real quandary! Right now I am sorting by the topLeft offset of the render objects bounding rect. Something we can talk about today - but there is probably room for gradually adjusting this algorithm based on feedback
There was a problem hiding this comment.
I tried on html, it seems like it uses div order https://www.w3schools.com/code/tryit.asp?filename=GQ94272JBPA3
Maybe we can just use widget order and web developers may already be used to it?
There was a problem hiding this comment.
Definitely worth trying - though what is interesting in that example is how the selection boxes are calculated. Maybe this is actually more of a hit test special case
There was a problem hiding this comment.
Each widget (widget's renderer) could report their selection value and the rectangle within the overall selection rectangle that their selection value came from. The overall selection widget could assemble the contributions top to bottom and sorted horizontally according to the text direction.
| bool update(Offset start, Offset end) { | ||
| final Offset localStart = globalToLocal(start); | ||
| final Offset localEnd = globalToLocal(end); | ||
| final TextPosition startText = getPositionForOffset(localStart); |
There was a problem hiding this comment.
This doesn't quite do what I want if start/end don't really intersect with the text bounding box
|
Gold has detected about 5 untriaged digest(s) on patchset 6. |
|
Gold has detected about 3 untriaged digest(s) on patchset 7. |
| const ClipboardData({ this.text }) : dataType = ClipboardDataType.text, image = null; | ||
|
|
||
| /// Creates image data for the system clipboard. | ||
| const ClipboardData.image(this.image) : dataType = ClipboardDataType.image, text = null; |
There was a problem hiding this comment.
I don't plan to fully implement this right now, adding to help design selection for multiple data types
There was a problem hiding this comment.
The support for this will be platform dependent, android doesn't support multimedia clipboard while windows/macOS/ios supports some multimedia I believe.
|
Gold has detected about 1 untriaged digest(s) on patchset 9. |
|
|
||
| @override | ||
| bool update(Offset start, Offset end) { | ||
| final Rect localRect = Rect.fromPoints(globalToLocal(start), globalToLocal(end)); |
There was a problem hiding this comment.
I think in theory we need to compute the intersection of the visible (interactable rather) part of the render object of interest, and the select rectangle right? So a full traversal from the root is needed to figure out whether/which part of the render object was painted and visible in case:
- the render object isn't visible at all
- the render object is being clipped and only part of the copyable content is visible (in which case I think we should only highlight and extract content from the visible part that's selected right?). For
RenderEditableit knows to some degree how it was clipped but might not be the case for other render objects that can contribute content.
I think extending the RenderObject protocol to add the capability of extracting content from within a given rect (and maybe order its children's contents in their logical order instead of visual order so we can assemble the contents) will help us solve similar problems (e.g., #75472). A full traversal may not be that bad performance-wise:
- we only care about contents on screen so we can short-circuit early during the traversal for many render objects,
- for gesture-based selection, the pointer event for the current drag gesture can update at most once per frame I think (unless we want to implement the multi-selection/caret in google docs)?
There was a problem hiding this comment.
This would definitely be useful, but i would be concerned about being too top down? I'm not quite sure. if we could basically hit test in a rect area that would solve this problem exactly
| } | ||
| _textSelection = TextSelection(baseOffset: startText.offset, extentOffset: endText.offset); | ||
| markNeedsPaint(); | ||
| return true; |
There was a problem hiding this comment.
nit: I tried selecting something close to the edge of the bottom of the screen on a web page and the page scrolled to reveal the content below. So maybe call RenderObject.showOnScreen.
|
Gold has detected about 5 untriaged digest(s) on patchset 10. |
|
I did walk things back to the inherited widget (disclaimer, I know I just used the state lookup method) style. Currently have some selection bugs with it but I'll see how far I can take it. |
|
Is there a good way to observe navigation changes without connecting to the app level navigator? I'd like to make sure selection is disabled for non-visible routes (I guess that problem is also solved with the hit test method) |
|
Gold has detected about 9 untriaged digest(s) on patchset 12. |
|
Gold has detected about 2 untriaged digest(s) on patchset 13. |
|
So I managed to get @HansMuller 's original suggestion of delegating entirely to the render objects for selection to work. I'm currently looking at how I could expand this support to work well with render editable and possibly share some of the existing logic. I looked more into the hit testing change and I think it would be too drastic at least for now. |
|
Gold has detected about 5 untriaged digest(s) on patchset 14. |
|
The status of this is that it works up until you hit a page with a text field, and then you hit #32433 which makes Ctrl+C and Ctrl+A no longer work. I wonder if shortcuts is the wrong approach? |
|
Gold has detected about 11 untriaged digest(s) on patchset 16. |
|
Blocked on #82575 |
|
(triage): What is the status of this PR? The blocking bug appears to be closed? |
|
@goderbauer I would appreciate if your triage meeting could skip drafts. Opening one was important to gather initial feedback and I'd rather not close it again and lose the context while I keep working on that |
This is a hacked together implementation of #81839 for the purposes of exploring the problem space.
Updated: Updated:
gallery.2021-05-12.10-22-28.mp4