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