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 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/rendering.dart'; |
7 | |
8 | /// The position information for a text selection toolbar. |
9 | /// |
10 | /// Typically, a menu will attempt to position itself at [primaryAnchor], and |
11 | /// if that's not possible, then it will use [secondaryAnchor] instead, if it |
12 | /// exists. |
13 | /// |
14 | /// See also: |
15 | /// |
16 | /// * [AdaptiveTextSelectionToolbar.anchors], which is of this type. |
17 | @immutable |
18 | class TextSelectionToolbarAnchors { |
19 | /// Creates an instance of [TextSelectionToolbarAnchors] directly from the |
20 | /// anchor points. |
21 | const TextSelectionToolbarAnchors({ |
22 | required this.primaryAnchor, |
23 | this.secondaryAnchor, |
24 | }); |
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 editingRegion = Rect.fromPoints( |
34 | renderBox.localToGlobal(Offset.zero), |
35 | renderBox.localToGlobal(renderBox.size.bottomRight(Offset.zero)), |
36 | ); |
37 | |
38 | if (editingRegion.left.isNaN || editingRegion.top.isNaN |
39 | || editingRegion.right.isNaN || editingRegion.bottom.isNaN) { |
40 | return const TextSelectionToolbarAnchors(primaryAnchor: Offset.zero); |
41 | } |
42 | |
43 | final bool isMultiline = selectionEndpoints.last.point.dy - selectionEndpoints.first.point.dy > |
44 | endGlyphHeight / 2; |
45 | |
46 | final Rect selectionRect = Rect.fromLTRB( |
47 | isMultiline |
48 | ? editingRegion.left |
49 | : editingRegion.left + selectionEndpoints.first.point.dx, |
50 | editingRegion.top + selectionEndpoints.first.point.dy - startGlyphHeight, |
51 | isMultiline |
52 | ? editingRegion.right |
53 | : editingRegion.left + selectionEndpoints.last.point.dx, |
54 | editingRegion.top + selectionEndpoints.last.point.dy, |
55 | ); |
56 | |
57 | return TextSelectionToolbarAnchors( |
58 | primaryAnchor: Offset( |
59 | selectionRect.left + selectionRect.width / 2, |
60 | clampDouble(selectionRect.top, editingRegion.top, editingRegion.bottom), |
61 | ), |
62 | secondaryAnchor: Offset( |
63 | selectionRect.left + selectionRect.width / 2, |
64 | clampDouble(selectionRect.bottom, editingRegion.top, editingRegion.bottom), |
65 | ), |
66 | ); |
67 | } |
68 | |
69 | /// The location that the toolbar should attempt to position itself at. |
70 | /// |
71 | /// If the toolbar doesn't fit at this location, use [secondaryAnchor] if it |
72 | /// exists. |
73 | final Offset primaryAnchor; |
74 | |
75 | /// The fallback position that should be used if [primaryAnchor] doesn't work. |
76 | final Offset? secondaryAnchor; |
77 | } |
78 | |