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:math';
6import 'dart:ui' as ui show Color;
7
8import 'package:flutter/animation.dart';
9import 'package:flutter/foundation.dart';
10import 'package:flutter/semantics.dart';
11
12import 'layer.dart';
13import 'object.dart';
14import 'proxy_box.dart';
15import '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.
34abstract 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.
99class 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}
213class 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.
307class 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.
406class 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.
424class 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