Repository navigation
Conversation
7f7b3ef to
9d16613
Compare
| } | ||
| } | ||
|
|
||
| class _SelectLastTapDownPositionOrEdgeTextAction extends CallbackAction<SingleTapUpTextIntent> { |
There was a problem hiding this comment.
These are NOT TextEditingActions like the other Actions in this file. The reason is that they don't need the machinery in TextEditingAction that checks for a focused TextEditingActionTarget (in fact, they wouldn't work with that, because they need to receive taps on unfocused fields too). They receive the relevant EditableTextState via the Intent.
| ); | ||
|
|
||
| static final Map<Type, Action<Intent>> _androidActions = <Type, Action<Intent>>{ | ||
| SingleTapUpTextIntent: _SelectLastTapDownPositionTextAction(), |
There was a problem hiding this comment.
I know you mentioned it in a previous message, but is there further context on why we want to intents to be the gestures and no named after the actual thing that needs to be done? Why is it treated one way with Shortcuts and another way with Gestures?
What makes sense in my mental model of Actions/Intents is:
- Gesture is triggered in the text editing gesture detector
- A parent widget translates those gestures to intents (can maintain a default mapping for all platforms)
- The action executes the command on the renderEditable
What are the disadvantages to a mapping of this kind?
I may confusing what the difference between an intent and action is though.
There was a problem hiding this comment.
When you support triple click select all text by Desktop ?
There was a problem hiding this comment.
@letungcntt There are plans for @venkatd to take a look at triple click in the future. This PR will not do that yet.
There was a problem hiding this comment.
@venkatd Here's some context from when the naming decision was made: #75032 (comment)
Basically my reasoning is separation of concerns. I don't want visual text editing widgets like TextField to be concerned with text editing business logic. I want to define text editing business logic exclusively in DefaultTextEditingActions and have it apply everywhere. Then, widgets like TextField are only concerned with visuals and detecting gestures.
There was a problem hiding this comment.
@justinmc thanks for sharing!
I understand the goal and read through the discussions in the google doc. I can't help but think that, if intents have this dual nature, then there's something that needs to be resolved at the Shortcuts/Intents/Actions abstraction layer. Perhaps it's just a matter of how Intents/Actions are named.
Either way, this discussion is probably out of the scope of what you're working on. I'll stick with whatever structure you have in this PR.
There was a problem hiding this comment.
@gspencergoog I want to resurrect the discussion about Shortcut/Action/Intent naming to talk about an idea that @venkatd posted in the design doc. The idea is to create a superclass of Shortcuts (we'll call it Events) which also trigger Intents, but which can be invoked directly instead of being tied to keyboard shortcuts.
Background
Currently, this PR does things according to our original decision, where we decided to stick with the standard naming for keyboard stuff:
- Shortcut: What keys were pressed.
LogicalKeySet(LogicalKeyboardKey.arrowLeft) - Intent: What we intend to do.
MoveSelectionLeftTextIntent - Action: What it does.
_MoveSelectionLeftTextAction
But for things invoked directly with Actions.invoke, like taps, we would name the Intent after what happened:
- Shortcut: Not used.
- Intent: What happened.
SingleTapUpTextIntent - Action: What it does:
_SelectLastTapDownPositionTextAction
The reason that I advocated for names like SingleTapUpTextIntent is so that classes like TextSelectionGestureDetectorBuilder can be ignorant of what the gestures it detects actually do, but you can see that Intents are used in two different ways, which is still a bit confusing.
New idea
The new idea would keep keyboard shortcuts the same, but change taps to work like this:
- Event: What happened.
SingleTapUpTextEvent - Intent: What we intend to do.
SelectLastTapDownPositionTextIntent - Action: What it does:
_SelectLastTapDownPositionTextAction
With this way, the whole system is used in the same way for both keyboard and taps. Rather than separating shortcutActions and platformActions in DefaultTextEditingActions, the platform mapping for taps would happen at the DefaultTextEditingEvents level in the same way that it happens for keyboard shortcuts in DefaultTextEditingShortcuts.
Do you think this is a viable solution that is in line with how Shortcuts/Intents/Actions are intended to be used?
There was a problem hiding this comment.
Yes, the distinction seems useful. Do you actually mean a superclass of Shortcuts like you said, or do you meanIntent, or maybe ShortcutManager? Shortcuts is a widget, and I'm not sure it's useful to make a superclass of that for this distinction.
Maybe you just create a separate class altogether that can be used to map to an Intent in a ShortcutManager (which is owned by a Shortcuts widget). [I realize that it's a little odd to continue to use the name "shortcut" in this context, but you could make a subclass of the Shortcuts widget that was basically just a wrapper with a different name to give it better readability.]
I also definitely wouldn't call them events, since that term is very overloaded. I don't have a great alternative name, but if we can qualify the name a bit I think it could work. ShortcutEvent or ActionEvent, or IntentEvent all seem wrong and confusing. Maybe Trigger?
There was a problem hiding this comment.
I did intend that it would be a widget, because I need a place to map the Triggers (let's call them) to Intents, and it needs to be overridable by users and all of that.
So in this case:
- TextSelectionGestureDetectorBuilder calls
Triggers.invoke<SingleTapUpTextTrigger>(... - DefaultTextEditingTriggers has mapped SingleTapUpTextTrigger to SelectLastTapDownPositionTextIntent and SelectLastTapDownPositionOrEdgeTextIntent based on the platform, and it invokes the right one.
- DefaultTextEditingActions has mapped SelectLastTapDownPositionTextIntent to _SelectLastTapDownPositionTextAction (and similar for the other Intent).
Is that adding too much complexity since we already have Shortcuts?
There was a problem hiding this comment.
You could just add a triggers map to the Shortcuts widget, and have it do double duty, but I guess that's pretty confusing naming-wise.
Should it really be a base class to Shortcuts, or should they just share a common base? For instance, I'm wondering how Shortcuts sets/uses the triggers map on the Triggers class if it is the base for Shortcuts. They seem more like siblings with common functionality.
In any case, those are all implementation details. I think it seems like a reasonable idea.
There was a problem hiding this comment.
Makes sense that the word Event is too overloaded to have a clear meaning.
Perhaps for now there could be a similar Triggers abstraction that can be treated as an implementation detail? That could allow us to compare differences with Shortcuts to decide if generalization makes sense, and later if a public API would be helpful.
|
Gold has detected about 2 untriaged digest(s) on patchset 6. |
|
@justinmc to confirm the flow:
Related to this I've got some questions:
|
|
Gold has detected about 2 untriaged digest(s) on patchset 7. |
|
@venkatd That looks right to me, except that the double clicking action would have a different name like _SelectLastTapDownWordTextAction or something like that, assuming that's what it does. My vision for RenderEditable is that has a low-level text editing API that is concerned only about text editing itself, and explicitly not concerned with:
Not all of that is true yet, but I'm trying to move in that direction. So in the original PR #75032, I chose methods like |
|
Hi @justinmc thanks for the explanation. I'll have time to dive into all this code starting next week. I might try to refactor the double-click to use actions/intents so I get a feel for how triple click might work. |
|
@justinmc I tried out this idea (for #79796). So far I'm having 2 problems creating an overridable and "global"-ish gesture handler (in the form of an
Do you have any suggestions? I'm ok with "canonicalize" the |
|
Examples for more context:
|
|
In response to #79414 (comment): I think we need an inherited widget like ShortcutManager that records the mapping from Intents to Actions for mouse and touch input gestures. I suggest Recognizing mouse/touch input gestures is complex so I'd suggest leaving that to widgets (and their renderers). So GestureShortcutManager would not have a method that's comparable to handleKeyPress. Here's the data flow. It's similar to what's been proposed before.
One notable difference is the meaning and naming of intents and actions. The intent identifies the input gesture and its source: it's a right button down event that occurred in a TextField. The action identifies the type of the widget it's to be applied to and what it actually does. The action's name does not include the input gesture. I would hope that we would try and create actions that can be reused in other contexts, or customized by subclassing. An app could replace or defeat this binding by creating a shadow GestureShortcutsManager that mapped TextfieldRightButtonDown to a custom action or a DoNothingAction. Using null for "do nothing" would be convenient, since it obviates creating a DoNothingAction of the right type. Some applications might want to change the mapping from input gestures to intents, for example to pass along additional information to the action. It doesn't seem important to deal with this case immediately however we could make it possible to specify a delegate class that did all of the mappings: TextFieldIntentDelegate {
TextFieldRightButtonDown? forRightButtonDown(...)
TextFieldTap? forTap(...)
TextFieldDoubleTap? forDoubleTap(...)
// ...
}To handle gestures that correspond to a series of events, notably press-drag-release gestures, the widget could invoke the same Action repeatedly, with differently configured intents. For example, to handle a press-drag-release gesture that changes the selection:
I realize this is all pretty sketchy and I may not have addressed all of the issues raised in #79414 (comment). Hopefully I've propelled the discussion forward. |
|
Gold has detected about 2 untriaged digest(s) on patchset 8. |
|
I'm currently trying to put a class TextEditingGestures extends InheritedWidget {
final Map<Type, GestureRecognizerFactoryWithBuildContext> gestures;
// If true `maybeOf` merges `gestures` with the gestures from its ancestor `TextEditingGestures`
final bool inherit;
static Map<Type, GestureRecognizerFactory>? maybeOf(BuildContext context);
}The gesture recognizers in the default map are set up to fire intents to invoke pre-defined actions (retrieved from the But I'm not exactly sure how best to implement an Action or an Intent that has secret/private states:
|
|
@LongCatIsLooong would this design help you? It would allow definition of actions that could be invoked be something that didn't have focus, just using the intent. I've also been musing with the idea of a similar registry for shortcuts, which should allow definition of the shortcut binding to an intent at a lower level where state is available instead of requiring it to be hoisted high enough for the definition. Then both actions and shortcuts could be defined privately, and be registered and effective at the level of the widget containing the registry and below. |
|
@gspencergoog for now I don't think I'm relying on the focus system for implementing gestures (just using |
|
Yes, that's right. If you aren't using the focus system, then the new design probably won't matter to you. |
|
With a
As I understand it Pros:
Cons:
An example, to add force press support:
** Update **: static final ContextGestureRecognizerFactory<TapGestureRecognizer> secondaryTapRecognizer = ContextGestureRecognizerFactory<TapGestureRecognizer>.withFunctions(
constructor: (BuildContext context) => TapGestureRecognizer(debugOwner: context),
initializer: (TapGestureRecognizer recognizer, BuildContext context) {
recognizer.onSecondaryTapUp = (TapUpDetails details) {
Actions.invoke(
context,
SelectTextAtPositionIntent(
fromPosition: details.globalPosition,
textBoundaryType: TextBoundary.word,
cause: SelectionChangedCause.tap,
),
);
Actions.maybeInvoke(context, SelectionToolbarControlIntent.show);
};
}
);and add a The current behavior was relatively easy to re-implement. 2 things I found may not be as easy to implement:
Current progress: https://github.com/flutter/flutter/pull/84232/files |
|
Closing this PR as most of its functionality should be superseded by #84232 and related work. |
This PR refactors TextSelectionGestureDetector to handle SingleTapUp using Actions in a way that is overrideable by the user. It is the first example of doing something related to text editing via Actions other than a keyboard event.
The reasons for doing this are:
Continues the work of #75032.
Partial progress for #75004.