Skip to content

Conversation

@justinmc
Copy link
Contributor

@justinmc justinmc commented Jan 29, 2021

This is the first PR to start migrating text editing to use Actions. See the design doc for the entire plan.

This PR focuses only on the arrow keys, including all modifier keys, but it also establishes the pattern for handling all keyboard, mouse, and touch events for text editing.

Example

This example shows how to use an additional Shortcuts widget to override some default text editing keyboard shortcuts to have new behavior. Instead of moving the cursor, alt + up/down will change the focused widget. See the docs for more examples.

@override
Widget build(BuildContext context) {
  // If using WidgetsApp or its descendents MaterialApp or CupertinoApp,
  // then DefaultTextEditingShortcuts is already being inserted into the
  // widget tree.
  return DefaultTextEditingShortcuts(
    child: Center(
      child: Shortcuts(
        shortcuts: const <ShortcutActivator, Intent>{
          SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): NextFocusIntent(),
          SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): PreviousFocusIntent(),
        },
        child: Column(
          children: const <Widget>[
            TextField(
              decoration: InputDecoration(
                hintText: 'alt + down moves to the next field.',
              ),
            ),
            TextField(
              decoration: InputDecoration(
                hintText: 'And alt + up moves to the previous.',
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

Issues

Partial fix for #75004.
Closes #76121

Tests

  • Test that it's possible to remap a shortcut to a different shortcut's Intent.
  • Test that's possible to write a custom Action to completely change a text editing Intent's behavior.
  • Update RenderEditable tests that relied on the keyboard and have them call new methods instead.

Breaking changes

None.

@justinmc justinmc force-pushed the text-editing-refactor branch from 10ea1ba to f2f4135 Compare March 10, 2021 21:34
@justinmc
Copy link
Contributor Author

justinmc commented Mar 10, 2021

@LongCatIsLooong @goderbauer Updated with your suggestions and ready for re-review, thanks!

One thing I didn't implement to look at: #75032 (comment)

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

/// * [DefaultTextEditingShortcuts], which maps keyboard keys to many of the
/// [Intent]s that are handled here.
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
class DefaultTextEditingActions extends StatelessWidget {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is it possible to just subclass Actions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and it saves a good amount of lines of code. Thanks for the suggestion. Same for Shortcuts as you mentioned below.

@override
Widget build(BuildContext context) {
return Actions(
actions: <Type, Action<Intent>>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: isn't this just:

Suggested change
actions: <Type, Action<Intent>>{
actions: _shortcutsActions,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I had a big setup for handling other types of actions that I deleted. I'll simplify to just _shortcutActions for now.

/// behavior.
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
class DefaultTextEditingShortcuts extends StatelessWidget {
/// Creates an instance of DefaultTextEditingShortcuts.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this seems to say very little about what this is by itself. maybe:

Creates a [Shortcuts] widget that provides the default text editing shortcuts on the current platform (basically copying the first summary line of the class).

/// respond to the [Intent]s in these shortcuts with the default text editing
/// behavior.
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
class DefaultTextEditingShortcuts extends StatelessWidget {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is basically a specialized Shortcuts widget. Maybe subclass Shortcuts too, so we get a shallower tree?

}

@override
bool isEnabled(Intent intent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit:

Suggested change
bool isEnabled(Intent intent) {
bool isEnabled(T intent) {

@TimWhiting
Copy link

TimWhiting commented Mar 11, 2021

Would #77512 be in scope for this? It involves Shortcuts and Text Editing and is a simple but important missing feature for desktop. I don't necessarily want to create a PR right now if text editing is already being refactored.

@justinmc
Copy link
Contributor Author

@TimWhiting I think that's not in scope for this PR, but thanks for bringing it to my attention. The Shortcuts approach for that might be viable, but we should figure out exactly how the behavior should work. Let's continue the discussion on #77512.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@justinmc justinmc merged commit 541bff4 into flutter:master Mar 11, 2021
@justinmc justinmc deleted the text-editing-refactor branch March 11, 2021 23:52
@venkatd
Copy link

venkatd commented Mar 12, 2021

@justinmc thanks for all the work on this PR! Love where things are headed with customizability of text editing.

Inability to override shortcuts in a TextField has been a blocker for releasing a Flutter web app to production. Does this PR apply to Flutter web? If not, maybe I can create a separate issue to discuss the use case?

I noticed the comment:

// The Action is disabled if there is no focused TextEditingActionTarget, or
// if the platform is web, because web lets the browser handle text editing.

I wonder if there's a way to allow the browser to handle text editing but still allow overriding the default browser behavior by creating Shortcuts.

@justinmc
Copy link
Contributor Author

@venkatd No problem, thank you for the reviews on this!

I think everything should work as expected with web. See this thread above: #75032 (comment)

So using a Shortcuts widget will allow you to intercept key events like up-arrow before they go to the web engine, just like they will also be intercepted on other platforms before being handled by the framework. This is important for us too, like for @shihaohong who is working on keyboard controls for Autocomplete now.

If something doesn't work as expected let me know, though. I think Autocomplete will be the first use of this in the framework itself and it's not finished yet.

@venkatd
Copy link

venkatd commented Mar 18, 2021

@justinmc great to hear! Will give the master branch a try this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In a TextField HOME and END keys do not work on platform Windows