Cherry pick: iOS context menu positioning (#122907)
* Cherry pick: Fix iOS context menu position when flipped below (#119565)
* Fix anchorBelow calculation, and share toolbar padding constant
* Fix constant references in test
* Test below position when padding is not offset by content distance
* Remove docs references to classes that don't exist yet
diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart
index a630b3d..481413f 100644
--- a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart
+++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart
@@ -17,9 +17,6 @@
// Vertical distance between the tip of the arrow and the line of text the arrow
// is pointing to. The value used here is eyeballed.
const double _kToolbarContentDistance = 8.0;
-// Minimal padding from all edges of the selection toolbar to all edges of the
-// screen.
-const double _kToolbarScreenPadding = 8.0;
const Size _kToolbarArrowSize = Size(14.0, 7.0);
// Minimal padding from tip of the selection toolbar arrow to horizontal edges of the
@@ -103,7 +100,15 @@
/// default Cupertino toolbar.
final CupertinoToolbarBuilder toolbarBuilder;
- // Add the visial vertical line spacer between children buttons.
+ /// Minimal padding from all edges of the selection toolbar to all edges of the
+ /// viewport.
+ ///
+ /// See also:
+ ///
+ /// * [TextSelectionToolbar], which uses this same value as well.
+ static const double kToolbarScreenPadding = 8.0;
+
+ // Add the visual vertical line spacer between children buttons.
static List<Widget> _addChildrenSpacers(List<Widget> children) {
final List<Widget> nextChildren = <Widget>[];
for (int i = 0; i < children.length; i++) {
@@ -134,7 +139,7 @@
assert(debugCheckHasMediaQuery(context));
final MediaQueryData mediaQuery = MediaQuery.of(context);
- final double paddingAbove = mediaQuery.padding.top + _kToolbarScreenPadding;
+ final double paddingAbove = mediaQuery.padding.top + kToolbarScreenPadding;
final double toolbarHeightNeeded = paddingAbove
+ _kToolbarContentDistance
+ _kToolbarHeight;
@@ -151,15 +156,15 @@
);
final Offset anchorBelowAdjusted = Offset(
clampDouble(anchorBelow.dx, leftMargin, rightMargin),
- anchorBelow.dy - _kToolbarContentDistance + paddingAbove,
+ anchorBelow.dy + _kToolbarContentDistance - paddingAbove,
);
return Padding(
padding: EdgeInsets.fromLTRB(
- _kToolbarScreenPadding,
+ kToolbarScreenPadding,
paddingAbove,
- _kToolbarScreenPadding,
- _kToolbarScreenPadding,
+ kToolbarScreenPadding,
+ kToolbarScreenPadding,
),
child: CustomSingleChildLayout(
delegate: TextSelectionToolbarLayoutDelegate(
diff --git a/packages/flutter/lib/src/material/text_selection_toolbar.dart b/packages/flutter/lib/src/material/text_selection_toolbar.dart
index a024968..fbd3614 100644
--- a/packages/flutter/lib/src/material/text_selection_toolbar.dart
+++ b/packages/flutter/lib/src/material/text_selection_toolbar.dart
@@ -4,9 +4,9 @@
import 'dart:math' as math;
+import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show listEquals;
import 'package:flutter/rendering.dart';
-import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'icon_button.dart';
@@ -14,15 +14,9 @@
import 'material.dart';
import 'material_localizations.dart';
-// Minimal padding from all edges of the selection toolbar to all edges of the
-// viewport.
-const double _kToolbarScreenPadding = 8.0;
const double _kToolbarHeight = 44.0;
-const double _kHandleSize = 22.0;
-
// Padding between the toolbar and the anchor.
-const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0;
const double _kToolbarContentDistance = 8.0;
/// A fully-functional Material-style text selection toolbar.
@@ -84,6 +78,12 @@
/// {@endtemplate}
final ToolbarBuilder toolbarBuilder;
+ /// The size of the text selection handles.
+ static const double kHandleSize = 22.0;
+
+ /// Padding between the toolbar and the anchor.
+ static const double kToolbarContentDistanceBelow = kHandleSize - 2.0;
+
// Build the default Android Material text selection menu toolbar.
static Widget _defaultToolbarBuilder(BuildContext context, Widget child) {
return _TextSelectionToolbarContainer(
@@ -97,21 +97,22 @@
final Offset anchorAbovePadded =
anchorAbove - const Offset(0.0, _kToolbarContentDistance);
final Offset anchorBelowPadded =
- anchorBelow + const Offset(0.0, _kToolbarContentDistanceBelow);
+ anchorBelow + const Offset(0.0, kToolbarContentDistanceBelow);
+ const double screenPadding = CupertinoTextSelectionToolbar.kToolbarScreenPadding;
final double paddingAbove = MediaQuery.of(context).padding.top
- + _kToolbarScreenPadding;
+ + screenPadding;
final double availableHeight = anchorAbovePadded.dy - _kToolbarContentDistance - paddingAbove;
final bool fitsAbove = _kToolbarHeight <= availableHeight;
// Makes up for the Padding above the Stack.
- final Offset localAdjustment = Offset(_kToolbarScreenPadding, paddingAbove);
+ final Offset localAdjustment = Offset(screenPadding, paddingAbove);
return Padding(
padding: EdgeInsets.fromLTRB(
- _kToolbarScreenPadding,
+ screenPadding,
paddingAbove,
- _kToolbarScreenPadding,
- _kToolbarScreenPadding,
+ screenPadding,
+ screenPadding,
),
child: CustomSingleChildLayout(
delegate: TextSelectionToolbarLayoutDelegate(
diff --git a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart
index 5052d1e..32be735 100644
--- a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart
+++ b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart
@@ -186,6 +186,7 @@
const double height = _kToolbarHeight;
const double anchorBelowY = 500.0;
double anchorAboveY = 0.0;
+ const double paddingAbove = 12.0;
await tester.pumpWidget(
CupertinoApp(
@@ -193,14 +194,26 @@
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
- return CupertinoTextSelectionToolbar(
- anchorAbove: Offset(50.0, anchorAboveY),
- anchorBelow: const Offset(50.0, anchorBelowY),
- children: <Widget>[
- Container(color: const Color(0xffff0000), width: 50.0, height: height),
- Container(color: const Color(0xff00ff00), width: 50.0, height: height),
- Container(color: const Color(0xff0000ff), width: 50.0, height: height),
- ],
+ final MediaQueryData data = MediaQuery.of(context);
+ // Add some custom vertical padding to make this test more strict.
+ // By default in the testing environment, _kToolbarContentDistance
+ // and the built-in padding from CupertinoApp can end up canceling
+ // each other out.
+ return MediaQuery(
+ data: data.copyWith(
+ padding: data.viewPadding.copyWith(
+ top: paddingAbove,
+ ),
+ ),
+ child: CupertinoTextSelectionToolbar(
+ anchorAbove: Offset(50.0, anchorAboveY),
+ anchorBelow: const Offset(50.0, anchorBelowY),
+ children: <Widget>[
+ Container(color: const Color(0xffff0000), width: 50.0, height: height),
+ Container(color: const Color(0xff00ff00), width: 50.0, height: height),
+ Container(color: const Color(0xff0000ff), width: 50.0, height: height),
+ ],
+ ),
);
},
),
@@ -212,10 +225,14 @@
// belowAnchor.
double toolbarY = tester.getTopLeft(findToolbar()).dy;
expect(toolbarY, equals(anchorBelowY + _kToolbarContentDistance));
+ expect(find.byType(CustomSingleChildLayout), findsOneWidget);
+ final CustomSingleChildLayout layout = tester.widget(find.byType(CustomSingleChildLayout));
+ final TextSelectionToolbarLayoutDelegate delegate = layout.delegate as TextSelectionToolbarLayoutDelegate;
+ expect(delegate.anchorBelow.dy, anchorBelowY - paddingAbove);
// Even when it barely doesn't fit.
setState(() {
- anchorAboveY = 50.0;
+ anchorAboveY = 70.0;
});
await tester.pump();
toolbarY = tester.getTopLeft(findToolbar()).dy;
@@ -223,7 +240,7 @@
// When it does fit above aboveAnchor, it positions itself there.
setState(() {
- anchorAboveY = 60.0;
+ anchorAboveY = 80.0;
});
await tester.pump();
toolbarY = tester.getTopLeft(findToolbar()).dy;