mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
2132 lines
89 KiB
Dart
2132 lines
89 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'clipboard_utils.dart';
|
|
import 'keyboard_utils.dart';
|
|
import 'semantics_tester.dart';
|
|
|
|
Offset textOffsetToPosition(RenderParagraph paragraph, int offset) {
|
|
const Rect caret = Rect.fromLTWH(0.0, 0.0, 2.0, 20.0);
|
|
final Offset localOffset = paragraph.getOffsetForCaret(TextPosition(offset: offset), caret);
|
|
return paragraph.localToGlobal(localOffset);
|
|
}
|
|
|
|
Offset globalize(Offset point, RenderBox box) {
|
|
return box.localToGlobal(point);
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
final MockClipboard mockClipboard = MockClipboard();
|
|
|
|
setUp(() async {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall);
|
|
await Clipboard.setData(const ClipboardData(text: 'empty'));
|
|
});
|
|
|
|
tearDown(() {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null);
|
|
});
|
|
|
|
group('SelectableRegion', () {
|
|
testWidgets('mouse selection sends correct events', (WidgetTester tester) async {
|
|
final UniqueKey spy = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: SelectionSpy(key: spy),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
|
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
renderSelectionSpy.events.clear();
|
|
|
|
await gesture.moveTo(const Offset(200.0, 100.0));
|
|
expect(renderSelectionSpy.events.length, 2);
|
|
expect(renderSelectionSpy.events[0].type, SelectionEventType.startEdgeUpdate);
|
|
final SelectionEdgeUpdateEvent startEdge = renderSelectionSpy.events[0] as SelectionEdgeUpdateEvent;
|
|
expect(startEdge.globalPosition, const Offset(200.0, 200.0));
|
|
expect(renderSelectionSpy.events[1].type, SelectionEventType.endEdgeUpdate);
|
|
SelectionEdgeUpdateEvent endEdge = renderSelectionSpy.events[1] as SelectionEdgeUpdateEvent;
|
|
expect(endEdge.globalPosition, const Offset(200.0, 100.0));
|
|
renderSelectionSpy.events.clear();
|
|
|
|
await gesture.moveTo(const Offset(100.0, 100.0));
|
|
expect(renderSelectionSpy.events.length, 1);
|
|
expect(renderSelectionSpy.events[0].type, SelectionEventType.endEdgeUpdate);
|
|
endEdge = renderSelectionSpy.events[0] as SelectionEdgeUpdateEvent;
|
|
expect(endEdge.globalPosition, const Offset(100.0, 100.0));
|
|
|
|
await gesture.up();
|
|
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/102410.
|
|
|
|
testWidgets('Does not crash when using Navigator pages', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/119776
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Navigator(
|
|
pages: <Page<void>> [
|
|
MaterialPage<void>(
|
|
child: Column(
|
|
children: <Widget>[
|
|
const Text('How are you?'),
|
|
SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const SelectAllWidget(child: SizedBox(width: 100, height: 100)),
|
|
),
|
|
const Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
const MaterialPage<void>(
|
|
child: Scaffold(body: Text('Foreground Page')),
|
|
),
|
|
],
|
|
onPopPage: (_, __) => false,
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('can draw handles when they are at rect boundaries', (WidgetTester tester) async {
|
|
final UniqueKey spy = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Column(
|
|
children: <Widget>[
|
|
const Text('How are you?'),
|
|
SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: SelectAllWidget(key: spy, child: const SizedBox(width: 100, height: 100)),
|
|
),
|
|
const Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byKey(spy)));
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
final RenderSelectAll renderSpy = tester.renderObject<RenderSelectAll>(find.byKey(spy));
|
|
expect(renderSpy.startHandle, isNotNull);
|
|
expect(renderSpy.endHandle, isNotNull);
|
|
});
|
|
|
|
testWidgets('touch does not accept drag', (WidgetTester tester) async {
|
|
final UniqueKey spy = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: SelectionSpy(key: spy),
|
|
),
|
|
)
|
|
);
|
|
|
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
|
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0));
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(const Offset(200.0, 100.0));
|
|
await gesture.up();
|
|
expect(
|
|
renderSelectionSpy.events.every((SelectionEvent element) => element is ClearSelectionEvent),
|
|
isTrue
|
|
);
|
|
});
|
|
|
|
testWidgets('does not merge semantics node of the children', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: Scaffold(
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
const Text('Line one'),
|
|
const Text('Line two'),
|
|
ElevatedButton(
|
|
onPressed: () {},
|
|
child: const Text('Button'),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
semantics,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'Line one',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
TestSemantics(
|
|
label: 'Line two',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isFocusable
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
label: 'Button',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreRect: true,
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
),
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('mouse selection always cancels previous selection', (WidgetTester tester) async {
|
|
final UniqueKey spy = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: SelectionSpy(key: spy),
|
|
),
|
|
)
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
|
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
expect(renderSelectionSpy.events.length, 1);
|
|
expect(renderSelectionSpy.events[0], isA<ClearSelectionEvent>());
|
|
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/102410.
|
|
|
|
testWidgets('touch long press sends select-word event', (WidgetTester tester) async {
|
|
final UniqueKey spy = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: SelectionSpy(key: spy),
|
|
),
|
|
)
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
|
renderSelectionSpy.events.clear();
|
|
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0));
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
expect(renderSelectionSpy.events.length, 1);
|
|
expect(renderSelectionSpy.events[0], isA<SelectWordSelectionEvent>());
|
|
final SelectWordSelectionEvent selectionEvent = renderSelectionSpy.events[0] as SelectWordSelectionEvent;
|
|
expect(selectionEvent.globalPosition, const Offset(200.0, 200.0));
|
|
});
|
|
|
|
testWidgets('touch long press and drag sends correct events', (WidgetTester tester) async {
|
|
final UniqueKey spy = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: SelectionSpy(key: spy),
|
|
),
|
|
)
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
|
renderSelectionSpy.events.clear();
|
|
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0));
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(renderSelectionSpy.events.length, 1);
|
|
expect(renderSelectionSpy.events[0], isA<SelectWordSelectionEvent>());
|
|
final SelectWordSelectionEvent selectionEvent = renderSelectionSpy.events[0] as SelectWordSelectionEvent;
|
|
expect(selectionEvent.globalPosition, const Offset(200.0, 200.0));
|
|
|
|
renderSelectionSpy.events.clear();
|
|
await gesture.moveTo(const Offset(200.0, 50.0));
|
|
await gesture.up();
|
|
expect(renderSelectionSpy.events.length, 1);
|
|
expect(renderSelectionSpy.events[0].type, SelectionEventType.endEdgeUpdate);
|
|
final SelectionEdgeUpdateEvent edgeEvent = renderSelectionSpy.events[0] as SelectionEdgeUpdateEvent;
|
|
expect(edgeEvent.globalPosition, const Offset(200.0, 50.0));
|
|
});
|
|
|
|
testWidgets('mouse long press does not send select-word event', (WidgetTester tester) async {
|
|
final UniqueKey spy = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: SelectionSpy(key: spy),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
|
renderSelectionSpy.events.clear();
|
|
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
expect(
|
|
renderSelectionSpy.events.every((SelectionEvent element) => element is ClearSelectionEvent),
|
|
isTrue,
|
|
);
|
|
});
|
|
});
|
|
|
|
testWidgets('dragging handle or selecting word triggers haptic feedback on Android', (WidgetTester tester) async {
|
|
final List<MethodCall> log = <MethodCall>[];
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
|
|
log.add(methodCall);
|
|
return null;
|
|
});
|
|
addTearDown(() {
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall);
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Text('How are you?'),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 6)); // at the 'r'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
// `are` is selected.
|
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
|
|
expect(
|
|
log.last,
|
|
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.selectionClick'),
|
|
);
|
|
log.clear();
|
|
final List<TextBox> boxes = paragraph.getBoxesForSelection(paragraph.selections[0]);
|
|
expect(boxes.length, 1);
|
|
final Offset handlePos = globalize(boxes[0].toRect().bottomRight, paragraph);
|
|
await gesture.down(handlePos);
|
|
final Offset endPos = Offset(textOffsetToPosition(paragraph, 8).dx, handlePos.dy);
|
|
|
|
// Select 1 more character by dragging end handle to trigger feedback.
|
|
await gesture.moveTo(endPos);
|
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 4, extentOffset: 8));
|
|
// Only Android vibrate when dragging the handle.
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
expect(
|
|
log.last,
|
|
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.selectionClick'),
|
|
);
|
|
case TargetPlatform.fuchsia:
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.macOS:
|
|
case TargetPlatform.windows:
|
|
expect(log, isEmpty);
|
|
}
|
|
await gesture.up();
|
|
}, variant: TargetPlatformVariant.all());
|
|
|
|
group('SelectionArea integration', () {
|
|
testWidgets('mouse can select single text', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Center(
|
|
child: Text('How are you'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 4));
|
|
await tester.pump();
|
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 4));
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 6));
|
|
await tester.pump();
|
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 6));
|
|
|
|
// Check backward selection.
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 1));
|
|
await tester.pump();
|
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 1));
|
|
|
|
// Start a new drag.
|
|
await gesture.up();
|
|
await gesture.down(textOffsetToPosition(paragraph, 5));
|
|
expect(paragraph.selections.isEmpty, isTrue);
|
|
|
|
// Selecting across line should select to the end.
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 5) + const Offset(0.0, 200.0));
|
|
await tester.pump();
|
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 5, extentOffset: 11));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('mouse can select multiple widgets', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 4));
|
|
await tester.pump();
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 4));
|
|
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 5));
|
|
// Should select the rest of paragraph 1.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
|
|
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 6));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 6));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('mouse can work with disabled container', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
SelectionContainer.disabled(child: Text('Good, and you?')),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 4));
|
|
await tester.pump();
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 4));
|
|
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 5));
|
|
// Should select the rest of paragraph 1.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
|
|
// paragraph2 is in a disabled container.
|
|
expect(paragraph2.selections.isEmpty, isTrue);
|
|
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 6));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
|
|
expect(paragraph2.selections.isEmpty, isTrue);
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 6));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('mouse can reverse selection', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph3, 10), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 4));
|
|
await tester.pump();
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 10, extentOffset: 4));
|
|
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 5));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 10, extentOffset: 0));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 14, extentOffset: 5));
|
|
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 6));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 10, extentOffset: 0));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 14, extentOffset: 0));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 12, extentOffset: 6));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('can copy a selection made with the mouse', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
// Select from offset 2 of paragraph 1 to offset 6 of paragraph3.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 6));
|
|
await gesture.up();
|
|
|
|
// keyboard copy.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyC, control: true));
|
|
|
|
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
|
|
expect(clipboardData['text'], 'w are you?Good, and you?Fine, ');
|
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.windows, TargetPlatform.linux, TargetPlatform.fuchsia }));
|
|
|
|
testWidgets(
|
|
'does not override TextField keyboard shortcuts if the TextField is focused - non apple',
|
|
(WidgetTester tester) async {
|
|
final TextEditingController controller = TextEditingController(text: 'I am fine, thank you.');
|
|
final FocusNode selectableRegionFocus = FocusNode();
|
|
final FocusNode textFieldFocus = FocusNode();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: SelectableRegion(
|
|
focusNode: selectableRegionFocus,
|
|
selectionControls: materialTextSelectionControls,
|
|
child: Column(
|
|
children: <Widget>[
|
|
const Text('How are you?'),
|
|
const Text('Good, and you?'),
|
|
TextField(controller: controller, focusNode: textFieldFocus),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
textFieldFocus.requestFocus();
|
|
await tester.pump();
|
|
|
|
// Make sure keyboard select all works on TextField.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyA, control: true));
|
|
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 21));
|
|
|
|
// Make sure no selection in SelectableRegion.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
expect(paragraph1.selections.isEmpty, isTrue);
|
|
expect(paragraph2.selections.isEmpty, isTrue);
|
|
|
|
// Focus selectable region.
|
|
selectableRegionFocus.requestFocus();
|
|
await tester.pump();
|
|
|
|
// Reset controller selection once the TextField is unfocused.
|
|
controller.selection = const TextSelection.collapsed(offset: -1);
|
|
|
|
// Make sure keyboard select all will be handled by selectable region now.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyA, control: true));
|
|
expect(controller.selection, const TextSelection.collapsed(offset: -1));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
|
},
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.windows, TargetPlatform.linux, TargetPlatform.fuchsia }),
|
|
skip: kIsWeb, // [intended] the web handles this on its own.
|
|
);
|
|
|
|
testWidgets(
|
|
'does not override TextField keyboard shortcuts if the TextField is focused - apple',
|
|
(WidgetTester tester) async {
|
|
final TextEditingController controller = TextEditingController(text: 'I am fine, thank you.');
|
|
final FocusNode selectableRegionFocus = FocusNode();
|
|
final FocusNode textFieldFocus = FocusNode();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: SelectableRegion(
|
|
focusNode: selectableRegionFocus,
|
|
selectionControls: materialTextSelectionControls,
|
|
child: Column(
|
|
children: <Widget>[
|
|
const Text('How are you?'),
|
|
const Text('Good, and you?'),
|
|
TextField(controller: controller, focusNode: textFieldFocus),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
textFieldFocus.requestFocus();
|
|
await tester.pump();
|
|
|
|
// Make sure keyboard select all works on TextField.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyA, meta: true));
|
|
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 21));
|
|
|
|
// Make sure no selection in SelectableRegion.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
expect(paragraph1.selections.isEmpty, isTrue);
|
|
expect(paragraph2.selections.isEmpty, isTrue);
|
|
|
|
// Focus selectable region.
|
|
selectableRegionFocus.requestFocus();
|
|
await tester.pump();
|
|
|
|
// Reset controller selection once the TextField is unfocused.
|
|
controller.selection = const TextSelection.collapsed(offset: -1);
|
|
|
|
// Make sure keyboard select all will be handled by selectable region now.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyA, meta: true));
|
|
expect(controller.selection, const TextSelection.collapsed(offset: -1));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
|
},
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
|
|
skip: kIsWeb, // [intended] the web handles this on its own.
|
|
);
|
|
|
|
testWidgets('select all', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: focusNode,
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
focusNode.requestFocus();
|
|
|
|
// keyboard select all.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyA, control: true));
|
|
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 16));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.windows, TargetPlatform.linux, TargetPlatform.fuchsia }));
|
|
|
|
testWidgets(
|
|
'mouse selection can handle widget span', (WidgetTester tester) async {
|
|
final UniqueKey outerText = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: Center(
|
|
child: Text.rich(
|
|
const TextSpan(
|
|
children: <InlineSpan>[
|
|
TextSpan(text: 'How are you?'),
|
|
WidgetSpan(child: Text('Good, and you?')),
|
|
TextSpan(text: 'Fine, thank you.'),
|
|
]
|
|
),
|
|
key: outerText,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.byKey(outerText), matching: find.byType(RichText)).first);
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 17)); // right after `Fine`.
|
|
await gesture.up();
|
|
|
|
// keyboard copy.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyC, control: true));
|
|
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
|
|
expect(clipboardData['text'], 'w are you?Good, and you?Fine');
|
|
},
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.windows, TargetPlatform.linux, TargetPlatform.fuchsia }),
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/61020
|
|
);
|
|
|
|
testWidgets(
|
|
'widget span is ignored if it does not contain text - non Apple',
|
|
(WidgetTester tester) async {
|
|
final UniqueKey outerText = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: Center(
|
|
child: Text.rich(
|
|
const TextSpan(
|
|
children: <InlineSpan>[
|
|
TextSpan(text: 'How are you?'),
|
|
WidgetSpan(child: Placeholder()),
|
|
TextSpan(text: 'Fine, thank you.'),
|
|
]
|
|
),
|
|
key: outerText,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.byKey(outerText), matching: find.byType(RichText)).first);
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 17)); // right after `Fine`.
|
|
await gesture.up();
|
|
|
|
// keyboard copy.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyC, control: true));
|
|
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
|
|
expect(clipboardData['text'], 'w are you?Fine');
|
|
},
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.windows, TargetPlatform.linux, TargetPlatform.fuchsia }),
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/61020
|
|
);
|
|
|
|
testWidgets(
|
|
'widget span is ignored if it does not contain text - Apple',
|
|
(WidgetTester tester) async {
|
|
final UniqueKey outerText = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: Center(
|
|
child: Text.rich(
|
|
const TextSpan(
|
|
children: <InlineSpan>[
|
|
TextSpan(text: 'How are you?'),
|
|
WidgetSpan(child: Placeholder()),
|
|
TextSpan(text: 'Fine, thank you.'),
|
|
]
|
|
),
|
|
key: outerText,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.byKey(outerText), matching: find.byType(RichText)).first);
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 17)); // right after `Fine`.
|
|
await gesture.up();
|
|
|
|
// keyboard copy.
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyC, meta: true));
|
|
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
|
|
expect(clipboardData['text'], 'w are you?Fine');
|
|
},
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/61020
|
|
);
|
|
|
|
testWidgets('mouse can select across bidi text', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('جيد وانت؟', textDirection: TextDirection.rtl),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 4));
|
|
await tester.pump();
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 4));
|
|
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('جيد وانت؟'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 5));
|
|
// Should select the rest of paragraph 1.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
|
|
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
// Add a little offset to cross the boundary between paragraph 2 and 3.
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 6) + const Offset(0, 1));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 9));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 6));
|
|
|
|
await gesture.up();
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
|
|
|
|
testWidgets('long press and drag touch selection', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 6)); // at the 'r'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
// `are` is selected.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
|
|
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 5));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 12));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('can drag end handle when not covering entire screen', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/104620.
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Column(
|
|
children: <Widget>[
|
|
const Text('How are you?'),
|
|
SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Text('Good, and you?'),
|
|
),
|
|
const Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph2, 7)); // at the 'a'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 6, extentOffset: 9));
|
|
final List<TextBox> boxes = paragraph2.getBoxesForSelection(paragraph2.selections[0]);
|
|
expect(boxes.length, 1);
|
|
|
|
final Offset handlePos = globalize(boxes[0].toRect().bottomRight, paragraph2);
|
|
await gesture.down(handlePos);
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 11) + Offset(0, paragraph2.size.height / 2));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 6, extentOffset: 11));
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('can drag start handle when not covering entire screen', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/104620.
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Column(
|
|
children: <Widget>[
|
|
const Text('How are you?'),
|
|
SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Text('Good, and you?'),
|
|
),
|
|
const Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph2, 7)); // at the 'a'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 6, extentOffset: 9));
|
|
final List<TextBox> boxes = paragraph2.getBoxesForSelection(paragraph2.selections[0]);
|
|
expect(boxes.length, 1);
|
|
|
|
final Offset handlePos = globalize(boxes[0].toRect().bottomLeft, paragraph2);
|
|
await gesture.down(handlePos);
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 11) + Offset(0, paragraph2.size.height / 2));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 11, extentOffset: 9));
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('can drag start selection handle', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph3, 7)); // at the 'h'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 6, extentOffset: 11));
|
|
final List<TextBox> boxes = paragraph3.getBoxesForSelection(paragraph3.selections[0]);
|
|
expect(boxes.length, 1);
|
|
|
|
final Offset handlePos = globalize(boxes[0].toRect().bottomLeft, paragraph3);
|
|
await gesture.down(handlePos);
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 5) + Offset(0, paragraph2.size.height / 2));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 11));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 5, extentOffset: 14));
|
|
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 6) + Offset(0, paragraph1.size.height / 2));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 11));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 6, extentOffset: 12));
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('can drag start selection handle across end selection handle', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph3, 7)); // at the 'h'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 6, extentOffset: 11));
|
|
final List<TextBox> boxes = paragraph3.getBoxesForSelection(paragraph3.selections[0]);
|
|
expect(boxes.length, 1);
|
|
|
|
final Offset handlePos = globalize(boxes[0].toRect().bottomLeft, paragraph3);
|
|
await gesture.down(handlePos);
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 14) + Offset(0, paragraph3.size.height / 2));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 14, extentOffset: 11));
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 4) + Offset(0, paragraph3.size.height / 2));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 4, extentOffset: 11));
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('can drag end selection handle across start selection handle', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph3, 7)); // at the 'h'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 6, extentOffset: 11));
|
|
final List<TextBox> boxes = paragraph3.getBoxesForSelection(paragraph3.selections[0]);
|
|
expect(boxes.length, 1);
|
|
|
|
final Offset handlePos = globalize(boxes[0].toRect().bottomRight, paragraph3);
|
|
await gesture.down(handlePos);
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 4) + Offset(0, paragraph3.size.height / 2));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 6, extentOffset: 4));
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph3, 12) + Offset(0, paragraph3.size.height / 2));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 6, extentOffset: 12));
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('can select all from toolbar', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph3, 7)); // at the 'h'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 6, extentOffset: 11));
|
|
expect(find.text('Select all'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Select all'));
|
|
await tester.pump();
|
|
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 16));
|
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
|
}, skip: kIsWeb); // [intended] Web uses its native context menu.
|
|
|
|
testWidgets('can copy from toolbar', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph3, 7)); // at the 'h'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(paragraph3.selections[0], const TextSelection(baseOffset: 6, extentOffset: 11));
|
|
expect(find.text('Copy'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Copy'));
|
|
await tester.pump();
|
|
|
|
// Selection should be cleared.
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
expect(paragraph3.selections.isEmpty, isTrue);
|
|
expect(paragraph2.selections.isEmpty, isTrue);
|
|
expect(paragraph1.selections.isEmpty, isTrue);
|
|
|
|
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
|
|
expect(clipboardData['text'], 'thank');
|
|
}, skip: kIsWeb); // [intended] Web uses its native context menu.
|
|
|
|
testWidgets('can use keyboard to granularly extend selection - character', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
// Select from offset 2 of paragraph1 to offset 6 of paragraph1.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 6));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
// Ho[w ar]e you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 6);
|
|
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true));
|
|
await tester.pump();
|
|
// Ho[w are] you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 7);
|
|
|
|
for (int i = 0; i < 5; i += 1) {
|
|
await sendKeyCombination(tester,
|
|
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true));
|
|
await tester.pump();
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 8 + i);
|
|
}
|
|
|
|
for (int i = 0; i < 5; i += 1) {
|
|
await sendKeyCombination(tester,
|
|
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true));
|
|
await tester.pump();
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 11 - i);
|
|
}
|
|
}, variant: TargetPlatformVariant.all());
|
|
|
|
testWidgets('can use keyboard to granularly extend selection - word', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
// Select from offset 2 of paragraph1 to offset 6 of paragraph1.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 6));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
final bool alt;
|
|
final bool control;
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.fuchsia:
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.windows:
|
|
alt = false;
|
|
control = true;
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.macOS:
|
|
alt = true;
|
|
control = false;
|
|
}
|
|
|
|
// Ho[w ar]e you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 6);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: alt, control: control));
|
|
await tester.pump();
|
|
// Ho[w are] you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 7);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: alt, control: control));
|
|
await tester.pump();
|
|
// Ho[w are you]?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 11);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: alt, control: control));
|
|
await tester.pump();
|
|
// Ho[w are you?]
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: alt, control: control));
|
|
await tester.pump();
|
|
// Ho[w are you?
|
|
// Good], and you?
|
|
// Fine, thank you.
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 0);
|
|
expect(paragraph2.selections[0].end, 4);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, control: control));
|
|
await tester.pump();
|
|
// Ho[w are you?
|
|
// ]Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
expect(paragraph2.selections.length, 0);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, control: control));
|
|
await tester.pump();
|
|
// Ho[w are ]you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 8);
|
|
expect(paragraph2.selections.length, 0);
|
|
}, variant: TargetPlatformVariant.all());
|
|
|
|
testWidgets('can use keyboard to granularly extend selection - line', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
// Select from offset 2 of paragraph1 to offset 6 of paragraph1.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 6));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
final bool alt;
|
|
final bool meta;
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.fuchsia:
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.windows:
|
|
meta = false;
|
|
alt = true;
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.macOS:
|
|
meta = true;
|
|
alt = false;
|
|
}
|
|
|
|
// Ho[w ar]e you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 6);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: alt, meta: meta));
|
|
await tester.pump();
|
|
// Ho[w are you?]
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: alt, meta: meta));
|
|
await tester.pump();
|
|
// Ho[w are you?
|
|
// Good, and you?]
|
|
// Fine, thank you.
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 0);
|
|
expect(paragraph2.selections[0].end, 14);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, meta: meta));
|
|
await tester.pump();
|
|
// Ho[w are you?]
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
expect(paragraph2.selections.length, 0);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, meta: meta));
|
|
await tester.pump();
|
|
// [Ho]w are you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 0);
|
|
expect(paragraph1.selections[0].end, 2);
|
|
}, variant: TargetPlatformVariant.all());
|
|
|
|
testWidgets('can use keyboard to granularly extend selection - document', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
// Select from offset 2 of paragraph1 to offset 6 of paragraph1.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 6));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
final bool alt;
|
|
final bool meta;
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.fuchsia:
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.windows:
|
|
meta = false;
|
|
alt = true;
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.macOS:
|
|
meta = true;
|
|
alt = false;
|
|
}
|
|
|
|
// Ho[w ar]e you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 6);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, meta: meta, alt: alt));
|
|
await tester.pump();
|
|
// Ho[w are you?
|
|
// Good, and you?
|
|
// Fine, thank you.]
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 2);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 0);
|
|
expect(paragraph2.selections[0].end, 14);
|
|
expect(paragraph3.selections.length, 1);
|
|
expect(paragraph3.selections[0].start, 0);
|
|
expect(paragraph3.selections[0].end, 16);
|
|
|
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, meta: meta, alt: alt));
|
|
await tester.pump();
|
|
// [Ho]w are you?
|
|
// Good, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 0);
|
|
expect(paragraph1.selections[0].end, 2);
|
|
expect(paragraph2.selections.length, 0);
|
|
expect(paragraph3.selections.length, 0);
|
|
}, variant: TargetPlatformVariant.all());
|
|
|
|
testWidgets('can use keyboard to directionally extend selection', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Text('How are you?'),
|
|
Text('Good, and you?'),
|
|
Text('Fine, thank you.'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
// Select from offset 2 of paragraph2 to offset 6 of paragraph2.
|
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph2, 2), kind: PointerDeviceKind.mouse);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(textOffsetToPosition(paragraph2, 6));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
// How are you?
|
|
// Go[od, ]and you?
|
|
// Fine, thank you.
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 2);
|
|
expect(paragraph2.selections[0].end, 6);
|
|
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true));
|
|
await tester.pump();
|
|
// How are you?
|
|
// Go[od, and you?
|
|
// Fine, t]hank you.
|
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 2);
|
|
expect(paragraph2.selections[0].end, 14);
|
|
expect(paragraph3.selections.length, 1);
|
|
expect(paragraph3.selections[0].start, 0);
|
|
expect(paragraph3.selections[0].end, 7);
|
|
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true));
|
|
await tester.pump();
|
|
// How are you?
|
|
// Go[od, and you?
|
|
// Fine, thank you.]
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 2);
|
|
expect(paragraph2.selections[0].end, 14);
|
|
expect(paragraph3.selections.length, 1);
|
|
expect(paragraph3.selections[0].start, 0);
|
|
expect(paragraph3.selections[0].end, 16);
|
|
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
|
|
await tester.pump();
|
|
// How are you?
|
|
// Go[od, ]and you?
|
|
// Fine, thank you.
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 2);
|
|
expect(paragraph2.selections[0].end, 6);
|
|
expect(paragraph3.selections.length, 0);
|
|
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
|
|
await tester.pump();
|
|
// How a[re you?
|
|
// Go]od, and you?
|
|
// Fine, thank you.
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 5);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 0);
|
|
expect(paragraph2.selections[0].end, 2);
|
|
|
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
|
|
await tester.pump();
|
|
// [How are you?
|
|
// Go]od, and you?
|
|
// Fine, thank you.
|
|
expect(paragraph1.selections.length, 1);
|
|
expect(paragraph1.selections[0].start, 0);
|
|
expect(paragraph1.selections[0].end, 12);
|
|
expect(paragraph2.selections.length, 1);
|
|
expect(paragraph2.selections[0].start, 0);
|
|
expect(paragraph2.selections[0].end, 2);
|
|
}, variant: TargetPlatformVariant.all());
|
|
|
|
group('magnifier', () {
|
|
late ValueNotifier<MagnifierInfo> magnifierInfo;
|
|
final Widget fakeMagnifier = Container(key: UniqueKey());
|
|
|
|
testWidgets('Can drag handles to show, unshow, and update magnifier',
|
|
(WidgetTester tester) async {
|
|
const String text = 'Monkeys and rabbits in my soup';
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
magnifierConfiguration: TextMagnifierConfiguration(
|
|
magnifierBuilder: (_,
|
|
MagnifierController controller,
|
|
ValueNotifier<MagnifierInfo>
|
|
localMagnifierInfo) {
|
|
magnifierInfo = localMagnifierInfo;
|
|
return fakeMagnifier;
|
|
},
|
|
),
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Text(text),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(
|
|
find.descendant(
|
|
of: find.text(text), matching: find.byType(RichText)));
|
|
|
|
// Show the selection handles.
|
|
final TestGesture activateSelectionGesture = await tester
|
|
.startGesture(textOffsetToPosition(paragraph, text.length ~/ 2));
|
|
addTearDown(activateSelectionGesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await activateSelectionGesture.up();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
// Drag the handle around so that the magnifier shows.
|
|
final TextBox selectionBox =
|
|
paragraph.getBoxesForSelection(paragraph.selections.first).first;
|
|
final Offset leftHandlePos =
|
|
globalize(selectionBox.toRect().bottomLeft, paragraph);
|
|
final TestGesture gesture = await tester.startGesture(leftHandlePos);
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, text.length - 2));
|
|
await tester.pump();
|
|
|
|
// Expect the magnifier to show and then store it's position.
|
|
expect(find.byKey(fakeMagnifier.key!), findsOneWidget);
|
|
final Offset firstDragGesturePosition =
|
|
magnifierInfo.value.globalGesturePosition;
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, text.length));
|
|
await tester.pump();
|
|
|
|
// Expect the position the magnifier gets to have moved.
|
|
expect(firstDragGesturePosition,
|
|
isNot(magnifierInfo.value.globalGesturePosition));
|
|
|
|
// Lift the pointer and expect the magnifier to disappear.
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
|
});
|
|
});
|
|
});
|
|
|
|
testWidgets('toolbar is hidden on mobile when orientation changes', (WidgetTester tester) async {
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Text('How are you?'),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 6)); // at the 'r'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
// `are` is selected.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
|
|
await tester.pumpAndSettle();
|
|
// Text selection toolbar has appeared.
|
|
expect(find.text('Copy'), findsOneWidget);
|
|
|
|
// Hide the toolbar by changing orientation.
|
|
tester.view.physicalSize = const Size(1800.0, 2400.0);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Copy'), findsNothing);
|
|
|
|
// Handles should be hidden as well on Android
|
|
expect(
|
|
find.descendant(
|
|
of: find.byType(CompositedTransformFollower),
|
|
matching: find.byType(Padding),
|
|
),
|
|
defaultTargetPlatform == TargetPlatform.android ? findsNothing : findsNWidgets(2),
|
|
);
|
|
},
|
|
skip: kIsWeb, // [intended] Web uses its native context menu.
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android }),
|
|
);
|
|
|
|
testWidgets('the selection behavior when clicking `Copy` item in mobile platforms', (WidgetTester tester) async {
|
|
List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionHandleControls,
|
|
contextMenuBuilder: (
|
|
BuildContext context,
|
|
SelectableRegionState selectableRegionState,
|
|
) {
|
|
buttonItems = selectableRegionState.contextMenuButtonItems;
|
|
return const SizedBox.shrink();
|
|
},
|
|
child: const Text('How are you?'),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
await tester.longPressAt(textOffsetToPosition(paragraph1, 6)); // at the 'r'
|
|
await tester.pump(kLongPressTimeout);
|
|
// `are` is selected.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
|
|
|
|
expect(buttonItems.length, 2);
|
|
expect(buttonItems[0].type, ContextMenuButtonType.copy);
|
|
|
|
// Press `Copy` item
|
|
buttonItems[0].onPressed?.call();
|
|
|
|
final SelectableRegionState regionState = tester.state<SelectableRegionState>(find.byType(SelectableRegion));
|
|
|
|
// In Android copy should clear the selection.
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.fuchsia:
|
|
expect(regionState.selectionOverlay, isNull);
|
|
expect(regionState.selectionOverlay?.startHandleLayerLink, isNull);
|
|
expect(regionState.selectionOverlay?.endHandleLayerLink, isNull);
|
|
case TargetPlatform.iOS:
|
|
expect(regionState.selectionOverlay, isNotNull);
|
|
expect(regionState.selectionOverlay?.startHandleLayerLink, isNotNull);
|
|
expect(regionState.selectionOverlay?.endHandleLayerLink, isNotNull);
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.macOS:
|
|
case TargetPlatform.windows:
|
|
expect(regionState.selectionOverlay, isNotNull);
|
|
}
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
);
|
|
|
|
testWidgets('the handles do not disappear when clicking `Select all` item in mobile platforms', (WidgetTester tester) async {
|
|
List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionHandleControls,
|
|
contextMenuBuilder: (
|
|
BuildContext context,
|
|
SelectableRegionState selectableRegionState,
|
|
) {
|
|
buttonItems = selectableRegionState.contextMenuButtonItems;
|
|
return const SizedBox.shrink();
|
|
},
|
|
child: const Text('How are you?'),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
await tester.longPressAt(textOffsetToPosition(paragraph1, 6)); // at the 'r'
|
|
await tester.pump(kLongPressTimeout);
|
|
// `are` is selected.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
|
|
|
|
expect(buttonItems.length, 2);
|
|
expect(buttonItems[1].type, ContextMenuButtonType.selectAll);
|
|
|
|
// Press `Select All` item
|
|
buttonItems[1].onPressed?.call();
|
|
|
|
final SelectableRegionState regionState = tester.state<SelectableRegionState>(find.byType(SelectableRegion));
|
|
|
|
switch (defaultTargetPlatform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.fuchsia:
|
|
expect(regionState.selectionOverlay, isNotNull);
|
|
expect(regionState.selectionOverlay?.startHandleLayerLink, isNotNull);
|
|
expect(regionState.selectionOverlay?.endHandleLayerLink, isNotNull);
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.macOS:
|
|
case TargetPlatform.windows:
|
|
// Test doesn't run these platforms.
|
|
break;
|
|
}
|
|
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android, TargetPlatform.fuchsia }),
|
|
);
|
|
|
|
testWidgets('builds the correct button items', (WidgetTester tester) async {
|
|
Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{};
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionHandleControls,
|
|
contextMenuBuilder: (
|
|
BuildContext context,
|
|
SelectableRegionState selectableRegionState,
|
|
) {
|
|
buttonTypes = selectableRegionState.contextMenuButtonItems
|
|
.map((ContextMenuButtonItem buttonItem) => buttonItem.type)
|
|
.toSet();
|
|
return const SizedBox.shrink();
|
|
},
|
|
child: const Text('How are you?'),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
|
|
|
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 6)); // at the 'r'
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
// `are` is selected.
|
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(buttonTypes, contains(ContextMenuButtonType.copy));
|
|
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
|
},
|
|
variant: TargetPlatformVariant.all(),
|
|
skip: kIsWeb, // [intended]
|
|
);
|
|
|
|
testWidgets('onSelectionChange is called when the selection changes', (WidgetTester tester) async {
|
|
SelectedContent? content;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
onSelectionChanged: (SelectedContent? selectedContent) => content = selectedContent,
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Center(
|
|
child: Text('How are you'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 4), kind: PointerDeviceKind.mouse);
|
|
expect(content, isNull);
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 7));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(content, isNotNull);
|
|
expect(content!.plainText, 'are');
|
|
|
|
// Backwards selection.
|
|
await gesture.down(textOffsetToPosition(paragraph, 3));
|
|
expect(content, isNull);
|
|
await gesture.moveTo(textOffsetToPosition(paragraph, 0));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(content, isNotNull);
|
|
expect(content!.plainText, 'How');
|
|
});
|
|
|
|
group('BrowserContextMenu', () {
|
|
setUp(() async {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.contextMenu, (MethodCall call) {
|
|
// Just complete successfully, so that BrowserContextMenu thinks that
|
|
// the engine successfully received its call.
|
|
return Future<void>.value();
|
|
});
|
|
await BrowserContextMenu.disableContextMenu();
|
|
});
|
|
|
|
tearDown(() async {
|
|
await BrowserContextMenu.enableContextMenu();
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.contextMenu, null);
|
|
});
|
|
|
|
testWidgets('web can show flutter context menu when the browser context menu is disabled', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: SelectableRegion(
|
|
onSelectionChanged: (SelectedContent? selectedContent) {},
|
|
focusNode: FocusNode(),
|
|
selectionControls: materialTextSelectionControls,
|
|
child: const Center(
|
|
child: Text('How are you'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final SelectableRegionState state =
|
|
tester.state<SelectableRegionState>(find.byType(SelectableRegion));
|
|
expect(find.text('Copy'), findsNothing);
|
|
|
|
state.selectAll(SelectionChangedCause.toolbar);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Copy'), findsOneWidget);
|
|
|
|
state.hideToolbar();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Copy'), findsNothing);
|
|
},
|
|
skip: !kIsWeb, // [intended]
|
|
);
|
|
});
|
|
}
|
|
|
|
class SelectionSpy extends LeafRenderObjectWidget {
|
|
const SelectionSpy({
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
RenderObject createRenderObject(BuildContext context) {
|
|
return RenderSelectionSpy(
|
|
SelectionContainer.maybeOf(context),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
|
|
}
|
|
|
|
class RenderSelectionSpy extends RenderProxyBox
|
|
with Selectable, SelectionRegistrant {
|
|
RenderSelectionSpy(
|
|
SelectionRegistrar? registrar,
|
|
) {
|
|
this.registrar = registrar;
|
|
}
|
|
|
|
final Set<VoidCallback> listeners = <VoidCallback>{};
|
|
List<SelectionEvent> events = <SelectionEvent>[];
|
|
|
|
@override
|
|
Size get size => _size;
|
|
Size _size = Size.zero;
|
|
|
|
@override
|
|
Size computeDryLayout(BoxConstraints constraints) {
|
|
_size = Size(constraints.maxWidth, constraints.maxHeight);
|
|
return _size;
|
|
}
|
|
|
|
@override
|
|
void addListener(VoidCallback listener) => listeners.add(listener);
|
|
|
|
@override
|
|
void removeListener(VoidCallback listener) => listeners.remove(listener);
|
|
|
|
@override
|
|
SelectionResult dispatchSelectionEvent(SelectionEvent event) {
|
|
events.add(event);
|
|
return SelectionResult.end;
|
|
}
|
|
|
|
@override
|
|
SelectedContent? getSelectedContent() {
|
|
return const SelectedContent(plainText: 'content');
|
|
}
|
|
|
|
@override
|
|
SelectionGeometry get value => _value;
|
|
SelectionGeometry _value = const SelectionGeometry(
|
|
hasContent: true,
|
|
status: SelectionStatus.uncollapsed,
|
|
startSelectionPoint: SelectionPoint(
|
|
localPosition: Offset.zero,
|
|
lineHeight: 0.0,
|
|
handleType: TextSelectionHandleType.left,
|
|
),
|
|
endSelectionPoint: SelectionPoint(
|
|
localPosition: Offset.zero,
|
|
lineHeight: 0.0,
|
|
handleType: TextSelectionHandleType.left,
|
|
),
|
|
);
|
|
set value(SelectionGeometry other) {
|
|
if (other == _value) {
|
|
return;
|
|
}
|
|
_value = other;
|
|
for (final VoidCallback callback in listeners) {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) { }
|
|
}
|
|
|
|
class SelectAllWidget extends SingleChildRenderObjectWidget {
|
|
const SelectAllWidget({
|
|
super.key,
|
|
super.child,
|
|
});
|
|
|
|
@override
|
|
RenderObject createRenderObject(BuildContext context) {
|
|
return RenderSelectAll(
|
|
SelectionContainer.maybeOf(context),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
|
|
}
|
|
|
|
class RenderSelectAll extends RenderProxyBox
|
|
with Selectable, SelectionRegistrant {
|
|
RenderSelectAll(
|
|
SelectionRegistrar? registrar,
|
|
) {
|
|
this.registrar = registrar;
|
|
}
|
|
|
|
final Set<VoidCallback> listeners = <VoidCallback>{};
|
|
LayerLink? startHandle;
|
|
LayerLink? endHandle;
|
|
|
|
@override
|
|
void addListener(VoidCallback listener) => listeners.add(listener);
|
|
|
|
@override
|
|
void removeListener(VoidCallback listener) => listeners.remove(listener);
|
|
|
|
@override
|
|
SelectionResult dispatchSelectionEvent(SelectionEvent event) {
|
|
value = SelectionGeometry(
|
|
hasContent: true,
|
|
status: SelectionStatus.uncollapsed,
|
|
startSelectionPoint: SelectionPoint(
|
|
localPosition: Offset(0, size.height),
|
|
lineHeight: 0.0,
|
|
handleType: TextSelectionHandleType.left,
|
|
),
|
|
endSelectionPoint: SelectionPoint(
|
|
localPosition: Offset(size.width, size.height),
|
|
lineHeight: 0.0,
|
|
handleType: TextSelectionHandleType.left,
|
|
),
|
|
);
|
|
return SelectionResult.end;
|
|
}
|
|
|
|
@override
|
|
SelectedContent? getSelectedContent() {
|
|
return const SelectedContent(plainText: 'content');
|
|
}
|
|
|
|
@override
|
|
SelectionGeometry get value => _value;
|
|
SelectionGeometry _value = const SelectionGeometry(
|
|
hasContent: true,
|
|
status: SelectionStatus.uncollapsed,
|
|
startSelectionPoint: SelectionPoint(
|
|
localPosition: Offset.zero,
|
|
lineHeight: 0.0,
|
|
handleType: TextSelectionHandleType.left,
|
|
),
|
|
endSelectionPoint: SelectionPoint(
|
|
localPosition: Offset.zero,
|
|
lineHeight: 0.0,
|
|
handleType: TextSelectionHandleType.left,
|
|
),
|
|
);
|
|
set value(SelectionGeometry other) {
|
|
if (other == _value) {
|
|
return;
|
|
}
|
|
_value = other;
|
|
for (final VoidCallback callback in listeners) {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) {
|
|
this.startHandle = startHandle;
|
|
this.endHandle = endHandle;
|
|
}
|
|
}
|