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/material.dart';
6///
7/// @docImport 'selectable_region.dart';
8library;
9
10import 'package:flutter/rendering.dart';
11
12import 'framework.dart';
13
14/// A container that handles [SelectionEvent]s for the [Selectable]s in
15/// the subtree.
16///
17/// This widget is useful when one wants to customize selection behaviors for
18/// a group of [Selectable]s
19///
20/// The state of this container is a single selectable and will register
21/// itself to the [registrar] if provided. Otherwise, it will register to the
22/// [SelectionRegistrar] from the context. Consider using a [SelectionArea]
23/// widget to provide a root registrar.
24///
25/// The containers handle the [SelectionEvent]s from the registered
26/// [SelectionRegistrar] and delegate the events to the [delegate].
27///
28/// This widget uses [SelectionRegistrarScope] to host the [delegate] as the
29/// [SelectionRegistrar] for the subtree to collect the [Selectable]s, and
30/// [SelectionEvent]s received by this container are sent to the [delegate] using
31/// the [SelectionHandler] API of the delegate.
32///
33/// {@tool dartpad}
34/// This sample demonstrates how to create a [SelectionContainer] that only
35/// allows selecting everything or nothing with no partial selection.
36///
37/// ** See code in examples/api/lib/material/selection_container/selection_container.0.dart **
38/// {@end-tool}
39///
40/// See also:
41/// * [SelectableRegion], which provides an overview of the selection system.
42/// * [SelectionContainer.disabled], which disable selection for a
43/// subtree.
44class SelectionContainer extends StatefulWidget {
45 /// Creates a selection container to collect the [Selectable]s in the subtree.
46 ///
47 /// If [registrar] is not provided, this selection container gets the
48 /// [SelectionRegistrar] from the context instead.
49 const SelectionContainer({
50 super.key,
51 this.registrar,
52 required SelectionContainerDelegate this.delegate,
53 required this.child,
54 });
55
56 /// Creates a selection container that disables selection for the
57 /// subtree.
58 ///
59 /// {@tool dartpad}
60 /// This sample demonstrates how to disable selection for a Text under a
61 /// SelectionArea.
62 ///
63 /// ** See code in examples/api/lib/material/selection_container/selection_container_disabled.0.dart **
64 /// {@end-tool}
65 const SelectionContainer.disabled({super.key, required this.child})
66 : registrar = null,
67 delegate = null;
68
69 /// The [SelectionRegistrar] this container is registered to.
70 ///
71 /// If null, this widget gets the [SelectionRegistrar] from the current
72 /// context.
73 final SelectionRegistrar? registrar;
74
75 /// {@macro flutter.widgets.ProxyWidget.child}
76 final Widget child;
77
78 /// The delegate for [SelectionEvent]s sent to this selection container.
79 ///
80 /// The [Selectable]s in the subtree are added or removed from this delegate
81 /// using [SelectionRegistrar] API.
82 ///
83 /// This delegate is responsible for updating the selections for the selectables
84 /// under this widget.
85 final SelectionContainerDelegate? delegate;
86
87 /// Gets the immediate ancestor [SelectionRegistrar] of the [BuildContext].
88 ///
89 /// If this returns null, either there is no [SelectionContainer] above
90 /// the [BuildContext] or the immediate [SelectionContainer] is not
91 /// enabled.
92 static SelectionRegistrar? maybeOf(BuildContext context) {
93 final SelectionRegistrarScope? scope =
94 context.dependOnInheritedWidgetOfExactType<SelectionRegistrarScope>();
95 return scope?.registrar;
96 }
97
98 bool get _disabled => delegate == null;
99
100 @override
101 State<SelectionContainer> createState() => _SelectionContainerState();
102}
103
104class _SelectionContainerState extends State<SelectionContainer>
105 with Selectable, SelectionRegistrant {
106 final Set<VoidCallback> _listeners = <VoidCallback>{};
107
108 static const SelectionGeometry _disabledGeometry = SelectionGeometry(
109 status: SelectionStatus.none,
110 hasContent: true,
111 );
112
113 @override
114 void initState() {
115 super.initState();
116 if (!widget._disabled) {
117 widget.delegate!._selectionContainerContext = context;
118 if (widget.registrar != null) {
119 registrar = widget.registrar;
120 }
121 }
122 }
123
124 @override
125 void didUpdateWidget(SelectionContainer oldWidget) {
126 super.didUpdateWidget(oldWidget);
127 if (oldWidget.delegate != widget.delegate) {
128 if (!oldWidget._disabled) {
129 oldWidget.delegate!._selectionContainerContext = null;
130 _listeners.forEach(oldWidget.delegate!.removeListener);
131 }
132 if (!widget._disabled) {
133 widget.delegate!._selectionContainerContext = context;
134 _listeners.forEach(widget.delegate!.addListener);
135 }
136 if (oldWidget.delegate?.value != widget.delegate?.value) {
137 // Avoid concurrent modification.
138 for (final VoidCallback listener in _listeners.toList(growable: false)) {
139 listener();
140 }
141 }
142 }
143 if (widget._disabled) {
144 registrar = null;
145 } else if (widget.registrar != null) {
146 registrar = widget.registrar;
147 }
148 assert(!widget._disabled || registrar == null);
149 }
150
151 @override
152 void didChangeDependencies() {
153 super.didChangeDependencies();
154 if (widget.registrar == null && !widget._disabled) {
155 registrar = SelectionContainer.maybeOf(context);
156 }
157 assert(!widget._disabled || registrar == null);
158 }
159
160 @override
161 void addListener(VoidCallback listener) {
162 assert(!widget._disabled);
163 widget.delegate!.addListener(listener);
164 _listeners.add(listener);
165 }
166
167 @override
168 void removeListener(VoidCallback listener) {
169 widget.delegate?.removeListener(listener);
170 _listeners.remove(listener);
171 }
172
173 @override
174 void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) {
175 assert(!widget._disabled);
176 widget.delegate!.pushHandleLayers(startHandle, endHandle);
177 }
178
179 @override
180 SelectedContent? getSelectedContent() {
181 assert(!widget._disabled);
182 return widget.delegate!.getSelectedContent();
183 }
184
185 @override
186 SelectedContentRange? getSelection() {
187 assert(!widget._disabled);
188 return widget.delegate!.getSelection();
189 }
190
191 @override
192 SelectionResult dispatchSelectionEvent(SelectionEvent event) {
193 assert(!widget._disabled);
194 return widget.delegate!.dispatchSelectionEvent(event);
195 }
196
197 @override
198 SelectionGeometry get value {
199 if (widget._disabled) {
200 return _disabledGeometry;
201 }
202 return widget.delegate!.value;
203 }
204
205 @override
206 Matrix4 getTransformTo(RenderObject? ancestor) {
207 assert(!widget._disabled);
208 return context.findRenderObject()!.getTransformTo(ancestor);
209 }
210
211 @override
212 int get contentLength => widget.delegate!.contentLength;
213
214 @override
215 Size get size => (context.findRenderObject()! as RenderBox).size;
216
217 @override
218 List<Rect> get boundingBoxes => <Rect>[(context.findRenderObject()! as RenderBox).paintBounds];
219
220 @override
221 void dispose() {
222 if (!widget._disabled) {
223 widget.delegate!._selectionContainerContext = null;
224 _listeners.forEach(widget.delegate!.removeListener);
225 }
226 super.dispose();
227 }
228
229 @override
230 Widget build(BuildContext context) {
231 if (widget._disabled) {
232 return SelectionRegistrarScope._disabled(child: widget.child);
233 }
234 return SelectionRegistrarScope(registrar: widget.delegate!, child: widget.child);
235 }
236}
237
238/// An inherited widget to host a [SelectionRegistrar] for the subtree.
239///
240/// Use [SelectionContainer.maybeOf] to get the SelectionRegistrar from
241/// a context.
242///
243/// This widget is automatically created as part of [SelectionContainer] and
244/// is generally not used directly, except for disabling selection for a part
245/// of subtree. In that case, one can wrap the subtree with
246/// [SelectionContainer.disabled].
247class SelectionRegistrarScope extends InheritedWidget {
248 /// Creates a selection registrar scope that host the [registrar].
249 const SelectionRegistrarScope({
250 super.key,
251 required SelectionRegistrar this.registrar,
252 required super.child,
253 });
254
255 /// Creates a selection registrar scope that disables selection for the
256 /// subtree.
257 const SelectionRegistrarScope._disabled({required super.child}) : registrar = null;
258
259 /// The [SelectionRegistrar] hosted by this widget.
260 final SelectionRegistrar? registrar;
261
262 @override
263 bool updateShouldNotify(SelectionRegistrarScope oldWidget) {
264 return oldWidget.registrar != registrar;
265 }
266}
267
268/// A delegate to handle [SelectionEvent]s for a [SelectionContainer].
269///
270/// This delegate needs to implement [SelectionRegistrar] to register
271/// [Selectable]s in the [SelectionContainer] subtree.
272abstract class SelectionContainerDelegate implements SelectionHandler, SelectionRegistrar {
273 BuildContext? _selectionContainerContext;
274
275 /// Gets the paint transform from the [Selectable] child to
276 /// [SelectionContainer] of this delegate.
277 ///
278 /// Returns a matrix that maps the [Selectable] paint coordinate system to the
279 /// coordinate system of [SelectionContainer].
280 ///
281 /// Can only be called after [SelectionContainer] is laid out.
282 Matrix4 getTransformFrom(Selectable child) {
283 assert(
284 _selectionContainerContext?.findRenderObject() != null,
285 'getTransformFrom cannot be called before SelectionContainer is laid out.',
286 );
287 return child.getTransformTo(_selectionContainerContext!.findRenderObject()! as RenderBox);
288 }
289
290 /// Gets the paint transform from the [SelectionContainer] of this delegate to
291 /// the `ancestor`.
292 ///
293 /// Returns a matrix that maps the [SelectionContainer] paint coordinate
294 /// system to the coordinate system of `ancestor`.
295 ///
296 /// If `ancestor` is null, this method returns a matrix that maps from the
297 /// local paint coordinate system to the coordinate system of the
298 /// [PipelineOwner.rootNode].
299 ///
300 /// Can only be called after [SelectionContainer] is laid out.
301 Matrix4 getTransformTo(RenderObject? ancestor) {
302 assert(
303 _selectionContainerContext?.findRenderObject() != null,
304 'getTransformTo cannot be called before SelectionContainer is laid out.',
305 );
306 final RenderBox box = _selectionContainerContext!.findRenderObject()! as RenderBox;
307 return box.getTransformTo(ancestor);
308 }
309
310 /// Whether the [SelectionContainer] has undergone layout and has a size.
311 ///
312 /// See also:
313 ///
314 /// * [RenderBox.hasSize], which is used internally by this method.
315 bool get hasSize {
316 assert(
317 _selectionContainerContext?.findRenderObject() != null,
318 'The _selectionContainerContext must have a renderObject, such as after the first build has completed.',
319 );
320 final RenderBox box = _selectionContainerContext!.findRenderObject()! as RenderBox;
321 return box.hasSize;
322 }
323
324 /// Gets the size of the [SelectionContainer] of this delegate.
325 ///
326 /// Can only be called after [SelectionContainer] is laid out.
327 Size get containerSize {
328 assert(hasSize, 'containerSize cannot be called before SelectionContainer is laid out.');
329 final RenderBox box = _selectionContainerContext!.findRenderObject()! as RenderBox;
330 return box.size;
331 }
332}
333

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com