|
|
|
@ -69,7 +69,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
EditorTextSelectionOverlay({ |
|
|
|
|
required this.value, |
|
|
|
|
required this.context, |
|
|
|
|
required this.toolbarLayerLink, |
|
|
|
|
required this.startHandleLayerLink, |
|
|
|
|
required this.endHandleLayerLink, |
|
|
|
|
required this.renderObject, |
|
|
|
@ -77,12 +76,11 @@ class EditorTextSelectionOverlay { |
|
|
|
|
required this.selectionCtrls, |
|
|
|
|
required this.selectionDelegate, |
|
|
|
|
required this.clipboardStatus, |
|
|
|
|
required this.contextMenuBuilder, |
|
|
|
|
this.onSelectionHandleTapped, |
|
|
|
|
this.dragStartBehavior = DragStartBehavior.start, |
|
|
|
|
this.handlesVisible = false, |
|
|
|
|
}) { |
|
|
|
|
final overlay = Overlay.of(context, rootOverlay: true); |
|
|
|
|
|
|
|
|
|
// Clipboard status is only checked on first instance of |
|
|
|
|
// ClipboardStatusNotifier |
|
|
|
|
// if state has changed after creation, but prior to |
|
|
|
@ -90,9 +88,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
// we won't know the status unless there is forced update |
|
|
|
|
// i.e. occasionally no paste |
|
|
|
|
clipboardStatus.update(); |
|
|
|
|
|
|
|
|
|
_toolbarController = AnimationController( |
|
|
|
|
duration: const Duration(milliseconds: 150), vsync: overlay); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TextEditingValue value; |
|
|
|
@ -122,10 +117,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
/// Debugging information for explaining why the [Overlay] is required. |
|
|
|
|
final Widget debugRequiredFor; |
|
|
|
|
|
|
|
|
|
/// The object supplied to the [CompositedTransformTarget] that wraps the text |
|
|
|
|
/// field. |
|
|
|
|
final LayerLink toolbarLayerLink; |
|
|
|
|
|
|
|
|
|
/// The objects supplied to the [CompositedTransformTarget] that wraps the |
|
|
|
|
/// location of start selection handle. |
|
|
|
|
final LayerLink startHandleLayerLink; |
|
|
|
@ -144,6 +135,11 @@ class EditorTextSelectionOverlay { |
|
|
|
|
/// text field. |
|
|
|
|
final TextSelectionDelegate selectionDelegate; |
|
|
|
|
|
|
|
|
|
/// {@macro flutter.widgets.EditableText.contextMenuBuilder} |
|
|
|
|
/// |
|
|
|
|
/// If not provided, no context menu will be built. |
|
|
|
|
final WidgetBuilder? contextMenuBuilder; |
|
|
|
|
|
|
|
|
|
/// Determines the way that drag start behavior is handled. |
|
|
|
|
/// |
|
|
|
|
/// If set to [DragStartBehavior.start], handle drag behavior will |
|
|
|
@ -177,7 +173,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
/// Useful because the actual value of the clipboard can only be checked |
|
|
|
|
/// asynchronously (see [Clipboard.getData]). |
|
|
|
|
final ClipboardStatusNotifier clipboardStatus; |
|
|
|
|
late AnimationController _toolbarController; |
|
|
|
|
|
|
|
|
|
/// A pair of handles. If this is non-null, there are always 2, though the |
|
|
|
|
/// second is hidden when the selection is collapsed. |
|
|
|
@ -188,8 +183,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
|
|
|
|
|
TextSelection get _selection => value.selection; |
|
|
|
|
|
|
|
|
|
Animation<double> get _toolbarOpacity => _toolbarController.view; |
|
|
|
|
|
|
|
|
|
void setHandlesVisible(bool visible) { |
|
|
|
|
if (handlesVisible == visible) { |
|
|
|
|
return; |
|
|
|
@ -220,7 +213,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
/// To hide the whole overlay, see [hide]. |
|
|
|
|
void hideToolbar() { |
|
|
|
|
assert(toolbar != null); |
|
|
|
|
_toolbarController.stop(); |
|
|
|
|
toolbar!.remove(); |
|
|
|
|
toolbar = null; |
|
|
|
|
} |
|
|
|
@ -228,10 +220,12 @@ class EditorTextSelectionOverlay { |
|
|
|
|
/// Shows the toolbar by inserting it into the [context]'s overlay. |
|
|
|
|
void showToolbar() { |
|
|
|
|
assert(toolbar == null); |
|
|
|
|
toolbar = OverlayEntry(builder: _buildToolbar); |
|
|
|
|
if (contextMenuBuilder == null) return; |
|
|
|
|
toolbar = OverlayEntry(builder: (context) { |
|
|
|
|
return contextMenuBuilder!(context); |
|
|
|
|
}); |
|
|
|
|
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor) |
|
|
|
|
.insert(toolbar!); |
|
|
|
|
_toolbarController.forward(from: 0); |
|
|
|
|
|
|
|
|
|
// make sure handles are visible as well |
|
|
|
|
if (_handles == null) { |
|
|
|
@ -319,63 +313,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
..bringIntoView(textPosition); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Widget _buildToolbar(BuildContext context) { |
|
|
|
|
// Find the horizontal midpoint, just above the selected text. |
|
|
|
|
List<TextSelectionPoint> endpoints; |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
// building with an invalid selection with throw an exception |
|
|
|
|
// This happens where the selection has changed, but the toolbar |
|
|
|
|
// hasn't been dismissed yet. |
|
|
|
|
endpoints = renderObject.getEndpointsForSelection(_selection); |
|
|
|
|
} catch (_) { |
|
|
|
|
return Container(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
final editingRegion = Rect.fromPoints( |
|
|
|
|
renderObject.localToGlobal(Offset.zero), |
|
|
|
|
renderObject.localToGlobal(renderObject.size.bottomRight(Offset.zero)), |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
final baseLineHeight = renderObject.preferredLineHeight(_selection.base); |
|
|
|
|
final extentLineHeight = |
|
|
|
|
renderObject.preferredLineHeight(_selection.extent); |
|
|
|
|
final smallestLineHeight = math.min(baseLineHeight, extentLineHeight); |
|
|
|
|
final isMultiline = endpoints.last.point.dy - endpoints.first.point.dy > |
|
|
|
|
smallestLineHeight / 2; |
|
|
|
|
|
|
|
|
|
// If the selected text spans more than 1 line, |
|
|
|
|
// horizontally center the toolbar. |
|
|
|
|
// Derived from both iOS and Android. |
|
|
|
|
final midX = isMultiline |
|
|
|
|
? editingRegion.width / 2 |
|
|
|
|
: (endpoints.first.point.dx + endpoints.last.point.dx) / 2; |
|
|
|
|
|
|
|
|
|
final midpoint = Offset( |
|
|
|
|
midX, |
|
|
|
|
// The y-coordinate won't be made use of most likely. |
|
|
|
|
endpoints[0].point.dy - baseLineHeight, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
return FadeTransition( |
|
|
|
|
opacity: _toolbarOpacity, |
|
|
|
|
child: CompositedTransformFollower( |
|
|
|
|
link: toolbarLayerLink, |
|
|
|
|
showWhenUnlinked: false, |
|
|
|
|
offset: -editingRegion.topLeft, |
|
|
|
|
child: selectionCtrls.buildToolbar( |
|
|
|
|
context, |
|
|
|
|
editingRegion, |
|
|
|
|
baseLineHeight, |
|
|
|
|
midpoint, |
|
|
|
|
endpoints, |
|
|
|
|
selectionDelegate, |
|
|
|
|
clipboardStatus, |
|
|
|
|
null), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void markNeedsBuild([Duration? duration]) { |
|
|
|
|
if (_handles != null) { |
|
|
|
|
_handles![0].markNeedsBuild(); |
|
|
|
@ -399,7 +336,6 @@ class EditorTextSelectionOverlay { |
|
|
|
|
/// Final cleanup. |
|
|
|
|
void dispose() { |
|
|
|
|
hide(); |
|
|
|
|
_toolbarController.dispose(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Builds the handles by inserting them into the [context]'s overlay. |
|
|
|
|