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
5import 'package:flutter/foundation.dart';
6import '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
18class 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