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' as ui show Color, Gradient, Image, ImageFilter;
6
7import 'package:flutter/animation.dart';
8import 'package:flutter/foundation.dart';
9import 'package:flutter/gestures.dart';
10import 'package:flutter/semantics.dart';
11import 'package:flutter/services.dart';
12
13import 'box.dart';
14import 'layer.dart';
15import 'layout_helper.dart';
16import 'object.dart';
17
18export 'package:flutter/gestures.dart' show
19 PointerCancelEvent,
20 PointerDownEvent,
21 PointerEvent,
22 PointerMoveEvent,
23 PointerUpEvent;
24
25/// A base class for render boxes that resemble their children.
26///
27/// A proxy box has a single child and mimics all the properties of that
28/// child by calling through to the child for each function in the render box
29/// protocol. For example, a proxy box determines its size by asking its child
30/// to layout with the same constraints and then matching the size.
31///
32/// A proxy box isn't useful on its own because you might as well just replace
33/// the proxy box with its child. However, RenderProxyBox is a useful base class
34/// for render objects that wish to mimic most, but not all, of the properties
35/// of their child.
36///
37/// See also:
38///
39/// * [RenderProxySliver], a base class for render slivers that resemble their
40/// children.
41class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin<RenderBox> {
42 /// Creates a proxy render box.
43 ///
44 /// Proxy render boxes are rarely created directly because they proxy
45 /// the render box protocol to [child]. Instead, consider using one of the
46 /// subclasses.
47 RenderProxyBox([RenderBox? child]) {
48 this.child = child;
49 }
50}
51
52/// Implementation of [RenderProxyBox].
53///
54/// Use this mixin in situations where the proxying behavior
55/// of [RenderProxyBox] is desired but inheriting from [RenderProxyBox] is
56/// impractical (e.g. because you want to inherit from a different class).
57///
58/// If a class already inherits from [RenderProxyBox] and also uses this mixin,
59/// you can safely delete the use of the mixin.
60@optionalTypeArgs
61mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChildMixin<T> {
62 @override
63 void setupParentData(RenderObject child) {
64 // We don't actually use the offset argument in BoxParentData, so let's
65 // avoid allocating it at all.
66 if (child.parentData is! ParentData) {
67 child.parentData = ParentData();
68 }
69 }
70
71 @override
72 double computeMinIntrinsicWidth(double height) {
73 return child?.getMinIntrinsicWidth(height) ?? 0.0;
74 }
75
76 @override
77 double computeMaxIntrinsicWidth(double height) {
78 return child?.getMaxIntrinsicWidth(height) ?? 0.0;
79 }
80
81 @override
82 double computeMinIntrinsicHeight(double width) {
83 return child?.getMinIntrinsicHeight(width) ?? 0.0;
84 }
85
86 @override
87 double computeMaxIntrinsicHeight(double width) {
88 return child?.getMaxIntrinsicHeight(width) ?? 0.0;
89 }
90
91 @override
92 double? computeDistanceToActualBaseline(TextBaseline baseline) {
93 return child?.getDistanceToActualBaseline(baseline)
94 ?? super.computeDistanceToActualBaseline(baseline);
95 }
96
97 @override
98 @protected
99 Size computeDryLayout(covariant BoxConstraints constraints) {
100 return child?.getDryLayout(constraints) ?? computeSizeForNoChild(constraints);
101 }
102
103 @override
104 void performLayout() {
105 size = (child?..layout(constraints, parentUsesSize: true))?.size
106 ?? computeSizeForNoChild(constraints);
107 return;
108 }
109
110 /// Calculate the size the [RenderProxyBox] would have under the given
111 /// [BoxConstraints] for the case where it does not have a child.
112 Size computeSizeForNoChild(BoxConstraints constraints) {
113 return constraints.smallest;
114 }
115
116 @override
117 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
118 return child?.hitTest(result, position: position) ?? false;
119 }
120
121 @override
122 void applyPaintTransform(RenderObject child, Matrix4 transform) { }
123
124 @override
125 void paint(PaintingContext context, Offset offset) {
126 final RenderBox? child = this.child;
127 if (child == null) {
128 return;
129 }
130 context.paintChild(child, offset);
131 }
132}
133
134/// How to behave during hit tests.
135enum HitTestBehavior {
136 /// Targets that defer to their children receive events within their bounds
137 /// only if one of their children is hit by the hit test.
138 deferToChild,
139
140 /// Opaque targets can be hit by hit tests, causing them to both receive
141 /// events within their bounds and prevent targets visually behind them from
142 /// also receiving events.
143 opaque,
144
145 /// Translucent targets both receive events within their bounds and permit
146 /// targets visually behind them to also receive events.
147 translucent,
148}
149
150/// A RenderProxyBox subclass that allows you to customize the
151/// hit-testing behavior.
152abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox {
153 /// Initializes member variables for subclasses.
154 ///
155 /// By default, the [behavior] is [HitTestBehavior.deferToChild].
156 RenderProxyBoxWithHitTestBehavior({
157 this.behavior = HitTestBehavior.deferToChild,
158 RenderBox? child,
159 }) : super(child);
160
161 /// How to behave during hit testing when deciding how the hit test propagates
162 /// to children and whether to consider targets behind this one.
163 ///
164 /// Defaults to [HitTestBehavior.deferToChild].
165 ///
166 /// See [HitTestBehavior] for the allowed values and their meanings.
167 HitTestBehavior behavior;
168
169 @override
170 bool hitTest(BoxHitTestResult result, { required Offset position }) {
171 bool hitTarget = false;
172 if (size.contains(position)) {
173 hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
174 if (hitTarget || behavior == HitTestBehavior.translucent) {
175 result.add(BoxHitTestEntry(this, position));
176 }
177 }
178 return hitTarget;
179 }
180
181 @override
182 bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
183
184 @override
185 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
186 super.debugFillProperties(properties);
187 properties.add(EnumProperty<HitTestBehavior>('behavior', behavior, defaultValue: null));
188 }
189}
190
191/// Imposes additional constraints on its child.
192///
193/// A render constrained box proxies most functions in the render box protocol
194/// to its child, except that when laying out its child, it tightens the
195/// constraints provided by its parent by enforcing the [additionalConstraints]
196/// as well.
197///
198/// For example, if you wanted [child] to have a minimum height of 50.0 logical
199/// pixels, you could use `const BoxConstraints(minHeight: 50.0)` as the
200/// [additionalConstraints].
201class RenderConstrainedBox extends RenderProxyBox {
202 /// Creates a render box that constrains its child.
203 ///
204 /// The [additionalConstraints] argument must be valid.
205 RenderConstrainedBox({
206 RenderBox? child,
207 required BoxConstraints additionalConstraints,
208 }) : assert(additionalConstraints.debugAssertIsValid()),
209 _additionalConstraints = additionalConstraints,
210 super(child);
211
212 /// Additional constraints to apply to [child] during layout.
213 BoxConstraints get additionalConstraints => _additionalConstraints;
214 BoxConstraints _additionalConstraints;
215 set additionalConstraints(BoxConstraints value) {
216 assert(value.debugAssertIsValid());
217 if (_additionalConstraints == value) {
218 return;
219 }
220 _additionalConstraints = value;
221 markNeedsLayout();
222 }
223
224 @override
225 double computeMinIntrinsicWidth(double height) {
226 if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth) {
227 return _additionalConstraints.minWidth;
228 }
229 final double width = super.computeMinIntrinsicWidth(height);
230 assert(width.isFinite);
231 if (!_additionalConstraints.hasInfiniteWidth) {
232 return _additionalConstraints.constrainWidth(width);
233 }
234 return width;
235 }
236
237 @override
238 double computeMaxIntrinsicWidth(double height) {
239 if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth) {
240 return _additionalConstraints.minWidth;
241 }
242 final double width = super.computeMaxIntrinsicWidth(height);
243 assert(width.isFinite);
244 if (!_additionalConstraints.hasInfiniteWidth) {
245 return _additionalConstraints.constrainWidth(width);
246 }
247 return width;
248 }
249
250 @override
251 double computeMinIntrinsicHeight(double width) {
252 if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight) {
253 return _additionalConstraints.minHeight;
254 }
255 final double height = super.computeMinIntrinsicHeight(width);
256 assert(height.isFinite);
257 if (!_additionalConstraints.hasInfiniteHeight) {
258 return _additionalConstraints.constrainHeight(height);
259 }
260 return height;
261 }
262
263 @override
264 double computeMaxIntrinsicHeight(double width) {
265 if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight) {
266 return _additionalConstraints.minHeight;
267 }
268 final double height = super.computeMaxIntrinsicHeight(width);
269 assert(height.isFinite);
270 if (!_additionalConstraints.hasInfiniteHeight) {
271 return _additionalConstraints.constrainHeight(height);
272 }
273 return height;
274 }
275
276 @override
277 void performLayout() {
278 final BoxConstraints constraints = this.constraints;
279 if (child != null) {
280 child!.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
281 size = child!.size;
282 } else {
283 size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
284 }
285 }
286
287 @override
288 @protected
289 Size computeDryLayout(covariant BoxConstraints constraints) {
290 if (child != null) {
291 return child!.getDryLayout(_additionalConstraints.enforce(constraints));
292 } else {
293 return _additionalConstraints.enforce(constraints).constrain(Size.zero);
294 }
295 }
296
297 @override
298 void debugPaintSize(PaintingContext context, Offset offset) {
299 super.debugPaintSize(context, offset);
300 assert(() {
301 final Paint paint;
302 if (child == null || child!.size.isEmpty) {
303 paint = Paint()
304 ..color = const Color(0x90909090);
305 context.canvas.drawRect(offset & size, paint);
306 }
307 return true;
308 }());
309 }
310
311 @override
312 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
313 super.debugFillProperties(properties);
314 properties.add(DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints));
315 }
316}
317
318/// Constrains the child's [BoxConstraints.maxWidth] and
319/// [BoxConstraints.maxHeight] if they're otherwise unconstrained.
320///
321/// This has the effect of giving the child a natural dimension in unbounded
322/// environments. For example, by providing a [maxHeight] to a widget that
323/// normally tries to be as big as possible, the widget will normally size
324/// itself to fit its parent, but when placed in a vertical list, it will take
325/// on the given height.
326///
327/// This is useful when composing widgets that normally try to match their
328/// parents' size, so that they behave reasonably in lists (which are
329/// unbounded).
330class RenderLimitedBox extends RenderProxyBox {
331 /// Creates a render box that imposes a maximum width or maximum height on its
332 /// child if the child is otherwise unconstrained.
333 ///
334 /// The [maxWidth] and [maxHeight] arguments not be null and must be
335 /// non-negative.
336 RenderLimitedBox({
337 RenderBox? child,
338 double maxWidth = double.infinity,
339 double maxHeight = double.infinity,
340 }) : assert(maxWidth >= 0.0),
341 assert(maxHeight >= 0.0),
342 _maxWidth = maxWidth,
343 _maxHeight = maxHeight,
344 super(child);
345
346 /// The value to use for maxWidth if the incoming maxWidth constraint is infinite.
347 double get maxWidth => _maxWidth;
348 double _maxWidth;
349 set maxWidth(double value) {
350 assert(value >= 0.0);
351 if (_maxWidth == value) {
352 return;
353 }
354 _maxWidth = value;
355 markNeedsLayout();
356 }
357
358 /// The value to use for maxHeight if the incoming maxHeight constraint is infinite.
359 double get maxHeight => _maxHeight;
360 double _maxHeight;
361 set maxHeight(double value) {
362 assert(value >= 0.0);
363 if (_maxHeight == value) {
364 return;
365 }
366 _maxHeight = value;
367 markNeedsLayout();
368 }
369
370 BoxConstraints _limitConstraints(BoxConstraints constraints) {
371 return BoxConstraints(
372 minWidth: constraints.minWidth,
373 maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth),
374 minHeight: constraints.minHeight,
375 maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight),
376 );
377 }
378
379 Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild }) {
380 if (child != null) {
381 final Size childSize = layoutChild(child!, _limitConstraints(constraints));
382 return constraints.constrain(childSize);
383 }
384 return _limitConstraints(constraints).constrain(Size.zero);
385 }
386
387 @override
388 @protected
389 Size computeDryLayout(covariant BoxConstraints constraints) {
390 return _computeSize(
391 constraints: constraints,
392 layoutChild: ChildLayoutHelper.dryLayoutChild,
393 );
394 }
395
396 @override
397 void performLayout() {
398 size = _computeSize(
399 constraints: constraints,
400 layoutChild: ChildLayoutHelper.layoutChild,
401 );
402 }
403
404 @override
405 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
406 super.debugFillProperties(properties);
407 properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
408 properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: double.infinity));
409 }
410}
411
412/// Attempts to size the child to a specific aspect ratio.
413///
414/// The render object first tries the largest width permitted by the layout
415/// constraints. The height of the render object is determined by applying the
416/// given aspect ratio to the width, expressed as a ratio of width to height.
417///
418/// For example, a 16:9 width:height aspect ratio would have a value of
419/// 16.0/9.0. If the maximum width is infinite, the initial width is determined
420/// by applying the aspect ratio to the maximum height.
421///
422/// Now consider a second example, this time with an aspect ratio of 2.0 and
423/// layout constraints that require the width to be between 0.0 and 100.0 and
424/// the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the
425/// biggest allowed) and a height of 50.0 (to match the aspect ratio).
426///
427/// In that same situation, if the aspect ratio is 0.5, we'll also select a
428/// width of 100.0 (still the biggest allowed) and we'll attempt to use a height
429/// of 200.0. Unfortunately, that violates the constraints because the child can
430/// be at most 100.0 pixels tall. The render object will then take that value
431/// and apply the aspect ratio again to obtain a width of 50.0. That width is
432/// permitted by the constraints and the child receives a width of 50.0 and a
433/// height of 100.0. If the width were not permitted, the render object would
434/// continue iterating through the constraints. If the render object does not
435/// find a feasible size after consulting each constraint, the render object
436/// will eventually select a size for the child that meets the layout
437/// constraints but fails to meet the aspect ratio constraints.
438class RenderAspectRatio extends RenderProxyBox {
439 /// Creates as render object with a specific aspect ratio.
440 ///
441 /// The [aspectRatio] argument must be a finite, positive value.
442 RenderAspectRatio({
443 RenderBox? child,
444 required double aspectRatio,
445 }) : assert(aspectRatio > 0.0),
446 assert(aspectRatio.isFinite),
447 _aspectRatio = aspectRatio,
448 super(child);
449
450 /// The aspect ratio to attempt to use.
451 ///
452 /// The aspect ratio is expressed as a ratio of width to height. For example,
453 /// a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
454 double get aspectRatio => _aspectRatio;
455 double _aspectRatio;
456 set aspectRatio(double value) {
457 assert(value > 0.0);
458 assert(value.isFinite);
459 if (_aspectRatio == value) {
460 return;
461 }
462 _aspectRatio = value;
463 markNeedsLayout();
464 }
465
466 @override
467 double computeMinIntrinsicWidth(double height) {
468 if (height.isFinite) {
469 return height * _aspectRatio;
470 }
471 if (child != null) {
472 return child!.getMinIntrinsicWidth(height);
473 }
474 return 0.0;
475 }
476
477 @override
478 double computeMaxIntrinsicWidth(double height) {
479 if (height.isFinite) {
480 return height * _aspectRatio;
481 }
482 if (child != null) {
483 return child!.getMaxIntrinsicWidth(height);
484 }
485 return 0.0;
486 }
487
488 @override
489 double computeMinIntrinsicHeight(double width) {
490 if (width.isFinite) {
491 return width / _aspectRatio;
492 }
493 if (child != null) {
494 return child!.getMinIntrinsicHeight(width);
495 }
496 return 0.0;
497 }
498
499 @override
500 double computeMaxIntrinsicHeight(double width) {
501 if (width.isFinite) {
502 return width / _aspectRatio;
503 }
504 if (child != null) {
505 return child!.getMaxIntrinsicHeight(width);
506 }
507 return 0.0;
508 }
509
510 Size _applyAspectRatio(BoxConstraints constraints) {
511 assert(constraints.debugAssertIsValid());
512 assert(() {
513 if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) {
514 throw FlutterError(
515 '$runtimeType has unbounded constraints.\n'
516 'This $runtimeType was given an aspect ratio of $aspectRatio but was given '
517 'both unbounded width and unbounded height constraints. Because both '
518 "constraints were unbounded, this render object doesn't know how much "
519 'size to consume.',
520 );
521 }
522 return true;
523 }());
524
525 if (constraints.isTight) {
526 return constraints.smallest;
527 }
528
529 double width = constraints.maxWidth;
530 double height;
531
532 // We default to picking the height based on the width, but if the width
533 // would be infinite, that's not sensible so we try to infer the height
534 // from the width.
535 if (width.isFinite) {
536 height = width / _aspectRatio;
537 } else {
538 height = constraints.maxHeight;
539 width = height * _aspectRatio;
540 }
541
542 // Similar to RenderImage, we iteratively attempt to fit within the given
543 // constraints while maintaining the given aspect ratio. The order of
544 // applying the constraints is also biased towards inferring the height
545 // from the width.
546
547 if (width > constraints.maxWidth) {
548 width = constraints.maxWidth;
549 height = width / _aspectRatio;
550 }
551
552 if (height > constraints.maxHeight) {
553 height = constraints.maxHeight;
554 width = height * _aspectRatio;
555 }
556
557 if (width < constraints.minWidth) {
558 width = constraints.minWidth;
559 height = width / _aspectRatio;
560 }
561
562 if (height < constraints.minHeight) {
563 height = constraints.minHeight;
564 width = height * _aspectRatio;
565 }
566
567 return constraints.constrain(Size(width, height));
568 }
569
570 @override
571 @protected
572 Size computeDryLayout(covariant BoxConstraints constraints) {
573 return _applyAspectRatio(constraints);
574 }
575
576 @override
577 void performLayout() {
578 size = computeDryLayout(constraints);
579 if (child != null) {
580 child!.layout(BoxConstraints.tight(size));
581 }
582 }
583
584 @override
585 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
586 super.debugFillProperties(properties);
587 properties.add(DoubleProperty('aspectRatio', aspectRatio));
588 }
589}
590
591/// Sizes its child to the child's maximum intrinsic width.
592///
593/// This class is useful, for example, when unlimited width is available and
594/// you would like a child that would otherwise attempt to expand infinitely to
595/// instead size itself to a more reasonable width.
596///
597/// The constraints that this object passes to its child will adhere to the
598/// parent's constraints, so if the constraints are not large enough to satisfy
599/// the child's maximum intrinsic width, then the child will get less width
600/// than it otherwise would. Likewise, if the minimum width constraint is
601/// larger than the child's maximum intrinsic width, the child will be given
602/// more width than it otherwise would.
603///
604/// If [stepWidth] is non-null, the child's width will be snapped to a multiple
605/// of the [stepWidth]. Similarly, if [stepHeight] is non-null, the child's
606/// height will be snapped to a multiple of the [stepHeight].
607///
608/// This class is relatively expensive, because it adds a speculative layout
609/// pass before the final layout phase. Avoid using it where possible. In the
610/// worst case, this render object can result in a layout that is O(N²) in the
611/// depth of the tree.
612///
613/// See also:
614///
615/// * [Align], a widget that aligns its child within itself. This can be used
616/// to loosen the constraints passed to the [RenderIntrinsicWidth],
617/// allowing the [RenderIntrinsicWidth]'s child to be smaller than that of
618/// its parent.
619/// * [Row], which when used with [CrossAxisAlignment.stretch] can be used
620/// to loosen just the width constraints that are passed to the
621/// [RenderIntrinsicWidth], allowing the [RenderIntrinsicWidth]'s child's
622/// width to be smaller than that of its parent.
623class RenderIntrinsicWidth extends RenderProxyBox {
624 /// Creates a render object that sizes itself to its child's intrinsic width.
625 ///
626 /// If [stepWidth] is non-null it must be > 0.0. Similarly If [stepHeight] is
627 /// non-null it must be > 0.0.
628 RenderIntrinsicWidth({
629 double? stepWidth,
630 double? stepHeight,
631 RenderBox? child,
632 }) : assert(stepWidth == null || stepWidth > 0.0),
633 assert(stepHeight == null || stepHeight > 0.0),
634 _stepWidth = stepWidth,
635 _stepHeight = stepHeight,
636 super(child);
637
638 /// If non-null, force the child's width to be a multiple of this value.
639 ///
640 /// This value must be null or > 0.0.
641 double? get stepWidth => _stepWidth;
642 double? _stepWidth;
643 set stepWidth(double? value) {
644 assert(value == null || value > 0.0);
645 if (value == _stepWidth) {
646 return;
647 }
648 _stepWidth = value;
649 markNeedsLayout();
650 }
651
652 /// If non-null, force the child's height to be a multiple of this value.
653 ///
654 /// This value must be null or > 0.0.
655 double? get stepHeight => _stepHeight;
656 double? _stepHeight;
657 set stepHeight(double? value) {
658 assert(value == null || value > 0.0);
659 if (value == _stepHeight) {
660 return;
661 }
662 _stepHeight = value;
663 markNeedsLayout();
664 }
665
666 static double _applyStep(double input, double? step) {
667 assert(input.isFinite);
668 if (step == null) {
669 return input;
670 }
671 return (input / step).ceil() * step;
672 }
673
674 @override
675 double computeMinIntrinsicWidth(double height) {
676 return computeMaxIntrinsicWidth(height);
677 }
678
679 @override
680 double computeMaxIntrinsicWidth(double height) {
681 if (child == null) {
682 return 0.0;
683 }
684 final double width = child!.getMaxIntrinsicWidth(height);
685 return _applyStep(width, _stepWidth);
686 }
687
688 @override
689 double computeMinIntrinsicHeight(double width) {
690 if (child == null) {
691 return 0.0;
692 }
693 if (!width.isFinite) {
694 width = computeMaxIntrinsicWidth(double.infinity);
695 }
696 assert(width.isFinite);
697 final double height = child!.getMinIntrinsicHeight(width);
698 return _applyStep(height, _stepHeight);
699 }
700
701 @override
702 double computeMaxIntrinsicHeight(double width) {
703 if (child == null) {
704 return 0.0;
705 }
706 if (!width.isFinite) {
707 width = computeMaxIntrinsicWidth(double.infinity);
708 }
709 assert(width.isFinite);
710 final double height = child!.getMaxIntrinsicHeight(width);
711 return _applyStep(height, _stepHeight);
712 }
713
714 Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
715 if (child != null) {
716 if (!constraints.hasTightWidth) {
717 final double width = child!.getMaxIntrinsicWidth(constraints.maxHeight);
718 assert(width.isFinite);
719 constraints = constraints.tighten(width: _applyStep(width, _stepWidth));
720 }
721 if (_stepHeight != null) {
722 final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth);
723 assert(height.isFinite);
724 constraints = constraints.tighten(height: _applyStep(height, _stepHeight));
725 }
726 return layoutChild(child!, constraints);
727 } else {
728 return constraints.smallest;
729 }
730 }
731
732 @override
733 @protected
734 Size computeDryLayout(covariant BoxConstraints constraints) {
735 return _computeSize(
736 layoutChild: ChildLayoutHelper.dryLayoutChild,
737 constraints: constraints,
738 );
739 }
740
741 @override
742 void performLayout() {
743 size = _computeSize(
744 layoutChild: ChildLayoutHelper.layoutChild,
745 constraints: constraints,
746 );
747 }
748
749 @override
750 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
751 super.debugFillProperties(properties);
752 properties.add(DoubleProperty('stepWidth', stepWidth));
753 properties.add(DoubleProperty('stepHeight', stepHeight));
754 }
755}
756
757/// Sizes its child to the child's intrinsic height.
758///
759/// This class is useful, for example, when unlimited height is available and
760/// you would like a child that would otherwise attempt to expand infinitely to
761/// instead size itself to a more reasonable height.
762///
763/// The constraints that this object passes to its child will adhere to the
764/// parent's constraints, so if the constraints are not large enough to satisfy
765/// the child's maximum intrinsic height, then the child will get less height
766/// than it otherwise would. Likewise, if the minimum height constraint is
767/// larger than the child's maximum intrinsic height, the child will be given
768/// more height than it otherwise would.
769///
770/// This class is relatively expensive, because it adds a speculative layout
771/// pass before the final layout phase. Avoid using it where possible. In the
772/// worst case, this render object can result in a layout that is O(N²) in the
773/// depth of the tree.
774///
775/// See also:
776///
777/// * [Align], a widget that aligns its child within itself. This can be used
778/// to loosen the constraints passed to the [RenderIntrinsicHeight],
779/// allowing the [RenderIntrinsicHeight]'s child to be smaller than that of
780/// its parent.
781/// * [Column], which when used with [CrossAxisAlignment.stretch] can be used
782/// to loosen just the height constraints that are passed to the
783/// [RenderIntrinsicHeight], allowing the [RenderIntrinsicHeight]'s child's
784/// height to be smaller than that of its parent.
785class RenderIntrinsicHeight extends RenderProxyBox {
786 /// Creates a render object that sizes itself to its child's intrinsic height.
787 RenderIntrinsicHeight({
788 RenderBox? child,
789 }) : super(child);
790
791 @override
792 double computeMinIntrinsicWidth(double height) {
793 if (child == null) {
794 return 0.0;
795 }
796 if (!height.isFinite) {
797 height = child!.getMaxIntrinsicHeight(double.infinity);
798 }
799 assert(height.isFinite);
800 return child!.getMinIntrinsicWidth(height);
801 }
802
803 @override
804 double computeMaxIntrinsicWidth(double height) {
805 if (child == null) {
806 return 0.0;
807 }
808 if (!height.isFinite) {
809 height = child!.getMaxIntrinsicHeight(double.infinity);
810 }
811 assert(height.isFinite);
812 return child!.getMaxIntrinsicWidth(height);
813 }
814
815 @override
816 double computeMinIntrinsicHeight(double width) {
817 return computeMaxIntrinsicHeight(width);
818 }
819
820 Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
821 if (child != null) {
822 if (!constraints.hasTightHeight) {
823 final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth);
824 assert(height.isFinite);
825 constraints = constraints.tighten(height: height);
826 }
827 return layoutChild(child!, constraints);
828 } else {
829 return constraints.smallest;
830 }
831 }
832
833 @override
834 @protected
835 Size computeDryLayout(covariant BoxConstraints constraints) {
836 return _computeSize(
837 layoutChild: ChildLayoutHelper.dryLayoutChild,
838 constraints: constraints,
839 );
840 }
841
842 @override
843 void performLayout() {
844 size = _computeSize(
845 layoutChild: ChildLayoutHelper.layoutChild,
846 constraints: constraints,
847 );
848 }
849}
850
851/// Excludes the child from baseline computations in the parent.
852class RenderIgnoreBaseline extends RenderProxyBox {
853 /// Create a render object that causes the parent to ignore the child for baseline computations.
854 RenderIgnoreBaseline({
855 RenderBox? child,
856 }) : super(child);
857
858 @override
859 double? computeDistanceToActualBaseline(TextBaseline baseline) {
860 return null;
861 }
862}
863
864/// Makes its child partially transparent.
865///
866/// This class paints its child into an intermediate buffer and then blends the
867/// child back into the scene partially transparent.
868///
869/// For values of opacity other than 0.0 and 1.0, this class is relatively
870/// expensive because it requires painting the child into an intermediate
871/// buffer. For the value 0.0, the child is not painted at all. For the
872/// value 1.0, the child is painted immediately without an intermediate buffer.
873class RenderOpacity extends RenderProxyBox {
874 /// Creates a partially transparent render object.
875 ///
876 /// The [opacity] argument must be between 0.0 and 1.0, inclusive.
877 RenderOpacity({
878 double opacity = 1.0,
879 bool alwaysIncludeSemantics = false,
880 RenderBox? child,
881 }) : assert(opacity >= 0.0 && opacity <= 1.0),
882 _opacity = opacity,
883 _alwaysIncludeSemantics = alwaysIncludeSemantics,
884 _alpha = ui.Color.getAlphaFromOpacity(opacity),
885 super(child);
886
887 @override
888 bool get alwaysNeedsCompositing => child != null && _alpha > 0;
889
890 @override
891 bool get isRepaintBoundary => alwaysNeedsCompositing;
892
893 int _alpha;
894
895 /// The fraction to scale the child's alpha value.
896 ///
897 /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
898 /// (i.e., invisible).
899 ///
900 /// Values 1.0 and 0.0 are painted with a fast path. Other values
901 /// require painting the child into an intermediate buffer, which is
902 /// expensive.
903 double get opacity => _opacity;
904 double _opacity;
905 set opacity(double value) {
906 assert(value >= 0.0 && value <= 1.0);
907 if (_opacity == value) {
908 return;
909 }
910 final bool didNeedCompositing = alwaysNeedsCompositing;
911 final bool wasVisible = _alpha != 0;
912 _opacity = value;
913 _alpha = ui.Color.getAlphaFromOpacity(_opacity);
914 if (didNeedCompositing != alwaysNeedsCompositing) {
915 markNeedsCompositingBitsUpdate();
916 }
917 markNeedsCompositedLayerUpdate();
918 if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) {
919 markNeedsSemanticsUpdate();
920 }
921 }
922
923 /// Whether child semantics are included regardless of the opacity.
924 ///
925 /// If false, semantics are excluded when [opacity] is 0.0.
926 ///
927 /// Defaults to false.
928 bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
929 bool _alwaysIncludeSemantics;
930 set alwaysIncludeSemantics(bool value) {
931 if (value == _alwaysIncludeSemantics) {
932 return;
933 }
934 _alwaysIncludeSemantics = value;
935 markNeedsSemanticsUpdate();
936 }
937
938 @override
939 bool paintsChild(RenderBox child) {
940 assert(child.parent == this);
941 return _alpha > 0;
942 }
943
944 @override
945 OffsetLayer updateCompositedLayer({required covariant OpacityLayer? oldLayer}) {
946 final OpacityLayer layer = oldLayer ?? OpacityLayer();
947 layer.alpha = _alpha;
948 return layer;
949 }
950
951 @override
952 void paint(PaintingContext context, Offset offset) {
953 if (child == null || _alpha == 0) {
954 return;
955 }
956 super.paint(context, offset);
957 }
958
959 @override
960 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
961 if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) {
962 visitor(child!);
963 }
964 }
965
966 @override
967 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
968 super.debugFillProperties(properties);
969 properties.add(DoubleProperty('opacity', opacity));
970 properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
971 }
972}
973
974/// Implementation of [RenderAnimatedOpacity] and [RenderSliverAnimatedOpacity].
975///
976/// This mixin allows the logic of animating opacity to be used with different
977/// layout models, e.g. the way that [RenderAnimatedOpacity] uses it for [RenderBox]
978/// and [RenderSliverAnimatedOpacity] uses it for [RenderSliver].
979mixin RenderAnimatedOpacityMixin<T extends RenderObject> on RenderObjectWithChildMixin<T> {
980 int? _alpha;
981
982 @override
983 bool get isRepaintBoundary => child != null && _currentlyIsRepaintBoundary!;
984 bool? _currentlyIsRepaintBoundary;
985
986 @override
987 OffsetLayer updateCompositedLayer({required covariant OpacityLayer? oldLayer}) {
988 final OpacityLayer updatedLayer = oldLayer ?? OpacityLayer();
989 updatedLayer.alpha = _alpha;
990 return updatedLayer;
991 }
992
993 /// The animation that drives this render object's opacity.
994 ///
995 /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
996 /// (i.e., invisible).
997 ///
998 /// To change the opacity of a child in a static manner, not animated,
999 /// consider [RenderOpacity] instead.
1000 ///
1001 /// This getter cannot be read until the value has been set. It should be set
1002 /// by the constructor of the class in which this mixin is included.
1003 Animation<double> get opacity => _opacity!;
1004 Animation<double>? _opacity;
1005 set opacity(Animation<double> value) {
1006 if (_opacity == value) {
1007 return;
1008 }
1009 if (attached && _opacity != null) {
1010 opacity.removeListener(_updateOpacity);
1011 }
1012 _opacity = value;
1013 if (attached) {
1014 opacity.addListener(_updateOpacity);
1015 }
1016 _updateOpacity();
1017 }
1018
1019 /// Whether child semantics are included regardless of the opacity.
1020 ///
1021 /// If false, semantics are excluded when [opacity] is 0.0.
1022 ///
1023 /// Defaults to false.
1024 ///
1025 /// This getter cannot be read until the value has been set. It should be set
1026 /// by the constructor of the class in which this mixin is included.
1027 bool get alwaysIncludeSemantics => _alwaysIncludeSemantics!;
1028 bool? _alwaysIncludeSemantics;
1029 set alwaysIncludeSemantics(bool value) {
1030 if (value == _alwaysIncludeSemantics) {
1031 return;
1032 }
1033 _alwaysIncludeSemantics = value;
1034 markNeedsSemanticsUpdate();
1035 }
1036
1037 @override
1038 void attach(PipelineOwner owner) {
1039 super.attach(owner);
1040 opacity.addListener(_updateOpacity);
1041 _updateOpacity(); // in case it changed while we weren't listening
1042 }
1043
1044 @override
1045 void detach() {
1046 opacity.removeListener(_updateOpacity);
1047 super.detach();
1048 }
1049
1050 void _updateOpacity() {
1051 final int? oldAlpha = _alpha;
1052 _alpha = ui.Color.getAlphaFromOpacity(opacity.value);
1053 if (oldAlpha != _alpha) {
1054 final bool? wasRepaintBoundary = _currentlyIsRepaintBoundary;
1055 _currentlyIsRepaintBoundary = _alpha! > 0;
1056 if (child != null && wasRepaintBoundary != _currentlyIsRepaintBoundary) {
1057 markNeedsCompositingBitsUpdate();
1058 }
1059 markNeedsCompositedLayerUpdate();
1060 if (oldAlpha == 0 || _alpha == 0) {
1061 markNeedsSemanticsUpdate();
1062 }
1063 }
1064 }
1065
1066 @override
1067 bool paintsChild(RenderObject child) {
1068 assert(child.parent == this);
1069 return opacity.value > 0;
1070 }
1071
1072 @override
1073 void paint(PaintingContext context, Offset offset) {
1074 if (_alpha == 0) {
1075 return;
1076 }
1077 super.paint(context, offset);
1078 }
1079
1080 @override
1081 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
1082 if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) {
1083 visitor(child!);
1084 }
1085 }
1086
1087 @override
1088 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1089 super.debugFillProperties(properties);
1090 properties.add(DiagnosticsProperty<Animation<double>>('opacity', opacity));
1091 properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
1092 }
1093}
1094
1095/// Makes its child partially transparent, driven from an [Animation].
1096///
1097/// This is a variant of [RenderOpacity] that uses an [Animation<double>] rather
1098/// than a [double] to control the opacity.
1099class RenderAnimatedOpacity extends RenderProxyBox with RenderAnimatedOpacityMixin<RenderBox> {
1100 /// Creates a partially transparent render object.
1101 RenderAnimatedOpacity({
1102 required Animation<double> opacity,
1103 bool alwaysIncludeSemantics = false,
1104 RenderBox? child,
1105 }) : super(child) {
1106 this.opacity = opacity;
1107 this.alwaysIncludeSemantics = alwaysIncludeSemantics;
1108 }
1109}
1110
1111/// Signature for a function that creates a [Shader] for a given [Rect].
1112///
1113/// Used by [RenderShaderMask] and the [ShaderMask] widget.
1114typedef ShaderCallback = Shader Function(Rect bounds);
1115
1116/// Applies a mask generated by a [Shader] to its child.
1117///
1118/// For example, [RenderShaderMask] can be used to gradually fade out the edge
1119/// of a child by using a [ui.Gradient.linear] mask.
1120class RenderShaderMask extends RenderProxyBox {
1121 /// Creates a render object that applies a mask generated by a [Shader] to its child.
1122 RenderShaderMask({
1123 RenderBox? child,
1124 required ShaderCallback shaderCallback,
1125 BlendMode blendMode = BlendMode.modulate,
1126 }) : _shaderCallback = shaderCallback,
1127 _blendMode = blendMode,
1128 super(child);
1129
1130 @override
1131 ShaderMaskLayer? get layer => super.layer as ShaderMaskLayer?;
1132
1133 /// Called to creates the [Shader] that generates the mask.
1134 ///
1135 /// The shader callback is called with the current size of the child so that
1136 /// it can customize the shader to the size and location of the child.
1137 ///
1138 /// The rectangle will always be at the origin when called by
1139 /// [RenderShaderMask].
1140 // TODO(abarth): Use the delegate pattern here to avoid generating spurious
1141 // repaints when the ShaderCallback changes identity.
1142 ShaderCallback get shaderCallback => _shaderCallback;
1143 ShaderCallback _shaderCallback;
1144 set shaderCallback(ShaderCallback value) {
1145 if (_shaderCallback == value) {
1146 return;
1147 }
1148 _shaderCallback = value;
1149 markNeedsPaint();
1150 }
1151
1152 /// The [BlendMode] to use when applying the shader to the child.
1153 ///
1154 /// The default, [BlendMode.modulate], is useful for applying an alpha blend
1155 /// to the child. Other blend modes can be used to create other effects.
1156 BlendMode get blendMode => _blendMode;
1157 BlendMode _blendMode;
1158 set blendMode(BlendMode value) {
1159 if (_blendMode == value) {
1160 return;
1161 }
1162 _blendMode = value;
1163 markNeedsPaint();
1164 }
1165
1166 @override
1167 bool get alwaysNeedsCompositing => child != null;
1168
1169 @override
1170 void paint(PaintingContext context, Offset offset) {
1171 if (child != null) {
1172 assert(needsCompositing);
1173 layer ??= ShaderMaskLayer();
1174 layer!
1175 ..shader = _shaderCallback(Offset.zero & size)
1176 ..maskRect = offset & size
1177 ..blendMode = _blendMode;
1178 context.pushLayer(layer!, super.paint, offset);
1179 assert(() {
1180 layer!.debugCreator = debugCreator;
1181 return true;
1182 }());
1183 } else {
1184 layer = null;
1185 }
1186 }
1187}
1188
1189/// Applies a filter to the existing painted content and then paints [child].
1190///
1191/// This effect is relatively expensive, especially if the filter is non-local,
1192/// such as a blur.
1193class RenderBackdropFilter extends RenderProxyBox {
1194 /// Creates a backdrop filter.
1195 //
1196 /// The [blendMode] argument defaults to [BlendMode.srcOver].
1197 RenderBackdropFilter({ RenderBox? child, required ui.ImageFilter filter, BlendMode blendMode = BlendMode.srcOver })
1198 : _filter = filter,
1199 _blendMode = blendMode,
1200 super(child);
1201
1202 @override
1203 BackdropFilterLayer? get layer => super.layer as BackdropFilterLayer?;
1204
1205 /// The image filter to apply to the existing painted content before painting
1206 /// the child.
1207 ///
1208 /// For example, consider using [ui.ImageFilter.blur] to create a backdrop
1209 /// blur effect.
1210 ui.ImageFilter get filter => _filter;
1211 ui.ImageFilter _filter;
1212 set filter(ui.ImageFilter value) {
1213 if (_filter == value) {
1214 return;
1215 }
1216 _filter = value;
1217 markNeedsPaint();
1218 }
1219
1220 /// The blend mode to use to apply the filtered background content onto the background
1221 /// surface.
1222 ///
1223 /// {@macro flutter.widgets.BackdropFilter.blendMode}
1224 BlendMode get blendMode => _blendMode;
1225 BlendMode _blendMode;
1226 set blendMode(BlendMode value) {
1227 if (_blendMode == value) {
1228 return;
1229 }
1230 _blendMode = value;
1231 markNeedsPaint();
1232 }
1233
1234 @override
1235 bool get alwaysNeedsCompositing => child != null;
1236
1237 @override
1238 void paint(PaintingContext context, Offset offset) {
1239 if (child != null) {
1240 assert(needsCompositing);
1241 layer ??= BackdropFilterLayer();
1242 layer!.filter = _filter;
1243 layer!.blendMode = _blendMode;
1244 context.pushLayer(layer!, super.paint, offset);
1245 assert(() {
1246 layer!.debugCreator = debugCreator;
1247 return true;
1248 }());
1249 } else {
1250 layer = null;
1251 }
1252 }
1253}
1254
1255/// An interface for providing custom clips.
1256///
1257/// This class is used by a number of clip widgets (e.g., [ClipRect] and
1258/// [ClipPath]).
1259///
1260/// The [getClip] method is called whenever the custom clip needs to be updated.
1261///
1262/// The [shouldReclip] method is called when a new instance of the class
1263/// is provided, to check if the new instance actually represents different
1264/// information.
1265///
1266/// The most efficient way to update the clip provided by this class is to
1267/// supply a `reclip` argument to the constructor of the [CustomClipper]. The
1268/// custom object will listen to this animation and update the clip whenever the
1269/// animation ticks, avoiding both the build and layout phases of the pipeline.
1270///
1271/// See also:
1272///
1273/// * [ClipRect], which can be customized with a [CustomClipper<Rect>].
1274/// * [ClipRRect], which can be customized with a [CustomClipper<RRect>].
1275/// * [ClipOval], which can be customized with a [CustomClipper<Rect>].
1276/// * [ClipPath], which can be customized with a [CustomClipper<Path>].
1277/// * [ShapeBorderClipper], for specifying a clip path using a [ShapeBorder].
1278abstract class CustomClipper<T> extends Listenable {
1279 /// Creates a custom clipper.
1280 ///
1281 /// The clipper will update its clip whenever [reclip] notifies its listeners.
1282 const CustomClipper({ Listenable? reclip }) : _reclip = reclip;
1283
1284 final Listenable? _reclip;
1285
1286 /// Register a closure to be notified when it is time to reclip.
1287 ///
1288 /// The [CustomClipper] implementation merely forwards to the same method on
1289 /// the [Listenable] provided to the constructor in the `reclip` argument, if
1290 /// it was not null.
1291 @override
1292 void addListener(VoidCallback listener) => _reclip?.addListener(listener);
1293
1294 /// Remove a previously registered closure from the list of closures that the
1295 /// object notifies when it is time to reclip.
1296 ///
1297 /// The [CustomClipper] implementation merely forwards to the same method on
1298 /// the [Listenable] provided to the constructor in the `reclip` argument, if
1299 /// it was not null.
1300 @override
1301 void removeListener(VoidCallback listener) => _reclip?.removeListener(listener);
1302
1303 /// Returns a description of the clip given that the render object being
1304 /// clipped is of the given size.
1305 T getClip(Size size);
1306
1307 /// Returns an approximation of the clip returned by [getClip], as
1308 /// an axis-aligned Rect. This is used by the semantics layer to
1309 /// determine whether widgets should be excluded.
1310 ///
1311 /// By default, this returns a rectangle that is the same size as
1312 /// the RenderObject. If getClip returns a shape that is roughly the
1313 /// same size as the RenderObject (e.g. it's a rounded rectangle
1314 /// with very small arcs in the corners), then this may be adequate.
1315 Rect getApproximateClipRect(Size size) => Offset.zero & size;
1316
1317 /// Called whenever a new instance of the custom clipper delegate class is
1318 /// provided to the clip object, or any time that a new clip object is created
1319 /// with a new instance of the custom clipper delegate class (which amounts to
1320 /// the same thing, because the latter is implemented in terms of the former).
1321 ///
1322 /// If the new instance represents different information than the old
1323 /// instance, then the method should return true, otherwise it should return
1324 /// false.
1325 ///
1326 /// If the method returns false, then the [getClip] call might be optimized
1327 /// away.
1328 ///
1329 /// It's possible that the [getClip] method will get called even if
1330 /// [shouldReclip] returns false or if the [shouldReclip] method is never
1331 /// called at all (e.g. if the box changes size).
1332 bool shouldReclip(covariant CustomClipper<T> oldClipper);
1333
1334 @override
1335 String toString() => objectRuntimeType(this, 'CustomClipper');
1336}
1337
1338/// A [CustomClipper] that clips to the outer path of a [ShapeBorder].
1339class ShapeBorderClipper extends CustomClipper<Path> {
1340 /// Creates a [ShapeBorder] clipper.
1341 ///
1342 /// The [textDirection] argument must be provided non-null if [shape]
1343 /// has a text direction dependency (for example if it is expressed in terms
1344 /// of "start" and "end" instead of "left" and "right"). It may be null if
1345 /// the border will not need the text direction to paint itself.
1346 const ShapeBorderClipper({
1347 required this.shape,
1348 this.textDirection,
1349 });
1350
1351 /// The shape border whose outer path this clipper clips to.
1352 final ShapeBorder shape;
1353
1354 /// The text direction to use for getting the outer path for [shape].
1355 ///
1356 /// [ShapeBorder]s can depend on the text direction (e.g having a "dent"
1357 /// towards the start of the shape).
1358 final TextDirection? textDirection;
1359
1360 /// Returns the outer path of [shape] as the clip.
1361 @override
1362 Path getClip(Size size) {
1363 return shape.getOuterPath(Offset.zero & size, textDirection: textDirection);
1364 }
1365
1366 @override
1367 bool shouldReclip(CustomClipper<Path> oldClipper) {
1368 if (oldClipper.runtimeType != ShapeBorderClipper) {
1369 return true;
1370 }
1371 final ShapeBorderClipper typedOldClipper = oldClipper as ShapeBorderClipper;
1372 return typedOldClipper.shape != shape
1373 || typedOldClipper.textDirection != textDirection;
1374 }
1375}
1376
1377abstract class _RenderCustomClip<T> extends RenderProxyBox {
1378 _RenderCustomClip({
1379 RenderBox? child,
1380 CustomClipper<T>? clipper,
1381 Clip clipBehavior = Clip.antiAlias,
1382 }) : _clipper = clipper,
1383 _clipBehavior = clipBehavior,
1384 super(child);
1385
1386 /// If non-null, determines which clip to use on the child.
1387 CustomClipper<T>? get clipper => _clipper;
1388 CustomClipper<T>? _clipper;
1389 set clipper(CustomClipper<T>? newClipper) {
1390 if (_clipper == newClipper) {
1391 return;
1392 }
1393 final CustomClipper<T>? oldClipper = _clipper;
1394 _clipper = newClipper;
1395 assert(newClipper != null || oldClipper != null);
1396 if (newClipper == null || oldClipper == null ||
1397 newClipper.runtimeType != oldClipper.runtimeType ||
1398 newClipper.shouldReclip(oldClipper)) {
1399 _markNeedsClip();
1400 }
1401 if (attached) {
1402 oldClipper?.removeListener(_markNeedsClip);
1403 newClipper?.addListener(_markNeedsClip);
1404 }
1405 }
1406
1407 @override
1408 void attach(PipelineOwner owner) {
1409 super.attach(owner);
1410 _clipper?.addListener(_markNeedsClip);
1411 }
1412
1413 @override
1414 void detach() {
1415 _clipper?.removeListener(_markNeedsClip);
1416 super.detach();
1417 }
1418
1419 void _markNeedsClip() {
1420 _clip = null;
1421 markNeedsPaint();
1422 markNeedsSemanticsUpdate();
1423 }
1424
1425 T get _defaultClip;
1426 T? _clip;
1427
1428 Clip get clipBehavior => _clipBehavior;
1429 set clipBehavior(Clip value) {
1430 if (value != _clipBehavior) {
1431 _clipBehavior = value;
1432 markNeedsPaint();
1433 }
1434 }
1435 Clip _clipBehavior;
1436
1437 @override
1438 void performLayout() {
1439 final Size? oldSize = hasSize ? size : null;
1440 super.performLayout();
1441 if (oldSize != size) {
1442 _clip = null;
1443 }
1444 }
1445
1446 void _updateClip() {
1447 _clip ??= _clipper?.getClip(size) ?? _defaultClip;
1448 }
1449
1450 @override
1451 Rect? describeApproximatePaintClip(RenderObject child) {
1452 switch (clipBehavior) {
1453 case Clip.none:
1454 return null;
1455 case Clip.hardEdge:
1456 case Clip.antiAlias:
1457 case Clip.antiAliasWithSaveLayer:
1458 return _clipper?.getApproximateClipRect(size) ?? Offset.zero & size;
1459 }
1460 }
1461
1462 Paint? _debugPaint;
1463 TextPainter? _debugText;
1464 @override
1465 void debugPaintSize(PaintingContext context, Offset offset) {
1466 assert(() {
1467 _debugPaint ??= Paint()
1468 ..shader = ui.Gradient.linear(
1469 Offset.zero,
1470 const Offset(10.0, 10.0),
1471 <Color>[const Color(0x00000000), const Color(0xFFFF00FF), const Color(0xFFFF00FF), const Color(0x00000000)],
1472 <double>[0.25, 0.25, 0.75, 0.75],
1473 TileMode.repeated,
1474 )
1475 ..strokeWidth = 2.0
1476 ..style = PaintingStyle.stroke;
1477 _debugText ??= TextPainter(
1478 text: const TextSpan(
1479 text: '✂',
1480 style: TextStyle(
1481 color: Color(0xFFFF00FF),
1482 fontSize: 14.0,
1483 ),
1484 ),
1485 textDirection: TextDirection.rtl, // doesn't matter, it's one character
1486 )
1487 ..layout();
1488 return true;
1489 }());
1490 }
1491
1492 @override
1493 void dispose() {
1494 _debugText?.dispose();
1495 _debugText = null;
1496 super.dispose();
1497 }
1498}
1499
1500/// Clips its child using a rectangle.
1501///
1502/// By default, [RenderClipRect] prevents its child from painting outside its
1503/// bounds, but the size and location of the clip rect can be customized using a
1504/// custom [clipper].
1505class RenderClipRect extends _RenderCustomClip<Rect> {
1506 /// Creates a rectangular clip.
1507 ///
1508 /// If [clipper] is null, the clip will match the layout size and position of
1509 /// the child.
1510 ///
1511 /// If [clipBehavior] is [Clip.none], no clipping will be applied.
1512 RenderClipRect({
1513 super.child,
1514 super.clipper,
1515 super.clipBehavior,
1516 });
1517
1518 @override
1519 Rect get _defaultClip => Offset.zero & size;
1520
1521 @override
1522 bool hitTest(BoxHitTestResult result, { required Offset position }) {
1523 if (_clipper != null) {
1524 _updateClip();
1525 assert(_clip != null);
1526 if (!_clip!.contains(position)) {
1527 return false;
1528 }
1529 }
1530 return super.hitTest(result, position: position);
1531 }
1532
1533 @override
1534 void paint(PaintingContext context, Offset offset) {
1535 if (child != null) {
1536 if (clipBehavior != Clip.none) {
1537 _updateClip();
1538 layer = context.pushClipRect(
1539 needsCompositing,
1540 offset,
1541 _clip!,
1542 super.paint,
1543 clipBehavior: clipBehavior,
1544 oldLayer: layer as ClipRectLayer?,
1545 );
1546 } else {
1547 context.paintChild(child!, offset);
1548 layer = null;
1549 }
1550 } else {
1551 layer = null;
1552 }
1553 }
1554
1555 @override
1556 void debugPaintSize(PaintingContext context, Offset offset) {
1557 assert(() {
1558 if (child != null) {
1559 super.debugPaintSize(context, offset);
1560 if (clipBehavior != Clip.none) {
1561 context.canvas.drawRect(_clip!.shift(offset), _debugPaint!);
1562 _debugText!.paint(context.canvas, offset + Offset(_clip!.width / 8.0, -_debugText!.text!.style!.fontSize! * 1.1));
1563 }
1564 }
1565 return true;
1566 }());
1567 }
1568}
1569
1570/// Clips its child using a rounded rectangle.
1571///
1572/// By default, [RenderClipRRect] uses its own bounds as the base rectangle for
1573/// the clip, but the size and location of the clip can be customized using a
1574/// custom [clipper].
1575class RenderClipRRect extends _RenderCustomClip<RRect> {
1576 /// Creates a rounded-rectangular clip.
1577 ///
1578 /// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with
1579 /// right-angled corners.
1580 ///
1581 /// If [clipper] is non-null, then [borderRadius] is ignored.
1582 ///
1583 /// If [clipBehavior] is [Clip.none], no clipping will be applied.
1584 RenderClipRRect({
1585 super.child,
1586 BorderRadiusGeometry borderRadius = BorderRadius.zero,
1587 super.clipper,
1588 super.clipBehavior,
1589 TextDirection? textDirection,
1590 }) : _borderRadius = borderRadius,
1591 _textDirection = textDirection;
1592
1593 /// The border radius of the rounded corners.
1594 ///
1595 /// Values are clamped so that horizontal and vertical radii sums do not
1596 /// exceed width/height.
1597 ///
1598 /// This value is ignored if [clipper] is non-null.
1599 BorderRadiusGeometry get borderRadius => _borderRadius;
1600 BorderRadiusGeometry _borderRadius;
1601 set borderRadius(BorderRadiusGeometry value) {
1602 if (_borderRadius == value) {
1603 return;
1604 }
1605 _borderRadius = value;
1606 _markNeedsClip();
1607 }
1608
1609 /// The text direction with which to resolve [borderRadius].
1610 TextDirection? get textDirection => _textDirection;
1611 TextDirection? _textDirection;
1612 set textDirection(TextDirection? value) {
1613 if (_textDirection == value) {
1614 return;
1615 }
1616 _textDirection = value;
1617 _markNeedsClip();
1618 }
1619
1620 @override
1621 RRect get _defaultClip => _borderRadius.resolve(textDirection).toRRect(Offset.zero & size);
1622
1623 @override
1624 bool hitTest(BoxHitTestResult result, { required Offset position }) {
1625 if (_clipper != null) {
1626 _updateClip();
1627 assert(_clip != null);
1628 if (!_clip!.contains(position)) {
1629 return false;
1630 }
1631 }
1632 return super.hitTest(result, position: position);
1633 }
1634
1635 @override
1636 void paint(PaintingContext context, Offset offset) {
1637 if (child != null) {
1638 if (clipBehavior != Clip.none) {
1639 _updateClip();
1640 layer = context.pushClipRRect(
1641 needsCompositing,
1642 offset,
1643 _clip!.outerRect,
1644 _clip!,
1645 super.paint,
1646 clipBehavior: clipBehavior,
1647 oldLayer: layer as ClipRRectLayer?,
1648 );
1649 } else {
1650 context.paintChild(child!, offset);
1651 layer = null;
1652 }
1653 } else {
1654 layer = null;
1655 }
1656 }
1657
1658 @override
1659 void debugPaintSize(PaintingContext context, Offset offset) {
1660 assert(() {
1661 if (child != null) {
1662 super.debugPaintSize(context, offset);
1663 if (clipBehavior != Clip.none) {
1664 context.canvas.drawRRect(_clip!.shift(offset), _debugPaint!);
1665 _debugText!.paint(context.canvas, offset + Offset(_clip!.tlRadiusX, -_debugText!.text!.style!.fontSize! * 1.1));
1666 }
1667 }
1668 return true;
1669 }());
1670 }
1671}
1672
1673/// Clips its child using an oval.
1674///
1675/// By default, inscribes an axis-aligned oval into its layout dimensions and
1676/// prevents its child from painting outside that oval, but the size and
1677/// location of the clip oval can be customized using a custom [clipper].
1678class RenderClipOval extends _RenderCustomClip<Rect> {
1679 /// Creates an oval-shaped clip.
1680 ///
1681 /// If [clipper] is null, the oval will be inscribed into the layout size and
1682 /// position of the child.
1683 ///
1684 /// If [clipBehavior] is [Clip.none], no clipping will be applied.
1685 RenderClipOval({
1686 super.child,
1687 super.clipper,
1688 super.clipBehavior,
1689 });
1690
1691 Rect? _cachedRect;
1692 late Path _cachedPath;
1693
1694 Path _getClipPath(Rect rect) {
1695 if (rect != _cachedRect) {
1696 _cachedRect = rect;
1697 _cachedPath = Path()..addOval(_cachedRect!);
1698 }
1699 return _cachedPath;
1700 }
1701
1702 @override
1703 Rect get _defaultClip => Offset.zero & size;
1704
1705 @override
1706 bool hitTest(BoxHitTestResult result, { required Offset position }) {
1707 _updateClip();
1708 assert(_clip != null);
1709 final Offset center = _clip!.center;
1710 // convert the position to an offset from the center of the unit circle
1711 final Offset offset = Offset(
1712 (position.dx - center.dx) / _clip!.width,
1713 (position.dy - center.dy) / _clip!.height,
1714 );
1715 // check if the point is outside the unit circle
1716 if (offset.distanceSquared > 0.25) { // x^2 + y^2 > r^2
1717 return false;
1718 }
1719 return super.hitTest(result, position: position);
1720 }
1721
1722 @override
1723 void paint(PaintingContext context, Offset offset) {
1724 if (child != null) {
1725 if (clipBehavior != Clip.none) {
1726 _updateClip();
1727 layer = context.pushClipPath(
1728 needsCompositing,
1729 offset,
1730 _clip!,
1731 _getClipPath(_clip!),
1732 super.paint,
1733 clipBehavior: clipBehavior,
1734 oldLayer: layer as ClipPathLayer?,
1735 );
1736 } else {
1737 context.paintChild(child!, offset);
1738 layer = null;
1739 }
1740 } else {
1741 layer = null;
1742 }
1743 }
1744
1745 @override
1746 void debugPaintSize(PaintingContext context, Offset offset) {
1747 assert(() {
1748 if (child != null) {
1749 super.debugPaintSize(context, offset);
1750 if (clipBehavior != Clip.none) {
1751 context.canvas.drawPath(_getClipPath(_clip!).shift(offset), _debugPaint!);
1752 _debugText!.paint(context.canvas, offset + Offset((_clip!.width - _debugText!.width) / 2.0, -_debugText!.text!.style!.fontSize! * 1.1));
1753 }
1754 }
1755 return true;
1756 }());
1757 }
1758}
1759
1760/// Clips its child using a path.
1761///
1762/// Takes a delegate whose primary method returns a path that should
1763/// be used to prevent the child from painting outside the path.
1764///
1765/// Clipping to a path is expensive. Certain shapes have more
1766/// optimized render objects:
1767///
1768/// * To clip to a rectangle, consider [RenderClipRect].
1769/// * To clip to an oval or circle, consider [RenderClipOval].
1770/// * To clip to a rounded rectangle, consider [RenderClipRRect].
1771class RenderClipPath extends _RenderCustomClip<Path> {
1772 /// Creates a path clip.
1773 ///
1774 /// If [clipper] is null, the clip will be a rectangle that matches the layout
1775 /// size and location of the child. However, rather than use this default,
1776 /// consider using a [RenderClipRect], which can achieve the same effect more
1777 /// efficiently.
1778 ///
1779 /// If [clipBehavior] is [Clip.none], no clipping will be applied.
1780 RenderClipPath({
1781 super.child,
1782 super.clipper,
1783 super.clipBehavior,
1784 });
1785
1786 @override
1787 Path get _defaultClip => Path()..addRect(Offset.zero & size);
1788
1789 @override
1790 bool hitTest(BoxHitTestResult result, { required Offset position }) {
1791 if (_clipper != null) {
1792 _updateClip();
1793 assert(_clip != null);
1794 if (!_clip!.contains(position)) {
1795 return false;
1796 }
1797 }
1798 return super.hitTest(result, position: position);
1799 }
1800
1801 @override
1802 void paint(PaintingContext context, Offset offset) {
1803 if (child != null) {
1804 if (clipBehavior != Clip.none) {
1805 _updateClip();
1806 layer = context.pushClipPath(
1807 needsCompositing,
1808 offset,
1809 Offset.zero & size,
1810 _clip!,
1811 super.paint,
1812 clipBehavior: clipBehavior,
1813 oldLayer: layer as ClipPathLayer?,
1814 );
1815 } else {
1816 context.paintChild(child!, offset);
1817 layer = null;
1818 }
1819 } else {
1820 layer = null;
1821 }
1822 }
1823
1824 @override
1825 void debugPaintSize(PaintingContext context, Offset offset) {
1826 assert(() {
1827 if (child != null) {
1828 super.debugPaintSize(context, offset);
1829 if (clipBehavior != Clip.none) {
1830 context.canvas.drawPath(_clip!.shift(offset), _debugPaint!);
1831 _debugText!.paint(context.canvas, offset);
1832 }
1833 }
1834 return true;
1835 }());
1836 }
1837}
1838
1839/// A physical model layer casts a shadow based on its [elevation].
1840///
1841/// The concrete implementations [RenderPhysicalModel] and [RenderPhysicalShape]
1842/// determine the actual shape of the physical model.
1843abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> {
1844 /// The [elevation] parameter must be non-negative.
1845 _RenderPhysicalModelBase({
1846 required super.child,
1847 required double elevation,
1848 required Color color,
1849 required Color shadowColor,
1850 super.clipBehavior = Clip.none,
1851 super.clipper,
1852 }) : assert(elevation >= 0.0),
1853 _elevation = elevation,
1854 _color = color,
1855 _shadowColor = shadowColor;
1856
1857 /// The z-coordinate relative to the parent at which to place this material.
1858 ///
1859 /// The value is non-negative.
1860 ///
1861 /// If [debugDisableShadows] is set, this value is ignored and no shadow is
1862 /// drawn (an outline is rendered instead).
1863 double get elevation => _elevation;
1864 double _elevation;
1865 set elevation(double value) {
1866 assert(value >= 0.0);
1867 if (elevation == value) {
1868 return;
1869 }
1870 final bool didNeedCompositing = alwaysNeedsCompositing;
1871 _elevation = value;
1872 if (didNeedCompositing != alwaysNeedsCompositing) {
1873 markNeedsCompositingBitsUpdate();
1874 }
1875 markNeedsPaint();
1876 }
1877
1878 /// The shadow color.
1879 Color get shadowColor => _shadowColor;
1880 Color _shadowColor;
1881 set shadowColor(Color value) {
1882 if (shadowColor == value) {
1883 return;
1884 }
1885 _shadowColor = value;
1886 markNeedsPaint();
1887 }
1888
1889 /// The background color.
1890 Color get color => _color;
1891 Color _color;
1892 set color(Color value) {
1893 if (color == value) {
1894 return;
1895 }
1896 _color = value;
1897 markNeedsPaint();
1898 }
1899
1900 @override
1901 void describeSemanticsConfiguration(SemanticsConfiguration config) {
1902 super.describeSemanticsConfiguration(config);
1903 config.elevation = elevation;
1904 }
1905
1906 @override
1907 void debugFillProperties(DiagnosticPropertiesBuilder description) {
1908 super.debugFillProperties(description);
1909 description.add(DoubleProperty('elevation', elevation));
1910 description.add(ColorProperty('color', color));
1911 description.add(ColorProperty('shadowColor', color));
1912 }
1913}
1914
1915/// Creates a physical model layer that clips its child to a rounded
1916/// rectangle.
1917///
1918/// A physical model layer casts a shadow based on its [elevation].
1919class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
1920 /// Creates a rounded-rectangular clip.
1921 ///
1922 /// The [color] is required.
1923 ///
1924 /// The [elevation] parameter must be non-negative.
1925 RenderPhysicalModel({
1926 super.child,
1927 BoxShape shape = BoxShape.rectangle,
1928 super.clipBehavior,
1929 BorderRadius? borderRadius,
1930 super.elevation = 0.0,
1931 required super.color,
1932 super.shadowColor = const Color(0xFF000000),
1933 }) : assert(elevation >= 0.0),
1934 _shape = shape,
1935 _borderRadius = borderRadius;
1936
1937 /// The shape of the layer.
1938 ///
1939 /// Defaults to [BoxShape.rectangle]. The [borderRadius] affects the corners
1940 /// of the rectangle.
1941 BoxShape get shape => _shape;
1942 BoxShape _shape;
1943 set shape(BoxShape value) {
1944 if (shape == value) {
1945 return;
1946 }
1947 _shape = value;
1948 _markNeedsClip();
1949 }
1950
1951 /// The border radius of the rounded corners.
1952 ///
1953 /// Values are clamped so that horizontal and vertical radii sums do not
1954 /// exceed width/height.
1955 ///
1956 /// This property is ignored if the [shape] is not [BoxShape.rectangle].
1957 ///
1958 /// The value null is treated like [BorderRadius.zero].
1959 BorderRadius? get borderRadius => _borderRadius;
1960 BorderRadius? _borderRadius;
1961 set borderRadius(BorderRadius? value) {
1962 if (borderRadius == value) {
1963 return;
1964 }
1965 _borderRadius = value;
1966 _markNeedsClip();
1967 }
1968
1969 @override
1970 RRect get _defaultClip {
1971 assert(hasSize);
1972 final Rect rect = Offset.zero & size;
1973 switch (_shape) {
1974 case BoxShape.rectangle:
1975 return (borderRadius ?? BorderRadius.zero).toRRect(rect);
1976 case BoxShape.circle:
1977 return RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
1978 }
1979 }
1980
1981 @override
1982 bool hitTest(BoxHitTestResult result, { required Offset position }) {
1983 if (_clipper != null) {
1984 _updateClip();
1985 assert(_clip != null);
1986 if (!_clip!.contains(position)) {
1987 return false;
1988 }
1989 }
1990 return super.hitTest(result, position: position);
1991 }
1992
1993 @override
1994 void paint(PaintingContext context, Offset offset) {
1995 if (child == null) {
1996 layer = null;
1997 return;
1998 }
1999
2000 _updateClip();
2001 final RRect offsetRRect = _clip!.shift(offset);
2002 final Path offsetRRectAsPath = Path()..addRRect(offsetRRect);
2003 bool paintShadows = true;
2004 assert(() {
2005 if (debugDisableShadows) {
2006 if (elevation > 0.0) {
2007 context.canvas.drawRRect(
2008 offsetRRect,
2009 Paint()
2010 ..color = shadowColor
2011 ..style = PaintingStyle.stroke
2012 ..strokeWidth = elevation * 2.0,
2013 );
2014 }
2015 paintShadows = false;
2016 }
2017 return true;
2018 }());
2019
2020 final Canvas canvas = context.canvas;
2021 if (elevation != 0.0 && paintShadows) {
2022 canvas.drawShadow(
2023 offsetRRectAsPath,
2024 shadowColor,
2025 elevation,
2026 color.alpha != 0xFF,
2027 );
2028 }
2029 final bool usesSaveLayer = clipBehavior == Clip.antiAliasWithSaveLayer;
2030 if (!usesSaveLayer) {
2031 canvas.drawRRect(
2032 offsetRRect,
2033 Paint()..color = color
2034 );
2035 }
2036 layer = context.pushClipRRect(
2037 needsCompositing,
2038 offset,
2039 Offset.zero & size,
2040 _clip!,
2041 (PaintingContext context, Offset offset) {
2042 if (usesSaveLayer) {
2043 // If we want to avoid the bleeding edge artifact
2044 // (https://github.com/flutter/flutter/issues/18057#issue-328003931)
2045 // using saveLayer, we have to call drawPaint instead of drawPath as
2046 // anti-aliased drawPath will always have such artifacts.
2047 context.canvas.drawPaint( Paint()..color = color);
2048 }
2049 super.paint(context, offset);
2050 },
2051 oldLayer: layer as ClipRRectLayer?,
2052 clipBehavior: clipBehavior,
2053 );
2054
2055 assert(() {
2056 layer?.debugCreator = debugCreator;
2057 return true;
2058 }());
2059 }
2060
2061 @override
2062 void debugFillProperties(DiagnosticPropertiesBuilder description) {
2063 super.debugFillProperties(description);
2064 description.add(DiagnosticsProperty<BoxShape>('shape', shape));
2065 description.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
2066 }
2067}
2068
2069/// Creates a physical shape layer that clips its child to a [Path].
2070///
2071/// A physical shape layer casts a shadow based on its [elevation].
2072///
2073/// See also:
2074///
2075/// * [RenderPhysicalModel], which is optimized for rounded rectangles and
2076/// circles.
2077class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
2078 /// Creates an arbitrary shape clip.
2079 ///
2080 /// The [color] and [clipper] parameters are required.
2081 ///
2082 /// The [elevation] parameter must be non-negative.
2083 RenderPhysicalShape({
2084 super.child,
2085 required CustomClipper<Path> super.clipper,
2086 super.clipBehavior,
2087 super.elevation = 0.0,
2088 required super.color,
2089 super.shadowColor = const Color(0xFF000000),
2090 }) : assert(elevation >= 0.0);
2091
2092 @override
2093 Path get _defaultClip => Path()..addRect(Offset.zero & size);
2094
2095 @override
2096 bool hitTest(BoxHitTestResult result, { required Offset position }) {
2097 if (_clipper != null) {
2098 _updateClip();
2099 assert(_clip != null);
2100 if (!_clip!.contains(position)) {
2101 return false;
2102 }
2103 }
2104 return super.hitTest(result, position: position);
2105 }
2106
2107 @override
2108 void paint(PaintingContext context, Offset offset) {
2109 if (child == null) {
2110 layer = null;
2111 return;
2112 }
2113
2114 _updateClip();
2115 final Path offsetPath = _clip!.shift(offset);
2116 bool paintShadows = true;
2117 assert(() {
2118 if (debugDisableShadows) {
2119 if (elevation > 0.0) {
2120 context.canvas.drawPath(
2121 offsetPath,
2122 Paint()
2123 ..color = shadowColor
2124 ..style = PaintingStyle.stroke
2125 ..strokeWidth = elevation * 2.0,
2126 );
2127 }
2128 paintShadows = false;
2129 }
2130 return true;
2131 }());
2132
2133 final Canvas canvas = context.canvas;
2134 if (elevation != 0.0 && paintShadows) {
2135 canvas.drawShadow(
2136 offsetPath,
2137 shadowColor,
2138 elevation,
2139 color.alpha != 0xFF,
2140 );
2141 }
2142 final bool usesSaveLayer = clipBehavior == Clip.antiAliasWithSaveLayer;
2143 if (!usesSaveLayer) {
2144 canvas.drawPath(
2145 offsetPath,
2146 Paint()..color = color
2147 );
2148 }
2149 layer = context.pushClipPath(
2150 needsCompositing,
2151 offset,
2152 Offset.zero & size,
2153 _clip!,
2154 (PaintingContext context, Offset offset) {
2155 if (usesSaveLayer) {
2156 // If we want to avoid the bleeding edge artifact
2157 // (https://github.com/flutter/flutter/issues/18057#issue-328003931)
2158 // using saveLayer, we have to call drawPaint instead of drawPath as
2159 // anti-aliased drawPath will always have such artifacts.
2160 context.canvas.drawPaint( Paint()..color = color);
2161 }
2162 super.paint(context, offset);
2163 },
2164 oldLayer: layer as ClipPathLayer?,
2165 clipBehavior: clipBehavior,
2166 );
2167
2168 assert(() {
2169 layer?.debugCreator = debugCreator;
2170 return true;
2171 }());
2172 }
2173
2174 @override
2175 void debugFillProperties(DiagnosticPropertiesBuilder description) {
2176 super.debugFillProperties(description);
2177 description.add(DiagnosticsProperty<CustomClipper<Path>>('clipper', clipper));
2178 }
2179}
2180
2181/// Where to paint a box decoration.
2182enum DecorationPosition {
2183 /// Paint the box decoration behind the children.
2184 background,
2185
2186 /// Paint the box decoration in front of the children.
2187 foreground,
2188}
2189
2190/// Paints a [Decoration] either before or after its child paints.
2191class RenderDecoratedBox extends RenderProxyBox {
2192 /// Creates a decorated box.
2193 ///
2194 /// The [decoration], [position], and [configuration] arguments must not be
2195 /// null. By default the decoration paints behind the child.
2196 ///
2197 /// The [ImageConfiguration] will be passed to the decoration (with the size
2198 /// filled in) to let it resolve images.
2199 RenderDecoratedBox({
2200 required Decoration decoration,
2201 DecorationPosition position = DecorationPosition.background,
2202 ImageConfiguration configuration = ImageConfiguration.empty,
2203 RenderBox? child,
2204 }) : _decoration = decoration,
2205 _position = position,
2206 _configuration = configuration,
2207 super(child);
2208
2209 BoxPainter? _painter;
2210
2211 /// What decoration to paint.
2212 ///
2213 /// Commonly a [BoxDecoration].
2214 Decoration get decoration => _decoration;
2215 Decoration _decoration;
2216 set decoration(Decoration value) {
2217 if (value == _decoration) {
2218 return;
2219 }
2220 _painter?.dispose();
2221 _painter = null;
2222 _decoration = value;
2223 markNeedsPaint();
2224 }
2225
2226 /// Whether to paint the box decoration behind or in front of the child.
2227 DecorationPosition get position => _position;
2228 DecorationPosition _position;
2229 set position(DecorationPosition value) {
2230 if (value == _position) {
2231 return;
2232 }
2233 _position = value;
2234 markNeedsPaint();
2235 }
2236
2237 /// The settings to pass to the decoration when painting, so that it can
2238 /// resolve images appropriately. See [ImageProvider.resolve] and
2239 /// [BoxPainter.paint].
2240 ///
2241 /// The [ImageConfiguration.textDirection] field is also used by
2242 /// direction-sensitive [Decoration]s for painting and hit-testing.
2243 ImageConfiguration get configuration => _configuration;
2244 ImageConfiguration _configuration;
2245 set configuration(ImageConfiguration value) {
2246 if (value == _configuration) {
2247 return;
2248 }
2249 _configuration = value;
2250 markNeedsPaint();
2251 }
2252
2253 @override
2254 void detach() {
2255 _painter?.dispose();
2256 _painter = null;
2257 super.detach();
2258 // Since we're disposing of our painter, we won't receive change
2259 // notifications. We mark ourselves as needing paint so that we will
2260 // resubscribe to change notifications. If we didn't do this, then, for
2261 // example, animated GIFs would stop animating when a DecoratedBox gets
2262 // moved around the tree due to GlobalKey reparenting.
2263 markNeedsPaint();
2264 }
2265
2266 @override
2267 void dispose() {
2268 _painter?.dispose();
2269 super.dispose();
2270 }
2271
2272 @override
2273 bool hitTestSelf(Offset position) {
2274 return _decoration.hitTest(size, position, textDirection: configuration.textDirection);
2275 }
2276
2277 @override
2278 void paint(PaintingContext context, Offset offset) {
2279 _painter ??= _decoration.createBoxPainter(markNeedsPaint);
2280 final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
2281 if (position == DecorationPosition.background) {
2282 int? debugSaveCount;
2283 assert(() {
2284 debugSaveCount = context.canvas.getSaveCount();
2285 return true;
2286 }());
2287 _painter!.paint(context.canvas, offset, filledConfiguration);
2288 assert(() {
2289 if (debugSaveCount != context.canvas.getSaveCount()) {
2290 throw FlutterError.fromParts(<DiagnosticsNode>[
2291 ErrorSummary('${_decoration.runtimeType} painter had mismatching save and restore calls.'),
2292 ErrorDescription(
2293 'Before painting the decoration, the canvas save count was $debugSaveCount. '
2294 'After painting it, the canvas save count was ${context.canvas.getSaveCount()}. '
2295 'Every call to save() or saveLayer() must be matched by a call to restore().',
2296 ),
2297 DiagnosticsProperty<Decoration>('The decoration was', decoration, style: DiagnosticsTreeStyle.errorProperty),
2298 DiagnosticsProperty<BoxPainter>('The painter was', _painter, style: DiagnosticsTreeStyle.errorProperty),
2299 ]);
2300 }
2301 return true;
2302 }());
2303 if (decoration.isComplex) {
2304 context.setIsComplexHint();
2305 }
2306 }
2307 super.paint(context, offset);
2308 if (position == DecorationPosition.foreground) {
2309 _painter!.paint(context.canvas, offset, filledConfiguration);
2310 if (decoration.isComplex) {
2311 context.setIsComplexHint();
2312 }
2313 }
2314 }
2315
2316 @override
2317 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2318 super.debugFillProperties(properties);
2319 properties.add(_decoration.toDiagnosticsNode(name: 'decoration'));
2320 properties.add(DiagnosticsProperty<ImageConfiguration>('configuration', configuration));
2321 }
2322}
2323
2324/// Applies a transformation before painting its child.
2325class RenderTransform extends RenderProxyBox {
2326 /// Creates a render object that transforms its child.
2327 RenderTransform({
2328 required Matrix4 transform,
2329 Offset? origin,
2330 AlignmentGeometry? alignment,
2331 TextDirection? textDirection,
2332 this.transformHitTests = true,
2333 FilterQuality? filterQuality,
2334 RenderBox? child,
2335 }) : super(child) {
2336 this.transform = transform;
2337 this.alignment = alignment;
2338 this.textDirection = textDirection;
2339 this.filterQuality = filterQuality;
2340 this.origin = origin;
2341 }
2342
2343 /// The origin of the coordinate system (relative to the upper left corner of
2344 /// this render object) in which to apply the matrix.
2345 ///
2346 /// Setting an origin is equivalent to conjugating the transform matrix by a
2347 /// translation. This property is provided just for convenience.
2348 Offset? get origin => _origin;
2349 Offset? _origin;
2350 set origin(Offset? value) {
2351 if (_origin == value) {
2352 return;
2353 }
2354 _origin = value;
2355 markNeedsPaint();
2356 markNeedsSemanticsUpdate();
2357 }
2358
2359 /// The alignment of the origin, relative to the size of the box.
2360 ///
2361 /// This is equivalent to setting an origin based on the size of the box.
2362 /// If it is specified at the same time as an offset, both are applied.
2363 ///
2364 /// An [AlignmentDirectional.centerStart] value is the same as an [Alignment]
2365 /// whose [Alignment.x] value is `-1.0` if [textDirection] is
2366 /// [TextDirection.ltr], and `1.0` if [textDirection] is [TextDirection.rtl].
2367 /// Similarly [AlignmentDirectional.centerEnd] is the same as an [Alignment]
2368 /// whose [Alignment.x] value is `1.0` if [textDirection] is
2369 /// [TextDirection.ltr], and `-1.0` if [textDirection] is [TextDirection.rtl].
2370 AlignmentGeometry? get alignment => _alignment;
2371 AlignmentGeometry? _alignment;
2372 set alignment(AlignmentGeometry? value) {
2373 if (_alignment == value) {
2374 return;
2375 }
2376 _alignment = value;
2377 markNeedsPaint();
2378 markNeedsSemanticsUpdate();
2379 }
2380
2381 /// The text direction with which to resolve [alignment].
2382 ///
2383 /// This may be changed to null, but only after [alignment] has been changed
2384 /// to a value that does not depend on the direction.
2385 TextDirection? get textDirection => _textDirection;
2386 TextDirection? _textDirection;
2387 set textDirection(TextDirection? value) {
2388 if (_textDirection == value) {
2389 return;
2390 }
2391 _textDirection = value;
2392 markNeedsPaint();
2393 markNeedsSemanticsUpdate();
2394 }
2395
2396 @override
2397 bool get alwaysNeedsCompositing => child != null && _filterQuality != null;
2398
2399 /// When set to true, hit tests are performed based on the position of the
2400 /// child as it is painted. When set to false, hit tests are performed
2401 /// ignoring the transformation.
2402 ///
2403 /// [applyPaintTransform], and therefore [localToGlobal] and [globalToLocal],
2404 /// always honor the transformation, regardless of the value of this property.
2405 bool transformHitTests;
2406
2407 Matrix4? _transform;
2408 /// The matrix to transform the child by during painting. The provided value
2409 /// is copied on assignment.
2410 ///
2411 /// There is no getter for [transform], because [Matrix4] is mutable, and
2412 /// mutations outside of the control of the render object could not reliably
2413 /// be reflected in the rendering.
2414 set transform(Matrix4 value) { // ignore: avoid_setters_without_getters
2415 if (_transform == value) {
2416 return;
2417 }
2418 _transform = Matrix4.copy(value);
2419 markNeedsPaint();
2420 markNeedsSemanticsUpdate();
2421 }
2422
2423 /// The filter quality with which to apply the transform as a bitmap operation.
2424 ///
2425 /// {@macro flutter.widgets.Transform.optional.FilterQuality}
2426 FilterQuality? get filterQuality => _filterQuality;
2427 FilterQuality? _filterQuality;
2428 set filterQuality(FilterQuality? value) {
2429 if (_filterQuality == value) {
2430 return;
2431 }
2432 final bool didNeedCompositing = alwaysNeedsCompositing;
2433 _filterQuality = value;
2434 if (didNeedCompositing != alwaysNeedsCompositing) {
2435 markNeedsCompositingBitsUpdate();
2436 }
2437 markNeedsPaint();
2438 }
2439
2440 /// Sets the transform to the identity matrix.
2441 void setIdentity() {
2442 _transform!.setIdentity();
2443 markNeedsPaint();
2444 markNeedsSemanticsUpdate();
2445 }
2446
2447 /// Concatenates a rotation about the x axis into the transform.
2448 void rotateX(double radians) {
2449 _transform!.rotateX(radians);
2450 markNeedsPaint();
2451 markNeedsSemanticsUpdate();
2452 }
2453
2454 /// Concatenates a rotation about the y axis into the transform.
2455 void rotateY(double radians) {
2456 _transform!.rotateY(radians);
2457 markNeedsPaint();
2458 markNeedsSemanticsUpdate();
2459 }
2460
2461 /// Concatenates a rotation about the z axis into the transform.
2462 void rotateZ(double radians) {
2463 _transform!.rotateZ(radians);
2464 markNeedsPaint();
2465 markNeedsSemanticsUpdate();
2466 }
2467
2468 /// Concatenates a translation by (x, y, z) into the transform.
2469 void translate(double x, [ double y = 0.0, double z = 0.0 ]) {
2470 _transform!.translate(x, y, z);
2471 markNeedsPaint();
2472 markNeedsSemanticsUpdate();
2473 }
2474
2475 /// Concatenates a scale into the transform.
2476 void scale(double x, [ double? y, double? z ]) {
2477 _transform!.scale(x, y, z);
2478 markNeedsPaint();
2479 markNeedsSemanticsUpdate();
2480 }
2481
2482 Matrix4? get _effectiveTransform {
2483 final Alignment? resolvedAlignment = alignment?.resolve(textDirection);
2484 if (_origin == null && resolvedAlignment == null) {
2485 return _transform;
2486 }
2487 final Matrix4 result = Matrix4.identity();
2488 if (_origin != null) {
2489 result.translate(_origin!.dx, _origin!.dy);
2490 }
2491 Offset? translation;
2492 if (resolvedAlignment != null) {
2493 translation = resolvedAlignment.alongSize(size);
2494 result.translate(translation.dx, translation.dy);
2495 }
2496 result.multiply(_transform!);
2497 if (resolvedAlignment != null) {
2498 result.translate(-translation!.dx, -translation.dy);
2499 }
2500 if (_origin != null) {
2501 result.translate(-_origin!.dx, -_origin!.dy);
2502 }
2503 return result;
2504 }
2505
2506 @override
2507 bool hitTest(BoxHitTestResult result, { required Offset position }) {
2508 // RenderTransform objects don't check if they are
2509 // themselves hit, because it's confusing to think about
2510 // how the untransformed size and the child's transformed
2511 // position interact.
2512 return hitTestChildren(result, position: position);
2513 }
2514
2515 @override
2516 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
2517 assert(!transformHitTests || _effectiveTransform != null);
2518 return result.addWithPaintTransform(
2519 transform: transformHitTests ? _effectiveTransform : null,
2520 position: position,
2521 hitTest: (BoxHitTestResult result, Offset position) {
2522 return super.hitTestChildren(result, position: position);
2523 },
2524 );
2525 }
2526
2527 @override
2528 void paint(PaintingContext context, Offset offset) {
2529 if (child != null) {
2530 final Matrix4 transform = _effectiveTransform!;
2531 if (filterQuality == null) {
2532 final Offset? childOffset = MatrixUtils.getAsTranslation(transform);
2533 if (childOffset == null) {
2534 // if the matrix is singular the children would be compressed to a line or
2535 // single point, instead short-circuit and paint nothing.
2536 final double det = transform.determinant();
2537 if (det == 0 || !det.isFinite) {
2538 layer = null;
2539 return;
2540 }
2541 layer = context.pushTransform(
2542 needsCompositing,
2543 offset,
2544 transform,
2545 super.paint,
2546 oldLayer: layer is TransformLayer ? layer as TransformLayer? : null,
2547 );
2548 } else {
2549 super.paint(context, offset + childOffset);
2550 layer = null;
2551 }
2552 } else {
2553 final Matrix4 effectiveTransform = Matrix4.translationValues(offset.dx, offset.dy, 0.0)
2554 ..multiply(transform)..translate(-offset.dx, -offset.dy);
2555 final ui.ImageFilter filter = ui.ImageFilter.matrix(
2556 effectiveTransform.storage,
2557 filterQuality: filterQuality!,
2558 );
2559 if (layer is ImageFilterLayer) {
2560 final ImageFilterLayer filterLayer = layer! as ImageFilterLayer;
2561 filterLayer.imageFilter = filter;
2562 } else {
2563 layer = ImageFilterLayer(imageFilter: filter);
2564 }
2565 context.pushLayer(layer!, super.paint, offset);
2566 assert(() {
2567 layer!.debugCreator = debugCreator;
2568 return true;
2569 }());
2570 }
2571 }
2572 }
2573
2574 @override
2575 void applyPaintTransform(RenderBox child, Matrix4 transform) {
2576 transform.multiply(_effectiveTransform!);
2577 }
2578
2579 @override
2580 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2581 super.debugFillProperties(properties);
2582 properties.add(TransformProperty('transform matrix', _transform));
2583 properties.add(DiagnosticsProperty<Offset>('origin', origin));
2584 properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
2585 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
2586 properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
2587 }
2588}
2589
2590/// Scales and positions its child within itself according to [fit].
2591class RenderFittedBox extends RenderProxyBox {
2592 /// Scales and positions its child within itself.
2593 RenderFittedBox({
2594 BoxFit fit = BoxFit.contain,
2595 AlignmentGeometry alignment = Alignment.center,
2596 TextDirection? textDirection,
2597 RenderBox? child,
2598 Clip clipBehavior = Clip.none,
2599 }) : _fit = fit,
2600 _alignment = alignment,
2601 _textDirection = textDirection,
2602 _clipBehavior = clipBehavior,
2603 super(child);
2604
2605 Alignment? _resolvedAlignment;
2606
2607 void _resolve() {
2608 if (_resolvedAlignment != null) {
2609 return;
2610 }
2611 _resolvedAlignment = alignment.resolve(textDirection);
2612 }
2613
2614 void _markNeedResolution() {
2615 _resolvedAlignment = null;
2616 markNeedsPaint();
2617 }
2618
2619 bool _fitAffectsLayout(BoxFit fit) {
2620 switch (fit) {
2621 case BoxFit.scaleDown:
2622 return true;
2623 case BoxFit.contain:
2624 case BoxFit.cover:
2625 case BoxFit.fill:
2626 case BoxFit.fitHeight:
2627 case BoxFit.fitWidth:
2628 case BoxFit.none:
2629 return false;
2630 }
2631 }
2632
2633 /// How to inscribe the child into the space allocated during layout.
2634 BoxFit get fit => _fit;
2635 BoxFit _fit;
2636 set fit(BoxFit value) {
2637 if (_fit == value) {
2638 return;
2639 }
2640 final BoxFit lastFit = _fit;
2641 _fit = value;
2642 if (_fitAffectsLayout(lastFit) || _fitAffectsLayout(value)) {
2643 markNeedsLayout();
2644 } else {
2645 _clearPaintData();
2646 markNeedsPaint();
2647 }
2648 }
2649
2650 /// How to align the child within its parent's bounds.
2651 ///
2652 /// An alignment of (0.0, 0.0) aligns the child to the top-left corner of its
2653 /// parent's bounds. An alignment of (1.0, 0.5) aligns the child to the middle
2654 /// of the right edge of its parent's bounds.
2655 ///
2656 /// If this is set to an [AlignmentDirectional] object, then
2657 /// [textDirection] must not be null.
2658 AlignmentGeometry get alignment => _alignment;
2659 AlignmentGeometry _alignment;
2660 set alignment(AlignmentGeometry value) {
2661 if (_alignment == value) {
2662 return;
2663 }
2664 _alignment = value;
2665 _clearPaintData();
2666 _markNeedResolution();
2667 }
2668
2669 /// The text direction with which to resolve [alignment].
2670 ///
2671 /// This may be changed to null, but only after [alignment] has been changed
2672 /// to a value that does not depend on the direction.
2673 TextDirection? get textDirection => _textDirection;
2674 TextDirection? _textDirection;
2675 set textDirection(TextDirection? value) {
2676 if (_textDirection == value) {
2677 return;
2678 }
2679 _textDirection = value;
2680 _clearPaintData();
2681 _markNeedResolution();
2682 }
2683
2684 // TODO(ianh): The intrinsic dimensions of this box are wrong.
2685
2686 @override
2687 @protected
2688 Size computeDryLayout(covariant BoxConstraints constraints) {
2689 if (child != null) {
2690 final Size childSize = child!.getDryLayout(const BoxConstraints());
2691
2692 // During [RenderObject.debugCheckingIntrinsics] a child that doesn't
2693 // support dry layout may provide us with an invalid size that triggers
2694 // assertions if we try to work with it. Instead of throwing, we bail
2695 // out early in that case.
2696 bool invalidChildSize = false;
2697 assert(() {
2698 if (RenderObject.debugCheckingIntrinsics && childSize.width * childSize.height == 0.0) {
2699 invalidChildSize = true;
2700 }
2701 return true;
2702 }());
2703 if (invalidChildSize) {
2704 assert(debugCannotComputeDryLayout(
2705 reason: 'Child provided invalid size of $childSize.',
2706 ));
2707 return Size.zero;
2708 }
2709
2710 switch (fit) {
2711 case BoxFit.scaleDown:
2712 final BoxConstraints sizeConstraints = constraints.loosen();
2713 final Size unconstrainedSize = sizeConstraints.constrainSizeAndAttemptToPreserveAspectRatio(childSize);
2714 return constraints.constrain(unconstrainedSize);
2715 case BoxFit.contain:
2716 case BoxFit.cover:
2717 case BoxFit.fill:
2718 case BoxFit.fitHeight:
2719 case BoxFit.fitWidth:
2720 case BoxFit.none:
2721 return constraints.constrainSizeAndAttemptToPreserveAspectRatio(childSize);
2722 }
2723 } else {
2724 return constraints.smallest;
2725 }
2726 }
2727
2728 @override
2729 void performLayout() {
2730 if (child != null) {
2731 child!.layout(const BoxConstraints(), parentUsesSize: true);
2732 switch (fit) {
2733 case BoxFit.scaleDown:
2734 final BoxConstraints sizeConstraints = constraints.loosen();
2735 final Size unconstrainedSize = sizeConstraints.constrainSizeAndAttemptToPreserveAspectRatio(child!.size);
2736 size = constraints.constrain(unconstrainedSize);
2737 case BoxFit.contain:
2738 case BoxFit.cover:
2739 case BoxFit.fill:
2740 case BoxFit.fitHeight:
2741 case BoxFit.fitWidth:
2742 case BoxFit.none:
2743 size = constraints.constrainSizeAndAttemptToPreserveAspectRatio(child!.size);
2744 }
2745 _clearPaintData();
2746 } else {
2747 size = constraints.smallest;
2748 }
2749 }
2750
2751 bool? _hasVisualOverflow;
2752 Matrix4? _transform;
2753
2754 /// {@macro flutter.material.Material.clipBehavior}
2755 ///
2756 /// Defaults to [Clip.none].
2757 Clip get clipBehavior => _clipBehavior;
2758 Clip _clipBehavior = Clip.none;
2759 set clipBehavior(Clip value) {
2760 if (value != _clipBehavior) {
2761 _clipBehavior = value;
2762 markNeedsPaint();
2763 markNeedsSemanticsUpdate();
2764 }
2765 }
2766
2767 void _clearPaintData() {
2768 _hasVisualOverflow = null;
2769 _transform = null;
2770 }
2771
2772 void _updatePaintData() {
2773 if (_transform != null) {
2774 return;
2775 }
2776
2777 if (child == null) {
2778 _hasVisualOverflow = false;
2779 _transform = Matrix4.identity();
2780 } else {
2781 _resolve();
2782 final Size childSize = child!.size;
2783 final FittedSizes sizes = applyBoxFit(_fit, childSize, size);
2784 final double scaleX = sizes.destination.width / sizes.source.width;
2785 final double scaleY = sizes.destination.height / sizes.source.height;
2786 final Rect sourceRect = _resolvedAlignment!.inscribe(sizes.source, Offset.zero & childSize);
2787 final Rect destinationRect = _resolvedAlignment!.inscribe(sizes.destination, Offset.zero & size);
2788 _hasVisualOverflow = sourceRect.width < childSize.width || sourceRect.height < childSize.height;
2789 assert(scaleX.isFinite && scaleY.isFinite);
2790 _transform = Matrix4.translationValues(destinationRect.left, destinationRect.top, 0.0)
2791 ..scale(scaleX, scaleY, 1.0)
2792 ..translate(-sourceRect.left, -sourceRect.top);
2793 assert(_transform!.storage.every((double value) => value.isFinite));
2794 }
2795 }
2796
2797 TransformLayer? _paintChildWithTransform(PaintingContext context, Offset offset) {
2798 final Offset? childOffset = MatrixUtils.getAsTranslation(_transform!);
2799 if (childOffset == null) {
2800 return context.pushTransform(
2801 needsCompositing,
2802 offset,
2803 _transform!,
2804 super.paint,
2805 oldLayer: layer is TransformLayer ? layer! as TransformLayer : null,
2806 );
2807 } else {
2808 super.paint(context, offset + childOffset);
2809 }
2810 return null;
2811 }
2812
2813 @override
2814 void paint(PaintingContext context, Offset offset) {
2815 if (child == null || size.isEmpty || child!.size.isEmpty) {
2816 return;
2817 }
2818 _updatePaintData();
2819 assert(child != null);
2820 if (_hasVisualOverflow! && clipBehavior != Clip.none) {
2821 layer = context.pushClipRect(
2822 needsCompositing,
2823 offset,
2824 Offset.zero & size,
2825 _paintChildWithTransform,
2826 oldLayer: layer is ClipRectLayer ? layer! as ClipRectLayer : null,
2827 clipBehavior: clipBehavior,
2828 );
2829 } else {
2830 layer = _paintChildWithTransform(context, offset);
2831 }
2832 }
2833
2834 @override
2835 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
2836 if (size.isEmpty || (child?.size.isEmpty ?? false)) {
2837 return false;
2838 }
2839 _updatePaintData();
2840 return result.addWithPaintTransform(
2841 transform: _transform,
2842 position: position,
2843 hitTest: (BoxHitTestResult result, Offset position) {
2844 return super.hitTestChildren(result, position: position);
2845 },
2846 );
2847 }
2848
2849 @override
2850 bool paintsChild(RenderBox child) {
2851 assert(child.parent == this);
2852 return !size.isEmpty && !child.size.isEmpty;
2853 }
2854
2855 @override
2856 void applyPaintTransform(RenderBox child, Matrix4 transform) {
2857 if (!paintsChild(child)) {
2858 transform.setZero();
2859 } else {
2860 _updatePaintData();
2861 transform.multiply(_transform!);
2862 }
2863 }
2864
2865 @override
2866 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2867 super.debugFillProperties(properties);
2868 properties.add(EnumProperty<BoxFit>('fit', fit));
2869 properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
2870 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
2871 }
2872}
2873
2874/// Applies a translation transformation before painting its child.
2875///
2876/// The translation is expressed as an [Offset] scaled to the child's size. For
2877/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
2878/// translation of one quarter the width of the child.
2879///
2880/// Hit tests will only be detected inside the bounds of the
2881/// [RenderFractionalTranslation], even if the contents are offset such that
2882/// they overflow.
2883class RenderFractionalTranslation extends RenderProxyBox {
2884 /// Creates a render object that translates its child's painting.
2885 RenderFractionalTranslation({
2886 required Offset translation,
2887 this.transformHitTests = true,
2888 RenderBox? child,
2889 }) : _translation = translation,
2890 super(child);
2891
2892 /// The translation to apply to the child, scaled to the child's size.
2893 ///
2894 /// For example, an [Offset] with a `dx` of 0.25 will result in a horizontal
2895 /// translation of one quarter the width of the child.
2896 Offset get translation => _translation;
2897 Offset _translation;
2898 set translation(Offset value) {
2899 if (_translation == value) {
2900 return;
2901 }
2902 _translation = value;
2903 markNeedsPaint();
2904 markNeedsSemanticsUpdate();
2905 }
2906
2907 @override
2908 bool hitTest(BoxHitTestResult result, { required Offset position }) {
2909 // RenderFractionalTranslation objects don't check if they are
2910 // themselves hit, because it's confusing to think about
2911 // how the untransformed size and the child's transformed
2912 // position interact.
2913 return hitTestChildren(result, position: position);
2914 }
2915
2916 /// When set to true, hit tests are performed based on the position of the
2917 /// child as it is painted. When set to false, hit tests are performed
2918 /// ignoring the transformation.
2919 ///
2920 /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
2921 /// always honor the transformation, regardless of the value of this property.
2922 bool transformHitTests;
2923
2924 @override
2925 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
2926 assert(!debugNeedsLayout);
2927 return result.addWithPaintOffset(
2928 offset: transformHitTests
2929 ? Offset(translation.dx * size.width, translation.dy * size.height)
2930 : null,
2931 position: position,
2932 hitTest: (BoxHitTestResult result, Offset position) {
2933 return super.hitTestChildren(result, position: position);
2934 },
2935 );
2936 }
2937
2938 @override
2939 void paint(PaintingContext context, Offset offset) {
2940 assert(!debugNeedsLayout);
2941 if (child != null) {
2942 super.paint(context, Offset(
2943 offset.dx + translation.dx * size.width,
2944 offset.dy + translation.dy * size.height,
2945 ));
2946 }
2947 }
2948
2949 @override
2950 void applyPaintTransform(RenderBox child, Matrix4 transform) {
2951 transform.translate(
2952 translation.dx * size.width,
2953 translation.dy * size.height,
2954 );
2955 }
2956
2957 @override
2958 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2959 super.debugFillProperties(properties);
2960 properties.add(DiagnosticsProperty<Offset>('translation', translation));
2961 properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
2962 }
2963}
2964
2965/// Signature for listening to [PointerDownEvent] events.
2966///
2967/// Used by [Listener] and [RenderPointerListener].
2968typedef PointerDownEventListener = void Function(PointerDownEvent event);
2969
2970/// Signature for listening to [PointerMoveEvent] events.
2971///
2972/// Used by [Listener] and [RenderPointerListener].
2973typedef PointerMoveEventListener = void Function(PointerMoveEvent event);
2974
2975/// Signature for listening to [PointerUpEvent] events.
2976///
2977/// Used by [Listener] and [RenderPointerListener].
2978typedef PointerUpEventListener = void Function(PointerUpEvent event);
2979
2980/// Signature for listening to [PointerCancelEvent] events.
2981///
2982/// Used by [Listener] and [RenderPointerListener].
2983typedef PointerCancelEventListener = void Function(PointerCancelEvent event);
2984
2985/// Signature for listening to [PointerPanZoomStartEvent] events.
2986///
2987/// Used by [Listener] and [RenderPointerListener].
2988typedef PointerPanZoomStartEventListener = void Function(PointerPanZoomStartEvent event);
2989
2990/// Signature for listening to [PointerPanZoomUpdateEvent] events.
2991///
2992/// Used by [Listener] and [RenderPointerListener].
2993typedef PointerPanZoomUpdateEventListener = void Function(PointerPanZoomUpdateEvent event);
2994
2995/// Signature for listening to [PointerPanZoomEndEvent] events.
2996///
2997/// Used by [Listener] and [RenderPointerListener].
2998typedef PointerPanZoomEndEventListener = void Function(PointerPanZoomEndEvent event);
2999
3000/// Signature for listening to [PointerSignalEvent] events.
3001///
3002/// Used by [Listener] and [RenderPointerListener].
3003typedef PointerSignalEventListener = void Function(PointerSignalEvent event);
3004
3005/// Calls callbacks in response to common pointer events.
3006///
3007/// It responds to events that can construct gestures, such as when the
3008/// pointer is pointer is pressed and moved, and then released or canceled.
3009///
3010/// It does not respond to events that are exclusive to mouse, such as when the
3011/// mouse enters and exits a region without pressing any buttons. For
3012/// these events, use [RenderMouseRegion].
3013///
3014/// If it has a child, defers to the child for sizing behavior.
3015///
3016/// If it does not have a child, grows to fit the parent-provided constraints.
3017class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
3018 /// Creates a render object that forwards pointer events to callbacks.
3019 ///
3020 /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
3021 RenderPointerListener({
3022 this.onPointerDown,
3023 this.onPointerMove,
3024 this.onPointerUp,
3025 this.onPointerHover,
3026 this.onPointerCancel,
3027 this.onPointerPanZoomStart,
3028 this.onPointerPanZoomUpdate,
3029 this.onPointerPanZoomEnd,
3030 this.onPointerSignal,
3031 super.behavior,
3032 super.child,
3033 });
3034
3035 /// Called when a pointer comes into contact with the screen (for touch
3036 /// pointers), or has its button pressed (for mouse pointers) at this widget's
3037 /// location.
3038 PointerDownEventListener? onPointerDown;
3039
3040 /// Called when a pointer that triggered an [onPointerDown] changes position.
3041 PointerMoveEventListener? onPointerMove;
3042
3043 /// Called when a pointer that triggered an [onPointerDown] is no longer in
3044 /// contact with the screen.
3045 PointerUpEventListener? onPointerUp;
3046
3047 /// Called when a pointer that has not an [onPointerDown] changes position.
3048 PointerHoverEventListener? onPointerHover;
3049
3050 /// Called when the input from a pointer that triggered an [onPointerDown] is
3051 /// no longer directed towards this receiver.
3052 PointerCancelEventListener? onPointerCancel;
3053
3054 /// Called when a pan/zoom begins such as from a trackpad gesture.
3055 PointerPanZoomStartEventListener? onPointerPanZoomStart;
3056
3057 /// Called when a pan/zoom is updated.
3058 PointerPanZoomUpdateEventListener? onPointerPanZoomUpdate;
3059
3060 /// Called when a pan/zoom finishes.
3061 PointerPanZoomEndEventListener? onPointerPanZoomEnd;
3062
3063 /// Called when a pointer signal occurs over this object.
3064 PointerSignalEventListener? onPointerSignal;
3065
3066 @override
3067 Size computeSizeForNoChild(BoxConstraints constraints) {
3068 return constraints.biggest;
3069 }
3070
3071 @override
3072 void handleEvent(PointerEvent event, HitTestEntry entry) {
3073 assert(debugHandleEvent(event, entry));
3074 if (event is PointerDownEvent) {
3075 return onPointerDown?.call(event);
3076 }
3077 if (event is PointerMoveEvent) {
3078 return onPointerMove?.call(event);
3079 }
3080 if (event is PointerUpEvent) {
3081 return onPointerUp?.call(event);
3082 }
3083 if (event is PointerHoverEvent) {
3084 return onPointerHover?.call(event);
3085 }
3086 if (event is PointerCancelEvent) {
3087 return onPointerCancel?.call(event);
3088 }
3089 if (event is PointerPanZoomStartEvent) {
3090 return onPointerPanZoomStart?.call(event);
3091 }
3092 if (event is PointerPanZoomUpdateEvent) {
3093 return onPointerPanZoomUpdate?.call(event);
3094 }
3095 if (event is PointerPanZoomEndEvent) {
3096 return onPointerPanZoomEnd?.call(event);
3097 }
3098 if (event is PointerSignalEvent) {
3099 return onPointerSignal?.call(event);
3100 }
3101 }
3102
3103 @override
3104 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3105 super.debugFillProperties(properties);
3106 properties.add(FlagsSummary<Function?>(
3107 'listeners',
3108 <String, Function?>{
3109 'down': onPointerDown,
3110 'move': onPointerMove,
3111 'up': onPointerUp,
3112 'hover': onPointerHover,
3113 'cancel': onPointerCancel,
3114 'panZoomStart': onPointerPanZoomStart,
3115 'panZoomUpdate': onPointerPanZoomUpdate,
3116 'panZoomEnd': onPointerPanZoomEnd,
3117 'signal': onPointerSignal,
3118 },
3119 ifEmpty: '<none>',
3120 ));
3121 }
3122}
3123
3124/// Calls callbacks in response to pointer events that are exclusive to mice.
3125///
3126/// It responds to events that are related to hovering, i.e. when the mouse
3127/// enters, exits (with or without pressing buttons), or moves over a region
3128/// without pressing buttons.
3129///
3130/// It does not respond to common events that construct gestures, such as when
3131/// the pointer is pressed, moved, then released or canceled. For these events,
3132/// use [RenderPointerListener].
3133///
3134/// If it has a child, it defers to the child for sizing behavior.
3135///
3136/// If it does not have a child, it grows to fit the parent-provided constraints.
3137///
3138/// See also:
3139///
3140/// * [MouseRegion], a widget that listens to hover events using
3141/// [RenderMouseRegion].
3142class RenderMouseRegion extends RenderProxyBoxWithHitTestBehavior implements MouseTrackerAnnotation {
3143 /// Creates a render object that forwards pointer events to callbacks.
3144 ///
3145 /// All parameters are optional. By default this method creates an opaque
3146 /// mouse region with no callbacks and cursor being [MouseCursor.defer].
3147 RenderMouseRegion({
3148 this.onEnter,
3149 this.onHover,
3150 this.onExit,
3151 MouseCursor cursor = MouseCursor.defer,
3152 bool validForMouseTracker = true,
3153 bool opaque = true,
3154 super.child,
3155 HitTestBehavior? hitTestBehavior = HitTestBehavior.opaque,
3156 }) : _cursor = cursor,
3157 _validForMouseTracker = validForMouseTracker,
3158 _opaque = opaque,
3159 super(behavior: hitTestBehavior ?? HitTestBehavior.opaque);
3160
3161 @override
3162 bool hitTest(BoxHitTestResult result, { required Offset position }) {
3163 return super.hitTest(result, position: position) && _opaque;
3164 }
3165
3166 @override
3167 void handleEvent(PointerEvent event, HitTestEntry entry) {
3168 assert(debugHandleEvent(event, entry));
3169 if (onHover != null && event is PointerHoverEvent) {
3170 return onHover!(event);
3171 }
3172 }
3173
3174 /// Whether this object should prevent [RenderMouseRegion]s visually behind it
3175 /// from detecting the pointer, thus affecting how their [onHover], [onEnter],
3176 /// and [onExit] behave.
3177 ///
3178 /// If [opaque] is true, this object will absorb the mouse pointer and
3179 /// prevent this object's siblings (or any other objects that are not
3180 /// ancestors or descendants of this object) from detecting the mouse
3181 /// pointer even when the pointer is within their areas.
3182 ///
3183 /// If [opaque] is false, this object will not affect how [RenderMouseRegion]s
3184 /// behind it behave, which will detect the mouse pointer as long as the
3185 /// pointer is within their areas.
3186 ///
3187 /// This defaults to true.
3188 bool get opaque => _opaque;
3189 bool _opaque;
3190 set opaque(bool value) {
3191 if (_opaque != value) {
3192 _opaque = value;
3193 // Trigger [MouseTracker]'s device update to recalculate mouse states.
3194 markNeedsPaint();
3195 }
3196 }
3197
3198 /// How to behave during hit testing.
3199 ///
3200 /// This defaults to [HitTestBehavior.opaque] if null.
3201 HitTestBehavior? get hitTestBehavior => behavior;
3202 set hitTestBehavior(HitTestBehavior? value) {
3203 final HitTestBehavior newValue = value ?? HitTestBehavior.opaque;
3204 if (behavior != newValue) {
3205 behavior = newValue;
3206 // Trigger [MouseTracker]'s device update to recalculate mouse states.
3207 markNeedsPaint();
3208 }
3209 }
3210
3211 @override
3212 PointerEnterEventListener? onEnter;
3213
3214 /// Triggered when a pointer has moved onto or within the region without
3215 /// buttons pressed.
3216 ///
3217 /// This callback is not triggered by the movement of the object.
3218 PointerHoverEventListener? onHover;
3219
3220 @override
3221 PointerExitEventListener? onExit;
3222
3223 @override
3224 MouseCursor get cursor => _cursor;
3225 MouseCursor _cursor;
3226 set cursor(MouseCursor value) {
3227 if (_cursor != value) {
3228 _cursor = value;
3229 // A repaint is needed in order to trigger a device update of
3230 // [MouseTracker] so that this new value can be found.
3231 markNeedsPaint();
3232 }
3233 }
3234
3235 @override
3236 bool get validForMouseTracker => _validForMouseTracker;
3237 bool _validForMouseTracker;
3238
3239 @override
3240 void attach(PipelineOwner owner) {
3241 super.attach(owner);
3242 _validForMouseTracker = true;
3243 }
3244
3245 @override
3246 void detach() {
3247 // It's possible that the renderObject be detached during mouse events
3248 // dispatching, set the [MouseTrackerAnnotation.validForMouseTracker] false to prevent
3249 // the callbacks from being called.
3250 _validForMouseTracker = false;
3251 super.detach();
3252 }
3253
3254 @override
3255 Size computeSizeForNoChild(BoxConstraints constraints) {
3256 return constraints.biggest;
3257 }
3258
3259 @override
3260 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3261 super.debugFillProperties(properties);
3262 properties.add(FlagsSummary<Function?>(
3263 'listeners',
3264 <String, Function?>{
3265 'enter': onEnter,
3266 'hover': onHover,
3267 'exit': onExit,
3268 },
3269 ifEmpty: '<none>',
3270 ));
3271 properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
3272 properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true));
3273 properties.add(FlagProperty('validForMouseTracker', value: validForMouseTracker, defaultValue: true, ifFalse: 'invalid for MouseTracker'));
3274 }
3275}
3276
3277/// Creates a separate display list for its child.
3278///
3279/// This render object creates a separate display list for its child, which
3280/// can improve performance if the subtree repaints at different times than
3281/// the surrounding parts of the tree. Specifically, when the child does not
3282/// repaint but its parent does, we can re-use the display list we recorded
3283/// previously. Similarly, when the child repaints but the surround tree does
3284/// not, we can re-record its display list without re-recording the display list
3285/// for the surround tree.
3286///
3287/// In some cases, it is necessary to place _two_ (or more) repaint boundaries
3288/// to get a useful effect. Consider, for example, an e-mail application that
3289/// shows an unread count and a list of e-mails. Whenever a new e-mail comes in,
3290/// the list would update, but so would the unread count. If only one of these
3291/// two parts of the application was behind a repaint boundary, the entire
3292/// application would repaint each time. On the other hand, if both were behind
3293/// a repaint boundary, a new e-mail would only change those two parts of the
3294/// application and the rest of the application would not repaint.
3295///
3296/// To tell if a particular RenderRepaintBoundary is useful, run your
3297/// application in debug mode, interacting with it in typical ways, and then
3298/// call [debugDumpRenderTree]. Each RenderRepaintBoundary will include the
3299/// ratio of cases where the repaint boundary was useful vs the cases where it
3300/// was not. These counts can also be inspected programmatically using
3301/// [debugAsymmetricPaintCount] and [debugSymmetricPaintCount] respectively.
3302class RenderRepaintBoundary extends RenderProxyBox {
3303 /// Creates a repaint boundary around [child].
3304 RenderRepaintBoundary({ RenderBox? child }) : super(child);
3305
3306 @override
3307 bool get isRepaintBoundary => true;
3308
3309 /// Capture an image of the current state of this render object and its
3310 /// children.
3311 ///
3312 /// The returned [ui.Image] has uncompressed raw RGBA bytes in the dimensions
3313 /// of the render object, multiplied by the [pixelRatio].
3314 ///
3315 /// To use [toImage], the render object must have gone through the paint phase
3316 /// (i.e. [debugNeedsPaint] must be false).
3317 ///
3318 /// The [pixelRatio] describes the scale between the logical pixels and the
3319 /// size of the output image. It is independent of the
3320 /// [dart:ui.FlutterView.devicePixelRatio] for the device, so specifying 1.0
3321 /// (the default) will give you a 1:1 mapping between logical pixels and the
3322 /// output pixels in the image.
3323 ///
3324 /// {@tool snippet}
3325 ///
3326 /// The following is an example of how to go from a `GlobalKey` on a
3327 /// `RepaintBoundary` to a PNG:
3328 ///
3329 /// ```dart
3330 /// class PngHome extends StatefulWidget {
3331 /// const PngHome({super.key});
3332 ///
3333 /// @override
3334 /// State<PngHome> createState() => _PngHomeState();
3335 /// }
3336 ///
3337 /// class _PngHomeState extends State<PngHome> {
3338 /// GlobalKey globalKey = GlobalKey();
3339 ///
3340 /// Future<void> _capturePng() async {
3341 /// final RenderRepaintBoundary boundary = globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
3342 /// final ui.Image image = await boundary.toImage();
3343 /// final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
3344 /// final Uint8List pngBytes = byteData!.buffer.asUint8List();
3345 /// print(pngBytes);
3346 /// }
3347 ///
3348 /// @override
3349 /// Widget build(BuildContext context) {
3350 /// return RepaintBoundary(
3351 /// key: globalKey,
3352 /// child: Center(
3353 /// child: TextButton(
3354 /// onPressed: _capturePng,
3355 /// child: const Text('Hello World', textDirection: TextDirection.ltr),
3356 /// ),
3357 /// ),
3358 /// );
3359 /// }
3360 /// }
3361 /// ```
3362 /// {@end-tool}
3363 ///
3364 /// See also:
3365 ///
3366 /// * [OffsetLayer.toImage] for a similar API at the layer level.
3367 /// * [dart:ui.Scene.toImage] for more information about the image returned.
3368 Future<ui.Image> toImage({ double pixelRatio = 1.0 }) {
3369 assert(!debugNeedsPaint);
3370 final OffsetLayer offsetLayer = layer! as OffsetLayer;
3371 return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
3372 }
3373
3374 /// Capture an image of the current state of this render object and its
3375 /// children synchronously.
3376 ///
3377 /// The returned [ui.Image] has uncompressed raw RGBA bytes in the dimensions
3378 /// of the render object, multiplied by the [pixelRatio].
3379 ///
3380 /// To use [toImageSync], the render object must have gone through the paint phase
3381 /// (i.e. [debugNeedsPaint] must be false).
3382 ///
3383 /// The [pixelRatio] describes the scale between the logical pixels and the
3384 /// size of the output image. It is independent of the
3385 /// [dart:ui.FlutterView.devicePixelRatio] for the device, so specifying 1.0
3386 /// (the default) will give you a 1:1 mapping between logical pixels and the
3387 /// output pixels in the image.
3388 ///
3389 /// This API functions like [toImage], except that rasterization begins eagerly
3390 /// on the raster thread and the image is returned before this is completed.
3391 ///
3392 /// {@tool snippet}
3393 ///
3394 /// The following is an example of how to go from a `GlobalKey` on a
3395 /// `RepaintBoundary` to an image handle:
3396 ///
3397 /// ```dart
3398 /// class ImageCaptureHome extends StatefulWidget {
3399 /// const ImageCaptureHome({super.key});
3400 ///
3401 /// @override
3402 /// State<ImageCaptureHome> createState() => _ImageCaptureHomeState();
3403 /// }
3404 ///
3405 /// class _ImageCaptureHomeState extends State<ImageCaptureHome> {
3406 /// GlobalKey globalKey = GlobalKey();
3407 ///
3408 /// void _captureImage() {
3409 /// final RenderRepaintBoundary boundary = globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
3410 /// final ui.Image image = boundary.toImageSync();
3411 /// print('Image dimensions: ${image.width}x${image.height}');
3412 /// }
3413 ///
3414 /// @override
3415 /// Widget build(BuildContext context) {
3416 /// return RepaintBoundary(
3417 /// key: globalKey,
3418 /// child: Center(
3419 /// child: TextButton(
3420 /// onPressed: _captureImage,
3421 /// child: const Text('Hello World', textDirection: TextDirection.ltr),
3422 /// ),
3423 /// ),
3424 /// );
3425 /// }
3426 /// }
3427 /// ```
3428 /// {@end-tool}
3429 ///
3430 /// See also:
3431 ///
3432 /// * [OffsetLayer.toImageSync] for a similar API at the layer level.
3433 /// * [dart:ui.Scene.toImageSync] for more information about the image returned.
3434 ui.Image toImageSync({ double pixelRatio = 1.0 }) {
3435 assert(!debugNeedsPaint);
3436 final OffsetLayer offsetLayer = layer! as OffsetLayer;
3437 return offsetLayer.toImageSync(Offset.zero & size, pixelRatio: pixelRatio);
3438 }
3439
3440 /// The number of times that this render object repainted at the same time as
3441 /// its parent. Repaint boundaries are only useful when the parent and child
3442 /// paint at different times. When both paint at the same time, the repaint
3443 /// boundary is redundant, and may be actually making performance worse.
3444 ///
3445 /// Only valid when asserts are enabled. In release builds, always returns
3446 /// zero.
3447 ///
3448 /// Can be reset using [debugResetMetrics]. See [debugAsymmetricPaintCount]
3449 /// for the corresponding count of times where only the parent or only the
3450 /// child painted.
3451 int get debugSymmetricPaintCount => _debugSymmetricPaintCount;
3452 int _debugSymmetricPaintCount = 0;
3453
3454 /// The number of times that either this render object repainted without the
3455 /// parent being painted, or the parent repainted without this object being
3456 /// painted. When a repaint boundary is used at a seam in the render tree
3457 /// where the parent tends to repaint at entirely different times than the
3458 /// child, it can improve performance by reducing the number of paint
3459 /// operations that have to be recorded each frame.
3460 ///
3461 /// Only valid when asserts are enabled. In release builds, always returns
3462 /// zero.
3463 ///
3464 /// Can be reset using [debugResetMetrics]. See [debugSymmetricPaintCount] for
3465 /// the corresponding count of times where both the parent and the child
3466 /// painted together.
3467 int get debugAsymmetricPaintCount => _debugAsymmetricPaintCount;
3468 int _debugAsymmetricPaintCount = 0;
3469
3470 /// Resets the [debugSymmetricPaintCount] and [debugAsymmetricPaintCount]
3471 /// counts to zero.
3472 ///
3473 /// Only valid when asserts are enabled. Does nothing in release builds.
3474 void debugResetMetrics() {
3475 assert(() {
3476 _debugSymmetricPaintCount = 0;
3477 _debugAsymmetricPaintCount = 0;
3478 return true;
3479 }());
3480 }
3481
3482 @override
3483 void debugRegisterRepaintBoundaryPaint({ bool includedParent = true, bool includedChild = false }) {
3484 assert(() {
3485 if (includedParent && includedChild) {
3486 _debugSymmetricPaintCount += 1;
3487 } else {
3488 _debugAsymmetricPaintCount += 1;
3489 }
3490 return true;
3491 }());
3492 }
3493
3494 @override
3495 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3496 super.debugFillProperties(properties);
3497 bool inReleaseMode = true;
3498 assert(() {
3499 inReleaseMode = false;
3500 if (debugSymmetricPaintCount + debugAsymmetricPaintCount == 0) {
3501 properties.add(MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)'));
3502 } else {
3503 final double fraction = debugAsymmetricPaintCount / (debugSymmetricPaintCount + debugAsymmetricPaintCount);
3504 final String diagnosis;
3505 if (debugSymmetricPaintCount + debugAsymmetricPaintCount < 5) {
3506 diagnosis = 'insufficient data to draw conclusion (less than five repaints)';
3507 } else if (fraction > 0.9) {
3508 diagnosis = 'this is an outstandingly useful repaint boundary and should definitely be kept';
3509 } else if (fraction > 0.5) {
3510 diagnosis = 'this is a useful repaint boundary and should be kept';
3511 } else if (fraction > 0.30) {
3512 diagnosis = 'this repaint boundary is probably useful, but maybe it would be more useful in tandem with adding more repaint boundaries elsewhere';
3513 } else if (fraction > 0.1) {
3514 diagnosis = 'this repaint boundary does sometimes show value, though currently not that often';
3515 } else if (debugAsymmetricPaintCount == 0) {
3516 diagnosis = 'this repaint boundary is astoundingly ineffectual and should be removed';
3517 } else {
3518 diagnosis = 'this repaint boundary is not very effective and should probably be removed';
3519 }
3520 properties.add(PercentProperty('metrics', fraction, unit: 'useful', tooltip: '$debugSymmetricPaintCount bad vs $debugAsymmetricPaintCount good'));
3521 properties.add(MessageProperty('diagnosis', diagnosis));
3522 }
3523 return true;
3524 }());
3525 if (inReleaseMode) {
3526 properties.add(DiagnosticsNode.message('(run in debug mode to collect repaint boundary statistics)'));
3527 }
3528 }
3529}
3530
3531/// A render object that is invisible during hit testing.
3532///
3533/// When [ignoring] is true, this render object (and its subtree) is invisible
3534/// to hit testing. It still consumes space during layout and paints its child
3535/// as usual. It just cannot be the target of located events, because its render
3536/// object returns false from [hitTest].
3537///
3538/// ## Semantics
3539///
3540/// Using this class may also affect how the semantics subtree underneath is
3541/// collected.
3542///
3543/// {@macro flutter.widgets.IgnorePointer.semantics}
3544///
3545/// {@macro flutter.widgets.IgnorePointer.ignoringSemantics}
3546///
3547/// See also:
3548///
3549/// * [RenderAbsorbPointer], which takes the pointer events but prevents any
3550/// nodes in the subtree from seeing them.
3551class RenderIgnorePointer extends RenderProxyBox {
3552 /// Creates a render object that is invisible to hit testing.
3553 RenderIgnorePointer({
3554 RenderBox? child,
3555 bool ignoring = true,
3556 @Deprecated(
3557 'Use ExcludeSemantics or create a custom ignore pointer widget instead. '
3558 'This feature was deprecated after v3.8.0-12.0.pre.'
3559 )
3560 bool? ignoringSemantics,
3561 }) : _ignoring = ignoring,
3562 _ignoringSemantics = ignoringSemantics,
3563 super(child);
3564
3565 /// Whether this render object is ignored during hit testing.
3566 ///
3567 /// Regardless of whether this render object is ignored during hit testing, it
3568 /// will still consume space during layout and be visible during painting.
3569 ///
3570 /// {@macro flutter.widgets.IgnorePointer.semantics}
3571 bool get ignoring => _ignoring;
3572 bool _ignoring;
3573 set ignoring(bool value) {
3574 if (value == _ignoring) {
3575 return;
3576 }
3577 _ignoring = value;
3578 if (ignoringSemantics == null) {
3579 markNeedsSemanticsUpdate();
3580 }
3581 }
3582
3583 /// Whether the semantics of this render object is ignored when compiling the semantics tree.
3584 ///
3585 /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics}
3586 ///
3587 /// See [SemanticsNode] for additional information about the semantics tree.
3588 @Deprecated(
3589 'Use ExcludeSemantics or create a custom ignore pointer widget instead. '
3590 'This feature was deprecated after v3.8.0-12.0.pre.'
3591 )
3592 bool? get ignoringSemantics => _ignoringSemantics;
3593 bool? _ignoringSemantics;
3594 set ignoringSemantics(bool? value) {
3595 if (value == _ignoringSemantics) {
3596 return;
3597 }
3598 _ignoringSemantics = value;
3599 markNeedsSemanticsUpdate();
3600 }
3601
3602 @override
3603 bool hitTest(BoxHitTestResult result, { required Offset position }) {
3604 return !ignoring && super.hitTest(result, position: position);
3605 }
3606
3607 @override
3608 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
3609 if (_ignoringSemantics ?? false) {
3610 return;
3611 }
3612 super.visitChildrenForSemantics(visitor);
3613 }
3614
3615 @override
3616 void describeSemanticsConfiguration(SemanticsConfiguration config) {
3617 super.describeSemanticsConfiguration(config);
3618 // Do not block user interactions if _ignoringSemantics is false; otherwise,
3619 // delegate to _ignoring
3620 config.isBlockingUserActions = _ignoring && (_ignoringSemantics ?? true);
3621 }
3622
3623 @override
3624 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3625 super.debugFillProperties(properties);
3626 properties.add(DiagnosticsProperty<bool>('ignoring', _ignoring));
3627 properties.add(
3628 DiagnosticsProperty<bool>(
3629 'ignoringSemantics',
3630 _ignoringSemantics,
3631 description: _ignoringSemantics == null ? null : 'implicitly $_ignoringSemantics',
3632 ),
3633 );
3634 }
3635}
3636
3637/// Lays the child out as if it was in the tree, but without painting anything,
3638/// without making the child available for hit testing, and without taking any
3639/// room in the parent.
3640class RenderOffstage extends RenderProxyBox {
3641 /// Creates an offstage render object.
3642 RenderOffstage({
3643 bool offstage = true,
3644 RenderBox? child,
3645 }) : _offstage = offstage,
3646 super(child);
3647
3648 /// Whether the child is hidden from the rest of the tree.
3649 ///
3650 /// If true, the child is laid out as if it was in the tree, but without
3651 /// painting anything, without making the child available for hit testing, and
3652 /// without taking any room in the parent.
3653 ///
3654 /// If false, the child is included in the tree as normal.
3655 bool get offstage => _offstage;
3656 bool _offstage;
3657 set offstage(bool value) {
3658 if (value == _offstage) {
3659 return;
3660 }
3661 _offstage = value;
3662 markNeedsLayoutForSizedByParentChange();
3663 }
3664
3665 @override
3666 double computeMinIntrinsicWidth(double height) {
3667 if (offstage) {
3668 return 0.0;
3669 }
3670 return super.computeMinIntrinsicWidth(height);
3671 }
3672
3673 @override
3674 double computeMaxIntrinsicWidth(double height) {
3675 if (offstage) {
3676 return 0.0;
3677 }
3678 return super.computeMaxIntrinsicWidth(height);
3679 }
3680
3681 @override
3682 double computeMinIntrinsicHeight(double width) {
3683 if (offstage) {
3684 return 0.0;
3685 }
3686 return super.computeMinIntrinsicHeight(width);
3687 }
3688
3689 @override
3690 double computeMaxIntrinsicHeight(double width) {
3691 if (offstage) {
3692 return 0.0;
3693 }
3694 return super.computeMaxIntrinsicHeight(width);
3695 }
3696
3697 @override
3698 double? computeDistanceToActualBaseline(TextBaseline baseline) {
3699 if (offstage) {
3700 return null;
3701 }
3702 return super.computeDistanceToActualBaseline(baseline);
3703 }
3704
3705 @override
3706 bool get sizedByParent => offstage;
3707
3708 @override
3709 @protected
3710 Size computeDryLayout(covariant BoxConstraints constraints) {
3711 if (offstage) {
3712 return constraints.smallest;
3713 }
3714 return super.computeDryLayout(constraints);
3715 }
3716
3717 @override
3718 void performResize() {
3719 assert(offstage);
3720 super.performResize();
3721 }
3722
3723 @override
3724 void performLayout() {
3725 if (offstage) {
3726 child?.layout(constraints);
3727 } else {
3728 super.performLayout();
3729 }
3730 }
3731
3732 @override
3733 bool hitTest(BoxHitTestResult result, { required Offset position }) {
3734 return !offstage && super.hitTest(result, position: position);
3735 }
3736
3737 @override
3738 bool paintsChild(RenderBox child) {
3739 assert(child.parent == this);
3740 return !offstage;
3741 }
3742
3743 @override
3744 void paint(PaintingContext context, Offset offset) {
3745 if (offstage) {
3746 return;
3747 }
3748 super.paint(context, offset);
3749 }
3750
3751 @override
3752 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
3753 if (offstage) {
3754 return;
3755 }
3756 super.visitChildrenForSemantics(visitor);
3757 }
3758
3759 @override
3760 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3761 super.debugFillProperties(properties);
3762 properties.add(DiagnosticsProperty<bool>('offstage', offstage));
3763 }
3764
3765 @override
3766 List<DiagnosticsNode> debugDescribeChildren() {
3767 if (child == null) {
3768 return <DiagnosticsNode>[];
3769 }
3770 return <DiagnosticsNode>[
3771 child!.toDiagnosticsNode(
3772 name: 'child',
3773 style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse,
3774 ),
3775 ];
3776 }
3777}
3778
3779/// A render object that absorbs pointers during hit testing.
3780///
3781/// When [absorbing] is true, this render object prevents its subtree from
3782/// receiving pointer events by terminating hit testing at itself. It still
3783/// consumes space during layout and paints its child as usual. It just prevents
3784/// its children from being the target of located events, because its render
3785/// object returns true from [hitTest].
3786///
3787/// ## Semantics
3788///
3789/// Using this class may also affect how the semantics subtree underneath is
3790/// collected.
3791///
3792/// {@macro flutter.widgets.AbsorbPointer.semantics}
3793///
3794/// {@macro flutter.widgets.AbsorbPointer.ignoringSemantics}
3795///
3796/// See also:
3797///
3798/// * [RenderIgnorePointer], which has the opposite effect: removing the
3799/// subtree from considering entirely for the purposes of hit testing.
3800class RenderAbsorbPointer extends RenderProxyBox {
3801 /// Creates a render object that absorbs pointers during hit testing.
3802 RenderAbsorbPointer({
3803 RenderBox? child,
3804 bool absorbing = true,
3805 @Deprecated(
3806 'Use ExcludeSemantics or create a custom absorb pointer widget instead. '
3807 'This feature was deprecated after v3.8.0-12.0.pre.'
3808 )
3809 bool? ignoringSemantics,
3810 }) : _absorbing = absorbing,
3811 _ignoringSemantics = ignoringSemantics,
3812 super(child);
3813
3814 /// Whether this render object absorbs pointers during hit testing.
3815 ///
3816 /// Regardless of whether this render object absorbs pointers during hit
3817 /// testing, it will still consume space during layout and be visible during
3818 /// painting.
3819 ///
3820 /// {@macro flutter.widgets.AbsorbPointer.semantics}
3821 bool get absorbing => _absorbing;
3822 bool _absorbing;
3823 set absorbing(bool value) {
3824 if (_absorbing == value) {
3825 return;
3826 }
3827 _absorbing = value;
3828 if (ignoringSemantics == null) {
3829 markNeedsSemanticsUpdate();
3830 }
3831 }
3832
3833 /// Whether the semantics of this render object is ignored when compiling the
3834 /// semantics tree.
3835 ///
3836 /// {@macro flutter.widgets.AbsorbPointer.ignoringSemantics}
3837 ///
3838 /// See [SemanticsNode] for additional information about the semantics tree.
3839 @Deprecated(
3840 'Use ExcludeSemantics or create a custom absorb pointer widget instead. '
3841 'This feature was deprecated after v3.8.0-12.0.pre.'
3842 )
3843 bool? get ignoringSemantics => _ignoringSemantics;
3844 bool? _ignoringSemantics;
3845 set ignoringSemantics(bool? value) {
3846 if (value == _ignoringSemantics) {
3847 return;
3848 }
3849 _ignoringSemantics = value;
3850 markNeedsSemanticsUpdate();
3851 }
3852
3853 @override
3854 bool hitTest(BoxHitTestResult result, { required Offset position }) {
3855 return absorbing
3856 ? size.contains(position)
3857 : super.hitTest(result, position: position);
3858 }
3859
3860 @override
3861 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
3862 if (_ignoringSemantics ?? false) {
3863 return;
3864 }
3865 super.visitChildrenForSemantics(visitor);
3866 }
3867
3868 @override
3869 void describeSemanticsConfiguration(SemanticsConfiguration config) {
3870 super.describeSemanticsConfiguration(config);
3871 // Do not block user interactions if _ignoringSemantics is false; otherwise,
3872 // delegate to absorbing
3873 config.isBlockingUserActions = absorbing && (_ignoringSemantics ?? true);
3874 }
3875
3876 @override
3877 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3878 super.debugFillProperties(properties);
3879 properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
3880 properties.add(
3881 DiagnosticsProperty<bool>(
3882 'ignoringSemantics',
3883 ignoringSemantics,
3884 description: ignoringSemantics == null ? null : 'implicitly $ignoringSemantics',
3885 ),
3886 );
3887 }
3888}
3889
3890/// Holds opaque meta data in the render tree.
3891///
3892/// Useful for decorating the render tree with information that will be consumed
3893/// later. For example, you could store information in the render tree that will
3894/// be used when the user interacts with the render tree but has no visual
3895/// impact prior to the interaction.
3896class RenderMetaData extends RenderProxyBoxWithHitTestBehavior {
3897 /// Creates a render object that hold opaque meta data.
3898 ///
3899 /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
3900 RenderMetaData({
3901 this.metaData,
3902 super.behavior,
3903 super.child,
3904 });
3905
3906 /// Opaque meta data ignored by the render tree.
3907 dynamic metaData;
3908
3909 @override
3910 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3911 super.debugFillProperties(properties);
3912 properties.add(DiagnosticsProperty<dynamic>('metaData', metaData));
3913 }
3914}
3915
3916/// Listens for the specified gestures from the semantics server (e.g.
3917/// an accessibility tool).
3918class RenderSemanticsGestureHandler extends RenderProxyBoxWithHitTestBehavior {
3919 /// Creates a render object that listens for specific semantic gestures.
3920 RenderSemanticsGestureHandler({
3921 super.child,
3922 GestureTapCallback? onTap,
3923 GestureLongPressCallback? onLongPress,
3924 GestureDragUpdateCallback? onHorizontalDragUpdate,
3925 GestureDragUpdateCallback? onVerticalDragUpdate,
3926 this.scrollFactor = 0.8,
3927 super.behavior,
3928 }) : _onTap = onTap,
3929 _onLongPress = onLongPress,
3930 _onHorizontalDragUpdate = onHorizontalDragUpdate,
3931 _onVerticalDragUpdate = onVerticalDragUpdate;
3932
3933 /// If non-null, the set of actions to allow. Other actions will be omitted,
3934 /// even if their callback is provided.
3935 ///
3936 /// For example, if [onTap] is non-null but [validActions] does not contain
3937 /// [SemanticsAction.tap], then the semantic description of this node will
3938 /// not claim to support taps.
3939 ///
3940 /// This is normally used to filter the actions made available by
3941 /// [onHorizontalDragUpdate] and [onVerticalDragUpdate]. Normally, these make
3942 /// both the right and left, or up and down, actions available. For example,
3943 /// if [onHorizontalDragUpdate] is set but [validActions] only contains
3944 /// [SemanticsAction.scrollLeft], then the [SemanticsAction.scrollRight]
3945 /// action will be omitted.
3946 Set<SemanticsAction>? get validActions => _validActions;
3947 Set<SemanticsAction>? _validActions;
3948 set validActions(Set<SemanticsAction>? value) {
3949 if (setEquals<SemanticsAction>(value, _validActions)) {
3950 return;
3951 }
3952 _validActions = value;
3953 markNeedsSemanticsUpdate();
3954 }
3955
3956 /// Called when the user taps on the render object.
3957 GestureTapCallback? get onTap => _onTap;
3958 GestureTapCallback? _onTap;
3959 set onTap(GestureTapCallback? value) {
3960 if (_onTap == value) {
3961 return;
3962 }
3963 final bool hadHandler = _onTap != null;
3964 _onTap = value;
3965 if ((value != null) != hadHandler) {
3966 markNeedsSemanticsUpdate();
3967 }
3968 }
3969
3970 /// Called when the user presses on the render object for a long period of time.
3971 GestureLongPressCallback? get onLongPress => _onLongPress;
3972 GestureLongPressCallback? _onLongPress;
3973 set onLongPress(GestureLongPressCallback? value) {
3974 if (_onLongPress == value) {
3975 return;
3976 }
3977 final bool hadHandler = _onLongPress != null;
3978 _onLongPress = value;
3979 if ((value != null) != hadHandler) {
3980 markNeedsSemanticsUpdate();
3981 }
3982 }
3983
3984 /// Called when the user scrolls to the left or to the right.
3985 GestureDragUpdateCallback? get onHorizontalDragUpdate => _onHorizontalDragUpdate;
3986 GestureDragUpdateCallback? _onHorizontalDragUpdate;
3987 set onHorizontalDragUpdate(GestureDragUpdateCallback? value) {
3988 if (_onHorizontalDragUpdate == value) {
3989 return;
3990 }
3991 final bool hadHandler = _onHorizontalDragUpdate != null;
3992 _onHorizontalDragUpdate = value;
3993 if ((value != null) != hadHandler) {
3994 markNeedsSemanticsUpdate();
3995 }
3996 }
3997
3998 /// Called when the user scrolls up or down.
3999 GestureDragUpdateCallback? get onVerticalDragUpdate => _onVerticalDragUpdate;
4000 GestureDragUpdateCallback? _onVerticalDragUpdate;
4001 set onVerticalDragUpdate(GestureDragUpdateCallback? value) {
4002 if (_onVerticalDragUpdate == value) {
4003 return;
4004 }
4005 final bool hadHandler = _onVerticalDragUpdate != null;
4006 _onVerticalDragUpdate = value;
4007 if ((value != null) != hadHandler) {
4008 markNeedsSemanticsUpdate();
4009 }
4010 }
4011
4012 /// The fraction of the dimension of this render box to use when
4013 /// scrolling. For example, if this is 0.8 and the box is 200 pixels
4014 /// wide, then when a left-scroll action is received from the
4015 /// accessibility system, it will translate into a 160 pixel
4016 /// leftwards drag.
4017 double scrollFactor;
4018
4019 @override
4020 void describeSemanticsConfiguration(SemanticsConfiguration config) {
4021 super.describeSemanticsConfiguration(config);
4022
4023 if (onTap != null && _isValidAction(SemanticsAction.tap)) {
4024 config.onTap = onTap;
4025 }
4026 if (onLongPress != null && _isValidAction(SemanticsAction.longPress)) {
4027 config.onLongPress = onLongPress;
4028 }
4029 if (onHorizontalDragUpdate != null) {
4030 if (_isValidAction(SemanticsAction.scrollRight)) {
4031 config.onScrollRight = _performSemanticScrollRight;
4032 }
4033 if (_isValidAction(SemanticsAction.scrollLeft)) {
4034 config.onScrollLeft = _performSemanticScrollLeft;
4035 }
4036 }
4037 if (onVerticalDragUpdate != null) {
4038 if (_isValidAction(SemanticsAction.scrollUp)) {
4039 config.onScrollUp = _performSemanticScrollUp;
4040 }
4041 if (_isValidAction(SemanticsAction.scrollDown)) {
4042 config.onScrollDown = _performSemanticScrollDown;
4043 }
4044 }
4045 }
4046
4047 bool _isValidAction(SemanticsAction action) {
4048 return validActions == null || validActions!.contains(action);
4049 }
4050
4051 void _performSemanticScrollLeft() {
4052 if (onHorizontalDragUpdate != null) {
4053 final double primaryDelta = size.width * -scrollFactor;
4054 onHorizontalDragUpdate!(DragUpdateDetails(
4055 delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
4056 globalPosition: localToGlobal(size.center(Offset.zero)),
4057 ));
4058 }
4059 }
4060
4061 void _performSemanticScrollRight() {
4062 if (onHorizontalDragUpdate != null) {
4063 final double primaryDelta = size.width * scrollFactor;
4064 onHorizontalDragUpdate!(DragUpdateDetails(
4065 delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
4066 globalPosition: localToGlobal(size.center(Offset.zero)),
4067 ));
4068 }
4069 }
4070
4071 void _performSemanticScrollUp() {
4072 if (onVerticalDragUpdate != null) {
4073 final double primaryDelta = size.height * -scrollFactor;
4074 onVerticalDragUpdate!(DragUpdateDetails(
4075 delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
4076 globalPosition: localToGlobal(size.center(Offset.zero)),
4077 ));
4078 }
4079 }
4080
4081 void _performSemanticScrollDown() {
4082 if (onVerticalDragUpdate != null) {
4083 final double primaryDelta = size.height * scrollFactor;
4084 onVerticalDragUpdate!(DragUpdateDetails(
4085 delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
4086 globalPosition: localToGlobal(size.center(Offset.zero)),
4087 ));
4088 }
4089 }
4090
4091 @override
4092 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
4093 super.debugFillProperties(properties);
4094 final List<String> gestures = <String>[
4095 if (onTap != null) 'tap',
4096 if (onLongPress != null) 'long press',
4097 if (onHorizontalDragUpdate != null) 'horizontal scroll',
4098 if (onVerticalDragUpdate != null) 'vertical scroll',
4099 ];
4100 if (gestures.isEmpty) {
4101 gestures.add('<none>');
4102 }
4103 properties.add(IterableProperty<String>('gestures', gestures));
4104 }
4105}
4106
4107/// Add annotations to the [SemanticsNode] for this subtree.
4108class RenderSemanticsAnnotations extends RenderProxyBox {
4109 /// Creates a render object that attaches a semantic annotation.
4110 ///
4111 /// If the [SemanticsProperties.attributedLabel] is not null, the [textDirection] must also not be null.
4112 RenderSemanticsAnnotations({
4113 RenderBox? child,
4114 required SemanticsProperties properties,
4115 bool container = false,
4116 bool explicitChildNodes = false,
4117 bool excludeSemantics = false,
4118 bool blockUserActions = false,
4119 TextDirection? textDirection,
4120 }) : _container = container,
4121 _explicitChildNodes = explicitChildNodes,
4122 _excludeSemantics = excludeSemantics,
4123 _blockUserActions = blockUserActions,
4124 _textDirection = textDirection,
4125 _properties = properties,
4126 super(child) {
4127 _updateAttributedFields(_properties);
4128 }
4129
4130 /// All of the [SemanticsProperties] for this [RenderSemanticsAnnotations].
4131 SemanticsProperties get properties => _properties;
4132 SemanticsProperties _properties;
4133 set properties(SemanticsProperties value) {
4134 if (_properties == value) {
4135 return;
4136 }
4137 _properties = value;
4138 _updateAttributedFields(_properties);
4139 markNeedsSemanticsUpdate();
4140 }
4141
4142 /// If 'container' is true, this [RenderObject] will introduce a new
4143 /// node in the semantics tree. Otherwise, the semantics will be
4144 /// merged with the semantics of any ancestors.
4145 ///
4146 /// Whether descendants of this [RenderObject] can add their semantic information
4147 /// to the [SemanticsNode] introduced by this configuration is controlled by
4148 /// [explicitChildNodes].
4149 bool get container => _container;
4150 bool _container;
4151 set container(bool value) {
4152 if (container == value) {
4153 return;
4154 }
4155 _container = value;
4156 markNeedsSemanticsUpdate();
4157 }
4158
4159 /// Whether descendants of this [RenderObject] are allowed to add semantic
4160 /// information to the [SemanticsNode] annotated by this widget.
4161 ///
4162 /// When set to false descendants are allowed to annotate [SemanticsNode]s of
4163 /// their parent with the semantic information they want to contribute to the
4164 /// semantic tree.
4165 /// When set to true the only way for descendants to contribute semantic
4166 /// information to the semantic tree is to introduce new explicit
4167 /// [SemanticsNode]s to the tree.
4168 ///
4169 /// This setting is often used in combination with
4170 /// [SemanticsConfiguration.isSemanticBoundary] to create semantic boundaries
4171 /// that are either writable or not for children.
4172 bool get explicitChildNodes => _explicitChildNodes;
4173 bool _explicitChildNodes;
4174 set explicitChildNodes(bool value) {
4175 if (_explicitChildNodes == value) {
4176 return;
4177 }
4178 _explicitChildNodes = value;
4179 markNeedsSemanticsUpdate();
4180 }
4181
4182 /// Whether descendants of this [RenderObject] should have their semantic
4183 /// information ignored.
4184 ///
4185 /// When this flag is set to true, all child semantics nodes are ignored.
4186 /// This can be used as a convenience for cases where a child is wrapped in
4187 /// an [ExcludeSemantics] widget and then another [Semantics] widget.
4188 bool get excludeSemantics => _excludeSemantics;
4189 bool _excludeSemantics;
4190 set excludeSemantics(bool value) {
4191 if (_excludeSemantics == value) {
4192 return;
4193 }
4194 _excludeSemantics = value;
4195 markNeedsSemanticsUpdate();
4196 }
4197
4198 /// Whether to block user interactions for the semantics subtree.
4199 ///
4200 /// Setting this true prevents user from activating pointer related
4201 /// [SemanticsAction]s, such as [SemanticsAction.tap] or
4202 /// [SemanticsAction.longPress].
4203 bool get blockUserActions => _blockUserActions;
4204 bool _blockUserActions;
4205 set blockUserActions(bool value) {
4206 if (_blockUserActions == value) {
4207 return;
4208 }
4209 _blockUserActions = value;
4210 markNeedsSemanticsUpdate();
4211 }
4212
4213 void _updateAttributedFields(SemanticsProperties value) {
4214 _attributedLabel = _effectiveAttributedLabel(value);
4215 _attributedValue = _effectiveAttributedValue(value);
4216 _attributedIncreasedValue = _effectiveAttributedIncreasedValue(value);
4217 _attributedDecreasedValue = _effectiveAttributedDecreasedValue(value);
4218 _attributedHint = _effectiveAttributedHint(value);
4219 }
4220
4221 AttributedString? _effectiveAttributedLabel(SemanticsProperties value) {
4222 return value.attributedLabel ??
4223 (value.label == null ? null : AttributedString(value.label!));
4224 }
4225
4226 AttributedString? _effectiveAttributedValue(SemanticsProperties value) {
4227 return value.attributedValue ??
4228 (value.value == null ? null : AttributedString(value.value!));
4229 }
4230
4231 AttributedString? _effectiveAttributedIncreasedValue(
4232 SemanticsProperties value) {
4233 return value.attributedIncreasedValue ??
4234 (value.increasedValue == null
4235 ? null
4236 : AttributedString(value.increasedValue!));
4237 }
4238
4239 AttributedString? _effectiveAttributedDecreasedValue(
4240 SemanticsProperties value) {
4241 return properties.attributedDecreasedValue ??
4242 (value.decreasedValue == null
4243 ? null
4244 : AttributedString(value.decreasedValue!));
4245 }
4246
4247 AttributedString? _effectiveAttributedHint(SemanticsProperties value) {
4248 return value.attributedHint ??
4249 (value.hint == null ? null : AttributedString(value.hint!));
4250 }
4251
4252 AttributedString? _attributedLabel;
4253 AttributedString? _attributedValue;
4254 AttributedString? _attributedIncreasedValue;
4255 AttributedString? _attributedDecreasedValue;
4256 AttributedString? _attributedHint;
4257
4258 /// If non-null, sets the [SemanticsNode.textDirection] semantic to the given
4259 /// value.
4260 ///
4261 /// This must not be null if [SemanticsProperties.attributedLabel],
4262 /// [SemanticsProperties.attributedHint],
4263 /// [SemanticsProperties.attributedValue],
4264 /// [SemanticsProperties.attributedIncreasedValue], or
4265 /// [SemanticsProperties.attributedDecreasedValue] are not null.
4266 TextDirection? get textDirection => _textDirection;
4267 TextDirection? _textDirection;
4268 set textDirection(TextDirection? value) {
4269 if (textDirection == value) {
4270 return;
4271 }
4272 _textDirection = value;
4273 markNeedsSemanticsUpdate();
4274 }
4275
4276 @override
4277 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
4278 if (excludeSemantics) {
4279 return;
4280 }
4281 super.visitChildrenForSemantics(visitor);
4282 }
4283
4284 @override
4285 void describeSemanticsConfiguration(SemanticsConfiguration config) {
4286 super.describeSemanticsConfiguration(config);
4287 config.isSemanticBoundary = container;
4288 config.explicitChildNodes = explicitChildNodes;
4289 config.isBlockingUserActions = blockUserActions;
4290 assert(
4291 ((_properties.scopesRoute ?? false) && explicitChildNodes) || !(_properties.scopesRoute ?? false),
4292 'explicitChildNodes must be set to true if scopes route is true',
4293 );
4294 assert(
4295 !((_properties.toggled ?? false) && (_properties.checked ?? false)),
4296 'A semantics node cannot be toggled and checked at the same time',
4297 );
4298
4299 if (_properties.enabled != null) {
4300 config.isEnabled = _properties.enabled;
4301 }
4302 if (_properties.checked != null) {
4303 config.isChecked = _properties.checked;
4304 }
4305 if (_properties.mixed != null) {
4306 config.isCheckStateMixed = _properties.mixed;
4307 }
4308 if (_properties.toggled != null) {
4309 config.isToggled = _properties.toggled;
4310 }
4311 if (_properties.selected != null) {
4312 config.isSelected = _properties.selected!;
4313 }
4314 if (_properties.button != null) {
4315 config.isButton = _properties.button!;
4316 }
4317 if (_properties.expanded != null) {
4318 config.isExpanded = _properties.expanded;
4319 }
4320 if (_properties.link != null) {
4321 config.isLink = _properties.link!;
4322 }
4323 if (_properties.slider != null) {
4324 config.isSlider = _properties.slider!;
4325 }
4326 if (_properties.keyboardKey != null) {
4327 config.isKeyboardKey = _properties.keyboardKey!;
4328 }
4329 if (_properties.header != null) {
4330 config.isHeader = _properties.header!;
4331 }
4332 if (_properties.textField != null) {
4333 config.isTextField = _properties.textField!;
4334 }
4335 if (_properties.readOnly != null) {
4336 config.isReadOnly = _properties.readOnly!;
4337 }
4338 if (_properties.focusable != null) {
4339 config.isFocusable = _properties.focusable!;
4340 }
4341 if (_properties.focused != null) {
4342 config.isFocused = _properties.focused!;
4343 }
4344 if (_properties.inMutuallyExclusiveGroup != null) {
4345 config.isInMutuallyExclusiveGroup = _properties.inMutuallyExclusiveGroup!;
4346 }
4347 if (_properties.obscured != null) {
4348 config.isObscured = _properties.obscured!;
4349 }
4350 if (_properties.multiline != null) {
4351 config.isMultiline = _properties.multiline!;
4352 }
4353 if (_properties.hidden != null) {
4354 config.isHidden = _properties.hidden!;
4355 }
4356 if (_properties.image != null) {
4357 config.isImage = _properties.image!;
4358 }
4359 if (_properties.identifier != null) {
4360 config.identifier = _properties.identifier!;
4361 }
4362 if (_attributedLabel != null) {
4363 config.attributedLabel = _attributedLabel!;
4364 }
4365 if (_attributedValue != null) {
4366 config.attributedValue = _attributedValue!;
4367 }
4368 if (_attributedIncreasedValue != null) {
4369 config.attributedIncreasedValue = _attributedIncreasedValue!;
4370 }
4371 if (_attributedDecreasedValue != null) {
4372 config.attributedDecreasedValue = _attributedDecreasedValue!;
4373 }
4374 if (_attributedHint != null) {
4375 config.attributedHint = _attributedHint!;
4376 }
4377 if (_properties.tooltip != null) {
4378 config.tooltip = _properties.tooltip!;
4379 }
4380 if (_properties.hintOverrides != null && _properties.hintOverrides!.isNotEmpty) {
4381 config.hintOverrides = _properties.hintOverrides;
4382 }
4383 if (_properties.scopesRoute != null) {
4384 config.scopesRoute = _properties.scopesRoute!;
4385 }
4386 if (_properties.namesRoute != null) {
4387 config.namesRoute = _properties.namesRoute!;
4388 }
4389 if (_properties.liveRegion != null) {
4390 config.liveRegion = _properties.liveRegion!;
4391 }
4392 if (_properties.maxValueLength != null) {
4393 config.maxValueLength = _properties.maxValueLength;
4394 }
4395 if (_properties.currentValueLength != null) {
4396 config.currentValueLength = _properties.currentValueLength;
4397 }
4398 if (textDirection != null) {
4399 config.textDirection = textDirection;
4400 }
4401 if (_properties.sortKey != null) {
4402 config.sortKey = _properties.sortKey;
4403 }
4404 if (_properties.tagForChildren != null) {
4405 config.addTagForChildren(_properties.tagForChildren!);
4406 }
4407 // Registering _perform* as action handlers instead of the user provided
4408 // ones to ensure that changing a user provided handler from a non-null to
4409 // another non-null value doesn't require a semantics update.
4410 if (_properties.onTap != null) {
4411 config.onTap = _performTap;
4412 }
4413 if (_properties.onLongPress != null) {
4414 config.onLongPress = _performLongPress;
4415 }
4416 if (_properties.onDismiss != null) {
4417 config.onDismiss = _performDismiss;
4418 }
4419 if (_properties.onScrollLeft != null) {
4420 config.onScrollLeft = _performScrollLeft;
4421 }
4422 if (_properties.onScrollRight != null) {
4423 config.onScrollRight = _performScrollRight;
4424 }
4425 if (_properties.onScrollUp != null) {
4426 config.onScrollUp = _performScrollUp;
4427 }
4428 if (_properties.onScrollDown != null) {
4429 config.onScrollDown = _performScrollDown;
4430 }
4431 if (_properties.onIncrease != null) {
4432 config.onIncrease = _performIncrease;
4433 }
4434 if (_properties.onDecrease != null) {
4435 config.onDecrease = _performDecrease;
4436 }
4437 if (_properties.onCopy != null) {
4438 config.onCopy = _performCopy;
4439 }
4440 if (_properties.onCut != null) {
4441 config.onCut = _performCut;
4442 }
4443 if (_properties.onPaste != null) {
4444 config.onPaste = _performPaste;
4445 }
4446 if (_properties.onMoveCursorForwardByCharacter != null) {
4447 config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
4448 }
4449 if (_properties.onMoveCursorBackwardByCharacter != null) {
4450 config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
4451 }
4452 if (_properties.onMoveCursorForwardByWord != null) {
4453 config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord;
4454 }
4455 if (_properties.onMoveCursorBackwardByWord != null) {
4456 config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord;
4457 }
4458 if (_properties.onSetSelection != null) {
4459 config.onSetSelection = _performSetSelection;
4460 }
4461 if (_properties.onSetText != null) {
4462 config.onSetText = _performSetText;
4463 }
4464 if (_properties.onDidGainAccessibilityFocus != null) {
4465 config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus;
4466 }
4467 if (_properties.onDidLoseAccessibilityFocus != null) {
4468 config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus;
4469 }
4470 if (_properties.customSemanticsActions != null) {
4471 config.customSemanticsActions = _properties.customSemanticsActions!;
4472 }
4473 }
4474
4475 void _performTap() {
4476 _properties.onTap?.call();
4477 }
4478
4479 void _performLongPress() {
4480 _properties.onLongPress?.call();
4481 }
4482
4483 void _performDismiss() {
4484 _properties.onDismiss?.call();
4485 }
4486
4487 void _performScrollLeft() {
4488 _properties.onScrollLeft?.call();
4489 }
4490
4491 void _performScrollRight() {
4492 _properties.onScrollRight?.call();
4493 }
4494
4495 void _performScrollUp() {
4496 _properties.onScrollUp?.call();
4497 }
4498
4499 void _performScrollDown() {
4500 _properties.onScrollDown?.call();
4501 }
4502
4503 void _performIncrease() {
4504 _properties.onIncrease?.call();
4505 }
4506
4507 void _performDecrease() {
4508 _properties.onDecrease?.call();
4509 }
4510
4511 void _performCopy() {
4512 _properties.onCopy?.call();
4513 }
4514
4515 void _performCut() {
4516 _properties.onCut?.call();
4517 }
4518
4519 void _performPaste() {
4520 _properties.onPaste?.call();
4521 }
4522
4523 void _performMoveCursorForwardByCharacter(bool extendSelection) {
4524 _properties.onMoveCursorForwardByCharacter?.call(extendSelection);
4525 }
4526
4527 void _performMoveCursorBackwardByCharacter(bool extendSelection) {
4528 _properties.onMoveCursorBackwardByCharacter?.call(extendSelection);
4529 }
4530
4531 void _performMoveCursorForwardByWord(bool extendSelection) {
4532 _properties.onMoveCursorForwardByWord?.call(extendSelection);
4533 }
4534
4535 void _performMoveCursorBackwardByWord(bool extendSelection) {
4536 _properties.onMoveCursorBackwardByWord?.call(extendSelection);
4537 }
4538
4539 void _performSetSelection(TextSelection selection) {
4540 _properties.onSetSelection?.call(selection);
4541 }
4542
4543 void _performSetText(String text) {
4544 _properties.onSetText?.call(text);
4545 }
4546
4547 void _performDidGainAccessibilityFocus() {
4548 _properties.onDidGainAccessibilityFocus?.call();
4549 }
4550
4551 void _performDidLoseAccessibilityFocus() {
4552 _properties.onDidLoseAccessibilityFocus?.call();
4553 }
4554}
4555
4556/// Causes the semantics of all earlier render objects below the same semantic
4557/// boundary to be dropped.
4558///
4559/// This is useful in a stack where an opaque mask should prevent interactions
4560/// with the render objects painted below the mask.
4561class RenderBlockSemantics extends RenderProxyBox {
4562 /// Create a render object that blocks semantics for nodes below it in paint
4563 /// order.
4564 RenderBlockSemantics({
4565 RenderBox? child,
4566 bool blocking = true,
4567 }) : _blocking = blocking,
4568 super(child);
4569
4570 /// Whether this render object is blocking semantics of previously painted
4571 /// [RenderObject]s below a common semantics boundary from the semantic tree.
4572 bool get blocking => _blocking;
4573 bool _blocking;
4574 set blocking(bool value) {
4575 if (value == _blocking) {
4576 return;
4577 }
4578 _blocking = value;
4579 markNeedsSemanticsUpdate();
4580 }
4581
4582 @override
4583 void describeSemanticsConfiguration(SemanticsConfiguration config) {
4584 super.describeSemanticsConfiguration(config);
4585 config.isBlockingSemanticsOfPreviouslyPaintedNodes = blocking;
4586 }
4587
4588 @override
4589 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
4590 super.debugFillProperties(properties);
4591 properties.add(DiagnosticsProperty<bool>('blocking', blocking));
4592 }
4593}
4594
4595/// Causes the semantics of all descendants to be merged into this
4596/// node such that the entire subtree becomes a single leaf in the
4597/// semantics tree.
4598///
4599/// Useful for combining the semantics of multiple render objects that
4600/// form part of a single conceptual widget, e.g. a checkbox, a label,
4601/// and the gesture detector that goes with them.
4602class RenderMergeSemantics extends RenderProxyBox {
4603 /// Creates a render object that merges the semantics from its descendants.
4604 RenderMergeSemantics({ RenderBox? child }) : super(child);
4605
4606 @override
4607 void describeSemanticsConfiguration(SemanticsConfiguration config) {
4608 super.describeSemanticsConfiguration(config);
4609 config
4610 ..isSemanticBoundary = true
4611 ..isMergingSemanticsOfDescendants = true;
4612 }
4613}
4614
4615/// Excludes this subtree from the semantic tree.
4616///
4617/// When [excluding] is true, this render object (and its subtree) is excluded
4618/// from the semantic tree.
4619///
4620/// Useful e.g. for hiding text that is redundant with other text next
4621/// to it (e.g. text included only for the visual effect).
4622class RenderExcludeSemantics extends RenderProxyBox {
4623 /// Creates a render object that ignores the semantics of its subtree.
4624 RenderExcludeSemantics({
4625 RenderBox? child,
4626 bool excluding = true,
4627 }) : _excluding = excluding,
4628 super(child);
4629
4630 /// Whether this render object is excluded from the semantic tree.
4631 bool get excluding => _excluding;
4632 bool _excluding;
4633 set excluding(bool value) {
4634 if (value == _excluding) {
4635 return;
4636 }
4637 _excluding = value;
4638 markNeedsSemanticsUpdate();
4639 }
4640
4641 @override
4642 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
4643 if (excluding) {
4644 return;
4645 }
4646 super.visitChildrenForSemantics(visitor);
4647 }
4648
4649 @override
4650 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
4651 super.debugFillProperties(properties);
4652 properties.add(DiagnosticsProperty<bool>('excluding', excluding));
4653 }
4654}
4655
4656/// A render objects that annotates semantics with an index.
4657///
4658/// Certain widgets will automatically provide a child index for building
4659/// semantics. For example, the [ScrollView] uses the index of the first
4660/// visible child semantics node to determine the
4661/// [SemanticsConfiguration.scrollIndex].
4662///
4663/// See also:
4664///
4665/// * [CustomScrollView], for an explanation of scroll semantics.
4666class RenderIndexedSemantics extends RenderProxyBox {
4667 /// Creates a render object that annotates the child semantics with an index.
4668 RenderIndexedSemantics({
4669 RenderBox? child,
4670 required int index,
4671 }) : _index = index,
4672 super(child);
4673
4674 /// The index used to annotated child semantics.
4675 int get index => _index;
4676 int _index;
4677 set index(int value) {
4678 if (value == index) {
4679 return;
4680 }
4681 _index = value;
4682 markNeedsSemanticsUpdate();
4683 }
4684
4685 @override
4686 void describeSemanticsConfiguration(SemanticsConfiguration config) {
4687 super.describeSemanticsConfiguration(config);
4688 config.indexInParent = index;
4689 }
4690
4691 @override
4692 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
4693 super.debugFillProperties(properties);
4694 properties.add(DiagnosticsProperty<int>('index', index));
4695 }
4696}
4697
4698/// Provides an anchor for a [RenderFollowerLayer].
4699///
4700/// See also:
4701///
4702/// * [CompositedTransformTarget], the corresponding widget.
4703/// * [LeaderLayer], the layer that this render object creates.
4704class RenderLeaderLayer extends RenderProxyBox {
4705 /// Creates a render object that uses a [LeaderLayer].
4706 RenderLeaderLayer({
4707 required LayerLink link,
4708 RenderBox? child,
4709 }) : _link = link,
4710 super(child);
4711
4712 /// The link object that connects this [RenderLeaderLayer] with one or more
4713 /// [RenderFollowerLayer]s.
4714 ///
4715 /// The object must not be associated with another [RenderLeaderLayer] that is
4716 /// also being painted.
4717 LayerLink get link => _link;
4718 LayerLink _link;
4719 set link(LayerLink value) {
4720 if (_link == value) {
4721 return;
4722 }
4723 _link.leaderSize = null;
4724 _link = value;
4725 if (_previousLayoutSize != null) {
4726 _link.leaderSize = _previousLayoutSize;
4727 }
4728 markNeedsPaint();
4729 }
4730
4731 @override
4732 bool get alwaysNeedsCompositing => true;
4733
4734 // The latest size of this [RenderBox], computed during the previous layout
4735 // pass. It should always be equal to [size], but can be accessed even when
4736 // [debugDoingThisResize] and [debugDoingThisLayout] are false.
4737 Size? _previousLayoutSize;
4738
4739 @override
4740 void performLayout() {
4741 super.performLayout();
4742 _previousLayoutSize = size;
4743 link.leaderSize = size;
4744 }
4745
4746 @override
4747 void paint(PaintingContext context, Offset offset) {
4748 if (layer == null) {
4749 layer = LeaderLayer(link: link, offset: offset);
4750 } else {
4751 final LeaderLayer leaderLayer = layer! as LeaderLayer;
4752 leaderLayer
4753 ..link = link
4754 ..offset = offset;
4755 }
4756 context.pushLayer(layer!, super.paint, Offset.zero);
4757 assert(() {
4758 layer!.debugCreator = debugCreator;
4759 return true;
4760 }());
4761 }
4762
4763 @override
4764 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
4765 super.debugFillProperties(properties);
4766 properties.add(DiagnosticsProperty<LayerLink>('link', link));
4767 }
4768}
4769
4770/// Transform the child so that its origin is [offset] from the origin of the
4771/// [RenderLeaderLayer] with the same [LayerLink].
4772///
4773/// The [RenderLeaderLayer] in question must be earlier in the paint order.
4774///
4775/// Hit testing on descendants of this render object will only work if the
4776/// target position is within the box that this render object's parent considers
4777/// to be hittable.
4778///
4779/// See also:
4780///
4781/// * [CompositedTransformFollower], the corresponding widget.
4782/// * [FollowerLayer], the layer that this render object creates.
4783class RenderFollowerLayer extends RenderProxyBox {
4784 /// Creates a render object that uses a [FollowerLayer].
4785 RenderFollowerLayer({
4786 required LayerLink link,
4787 bool showWhenUnlinked = true,
4788 Offset offset = Offset.zero,
4789 Alignment leaderAnchor = Alignment.topLeft,
4790 Alignment followerAnchor = Alignment.topLeft,
4791 RenderBox? child,
4792 }) : _link = link,
4793 _showWhenUnlinked = showWhenUnlinked,
4794 _offset = offset,
4795 _leaderAnchor = leaderAnchor,
4796 _followerAnchor = followerAnchor,
4797 super(child);
4798
4799 /// The link object that connects this [RenderFollowerLayer] with a
4800 /// [RenderLeaderLayer] earlier in the paint order.
4801 LayerLink get link => _link;
4802 LayerLink _link;
4803 set link(LayerLink value) {
4804 if (_link == value) {
4805 return;
4806 }
4807 _link = value;
4808 markNeedsPaint();
4809 }
4810
4811 /// Whether to show the render object's contents when there is no
4812 /// corresponding [RenderLeaderLayer] with the same [link].
4813 ///
4814 /// When the render object is linked, the child is positioned such that it has
4815 /// the same global position as the linked [RenderLeaderLayer].
4816 ///
4817 /// When the render object is not linked, then: if [showWhenUnlinked] is true,
4818 /// the child is visible and not repositioned; if it is false, then child is
4819 /// hidden, and its hit testing is also disabled.
4820 bool get showWhenUnlinked => _showWhenUnlinked;
4821 bool _showWhenUnlinked;
4822 set showWhenUnlinked(bool value) {
4823 if (_showWhenUnlinked == value) {
4824 return;
4825 }
4826 _showWhenUnlinked = value;
4827 markNeedsPaint();
4828 }
4829
4830 /// The offset to apply to the origin of the linked [RenderLeaderLayer] to
4831 /// obtain this render object's origin.
4832 Offset get offset => _offset;
4833 Offset _offset;
4834 set offset(Offset value) {
4835 if (_offset == value) {
4836 return;
4837 }
4838 _offset = value;
4839 markNeedsPaint();
4840 }
4841
4842 /// The anchor point on the linked [RenderLeaderLayer] that [followerAnchor]
4843 /// will line up with.
4844 ///
4845 /// {@template flutter.rendering.RenderFollowerLayer.leaderAnchor}
4846 /// For example, when [leaderAnchor] and [followerAnchor] are both
4847 /// [Alignment.topLeft], this [RenderFollowerLayer] will be top left aligned
4848 /// with the linked [RenderLeaderLayer]. When [leaderAnchor] is
4849 /// [Alignment.bottomLeft] and [followerAnchor] is [Alignment.topLeft], this
4850 /// [RenderFollowerLayer] will be left aligned with the linked
4851 /// [RenderLeaderLayer], and its top edge will line up with the
4852 /// [RenderLeaderLayer]'s bottom edge.
4853 /// {@endtemplate}
4854 ///
4855 /// Defaults to [Alignment.topLeft].
4856 Alignment get leaderAnchor => _leaderAnchor;
4857 Alignment _leaderAnchor;
4858 set leaderAnchor(Alignment value) {
4859 if (_leaderAnchor == value) {
4860 return;
4861 }
4862 _leaderAnchor = value;
4863 markNeedsPaint();
4864 }
4865
4866 /// The anchor point on this [RenderFollowerLayer] that will line up with
4867 /// [followerAnchor] on the linked [RenderLeaderLayer].
4868 ///
4869 /// {@macro flutter.rendering.RenderFollowerLayer.leaderAnchor}
4870 ///
4871 /// Defaults to [Alignment.topLeft].
4872 Alignment get followerAnchor => _followerAnchor;
4873 Alignment _followerAnchor;
4874 set followerAnchor(Alignment value) {
4875 if (_followerAnchor == value) {
4876 return;
4877 }
4878 _followerAnchor = value;
4879 markNeedsPaint();
4880 }
4881
4882 @override
4883 void detach() {
4884 layer = null;
4885 super.detach();
4886 }
4887
4888 @override
4889 bool get alwaysNeedsCompositing => true;
4890
4891 /// The layer we created when we were last painted.
4892 @override
4893 FollowerLayer? get layer => super.layer as FollowerLayer?;
4894
4895 /// Return the transform that was used in the last composition phase, if any.
4896 ///
4897 /// If the [FollowerLayer] has not yet been created, was never composited, or
4898 /// was unable to determine the transform (see
4899 /// [FollowerLayer.getLastTransform]), this returns the identity matrix (see
4900 /// [Matrix4.identity].
4901 Matrix4 getCurrentTransform() {
4902 return layer?.getLastTransform() ?? Matrix4.identity();
4903 }
4904
4905 @override
4906 bool hitTest(BoxHitTestResult result, { required Offset position }) {
4907 // Disables the hit testing if this render object is hidden.
4908 if (link.leader == null && !showWhenUnlinked) {
4909 return false;
4910 }
4911 // RenderFollowerLayer objects don't check if they are
4912 // themselves hit, because it's confusing to think about
4913 // how the untransformed size and the child's transformed
4914 // position interact.
4915 return hitTestChildren(result, position: position);
4916 }
4917
4918 @override
4919 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
4920 return result.addWithPaintTransform(
4921 transform: getCurrentTransform(),
4922 position: position,
4923 hitTest: (BoxHitTestResult result, Offset position) {
4924 return super.hitTestChildren(result, position: position);
4925 },
4926 );
4927 }
4928
4929 @override
4930 void paint(PaintingContext context, Offset offset) {
4931 final Size? leaderSize = link.leaderSize;
4932 assert(
4933 link.leaderSize != null || link.leader == null || leaderAnchor == Alignment.topLeft,
4934 '$link: layer is linked to ${link.leader} but a valid leaderSize is not set. '
4935 'leaderSize is required when leaderAnchor is not Alignment.topLeft '
4936 '(current value is $leaderAnchor).',
4937 );
4938 final Offset effectiveLinkedOffset = leaderSize == null
4939 ? this.offset
4940 : leaderAnchor.alongSize(leaderSize) - followerAnchor.alongSize(size) + this.offset;
4941 if (layer == null) {
4942 layer = FollowerLayer(
4943 link: link,
4944 showWhenUnlinked: showWhenUnlinked,
4945 linkedOffset: effectiveLinkedOffset,
4946 unlinkedOffset: offset,
4947 );
4948 } else {
4949 layer
4950 ?..link = link
4951 ..showWhenUnlinked = showWhenUnlinked
4952 ..linkedOffset = effectiveLinkedOffset
4953 ..unlinkedOffset = offset;
4954 }
4955 context.pushLayer(
4956 layer!,
4957 super.paint,
4958 Offset.zero,
4959 childPaintBounds: const Rect.fromLTRB(
4960 // We don't know where we'll end up, so we have no idea what our cull rect should be.
4961 double.negativeInfinity,
4962 double.negativeInfinity,
4963 double.infinity,
4964 double.infinity,
4965 ),
4966 );
4967 assert(() {
4968 layer!.debugCreator = debugCreator;
4969 return true;
4970 }());
4971 }
4972
4973 @override
4974 void applyPaintTransform(RenderBox child, Matrix4 transform) {
4975 transform.multiply(getCurrentTransform());
4976 }
4977
4978 @override
4979 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
4980 super.debugFillProperties(properties);
4981 properties.add(DiagnosticsProperty<LayerLink>('link', link));
4982 properties.add(DiagnosticsProperty<bool>('showWhenUnlinked', showWhenUnlinked));
4983 properties.add(DiagnosticsProperty<Offset>('offset', offset));
4984 properties.add(TransformProperty('current transform matrix', getCurrentTransform()));
4985 }
4986}
4987
4988/// Render object which inserts an [AnnotatedRegionLayer] into the layer tree.
4989///
4990/// See also:
4991///
4992/// * [Layer.find], for an example of how this value is retrieved.
4993/// * [AnnotatedRegionLayer], the layer this render object creates.
4994class RenderAnnotatedRegion<T extends Object> extends RenderProxyBox {
4995
4996 /// Creates a new [RenderAnnotatedRegion] to insert [value] into the
4997 /// layer tree.
4998 ///
4999 /// If [sized] is true, the layer is provided with the size of this render
5000 /// object to clip the results of [Layer.find].
5001 ///
5002 /// Neither [value] nor [sized] can be null.
5003 RenderAnnotatedRegion({
5004 required T value,
5005 required bool sized,
5006 RenderBox? child,
5007 }) : _value = value,
5008 _sized = sized,
5009 _layerHandle = LayerHandle<AnnotatedRegionLayer<T>>(),
5010 super(child);
5011
5012 /// A value which can be retrieved using [Layer.find].
5013 T get value => _value;
5014 T _value;
5015 set value (T newValue) {
5016 if (_value == newValue) {
5017 return;
5018 }
5019 _value = newValue;
5020 markNeedsPaint();
5021 }
5022
5023 /// Whether the render object will pass its [size] to the [AnnotatedRegionLayer].
5024 bool get sized => _sized;
5025 bool _sized;
5026 set sized(bool value) {
5027 if (_sized == value) {
5028 return;
5029 }
5030 _sized = value;
5031 markNeedsPaint();
5032 }
5033
5034 final LayerHandle<AnnotatedRegionLayer<T>> _layerHandle;
5035
5036 @override
5037 final bool alwaysNeedsCompositing = true;
5038
5039 @override
5040 void paint(PaintingContext context, Offset offset) {
5041 // Annotated region layers are not retained because they do not create engine layers.
5042 final AnnotatedRegionLayer<T> layer = AnnotatedRegionLayer<T>(
5043 value,
5044 size: sized ? size : null,
5045 offset: sized ? offset : null,
5046 );
5047 _layerHandle.layer = layer;
5048 context.pushLayer(layer, super.paint, offset);
5049 }
5050
5051 @override
5052 void dispose() {
5053 _layerHandle.layer = null;
5054 super.dispose();
5055 }
5056}
5057