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 'dart:math' as math; |
6 | |
7 | import 'package:flutter/rendering.dart'; |
8 | |
9 | /// Positions the toolbar above [anchorAbove] if it fits, or otherwise below |
10 | /// [anchorBelow]. |
11 | /// |
12 | /// See also: |
13 | /// |
14 | /// * [TextSelectionToolbar], which uses this to position itself. |
15 | /// * [CupertinoTextSelectionToolbar], which also uses this to position |
16 | /// itself. |
17 | class TextSelectionToolbarLayoutDelegate extends SingleChildLayoutDelegate { |
18 | /// Creates an instance of TextSelectionToolbarLayoutDelegate. |
19 | TextSelectionToolbarLayoutDelegate({ |
20 | required this.anchorAbove, |
21 | required this.anchorBelow, |
22 | this.fitsAbove, |
23 | }); |
24 | |
25 | /// {@macro flutter.material.TextSelectionToolbar.anchorAbove} |
26 | /// |
27 | /// Should be provided in local coordinates. |
28 | final Offset anchorAbove; |
29 | |
30 | /// {@macro flutter.material.TextSelectionToolbar.anchorAbove} |
31 | /// |
32 | /// Should be provided in local coordinates. |
33 | final Offset anchorBelow; |
34 | |
35 | /// Whether or not the child should be considered to fit above anchorAbove. |
36 | /// |
37 | /// Typically used to force the child to be drawn at anchorAbove even when it |
38 | /// doesn't fit, such as when the Material [TextSelectionToolbar] draws an |
39 | /// open overflow menu. |
40 | /// |
41 | /// If not provided, it will be calculated. |
42 | final bool? fitsAbove; |
43 | |
44 | /// Return the value that centers width as closely as possible to position |
45 | /// while fitting inside of min and max. |
46 | static double centerOn(double position, double width, double max) { |
47 | // If it overflows on the left, put it as far left as possible. |
48 | if (position - width / 2.0 < 0.0) { |
49 | return 0.0; |
50 | } |
51 | |
52 | // If it overflows on the right, put it as far right as possible. |
53 | if (position + width / 2.0 > max) { |
54 | return max - width; |
55 | } |
56 | |
57 | // Otherwise it fits while perfectly centered. |
58 | return position - width / 2.0; |
59 | } |
60 | |
61 | @override |
62 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) { |
63 | return constraints.loosen(); |
64 | } |
65 | |
66 | @override |
67 | Offset getPositionForChild(Size size, Size childSize) { |
68 | final bool fitsAbove = this.fitsAbove ?? anchorAbove.dy >= childSize.height; |
69 | final Offset anchor = fitsAbove ? anchorAbove : anchorBelow; |
70 | |
71 | return Offset( |
72 | centerOn( |
73 | anchor.dx, |
74 | childSize.width, |
75 | size.width, |
76 | ), |
77 | fitsAbove |
78 | ? math.max(0.0, anchor.dy - childSize.height) |
79 | : anchor.dy, |
80 | ); |
81 | } |
82 | |
83 | @override |
84 | bool shouldRelayout(TextSelectionToolbarLayoutDelegate oldDelegate) { |
85 | return anchorAbove != oldDelegate.anchorAbove |
86 | || anchorBelow != oldDelegate.anchorBelow |
87 | || fitsAbove != oldDelegate.fitsAbove; |
88 | } |
89 | } |
90 | |