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:ui';
6
7import 'framework.dart';
8
9/// The interface for defining the algorithm for a boundary that a specified shape is dragged within.
10///
11/// See also:
12/// * [DragBoundary], an [InheritedWidget] that provides a [DragBoundaryDelegate] to its descendants.
13///
14/// `T` is a data class that defines the shape being dragged. For example, when dragging a rectangle within the boundary,
15/// `T` should be a `Rect`.
16abstract class DragBoundaryDelegate<T> {
17 /// Returns whether the specified dragged object is within the boundary.
18 bool isWithinBoundary(T draggedObject);
19
20 /// Returns the given dragged object after moving it fully inside
21 /// the boundary with the shortest distance.
22 ///
23 /// If the bounds cannot contain the dragged object, an exception is thrown.
24 T nearestPositionWithinBoundary(T draggedObject);
25}
26
27class _DragBoundaryDelegateForRect extends DragBoundaryDelegate<Rect> {
28 _DragBoundaryDelegateForRect(this.boundary);
29 final Rect? boundary;
30 @override
31 bool isWithinBoundary(Rect draggedObject) {
32 if (boundary == null) {
33 return true;
34 }
35 return boundary!.contains(draggedObject.topLeft) &&
36 boundary!.contains(draggedObject.bottomRight);
37 }
38
39 @override
40 Rect nearestPositionWithinBoundary(Rect draggedObject) {
41 if (boundary == null) {
42 return draggedObject;
43 }
44 if (boundary!.right - draggedObject.width < boundary!.left ||
45 boundary!.bottom - draggedObject.height < boundary!.top) {
46 throw FlutterError(
47 'The rect is larger than the boundary. '
48 'The rect width must be less than the boundary width, and the rect height must be less than the boundary height.',
49 );
50 }
51 final double left = clampDouble(
52 draggedObject.left,
53 boundary!.left,
54 boundary!.right - draggedObject.width,
55 );
56 final double top = clampDouble(
57 draggedObject.top,
58 boundary!.top,
59 boundary!.bottom - draggedObject.height,
60 );
61 return Rect.fromLTWH(left, top, draggedObject.width, draggedObject.height);
62 }
63}
64
65/// Provides a [DragBoundaryDelegate] for its descendants whose bounds are those defined by this widget.
66///
67/// [forRectOf] and [forRectMaybeOf] returns a delegate for a drag object of type [Rect].
68///
69/// {@tool dartpad}
70/// This example demonstrates dragging a red box, constrained within the bounds
71/// of a green box.
72///
73/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.3.dart **
74/// {@end-tool}
75class DragBoundary extends InheritedWidget {
76 /// Creates a widget that provides a boundary to its descendants.
77 const DragBoundary({required super.child, super.key});
78
79 /// {@template flutter.widgets.DragBoundary.forRectOf}
80 /// Retrieve the [DragBoundary] from the nearest ancestor to
81 /// get its [DragBoundaryDelegate] of [Rect].
82 ///
83 /// The [useGlobalPosition] specifies whether to retrieve the [DragBoundaryDelegate] of type
84 /// [Rect] in global coordinates. If false, the local coordinates of the boundary are used. Defaults to true.
85 /// {@endtemplate}
86 ///
87 /// If no [DragBoundary] ancestor is found, the delegate will return a delegate that allows the drag object to move freely.
88 static DragBoundaryDelegate<Rect> forRectOf(
89 BuildContext context, {
90 bool useGlobalPosition = true,
91 }) {
92 return forRectMaybeOf(context, useGlobalPosition: useGlobalPosition) ??
93 _DragBoundaryDelegateForRect(null);
94 }
95
96 /// {@macro flutter.widgets.DragBoundary.forRectOf}
97 ///
98 /// returns null if not ancestor is found.
99 static DragBoundaryDelegate<Rect>? forRectMaybeOf(
100 BuildContext context, {
101 bool useGlobalPosition = true,
102 }) {
103 final InheritedElement? element = context
104 .getElementForInheritedWidgetOfExactType<DragBoundary>();
105 if (element == null) {
106 return null;
107 }
108 final RenderBox? rb = element.findRenderObject() as RenderBox?;
109 assert(rb != null && rb.hasSize, 'DragBoundary is not available');
110 final Rect boundary = useGlobalPosition
111 ? Rect.fromPoints(
112 rb!.localToGlobal(Offset.zero),
113 rb.localToGlobal(rb.size.bottomRight(Offset.zero)),
114 )
115 : Offset.zero & rb!.size;
116 return _DragBoundaryDelegateForRect(boundary);
117 }
118
119 @override
120 bool updateShouldNotify(covariant InheritedWidget oldWidget) {
121 return true;
122 }
123}
124