| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | /// @docImport 'package:flutter/material.dart'; |
| 6 | library; |
| 7 | |
| 8 | import 'package:flutter/foundation.dart'; |
| 9 | import 'package:flutter/rendering.dart'; |
| 10 | |
| 11 | /// The position information for a text selection toolbar. |
| 12 | /// |
| 13 | /// Typically, a menu will attempt to position itself at [primaryAnchor], and |
| 14 | /// if that's not possible, then it will use [secondaryAnchor] instead, if it |
| 15 | /// exists. |
| 16 | /// |
| 17 | /// See also: |
| 18 | /// |
| 19 | /// * [AdaptiveTextSelectionToolbar.anchors], which is of this type. |
| 20 | @immutable |
| 21 | class TextSelectionToolbarAnchors { |
| 22 | /// Creates an instance of [TextSelectionToolbarAnchors] directly from the |
| 23 | /// anchor points. |
| 24 | const TextSelectionToolbarAnchors({required this.primaryAnchor, this.secondaryAnchor}); |
| 25 | |
| 26 | /// Creates an instance of [TextSelectionToolbarAnchors] for some selection. |
| 27 | factory TextSelectionToolbarAnchors.fromSelection({ |
| 28 | required RenderBox renderBox, |
| 29 | required double startGlyphHeight, |
| 30 | required double endGlyphHeight, |
| 31 | required List<TextSelectionPoint> selectionEndpoints, |
| 32 | }) { |
| 33 | final Rect selectionRect = getSelectionRect( |
| 34 | renderBox, |
| 35 | startGlyphHeight, |
| 36 | endGlyphHeight, |
| 37 | selectionEndpoints, |
| 38 | ); |
| 39 | if (selectionRect == Rect.zero) { |
| 40 | return const TextSelectionToolbarAnchors(primaryAnchor: Offset.zero); |
| 41 | } |
| 42 | |
| 43 | final Rect editingRegion = _getEditingRegion(renderBox); |
| 44 | return TextSelectionToolbarAnchors( |
| 45 | primaryAnchor: Offset( |
| 46 | selectionRect.left + selectionRect.width / 2, |
| 47 | clampDouble(selectionRect.top, editingRegion.top, editingRegion.bottom), |
| 48 | ), |
| 49 | secondaryAnchor: Offset( |
| 50 | selectionRect.left + selectionRect.width / 2, |
| 51 | clampDouble(selectionRect.bottom, editingRegion.top, editingRegion.bottom), |
| 52 | ), |
| 53 | ); |
| 54 | } |
| 55 | |
| 56 | /// Returns the [Rect] of the [RenderBox] in global coordinates. |
| 57 | static Rect _getEditingRegion(RenderBox renderBox) { |
| 58 | return Rect.fromPoints( |
| 59 | renderBox.localToGlobal(Offset.zero), |
| 60 | renderBox.localToGlobal(renderBox.size.bottomRight(Offset.zero)), |
| 61 | ); |
| 62 | } |
| 63 | |
| 64 | /// Returns the [Rect] covering the given selection in the given [RenderBox] |
| 65 | /// in global coordinates. |
| 66 | static Rect getSelectionRect( |
| 67 | RenderBox renderBox, |
| 68 | double startGlyphHeight, |
| 69 | double endGlyphHeight, |
| 70 | List<TextSelectionPoint> selectionEndpoints, |
| 71 | ) { |
| 72 | final Rect editingRegion = _getEditingRegion(renderBox); |
| 73 | |
| 74 | if (editingRegion.left.isNaN || |
| 75 | editingRegion.top.isNaN || |
| 76 | editingRegion.right.isNaN || |
| 77 | editingRegion.bottom.isNaN) { |
| 78 | return Rect.zero; |
| 79 | } |
| 80 | |
| 81 | final bool isMultiline = |
| 82 | selectionEndpoints.last.point.dy - selectionEndpoints.first.point.dy > endGlyphHeight / 2; |
| 83 | |
| 84 | return Rect.fromLTRB( |
| 85 | isMultiline ? editingRegion.left : editingRegion.left + selectionEndpoints.first.point.dx, |
| 86 | editingRegion.top + selectionEndpoints.first.point.dy - startGlyphHeight, |
| 87 | isMultiline ? editingRegion.right : editingRegion.left + selectionEndpoints.last.point.dx, |
| 88 | editingRegion.top + selectionEndpoints.last.point.dy, |
| 89 | ); |
| 90 | } |
| 91 | |
| 92 | /// The location that the toolbar should attempt to position itself at. |
| 93 | /// |
| 94 | /// If the toolbar doesn't fit at this location, use [secondaryAnchor] if it |
| 95 | /// exists. |
| 96 | final Offset primaryAnchor; |
| 97 | |
| 98 | /// The fallback position that should be used if [primaryAnchor] doesn't work. |
| 99 | final Offset? secondaryAnchor; |
| 100 | } |
| 101 | |