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 | import 'dart:math'; |
6 | import 'dart:ui' as ui show Color; |
7 | |
8 | import 'package:flutter/animation.dart'; |
9 | import 'package:flutter/foundation.dart'; |
10 | import 'package:flutter/semantics.dart'; |
11 | |
12 | import 'layer.dart'; |
13 | import 'object.dart'; |
14 | import 'proxy_box.dart'; |
15 | import 'sliver.dart'; |
16 | |
17 | /// A base class for sliver render objects that resemble their children. |
18 | /// |
19 | /// A proxy sliver has a single child and mimics all the properties of |
20 | /// that child by calling through to the child for each function in the render |
21 | /// sliver protocol. For example, a proxy sliver determines its geometry by |
22 | /// asking its sliver child to layout with the same constraints and then |
23 | /// matching the geometry. |
24 | /// |
25 | /// A proxy sliver isn't useful on its own because you might as well just |
26 | /// replace the proxy sliver with its child. However, RenderProxySliver is a |
27 | /// useful base class for render objects that wish to mimic most, but not all, |
28 | /// of the properties of their sliver child. |
29 | /// |
30 | /// See also: |
31 | /// |
32 | /// * [RenderProxyBox], a base class for render boxes that resemble their |
33 | /// children. |
34 | abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> { |
35 | /// Creates a proxy render sliver. |
36 | /// |
37 | /// Proxy render slivers aren't created directly because they proxy |
38 | /// the render sliver protocol to their sliver [child]. Instead, use one of |
39 | /// the subclasses. |
40 | RenderProxySliver([RenderSliver? child]) { |
41 | this.child = child; |
42 | } |
43 | |
44 | @override |
45 | void setupParentData(RenderObject child) { |
46 | if (child.parentData is! SliverPhysicalParentData) { |
47 | child.parentData = SliverPhysicalParentData(); |
48 | } |
49 | } |
50 | |
51 | @override |
52 | void performLayout() { |
53 | assert(child != null); |
54 | child!.layout(constraints, parentUsesSize: true); |
55 | geometry = child!.geometry; |
56 | } |
57 | |
58 | @override |
59 | void paint(PaintingContext context, Offset offset) { |
60 | if (child != null) { |
61 | context.paintChild(child!, offset); |
62 | } |
63 | } |
64 | |
65 | @override |
66 | bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) { |
67 | return child != null |
68 | && child!.geometry!.hitTestExtent > 0 |
69 | && child!.hitTest( |
70 | result, |
71 | mainAxisPosition: mainAxisPosition, |
72 | crossAxisPosition: crossAxisPosition, |
73 | ); |
74 | } |
75 | |
76 | @override |
77 | double childMainAxisPosition(RenderSliver child) { |
78 | assert(child == this.child); |
79 | return 0.0; |
80 | } |
81 | |
82 | @override |
83 | void applyPaintTransform(RenderObject child, Matrix4 transform) { |
84 | final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; |
85 | childParentData.applyPaintTransform(transform); |
86 | } |
87 | } |
88 | |
89 | /// Makes its sliver child partially transparent. |
90 | /// |
91 | /// This class paints its sliver child into an intermediate buffer and then |
92 | /// blends the sliver child back into the scene, partially transparent. |
93 | /// |
94 | /// For values of opacity other than 0.0 and 1.0, this class is relatively |
95 | /// expensive, because it requires painting the sliver child into an intermediate |
96 | /// buffer. For the value 0.0, the sliver child is not painted at all. |
97 | /// For the value 1.0, the sliver child is painted immediately without an |
98 | /// intermediate buffer. |
99 | class RenderSliverOpacity extends RenderProxySliver { |
100 | /// Creates a partially transparent render object. |
101 | /// |
102 | /// The [opacity] argument must be between 0.0 and 1.0, inclusive. |
103 | RenderSliverOpacity({ |
104 | double opacity = 1.0, |
105 | bool alwaysIncludeSemantics = false, |
106 | RenderSliver? sliver, |
107 | }) : assert(opacity >= 0.0 && opacity <= 1.0), |
108 | _opacity = opacity, |
109 | _alwaysIncludeSemantics = alwaysIncludeSemantics, |
110 | _alpha = ui.Color.getAlphaFromOpacity(opacity) { |
111 | child = sliver; |
112 | } |
113 | |
114 | @override |
115 | bool get alwaysNeedsCompositing => child != null && (_alpha > 0); |
116 | |
117 | int _alpha; |
118 | |
119 | /// The fraction to scale the child's alpha value. |
120 | /// |
121 | /// An opacity of one is fully opaque. An opacity of zero is fully transparent |
122 | /// (i.e. invisible). |
123 | /// |
124 | /// Values one and zero are painted with a fast path. Other values require |
125 | /// painting the child into an intermediate buffer, which is expensive. |
126 | double get opacity => _opacity; |
127 | double _opacity; |
128 | set opacity(double value) { |
129 | assert(value >= 0.0 && value <= 1.0); |
130 | if (_opacity == value) { |
131 | return; |
132 | } |
133 | final bool didNeedCompositing = alwaysNeedsCompositing; |
134 | final bool wasVisible = _alpha != 0; |
135 | _opacity = value; |
136 | _alpha = ui.Color.getAlphaFromOpacity(_opacity); |
137 | if (didNeedCompositing != alwaysNeedsCompositing) { |
138 | markNeedsCompositingBitsUpdate(); |
139 | } |
140 | markNeedsPaint(); |
141 | if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) { |
142 | markNeedsSemanticsUpdate(); |
143 | } |
144 | } |
145 | |
146 | /// Whether child semantics are included regardless of the opacity. |
147 | /// |
148 | /// If false, semantics are excluded when [opacity] is 0.0. |
149 | /// |
150 | /// Defaults to false. |
151 | bool get alwaysIncludeSemantics => _alwaysIncludeSemantics; |
152 | bool _alwaysIncludeSemantics; |
153 | set alwaysIncludeSemantics(bool value) { |
154 | if (value == _alwaysIncludeSemantics) { |
155 | return; |
156 | } |
157 | _alwaysIncludeSemantics = value; |
158 | markNeedsSemanticsUpdate(); |
159 | } |
160 | |
161 | @override |
162 | void paint(PaintingContext context, Offset offset) { |
163 | if (child != null && child!.geometry!.visible) { |
164 | if (_alpha == 0) { |
165 | // No need to keep the layer. We'll create a new one if necessary. |
166 | layer = null; |
167 | return; |
168 | } |
169 | assert(needsCompositing); |
170 | layer = context.pushOpacity( |
171 | offset, |
172 | _alpha, |
173 | super.paint, |
174 | oldLayer: layer as OpacityLayer?, |
175 | ); |
176 | assert(() { |
177 | layer!.debugCreator = debugCreator; |
178 | return true; |
179 | }()); |
180 | } |
181 | } |
182 | |
183 | @override |
184 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
185 | if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) { |
186 | visitor(child!); |
187 | } |
188 | } |
189 | |
190 | @override |
191 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
192 | super.debugFillProperties(properties); |
193 | properties.add(DoubleProperty('opacity' , opacity)); |
194 | properties.add(FlagProperty('alwaysIncludeSemantics' , value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics' )); |
195 | } |
196 | } |
197 | |
198 | /// A render object that is invisible during hit testing. |
199 | /// |
200 | /// When [ignoring] is true, this render object (and its subtree) is invisible |
201 | /// to hit testing. It still consumes space during layout and paints its sliver |
202 | /// child as usual. It just cannot be the target of located events, because its |
203 | /// render object returns false from [hitTest]. |
204 | /// |
205 | /// ## Semantics |
206 | /// |
207 | /// Using this class may also affect how the semantics subtree underneath is |
208 | /// collected. |
209 | /// |
210 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
211 | /// |
212 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
213 | class RenderSliverIgnorePointer extends RenderProxySliver { |
214 | /// Creates a render object that is invisible to hit testing. |
215 | RenderSliverIgnorePointer({ |
216 | RenderSliver? sliver, |
217 | bool ignoring = true, |
218 | @Deprecated( |
219 | 'Create a custom sliver ignore pointer widget instead. ' |
220 | 'This feature was deprecated after v3.8.0-12.0.pre.' |
221 | ) |
222 | bool? ignoringSemantics, |
223 | }) : _ignoring = ignoring, |
224 | _ignoringSemantics = ignoringSemantics { |
225 | child = sliver; |
226 | } |
227 | |
228 | /// Whether this render object is ignored during hit testing. |
229 | /// |
230 | /// Regardless of whether this render object is ignored during hit testing, it |
231 | /// will still consume space during layout and be visible during painting. |
232 | /// |
233 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
234 | bool get ignoring => _ignoring; |
235 | bool _ignoring; |
236 | set ignoring(bool value) { |
237 | if (value == _ignoring) { |
238 | return; |
239 | } |
240 | _ignoring = value; |
241 | if (ignoringSemantics == null) { |
242 | markNeedsSemanticsUpdate(); |
243 | } |
244 | } |
245 | |
246 | /// Whether the semantics of this render object is ignored when compiling the |
247 | /// semantics tree. |
248 | /// |
249 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
250 | @Deprecated( |
251 | 'Create a custom sliver ignore pointer widget instead. ' |
252 | 'This feature was deprecated after v3.8.0-12.0.pre.' |
253 | ) |
254 | bool? get ignoringSemantics => _ignoringSemantics; |
255 | bool? _ignoringSemantics; |
256 | set ignoringSemantics(bool? value) { |
257 | if (value == _ignoringSemantics) { |
258 | return; |
259 | } |
260 | _ignoringSemantics = value; |
261 | markNeedsSemanticsUpdate(); |
262 | } |
263 | |
264 | @override |
265 | bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) { |
266 | return !ignoring |
267 | && super.hitTest( |
268 | result, |
269 | mainAxisPosition: mainAxisPosition, |
270 | crossAxisPosition: crossAxisPosition, |
271 | ); |
272 | } |
273 | |
274 | @override |
275 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
276 | if (_ignoringSemantics ?? false) { |
277 | return; |
278 | } |
279 | super.visitChildrenForSemantics(visitor); |
280 | } |
281 | |
282 | @override |
283 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
284 | super.describeSemanticsConfiguration(config); |
285 | // Do not block user interactions if _ignoringSemantics is false; otherwise, |
286 | // delegate to absorbing |
287 | config.isBlockingUserActions = ignoring && (_ignoringSemantics ?? true); |
288 | } |
289 | |
290 | @override |
291 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
292 | super.debugFillProperties(properties); |
293 | properties.add(DiagnosticsProperty<bool>('ignoring' , ignoring)); |
294 | properties.add( |
295 | DiagnosticsProperty<bool>( |
296 | 'ignoringSemantics' , |
297 | ignoringSemantics, |
298 | description: ignoringSemantics == null ? null : 'implicitly $ignoringSemantics' , |
299 | ), |
300 | ); |
301 | } |
302 | } |
303 | |
304 | /// Lays the sliver child out as if it was in the tree, but without painting |
305 | /// anything, without making the sliver child available for hit testing, and |
306 | /// without taking any room in the parent. |
307 | class RenderSliverOffstage extends RenderProxySliver { |
308 | /// Creates an offstage render object. |
309 | RenderSliverOffstage({ |
310 | bool offstage = true, |
311 | RenderSliver? sliver, |
312 | }) : _offstage = offstage { |
313 | child = sliver; |
314 | } |
315 | |
316 | /// Whether the sliver child is hidden from the rest of the tree. |
317 | /// |
318 | /// If true, the sliver child is laid out as if it was in the tree, but |
319 | /// without painting anything, without making the sliver child available for |
320 | /// hit testing, and without taking any room in the parent. |
321 | /// |
322 | /// If false, the sliver child is included in the tree as normal. |
323 | bool get offstage => _offstage; |
324 | bool _offstage; |
325 | |
326 | set offstage(bool value) { |
327 | if (value == _offstage) { |
328 | return; |
329 | } |
330 | _offstage = value; |
331 | markNeedsLayoutForSizedByParentChange(); |
332 | } |
333 | |
334 | @override |
335 | void performLayout() { |
336 | assert(child != null); |
337 | child!.layout(constraints, parentUsesSize: true); |
338 | if (!offstage) { |
339 | geometry = child!.geometry; |
340 | } else { |
341 | geometry = SliverGeometry.zero; |
342 | } |
343 | } |
344 | |
345 | @override |
346 | bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) { |
347 | return !offstage && super.hitTest( |
348 | result, |
349 | mainAxisPosition: mainAxisPosition, |
350 | crossAxisPosition: crossAxisPosition, |
351 | ); |
352 | } |
353 | |
354 | @override |
355 | bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) { |
356 | return !offstage |
357 | && child != null |
358 | && child!.geometry!.hitTestExtent > 0 |
359 | && child!.hitTest( |
360 | result, |
361 | mainAxisPosition: mainAxisPosition, |
362 | crossAxisPosition: crossAxisPosition, |
363 | ); |
364 | } |
365 | |
366 | @override |
367 | void paint(PaintingContext context, Offset offset) { |
368 | if (offstage) { |
369 | return; |
370 | } |
371 | context.paintChild(child!, offset); |
372 | } |
373 | |
374 | @override |
375 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
376 | if (offstage) { |
377 | return; |
378 | } |
379 | super.visitChildrenForSemantics(visitor); |
380 | } |
381 | |
382 | @override |
383 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
384 | super.debugFillProperties(properties); |
385 | properties.add(DiagnosticsProperty<bool>('offstage' , offstage)); |
386 | } |
387 | |
388 | @override |
389 | List<DiagnosticsNode> debugDescribeChildren() { |
390 | if (child == null) { |
391 | return <DiagnosticsNode>[]; |
392 | } |
393 | return <DiagnosticsNode>[ |
394 | child!.toDiagnosticsNode( |
395 | name: 'child' , |
396 | style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse, |
397 | ), |
398 | ]; |
399 | } |
400 | } |
401 | |
402 | /// Makes its sliver child partially transparent, driven from an [Animation]. |
403 | /// |
404 | /// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>] |
405 | /// rather than a [double] to control the opacity. |
406 | class RenderSliverAnimatedOpacity extends RenderProxySliver with RenderAnimatedOpacityMixin<RenderSliver> { |
407 | /// Creates a partially transparent render object. |
408 | RenderSliverAnimatedOpacity({ |
409 | required Animation<double> opacity, |
410 | bool alwaysIncludeSemantics = false, |
411 | RenderSliver? sliver, |
412 | }) { |
413 | this.opacity = opacity; |
414 | this.alwaysIncludeSemantics = alwaysIncludeSemantics; |
415 | child = sliver; |
416 | } |
417 | } |
418 | |
419 | /// Applies a cross-axis constraint to its sliver child. |
420 | /// |
421 | /// This render object takes a [maxExtent] parameter and uses the smaller of |
422 | /// [maxExtent] and the parent's [SliverConstraints.crossAxisExtent] as the |
423 | /// cross axis extent of the [SliverConstraints] passed to the sliver child. |
424 | class RenderSliverConstrainedCrossAxis extends RenderProxySliver { |
425 | /// Creates a render object that constrains the cross axis extent of its sliver child. |
426 | /// |
427 | /// The [maxExtent] parameter must be nonnegative. |
428 | RenderSliverConstrainedCrossAxis({ |
429 | required double maxExtent |
430 | }) : _maxExtent = maxExtent, |
431 | assert(maxExtent >= 0.0); |
432 | |
433 | /// The cross axis extent to apply to the sliver child. |
434 | /// |
435 | /// This value must be nonnegative. |
436 | double get maxExtent => _maxExtent; |
437 | double _maxExtent; |
438 | set maxExtent(double value) { |
439 | if (_maxExtent == value) { |
440 | return; |
441 | } |
442 | _maxExtent = value; |
443 | markNeedsLayout(); |
444 | } |
445 | |
446 | @override |
447 | void performLayout() { |
448 | assert(child != null); |
449 | assert(maxExtent >= 0.0); |
450 | child!.layout( |
451 | constraints.copyWith(crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent)), |
452 | parentUsesSize: true, |
453 | ); |
454 | final SliverGeometry childLayoutGeometry = child!.geometry!; |
455 | geometry = childLayoutGeometry.copyWith(crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent)); |
456 | } |
457 | } |
458 | |