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

Provided by KDAB

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