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 'dart:math' as math;
6
7import '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.
17class 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