Skip to content

Commit 3a89307

Browse files
angelosilvestrematthew-carroll
authored andcommitted
[SuperEditor][SuperTextField][mobile] - Do not crop magnifier at screen edges (Resolves #2167) (#2482)
1 parent 7120ea1 commit 3a89307

21 files changed

+383
-100
lines changed

super_editor/example/lib/demos/editor_configs/demo_mobile_editing_ios.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,10 @@ class _MobileEditingIOSDemoState extends State<MobileEditingIOSDemo> with Single
136136
child: IOSFollowingMagnifier.roundedRectangle(
137137
magnifierKey: magnifierKey,
138138
leaderLink: focalPoint,
139-
// The bottom of the magnifier sits above the focal point.
140-
// Leave a few pixels between the bottom of the magnifier and the focal point. This
141-
// value was chosen empirically.
142-
offsetFromFocalPoint: const Offset(0, -20),
139+
// The magnifier is centered with the focal point. Translate it so that it sits
140+
// above the focal point and leave a few pixels between the bottom of the magnifier
141+
// and the focal point. This value was chosen empirically.
142+
offsetFromFocalPoint: Offset(0, (-defaultIosMagnifierSize.height / 2) - 20),
143143
show: isVisible,
144144
),
145145
);

super_editor/lib/src/default_editor/document_gestures_touch_android.dart

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,23 +1945,20 @@ class SuperEditorAndroidControlsOverlayManagerState extends State<SuperEditorAnd
19451945
return const SizedBox();
19461946
}
19471947

1948+
final devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
19481949
return Follower.withOffset(
19491950
link: _controlsController!.magnifierFocalPoint,
1950-
offset: const Offset(0, -150),
1951+
offset: Offset(0, -54 * devicePixelRatio),
19511952
leaderAnchor: Alignment.center,
1952-
followerAnchor: Alignment.topLeft,
1953-
// Theoretically, we should be able to use a leaderAnchor and followerAnchor of "center"
1954-
// and avoid the following FractionalTranslation. However, when centering the follower,
1955-
// we don't get the expect focal point within the magnified area. It's off-center. I'm not
1956-
// sure why that happens, but using a followerAnchor of "topLeft" and then pulling back
1957-
// by 50% solve the problem.
1958-
child: FractionalTranslation(
1959-
translation: const Offset(-0.5, -0.5),
1960-
child: AndroidMagnifyingGlass(
1961-
key: magnifierKey,
1962-
magnificationScale: 1.5,
1963-
offsetFromFocalPoint: Offset(0, -150 / MediaQuery.devicePixelRatioOf(context)),
1964-
),
1953+
followerAnchor: Alignment.center,
1954+
boundary: ScreenFollowerBoundary(
1955+
screenSize: MediaQuery.sizeOf(context),
1956+
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
1957+
),
1958+
child: AndroidMagnifyingGlass(
1959+
key: magnifierKey,
1960+
magnificationScale: 1.5,
1961+
offsetFromFocalPoint: const Offset(0, -54),
19651962
),
19661963
);
19671964
}

super_editor/lib/src/default_editor/document_gestures_touch_ios.dart

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,23 +1506,25 @@ class SuperEditorIosMagnifierOverlayManagerState extends State<SuperEditorIosMag
15061506
// When the user is dragging an overlay handle, SuperEditor
15071507
// position a Leader with a LeaderLink. This magnifier follows that Leader
15081508
// via the LeaderLink.
1509-
return ValueListenableBuilder(
1510-
valueListenable: _controlsController!.shouldShowMagnifier,
1511-
builder: (context, shouldShowMagnifier, child) {
1512-
return _controlsController!.magnifierBuilder != null //
1513-
? _controlsController!.magnifierBuilder!(
1514-
context,
1515-
DocumentKeys.magnifier,
1516-
_controlsController!.magnifierFocalPoint,
1517-
shouldShowMagnifier,
1518-
)
1519-
: _buildDefaultMagnifier(
1520-
context,
1521-
DocumentKeys.magnifier,
1522-
_controlsController!.magnifierFocalPoint,
1523-
shouldShowMagnifier,
1524-
);
1525-
},
1509+
return IgnorePointer(
1510+
child: ValueListenableBuilder(
1511+
valueListenable: _controlsController!.shouldShowMagnifier,
1512+
builder: (context, shouldShowMagnifier, child) {
1513+
return _controlsController!.magnifierBuilder != null //
1514+
? _controlsController!.magnifierBuilder!(
1515+
context,
1516+
DocumentKeys.magnifier,
1517+
_controlsController!.magnifierFocalPoint,
1518+
shouldShowMagnifier,
1519+
)
1520+
: _buildDefaultMagnifier(
1521+
context,
1522+
DocumentKeys.magnifier,
1523+
_controlsController!.magnifierFocalPoint,
1524+
shouldShowMagnifier,
1525+
);
1526+
},
1527+
),
15261528
);
15271529
}
15281530

@@ -1537,10 +1539,10 @@ class SuperEditorIosMagnifierOverlayManagerState extends State<SuperEditorIosMag
15371539
magnifierKey: magnifierKey,
15381540
leaderLink: magnifierFocalPoint,
15391541
show: isVisible,
1540-
// The bottom of the magnifier sits above the focal point.
1541-
// Leave a few pixels between the bottom of the magnifier and the focal point. This
1542-
// value was chosen empirically.
1543-
offsetFromFocalPoint: const Offset(0, -20),
1542+
// The magnifier is centered with the focal point. Translate it so that it sits
1543+
// above the focal point and leave a few pixels between the bottom of the magnifier
1544+
// and the focal point. This value was chosen empirically.
1545+
offsetFromFocalPoint: Offset(0, (-defaultIosMagnifierSize.height / 2) - 20),
15441546
handleColor: _controlsController!.handleColor,
15451547
);
15461548
}

super_editor/lib/src/infrastructure/platforms/android/magnifier.dart

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter/widgets.dart';
3+
import 'package:follow_the_leader/follow_the_leader.dart';
34
import 'package:super_editor/src/super_textfield/infrastructure/magnifier.dart';
45
import 'package:super_editor/src/super_textfield/infrastructure/outer_box_shadow.dart';
56

6-
/// An Android magnifying glass that follows a [LayerLink].
7+
/// An Android magnifying glass that follows a [LeaderLink].
78
class AndroidFollowingMagnifier extends StatelessWidget {
89
const AndroidFollowingMagnifier({
910
Key? key,
1011
required this.layerLink,
1112
this.offsetFromFocalPoint = Offset.zero,
1213
}) : super(key: key);
1314

14-
final LayerLink layerLink;
15+
final LeaderLink layerLink;
1516
final Offset offsetFromFocalPoint;
1617

1718
@override
1819
Widget build(BuildContext context) {
19-
return CompositedTransformFollower(
20+
final devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
21+
22+
return Follower.withOffset(
2023
link: layerLink,
2124
offset: offsetFromFocalPoint,
22-
child: FractionalTranslation(
23-
translation: const Offset(-0.5, -0.5),
24-
child: AndroidMagnifyingGlass(
25-
offsetFromFocalPoint: offsetFromFocalPoint,
25+
leaderAnchor: Alignment.center,
26+
followerAnchor: Alignment.center,
27+
boundary: ScreenFollowerBoundary(
28+
screenSize: MediaQuery.sizeOf(context),
29+
devicePixelRatio: devicePixelRatio,
30+
),
31+
child: AndroidMagnifyingGlass(
32+
magnificationScale: 1.5,
33+
offsetFromFocalPoint: Offset(
34+
offsetFromFocalPoint.dx / devicePixelRatio,
35+
offsetFromFocalPoint.dy / devicePixelRatio,
2636
),
2737
),
2838
);

super_editor/lib/src/infrastructure/platforms/ios/magnifier.dart

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -116,30 +116,25 @@ class _IOSFollowingMagnifierState extends State<IOSFollowingMagnifier> with Sing
116116
// Animate the magnfier up on entrance and down on exit.
117117
widget.offsetFromFocalPoint.dy * devicePixelRatio * percentage,
118118
),
119-
// Translate the magnifier so it's displayed above the focal point
120-
// when the animation ends.
121-
child: FractionalTranslation(
122-
translation: Offset(0.0, -0.5 * percentage),
123-
child: widget.magnifierBuilder(
124-
context,
125-
IosMagnifierViewModel(
126-
// In theory, the offsetFromFocalPoint should either be `widget.offsetFromFocalPoint.dy` to match
127-
// the actual offset, or it should be `widget.offsetFromFocalPoint.dy / magnificationLevel`. Neither
128-
// of those align the focal point correctly. The following offset was found empirically to give the
129-
// desired results. These values seem to work even with different pixel densities.
130-
offsetFromFocalPoint: Offset(
131-
-22 * percentage,
132-
(-defaultIosMagnifierSize.height + 14) * percentage,
133-
),
134-
animationValue: _animationController.value,
135-
animationDirection:
136-
const [AnimationStatus.forward, AnimationStatus.completed].contains(_animationController.status)
137-
? AnimationDirection.forward
138-
: AnimationDirection.reverse,
139-
borderColor: widget.handleColor ?? Theme.of(context).primaryColor,
119+
boundary: ScreenFollowerBoundary(
120+
screenSize: MediaQuery.sizeOf(context),
121+
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
122+
),
123+
child: widget.magnifierBuilder(
124+
context,
125+
IosMagnifierViewModel(
126+
offsetFromFocalPoint: Offset(
127+
widget.offsetFromFocalPoint.dx * percentage,
128+
widget.offsetFromFocalPoint.dy * percentage,
140129
),
141-
widget.magnifierKey,
130+
animationValue: _animationController.value,
131+
animationDirection:
132+
const [AnimationStatus.forward, AnimationStatus.completed].contains(_animationController.status)
133+
? AnimationDirection.forward
134+
: AnimationDirection.reverse,
135+
borderColor: widget.handleColor ?? Theme.of(context).primaryColor,
142136
),
137+
widget.magnifierKey,
143138
),
144139
);
145140
},

super_editor/lib/src/infrastructure/platforms/mobile_documents.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ class GestureEditingController with ChangeNotifier {
370370
GestureEditingController({
371371
required this.selectionLinks,
372372
required MagnifierAndToolbarController overlayController,
373-
required LayerLink magnifierFocalPointLink,
373+
required LeaderLink magnifierFocalPointLink,
374374
}) : _magnifierFocalPointLink = magnifierFocalPointLink,
375375
_overlayController = overlayController {
376376
_overlayController.addListener(_toolbarChanged);
@@ -386,8 +386,8 @@ class GestureEditingController with ChangeNotifier {
386386

387387
/// A `LayerLink` whose top-left corner sits at the location where the
388388
/// magnifier should magnify.
389-
LayerLink get magnifierFocalPointLink => _magnifierFocalPointLink;
390-
final LayerLink _magnifierFocalPointLink;
389+
LeaderLink get magnifierFocalPointLink => _magnifierFocalPointLink;
390+
final LeaderLink _magnifierFocalPointLink;
391391

392392
/// Controls the magnifier and the toolbar.
393393
MagnifierAndToolbarController get overlayController => _overlayController;

super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State<ReadOnlyAndroid
132132
GroupedOverlayPortalController(displayPriority: OverlayGroupPriority.editingControls);
133133
final _overlayPortalRebuildSignal = SignalNotifier();
134134
late AndroidDocumentGestureEditingController _editingController;
135-
final _magnifierFocalPointLink = LayerLink();
135+
final _magnifierFocalPointLink = LeaderLink();
136136

137137
late DragHandleAutoScroller _handleAutoScrolling;
138138
Offset? _globalStartDragOffset;
@@ -1480,7 +1480,7 @@ class _AndroidDocumentTouchEditingControlsState extends State<AndroidDocumentTou
14801480
left: magnifierOffset.dx,
14811481
// TODO: select focal position based on type of content
14821482
top: magnifierOffset.dy,
1483-
child: CompositedTransformTarget(
1483+
child: Leader(
14841484
link: widget.editingController.magnifierFocalPointLink,
14851485
child: const SizedBox(width: 1, height: 1),
14861486
),
@@ -1492,11 +1492,9 @@ class _AndroidDocumentTouchEditingControlsState extends State<AndroidDocumentTou
14921492
//
14931493
// When the user is dragging an overlay handle, we place a LayerLink
14941494
// target. This magnifier follows that target.
1495-
return Center(
1496-
child: AndroidFollowingMagnifier(
1497-
layerLink: widget.editingController.magnifierFocalPointLink,
1498-
offsetFromFocalPoint: const Offset(0, -72),
1499-
),
1495+
return AndroidFollowingMagnifier(
1496+
layerLink: widget.editingController.magnifierFocalPointLink,
1497+
offsetFromFocalPoint: Offset(0, -54 * MediaQuery.devicePixelRatioOf(context)),
15001498
);
15011499
}
15021500

super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,10 +1164,10 @@ class SuperReaderIosMagnifierOverlayManagerState extends State<SuperReaderIosMag
11641164
magnifierKey: magnifierKey,
11651165
show: visible,
11661166
leaderLink: magnifierFocalPoint,
1167-
// The bottom of the magnifier sits above the focal point.
1168-
// Leave a few pixels between the bottom of the magnifier and the focal point. This
1169-
// value was chosen empirically.
1170-
offsetFromFocalPoint: const Offset(0, -20),
1167+
// The magnifier is centered with the focal point. Translate it so that it sits
1168+
// above the focal point and leave a few pixels between the bottom of the magnifier
1169+
// and the focal point. This value was chosen empirically.
1170+
offsetFromFocalPoint: Offset(0, (-defaultIosMagnifierSize.height / 2) - 20),
11711171
handleColor: _controlsContext!.handleColor,
11721172
);
11731173
}

super_editor/lib/src/super_textfield/android/_editing_controls.dart

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:math';
44
import 'package:flutter/gestures.dart';
55
import 'package:flutter/material.dart';
66
import 'package:flutter/services.dart';
7+
import 'package:follow_the_leader/follow_the_leader.dart';
78
import 'package:super_editor/src/infrastructure/flutter/flutter_scheduler.dart';
89
import 'package:super_editor/src/infrastructure/multi_listenable_builder.dart';
910
import 'package:super_editor/src/infrastructure/_logging.dart';
@@ -805,7 +806,7 @@ class _AndroidEditingOverlayControlsState extends State<AndroidEditingOverlayCon
805806
return Positioned(
806807
left: _localDragOffset!.dx,
807808
top: focalPointOffsetInMiddleOfLine.dy,
808-
child: CompositedTransformTarget(
809+
child: Leader(
809810
link: widget.editingController.magnifierFocalPoint,
810811
child: const SizedBox(width: 1, height: 1),
811812
),
@@ -821,11 +822,9 @@ class _AndroidEditingOverlayControlsState extends State<AndroidEditingOverlayCon
821822
// When some other interaction wants to show the magnifier, then
822823
// that other area of the widget tree is responsible for
823824
// positioning the LayerLink target.
824-
return Center(
825-
child: AndroidFollowingMagnifier(
826-
layerLink: widget.editingController.magnifierFocalPoint,
827-
offsetFromFocalPoint: const Offset(0, -72),
828-
),
825+
return AndroidFollowingMagnifier(
826+
layerLink: widget.editingController.magnifierFocalPoint,
827+
offsetFromFocalPoint: Offset(0, -54 * MediaQuery.devicePixelRatioOf(context)),
829828
);
830829
}
831830

@@ -838,7 +837,7 @@ class AndroidEditingOverlayController with ChangeNotifier {
838837
AndroidEditingOverlayController({
839838
required this.textController,
840839
required this.caretBlinkController,
841-
required LayerLink magnifierFocalPoint,
840+
required LeaderLink magnifierFocalPoint,
842841
}) : _magnifierFocalPoint = magnifierFocalPoint;
843842

844843
@override
@@ -894,8 +893,8 @@ class AndroidEditingOverlayController with ChangeNotifier {
894893
notifyListeners();
895894
}
896895

897-
final LayerLink _magnifierFocalPoint;
898-
LayerLink get magnifierFocalPoint => _magnifierFocalPoint;
896+
final LeaderLink _magnifierFocalPoint;
897+
LeaderLink get magnifierFocalPoint => _magnifierFocalPoint;
899898

900899
bool _isMagnifierVisible = false;
901900
bool get isMagnifierVisible => _isMagnifierVisible;

super_editor/lib/src/super_textfield/android/_user_interaction.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter/gestures.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter/services.dart';
4+
import 'package:follow_the_leader/follow_the_leader.dart';
45
import 'package:super_editor/src/infrastructure/_logging.dart';
56
import 'package:super_editor/src/infrastructure/document_gestures_interaction_overrides.dart';
67
import 'package:super_editor/src/infrastructure/flutter/flutter_scheduler.dart';
@@ -691,7 +692,7 @@ class AndroidTextFieldTouchInteractorState extends State<AndroidTextFieldTouchIn
691692
return Positioned(
692693
left: extentOffsetInViewport.dx,
693694
top: extentOffsetInViewport.dy + (extentLineHeight / 2),
694-
child: CompositedTransformTarget(
695+
child: Leader(
695696
link: widget.editingOverlayController.magnifierFocalPoint,
696697
child: widget.showDebugPaint
697698
? FractionalTranslation(

0 commit comments

Comments
 (0)