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';
6library;
7
8import 'package:flutter/foundation.dart';
9import '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
21class 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