Skip to content

Conversation

justinmc
Copy link
Contributor

@justinmc justinmc commented Jan 8, 2021

It's now possible to customize the buttons and the appearance of our iOS text selection menu.

Screen Shot 2021-01-08 at 9 52 22 AM

This follows my previous work to do the same thing for Material. The API is as identical as possible.

The API for creating custom menus is still not ideal, though, because I limited these two PRs to non-breaking changes for now. In the long term, I'd like to refactor TextSelectionControls (tracked in #73574).

I've created a separate example repo with full examples of how to create custom menus in the meantime.

New public classes

CupertinoTextSelectionToolbar

A fully-featured iOS-style toolbar, including overflow handling and positioning. Since you still must use TextSelectionControls to render this for text selection, there is a bit of extra work to do to get the numbers right (see #73574).

CupertinoTextSelectionToolbarButton

A simple wrapper around CupertinoButton to look exactly like the default iOS text selection toolbar button.

TextSelectionToolbarLayoutDelegate

Positions the toolbar at an anchor, while keeping the menu on the screen. This was abstracted from the Cupertino and Material versions of this and used by both.

Related issues

Closes #36985
Closes #31412
Closes #24407

Tests

  • Tested positioning with TextSelectionToolbarLayoutDelegate.
  • Tested positioning with CupertinoTextSelectionToolbar.
  • Tested overflow with CupertinoTextSelectionToolbar.
  • Tested creating a custom CupertinoTextSelectionToolbar.
  • Tested CupertinoTextSelectionToolbarButton opacity behavior and onPressed.

Breaking change

Not a breaking change, just exposes new API.

Screen.Recording.2021-01-07.at.1.52.01.PM.mov

@flutter-dashboard flutter-dashboard bot added f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. labels Jan 8, 2021
@google-cla google-cla bot added the cla: yes label Jan 8, 2021
@skia-gold
Copy link

Gold has detected about 3 untriaged digest(s) on patchset 19.
View them at https://flutter-gold.skia.org/cl/github/73578

@@ -0,0 +1,86 @@
import 'dart:math' as math;
Copy link
Contributor Author

@justinmc justinmc Jan 8, 2021

Choose a reason for hiding this comment

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

Is widgets the right place for this class? I could also package this as a widget or something.

@@ -117,66 +131,6 @@ class TextSelectionToolbar extends StatelessWidget {
}
}

// Positions the toolbar at the given anchor, ensuring that it remains on
// screen.
class _TextSelectionToolbarLayoutDelegate extends SingleChildLayoutDelegate {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been moved to its own file and made public to be shared here and with CupertinoTextSelectionToolbar. See packages/flutter/lib/src/widgets/text_selection_toolbar_layout_delegate.dart.

Comment on lines -84 to +105
final double paddingTop = MediaQuery.of(context).padding.top
final double paddingAbove = MediaQuery.of(context).padding.top
+ _kToolbarScreenPadding;
final double availableHeight = anchorAbove.dy - paddingTop;
final double availableHeight = anchorAbove.dy - paddingAbove;
final bool fitsAbove = _kToolbarHeight <= availableHeight;
final Offset anchor = fitsAbove ? anchorAbove : anchorBelow;
final Offset localAnchor = Offset(
anchor.dx - _kToolbarScreenPadding,
anchor.dy - paddingTop,
);
final Offset localAdjustment = Offset(_kToolbarScreenPadding, paddingAbove);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Trying to make things look the same as in the Cupertino version.

return _CupertinoTextSelectionToolbarWrapper(
arrowTipX: arrowTipX,
barTopY: localBarTopY + globalEditableRegion.top,
return _CupertinoTextSelectionControlsToolbar(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved all of the deleted math above into this class here to be consistent with Material. I'd like to refactor this in the long term (see #73574), but for now the idea is to encapsulate the complexity and keep the public APIs nice and clean.

@@ -570,694 +313,5 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
}
}

// Renders the content of the selection menu and maintains the page state.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All of the deleted code here and below was moved to packages/flutter/lib/src/cupertino/text_selection_toolbar.dart and sometimes refactored.

_kToolbarScreenPadding,
),
child: CustomSingleChildLayout(
delegate: TextSelectionToolbarLayoutDelegate(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This logic is now shared with Material via TextSelectionToolbarLayoutDelegate. I deleted the duplicate logic that did this for Cupertino.

@skia-gold
Copy link

Gold has detected about 3 untriaged digest(s) on patchset 21.
View them at https://flutter-gold.skia.org/cl/github/73578

// toolbar, with rounded corners and an arrow pointing at the anchor.
//
// The anchor should be in global coordinates.
class _CupertinoTextSelectionToolbarShape extends SingleChildRenderObjectWidget {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Most of the rest of the code in this file was copied over from cupertino/text_selection.dart with a little refactoring. This class in particular was separated out from the positioning logic.

@justinmc justinmc marked this pull request as ready for review January 8, 2021 19:01
@skia-gold
Copy link

Gold has detected about 3 untriaged digest(s) on patchset 23.
View them at https://flutter-gold.skia.org/cl/github/73578

@skia-gold
Copy link

Gold has detected about 6 untriaged digest(s) on patchset 24.
View them at https://flutter-gold.skia.org/cl/github/73578

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 but just to make sure I understand this correctly:

To add custom buttons one must create a new TextSelectionControls whose buiildToolbar returns a custom CupertinoTextSelectionToolbar with custom children? Is there an easy way to just add a new selection menu item, in addition to whatever control buttons (cut/copy/paste etc.) the system provides?

}
_anchor = value;
markNeedsLayout();
markNeedsSemanticsUpdate();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think we can get rid of both markNeedsSemanticsUpdates.

final Widget child;

/// Called when this button is pressed.
final VoidCallback? onPressed;
Copy link
Contributor

Choose a reason for hiding this comment

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

should we may this non-nullable? If a menu item is not available shouldn't we take it off from the menu?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used for the "next" button when on the final page of a menu that overflows. Here's a terrible screenshot of a custom menu that has it, but it also exists in the native-looking menu:

Screen Shot 2021-01-12 at 1 51 30 PM

///
/// Pass the resulting widget into the [child] parameter when using a
/// CupertinoTextSelectionToolbarButton in a CupertinoTextSelectionToolbar.
static Text getText(String string, [bool enabled = true]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: getText sounds like we're retrieving something that has already been created. Maybe turn this into a named constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, that was a great suggestion. The code is a lot simpler and users can still easily create text buttons or other things like icon buttons.

@justinmc
Copy link
Contributor Author

@LongCatIsLooong That's right, it's still overly complicated to make simple changes to the text selection toolbar even with this PR (and same for Material). Here I've attempted to hit the major wins without making any breaking changes. Long term I'd like to also refactor TextSelectionControls, maybe even get rid of it altogether. I'm tracking this in #73574.

@justinmc justinmc merged commit d9f3d2e into flutter:master Jan 13, 2021
@justinmc justinmc deleted the cupertino-text-selection-menu-customization branch January 13, 2021 19:14
@justinmc justinmc added the a: text input Entering text in a text field or keyboard related problems label Feb 23, 2021
@RAHEYO
Copy link

RAHEYO commented May 4, 2021

It shows:

Missing concrete implementations of 'TextSelectionControls.buildHandle', 'TextSelectionControls.getHandleAnchor', and 'TextSelectionControls.getHandleSize'.
Try implementing the missing methods, or make the class abstract.dart(non_abstract_class_inherits_abstract_member)

When using it...

@justinmc
Copy link
Contributor Author

@RAHEYO I just tried my example of customizing the Cupertino menu on Flutter's master branch and it works for me: https://github.com/justinmc/flutter-text-selection-menu-examples/blob/master/lib/custom_cupertino_menu_page.dart

Can you try that and let me know if it still doesn't work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: text input Entering text in a text field or keyboard related problems 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.

Allow custom TextSelectionControls inside SelectableText Add custom option to context menu. Allow more button options to the text selection toolbar
4 participants