Unverified Commit 2c477b18 authored by Renzo Olivares's avatar Renzo Olivares Committed by GitHub

SelectableRegion onSelectionChange should be called when the selection changes (#134215)

This change makes sure to call `onSelectionChange` in all cases when selection might change including:
* Dragging selection handles
* Mouse drag to select
* Keyboard actions
* Long press drag to select
parent c7665ff8
......@@ -502,6 +502,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
case 2:
_selectWordAt(offset: details.globalPosition);
}
_updateSelectedContentIfNeeded();
}
void _handleMouseDragStart(TapDragStartDetails details) {
......@@ -509,6 +510,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
case 1:
_selectStartTo(offset: details.globalPosition);
}
_updateSelectedContentIfNeeded();
}
void _handleMouseDragUpdate(TapDragUpdateDetails details) {
......@@ -518,6 +520,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
case 2:
_selectEndTo(offset: details.globalPosition, continuous: true, textGranularity: TextGranularity.word);
}
_updateSelectedContentIfNeeded();
}
void _handleMouseDragEnd(TapDragEndDetails details) {
......@@ -543,6 +546,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
void _handleTouchLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
_selectEndTo(offset: details.globalPosition, textGranularity: TextGranularity.word);
_updateSelectedContentIfNeeded();
}
void _handleTouchLongPressEnd(LongPressEndDetails details) {
......@@ -717,6 +721,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
details.globalPosition,
_selectionDelegate.value.startSelectionPoint!,
));
_updateSelectedContentIfNeeded();
}
void _handleSelectionStartHandleDragUpdate(DragUpdateDetails details) {
......@@ -730,6 +735,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
details.globalPosition,
_selectionDelegate.value.startSelectionPoint!,
));
_updateSelectedContentIfNeeded();
}
void _handleSelectionEndHandleDragStart(DragStartDetails details) {
......@@ -742,6 +748,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
details.globalPosition,
_selectionDelegate.value.endSelectionPoint!,
));
_updateSelectedContentIfNeeded();
}
void _handleSelectionEndHandleDragUpdate(DragUpdateDetails details) {
......@@ -755,6 +762,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
details.globalPosition,
_selectionDelegate.value.endSelectionPoint!,
));
_updateSelectedContentIfNeeded();
}
MagnifierInfo _buildInfoForMagnifier(Offset globalGesturePosition, SelectionPoint selectionPoint) {
......@@ -1067,6 +1075,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
granularity: granularity,
),
);
_updateSelectedContentIfNeeded();
}
double? _directionalHorizontalBaseline;
......@@ -1088,6 +1097,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
dx: globalSelectionPointOffset.dx,
),
);
_updateSelectedContentIfNeeded();
}
// [TextSelectionDelegate] overrides.
......
......@@ -2713,7 +2713,7 @@ void main() {
skip: kIsWeb, // [intended]
);
testWidgets('onSelectionChange is called when the selection changes', (WidgetTester tester) async {
testWidgets('onSelectionChange is called when the selection changes through gestures', (WidgetTester tester) async {
SelectedContent? content;
await tester.pumpWidget(
......@@ -2728,27 +2728,264 @@ void main() {
),
),
);
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);
final TestGesture mouseGesture = await tester.startGesture(textOffsetToPosition(paragraph, 4), kind: PointerDeviceKind.mouse);
final TestGesture touchGesture = await tester.createGesture();
expect(content, isNull);
addTearDown(gesture.removePointer);
addTearDown(mouseGesture.removePointer);
addTearDown(touchGesture.removePointer);
await tester.pump();
await gesture.moveTo(textOffsetToPosition(paragraph, 7));
await gesture.up();
await tester.pump();
// Called on drag.
await mouseGesture.moveTo(textOffsetToPosition(paragraph, 7));
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'are');
// Updates on drag.
await mouseGesture.moveTo(textOffsetToPosition(paragraph, 10));
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'are yo');
// Called on drag end.
await mouseGesture.up();
await tester.pump();
expect(content, isNotNull);
expect(content!.plainText, 'are yo');
// Backwards selection.
await gesture.down(textOffsetToPosition(paragraph, 3));
await mouseGesture.down(textOffsetToPosition(paragraph, 3));
await tester.pumpAndSettle();
expect(content, isNull);
await gesture.moveTo(textOffsetToPosition(paragraph, 0));
await gesture.up();
await mouseGesture.moveTo(textOffsetToPosition(paragraph, 0));
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'How');
await mouseGesture.up();
await tester.pump();
expect(content, isNotNull);
expect(content!.plainText, 'How');
// Called on double tap.
await mouseGesture.down(textOffsetToPosition(paragraph, 6));
await tester.pump();
await mouseGesture.up();
await tester.pump();
await mouseGesture.down(textOffsetToPosition(paragraph, 6));
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'are');
await mouseGesture.up();
await tester.pumpAndSettle();
// Called on tap.
await mouseGesture.down(textOffsetToPosition(paragraph, 0));
await tester.pumpAndSettle();
expect(content, isNull);
await mouseGesture.up();
await tester.pumpAndSettle();
// With touch gestures.
// Called on long press start.
await touchGesture.down(textOffsetToPosition(paragraph, 0));
await tester.pumpAndSettle(kLongPressTimeout);
expect(content, isNotNull);
expect(content!.plainText, 'How');
// Called on long press update.
await touchGesture.moveTo(textOffsetToPosition(paragraph, 5));
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'How are');
// Called on long press end.
await touchGesture.up();
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'How are');
// Long press to select 'you'.
await touchGesture.down(textOffsetToPosition(paragraph, 9));
await tester.pumpAndSettle(kLongPressTimeout);
expect(content, isNotNull);
expect(content!.plainText, 'you');
await touchGesture.up();
await tester.pumpAndSettle();
// Called while moving selection handles.
final List<TextBox> boxes = paragraph.getBoxesForSelection(paragraph.selections[0]);
expect(boxes.length, 1);
final Offset startHandlePos = globalize(boxes[0].toRect().bottomLeft, paragraph);
final Offset endHandlePos = globalize(boxes[0].toRect().bottomRight, paragraph);
final Offset startPos = Offset(textOffsetToPosition(paragraph, 4).dx, startHandlePos.dy);
final Offset endPos = Offset(textOffsetToPosition(paragraph, 6).dx, endHandlePos.dy);
// Start handle.
await touchGesture.down(startHandlePos);
await touchGesture.moveTo(startPos);
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'are you');
await touchGesture.up();
await tester.pumpAndSettle();
// End handle.
await touchGesture.down(endHandlePos);
await touchGesture.moveTo(endPos);
await tester.pumpAndSettle();
expect(content, isNotNull);
expect(content!.plainText, 'ar');
await touchGesture.up();
await tester.pumpAndSettle();
});
testWidgets('onSelectionChange is called when the selection changes through keyboard actions', (WidgetTester tester) async {
SelectedContent? content;
await tester.pumpWidget(
MaterialApp(
home: SelectableRegion(
onSelectionChanged: (SelectedContent? selectedContent) => content = selectedContent,
focusNode: FocusNode(),
selectionControls: materialTextSelectionControls,
child: const Column(
children: <Widget>[
Text('How are you?'),
Text('Good, and you?'),
Text('Fine, thank you.'),
],
),
),
),
);
expect(content, isNull);
await tester.pump();
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)));
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(paragraph1, 2), kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
await tester.pump();
await gesture.moveTo(textOffsetToPosition(paragraph1, 6));
await gesture.up();
await tester.pump();
expect(paragraph1.selections.length, 1);
expect(paragraph1.selections[0].start, 2);
expect(paragraph1.selections[0].end, 6);
expect(content, isNotNull);
expect(content!.plainText, 'w ar');
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, 7);
expect(content, isNotNull);
expect(content!.plainText, 'w are');
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);
expect(content, isNotNull);
}
expect(content, isNotNull);
expect(content!.plainText, 'w are you?');
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);
expect(content, isNotNull);
}
expect(content, isNotNull);
expect(content!.plainText, 'w are');
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true));
await tester.pump();
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, 8);
expect(content, isNotNull);
expect(content!.plainText, 'w are you?Good, an');
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true));
await tester.pump();
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, 9);
expect(content, isNotNull);
expect(content!.plainText, 'w are you?Good, and you?Fine, tha');
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true));
await tester.pump();
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);
expect(content, isNotNull);
expect(content!.plainText, 'w are you?Good, and you?Fine, thank you.');
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
await tester.pump();
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, 8);
expect(paragraph3.selections.length, 0);
expect(content, isNotNull);
expect(content!.plainText, 'w are you?Good, an');
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
await tester.pump();
expect(paragraph1.selections.length, 1);
expect(paragraph1.selections[0].start, 2);
expect(paragraph1.selections[0].end, 7);
expect(paragraph2.selections.length, 0);
expect(paragraph3.selections.length, 0);
expect(content, isNotNull);
expect(content!.plainText, 'w are');
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
await tester.pump();
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);
expect(content, isNotNull);
expect(content!.plainText, 'Ho');
});
group('BrowserContextMenu', () {
......
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