Unverified Commit 26021191 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Cupertino text edit tooltip rework (#34095)

parent 7472fad1
...@@ -141,6 +141,7 @@ class _MaterialTextSelectionControls extends TextSelectionControls { ...@@ -141,6 +141,7 @@ class _MaterialTextSelectionControls extends TextSelectionControls {
Widget buildToolbar( Widget buildToolbar(
BuildContext context, BuildContext context,
Rect globalEditableRegion, Rect globalEditableRegion,
double textLineHeight,
Offset position, Offset position,
List<TextSelectionPoint> endpoints, List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate, TextSelectionDelegate delegate,
......
...@@ -108,12 +108,16 @@ abstract class TextSelectionControls { ...@@ -108,12 +108,16 @@ abstract class TextSelectionControls {
/// [globalEditableRegion] is the TextField size of the global coordinate system /// [globalEditableRegion] is the TextField size of the global coordinate system
/// in logical pixels. /// in logical pixels.
/// ///
/// [textLineHeight] is the `preferredLineHeight` of the [RenderEditable] we
/// are building a toolbar for.
///
/// The [position] is a general calculation midpoint parameter of the toolbar. /// The [position] is a general calculation midpoint parameter of the toolbar.
/// If you want more detailed position information, can use [endpoints] /// If you want more detailed position information, can use [endpoints]
/// to calculate it. /// to calculate it.
Widget buildToolbar( Widget buildToolbar(
BuildContext context, BuildContext context,
Rect globalEditableRegion, Rect globalEditableRegion,
double textLineHeight,
Offset position, Offset position,
List<TextSelectionPoint> endpoints, List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate, TextSelectionDelegate delegate,
...@@ -509,19 +513,29 @@ class TextSelectionOverlay { ...@@ -509,19 +513,29 @@ class TextSelectionOverlay {
return Container(); return Container();
// Find the horizontal midpoint, just above the selected text. // Find the horizontal midpoint, just above the selected text.
final List<TextSelectionPoint> endpoints = renderObject.getEndpointsForSelection(_selection); final List<TextSelectionPoint> endpoints =
final Offset midpoint = Offset( renderObject.getEndpointsForSelection(_selection);
(endpoints.length == 1) ?
endpoints[0].point.dx :
(endpoints[0].point.dx + endpoints[1].point.dx) / 2.0,
endpoints[0].point.dy - renderObject.preferredLineHeight,
);
final Rect editingRegion = Rect.fromPoints( final Rect editingRegion = Rect.fromPoints(
renderObject.localToGlobal(Offset.zero), renderObject.localToGlobal(Offset.zero),
renderObject.localToGlobal(renderObject.size.bottomRight(Offset.zero)), renderObject.localToGlobal(renderObject.size.bottomRight(Offset.zero)),
); );
final bool isMultiline = endpoints.last.point.dy - endpoints.first.point.dy >
renderObject.preferredLineHeight / 2;
// If the selected text spans more than 1 line, horizontally center the toolbar.
// Derived from both iOS and Android.
final double midX = isMultiline
? editingRegion.width / 2
: (endpoints.first.point.dx + endpoints.last.point.dx) / 2;
final Offset midpoint = Offset(
midX,
// The y-coordinate won't be made use of most likely.
endpoints[0].point.dy - renderObject.preferredLineHeight,
);
return FadeTransition( return FadeTransition(
opacity: _toolbarOpacity, opacity: _toolbarOpacity,
child: CompositedTransformFollower( child: CompositedTransformFollower(
...@@ -531,6 +545,7 @@ class TextSelectionOverlay { ...@@ -531,6 +545,7 @@ class TextSelectionOverlay {
child: selectionControls.buildToolbar( child: selectionControls.buildToolbar(
context, context,
editingRegion, editingRegion,
renderObject.preferredLineHeight,
midpoint, midpoint,
endpoints, endpoints,
selectionDelegate, selectionDelegate,
......
...@@ -15,6 +15,7 @@ import 'package:flutter/services.dart'; ...@@ -15,6 +15,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior, PointerDeviceKind; import 'package:flutter/gestures.dart' show DragStartBehavior, PointerDeviceKind;
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart'; import 'feedback_tester.dart';
...@@ -864,7 +865,7 @@ void main() { ...@@ -864,7 +865,7 @@ void main() {
expect(find.text('CUT'), findsNothing); expect(find.text('CUT'), findsNothing);
}); });
testWidgets('text field build empty tool bar when no options available ios', (WidgetTester tester) async { testWidgets('does not paint tool bar when no options available on ios', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS), theme: ThemeData(platform: TargetPlatform.iOS),
...@@ -882,11 +883,8 @@ void main() { ...@@ -882,11 +883,8 @@ void main() {
await tester.tap(find.byType(TextField)); await tester.tap(find.byType(TextField));
// Wait for context menu to be built. // Wait for context menu to be built.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final RenderBox container = tester.renderObject(find.descendant(
of: find.byType(FadeTransition), expect(find.byType(CupertinoTextSelectionToolbar), paintsNothing);
matching: find.byType(Container),
));
expect(container.size, Size.zero);
}); });
testWidgets('text field build empty tool bar when no options available android', (WidgetTester tester) async { testWidgets('text field build empty tool bar when no options available android', (WidgetTester tester) async {
......
...@@ -1546,7 +1546,7 @@ void main() { ...@@ -1546,7 +1546,7 @@ void main() {
controls = MockTextSelectionControls(); controls = MockTextSelectionControls();
when(controls.buildHandle(any, any, any)).thenReturn(Container()); when(controls.buildHandle(any, any, any)).thenReturn(Container());
when(controls.buildToolbar(any, any, any, any, any)) when(controls.buildToolbar(any, any, any, any, any, any))
.thenReturn(Container()); .thenReturn(Container());
}); });
......
...@@ -1108,7 +1108,8 @@ class _IsWithinDistance<T> extends Matcher { ...@@ -1108,7 +1108,8 @@ class _IsWithinDistance<T> extends Matcher {
} }
class _MoreOrLessEquals extends Matcher { class _MoreOrLessEquals extends Matcher {
const _MoreOrLessEquals(this.value, this.epsilon); const _MoreOrLessEquals(this.value, this.epsilon)
: assert(epsilon >= 0);
final double value; final double value;
final double epsilon; final double epsilon;
...@@ -1125,6 +1126,12 @@ class _MoreOrLessEquals extends Matcher { ...@@ -1125,6 +1126,12 @@ class _MoreOrLessEquals extends Matcher {
@override @override
Description describe(Description description) => description.add('$value$epsilon)'); Description describe(Description description) => description.add('$value$epsilon)');
@override
Description describeMismatch(Object item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
return super.describeMismatch(item, mismatchDescription, matchState, verbose)
..add('$item is not in the range of $value$epsilon).');
}
} }
class _IsMethodCall extends Matcher { class _IsMethodCall extends Matcher {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment