Skip to content

Commit

Permalink
Merge pull request #8 from BazinC/2075_cherry_pick_increase_touch_are…
Browse files Browse the repository at this point in the history
…a_for_android_handles

2075 cherry pick increase touch area for android handles
  • Loading branch information
BazinC authored Jun 17, 2024
2 parents f95c914 + ed4c371 commit c422e93
Show file tree
Hide file tree
Showing 60 changed files with 103 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,9 @@ class SuperEditorAndroidControlsOverlayManagerState extends State<SuperEditorAnd
link: _controlsController!.collapsedHandleFocalPoint,
leaderAnchor: Alignment.bottomCenter,
followerAnchor: Alignment.topCenter,
// Use the offset to account for the invisible expanded touch region around the handle.
offset: -Offset(0, AndroidSelectionHandle.defaultTouchRegionExpansion.top) *
MediaQuery.devicePixelRatioOf(context),
child: AnimatedOpacity(
// When the controller doesn't want the handle to be visible, hide it.
opacity: shouldShow ? 1.0 : 0.0,
Expand Down Expand Up @@ -1658,6 +1661,9 @@ class SuperEditorAndroidControlsOverlayManagerState extends State<SuperEditorAnd
link: _controlsController!.upstreamHandleFocalPoint,
leaderAnchor: Alignment.bottomLeft,
followerAnchor: Alignment.topRight,
// Use the offset to account for the invisible expanded touch region around the handle.
offset:
-AndroidSelectionHandle.defaultTouchRegionExpansion.topRight * MediaQuery.devicePixelRatioOf(context),
child: GestureDetector(
onTapDown: (_) {
// Register tap down to win gesture arena ASAP.
Expand Down Expand Up @@ -1687,6 +1693,9 @@ class SuperEditorAndroidControlsOverlayManagerState extends State<SuperEditorAnd
link: _controlsController!.downstreamHandleFocalPoint,
leaderAnchor: Alignment.bottomRight,
followerAnchor: Alignment.topLeft,
// Use the offset to account for the invisible expanded touch region around the handle.
offset:
-AndroidSelectionHandle.defaultTouchRegionExpansion.topLeft * MediaQuery.devicePixelRatioOf(context),
child: GestureDetector(
onTapDown: (_) {
// Register tap down to win gesture arena ASAP.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,71 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:super_editor/src/infrastructure/touch_controls.dart';

/// An Android-style mobile selection drag handle.
///
/// Android renders three different types of handles: collapsed, upstream, and downstream.
///
/// All three types of handles look like 3/4 of a circle combined with 1/4 of a square (with
/// a pointy corner). The primary difference between each handle appearance is the way the
/// pointy corner is directed.
///
/// * Collapsed: The pointy corner points up.
/// * Upstream: The pointy corner points to the upper right (marking the start of a selection).
/// * Downstream: The pointy corner points to the upper left (marking the end of a selection).
class AndroidSelectionHandle extends StatelessWidget {
static const defaultTouchRegionExpansion = EdgeInsets.only(left: 16, right: 16, bottom: 16);

const AndroidSelectionHandle({
Key? key,
required this.handleType,
required this.color,
this.radius = 10,
this.touchRegionExpansion = defaultTouchRegionExpansion,
this.showDebugTouchRegion = false,
}) : super(key: key);

/// The type of handle, e.g., collapsed, upstream, downstream.
final HandleType handleType;

/// The color of the handle.
final Color color;

/// The radius of the handle - each handle is essentially a circle with one pointy
/// corner.
final double radius;

/// Invisible space added around the handle to increase the touch area the handle.
///
/// This invisible area expands the intrinsic size of the handle, and therefore the
/// visual handle will no longer be aligned exactly with the content that's following.
/// The parent layout needs to adjust the positioning of the handle to account for
/// the [touchRegionExpansion].
final EdgeInsets touchRegionExpansion;

/// Whether to render the [touchRegionExpansion] with a translucent color for visual
/// debugging.
final bool showDebugTouchRegion;

@override
Widget build(BuildContext context) {
late final Widget handle;
switch (handleType) {
case HandleType.collapsed:
return _buildCollapsed();
handle = _buildCollapsed();
case HandleType.upstream:
return _buildUpstream();
handle = _buildUpstream();
case HandleType.downstream:
return _buildDownstream();
handle = _buildDownstream();
}

return Container(
padding: touchRegionExpansion,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: showDebugTouchRegion ? Colors.red.withOpacity(0.5) : Colors.transparent,
),
child: handle,
);
}

Widget _buildCollapsed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,22 +716,26 @@ class _AndroidEditingOverlayControlsState extends State<AndroidEditingOverlayCon
required void Function(DragStartDetails) onPanStart,
}) {
late Offset fractionalTranslation;
late final Offset expandedTouchAreaAdjustment;
switch (handleType) {
case HandleType.collapsed:
fractionalTranslation = const Offset(-0.5, 0.0);
expandedTouchAreaAdjustment = Offset(0, -AndroidSelectionHandle.defaultTouchRegionExpansion.top);
break;
case HandleType.upstream:
fractionalTranslation = const Offset(-1.0, 0.0);
expandedTouchAreaAdjustment = -AndroidSelectionHandle.defaultTouchRegionExpansion.topRight;
break;
case HandleType.downstream:
fractionalTranslation = Offset.zero;
expandedTouchAreaAdjustment = -AndroidSelectionHandle.defaultTouchRegionExpansion.topLeft;
break;
}

return CompositedTransformFollower(
key: handleKey,
link: widget.textContentLayerLink,
offset: followerOffset,
offset: followerOffset + expandedTouchAreaAdjustment,
child: FractionalTranslation(
translation: fractionalTranslation,
child: GestureDetector(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_robots/flutter_test_robots.dart';
import 'package:flutter_test_runners/flutter_test_runners.dart';
import 'package:super_editor/src/infrastructure/platforms/android/selection_handles.dart';
import 'package:super_editor/super_editor.dart';
import 'package:super_editor/super_editor_test.dart';
import 'package:super_text_layout/super_text_layout.dart';
Expand Down Expand Up @@ -114,6 +115,29 @@ void main() {
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);
});

testWidgetsOnAndroid("does not show toolbar upon first tap", (tester) async {
await tester //
.createDocument()
.withTwoEmptyParagraphs()
.pump();

// Place the caret at the beginning of the document.
await tester.placeCaretInParagraph("1", 0);

// Ensure the toolbar isn't visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);

// Wait for the collapsed handle to disappear so that it doesn't cover the
// line below.
await tester.pump(const Duration(seconds: 5));

// Place the caret at the beginning of the second paragraph, at the same offset.
await tester.placeCaretInParagraph("2", 0);

// Ensure the toolbar isn't visible.
expect(SuperEditorInspector.isMobileToolbarVisible(), isFalse);
});

testWidgetsOnAndroid("shows toolbar when selection is expanded", (tester) async {
await _pumpSingleParagraphApp(tester);

Expand Down Expand Up @@ -196,11 +220,13 @@ void main() {
expect(SuperEditorInspector.findAllMobileDragHandles(), findsExactly(2));
expect(
tester.getTopLeft(SuperEditorInspector.findMobileDownstreamDragHandle()),
offsetMoreOrLessEquals(documentLayout.getGlobalOffsetFromDocumentOffset(selectedPositionRect.bottomRight)),
offsetMoreOrLessEquals(documentLayout.getGlobalOffsetFromDocumentOffset(selectedPositionRect.bottomRight) -
Offset(AndroidSelectionHandle.defaultTouchRegionExpansion.left, 0)),
);
expect(
tester.getTopRight(SuperEditorInspector.findMobileUpstreamDragHandle()),
offsetMoreOrLessEquals(documentLayout.getGlobalOffsetFromDocumentOffset(selectedPositionRect.bottomRight)),
offsetMoreOrLessEquals(documentLayout.getGlobalOffsetFromDocumentOffset(selectedPositionRect.bottomRight) +
Offset(AndroidSelectionHandle.defaultTouchRegionExpansion.right, 0)),
);

// Release the drag handle.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified super_editor/test_goldens/editor/goldens/text-scaling-header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 15 additions & 2 deletions super_editor/test_goldens/editor/mobile/mobile_selection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ void main() {
);
await tester.pumpAndSettle();
},
maxPixelMismatchCount: 51,
);

_testParagraphSelection(
Expand All @@ -115,10 +116,20 @@ void main() {

final dragDelta = SuperEditorInspector.findDeltaBetweenCharactersInTextNode("1", 34, 28);
final handleRectGlobal = SuperEditorInspector.findMobileCaretDragHandle().globalRect;
await tester.dragFrom(handleRectGlobal.center, dragDelta);

// Calculate the center of the visual handle, accounting for addition of invisible
// touch area expansion. The touch area only expands below the handle, not above
// the handle, so using the "center" of the widget would product a point that's
// too far down. The invisible height below the handle is about the same height as
// the handle, so we'll use the 25% y-value of the whole widget, which is roughly
// at the 50% y-value of the visible handle.
final centerOfVisualHandle =
Offset(handleRectGlobal.center.dx, handleRectGlobal.top + handleRectGlobal.height / 4);

await tester.dragFrom(centerOfVisualHandle, dragDelta);

// Update the drag line for debug purposes
dragLine.value = _Line(handleRectGlobal.center, handleRectGlobal.center + dragDelta);
dragLine.value = _Line(handleRectGlobal.center, centerOfVisualHandle + dragDelta);

// Even though this is a golden test, we verify the final selection
// to make it easier to spot rendering problems vs selection problems.
Expand All @@ -132,6 +143,7 @@ void main() {
),
);
},
maxPixelMismatchCount: 51,
);

_testParagraphSelection(
Expand Down Expand Up @@ -163,6 +175,7 @@ void main() {
),
);
},
maxPixelMismatchCount: 51,
);

_testParagraphSelection(
Expand Down

0 comments on commit c422e93

Please sign in to comment.