| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| |
| import 'package:flutter/rendering.dart'; |
| |
| /// Positions the toolbar above [anchorAbove] if it fits, or otherwise below |
| /// [anchorBelow]. |
| /// |
| /// See also: |
| /// |
| /// * [TextSelectionToolbar], which uses this to position itself. |
| /// * [CupertinoTextSelectionToolbar], which also uses this to position |
| /// itself. |
| class TextSelectionToolbarLayoutDelegate extends SingleChildLayoutDelegate { |
| /// Creates an instance of TextSelectionToolbarLayoutDelegate. |
| TextSelectionToolbarLayoutDelegate({ |
| required this.anchorAbove, |
| required this.anchorBelow, |
| this.fitsAbove, |
| }); |
| |
| /// {@macro flutter.material.TextSelectionToolbar.anchorAbove} |
| /// |
| /// Should be provided in local coordinates. |
| final Offset anchorAbove; |
| |
| /// {@macro flutter.material.TextSelectionToolbar.anchorAbove} |
| /// |
| /// Should be provided in local coordinates. |
| final Offset anchorBelow; |
| |
| /// Whether or not the child should be considered to fit above anchorAbove. |
| /// |
| /// Typically used to force the child to be drawn at anchorAbove even when it |
| /// doesn't fit, such as when the Material [TextSelectionToolbar] draws an |
| /// open overflow menu. |
| /// |
| /// If not provided, it will be calculated. |
| final bool? fitsAbove; |
| |
| // Return the value that centers width as closely as possible to position |
| // while fitting inside of min and max. |
| static double _centerOn(double position, double width, double max) { |
| // If it overflows on the left, put it as far left as possible. |
| if (position - width / 2.0 < 0.0) { |
| return 0.0; |
| } |
| |
| // If it overflows on the right, put it as far right as possible. |
| if (position + width / 2.0 > max) { |
| return max - width; |
| } |
| |
| // Otherwise it fits while perfectly centered. |
| return position - width / 2.0; |
| } |
| |
| @override |
| BoxConstraints getConstraintsForChild(BoxConstraints constraints) { |
| return constraints.loosen(); |
| } |
| |
| @override |
| Offset getPositionForChild(Size size, Size childSize) { |
| final bool fitsAbove = this.fitsAbove ?? anchorAbove.dy >= childSize.height; |
| final Offset anchor = fitsAbove ? anchorAbove : anchorBelow; |
| |
| return Offset( |
| _centerOn( |
| anchor.dx, |
| childSize.width, |
| size.width, |
| ), |
| fitsAbove |
| ? math.max(0.0, anchor.dy - childSize.height) |
| : anchor.dy, |
| ); |
| } |
| |
| @override |
| bool shouldRelayout(TextSelectionToolbarLayoutDelegate oldDelegate) { |
| return anchorAbove != oldDelegate.anchorAbove |
| || anchorBelow != oldDelegate.anchorBelow |
| || fitsAbove != oldDelegate.fitsAbove; |
| } |
| } |