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';
6library;
7
8import 'dart:collection';
9
10import 'package:flutter/foundation.dart';
11import 'package:flutter/semantics.dart';
12
13import 'box.dart';
14import 'object.dart';
15import 'proxy_box.dart';
16
17/// Signature of the function returned by [CustomPainter.semanticsBuilder].
18///
19/// Builds semantics information describing the picture drawn by a
20/// [CustomPainter]. Each [CustomPainterSemantics] in the returned list is
21/// converted into a [SemanticsNode] by copying its properties.
22///
23/// The returned list must not be mutated after this function completes. To
24/// change the semantic information, the function must return a new list
25/// instead.
26typedef SemanticsBuilderCallback = List<CustomPainterSemantics> Function(Size size);
27
28/// The interface used by [CustomPaint] (in the widgets library) and
29/// [RenderCustomPaint] (in the rendering library).
30///
31/// To implement a custom painter, either subclass or implement this interface
32/// to define your custom paint delegate. [CustomPainter] subclasses must
33/// implement the [paint] and [shouldRepaint] methods, and may optionally also
34/// implement the [hitTest] and [shouldRebuildSemantics] methods, and the
35/// [semanticsBuilder] getter.
36///
37/// The [paint] method is called whenever the custom object needs to be repainted.
38///
39/// The [shouldRepaint] method is called when a new instance of the class
40/// is provided, to check if the new instance actually represents different
41/// information.
42///
43/// {@youtube 560 315 https://www.youtube.com/watch?v=vvI_NUXK00s}
44///
45/// The most efficient way to trigger a repaint is to either:
46///
47/// * Extend this class and supply a `repaint` argument to the constructor of
48/// the [CustomPainter], where that object notifies its listeners when it is
49/// time to repaint.
50/// * Extend [Listenable] (e.g. via [ChangeNotifier]) and implement
51/// [CustomPainter], so that the object itself provides the notifications
52/// directly.
53///
54/// In either case, the [CustomPaint] widget or [RenderCustomPaint]
55/// render object will listen to the [Listenable] and repaint whenever the
56/// animation ticks, avoiding both the build and layout phases of the pipeline.
57///
58/// The [hitTest] method is called when the user interacts with the underlying
59/// render object, to determine if the user hit the object or missed it.
60///
61/// The [semanticsBuilder] is called whenever the custom object needs to rebuild
62/// its semantics information.
63///
64/// The [shouldRebuildSemantics] method is called when a new instance of the
65/// class is provided, to check if the new instance contains different
66/// information that affects the semantics tree.
67///
68/// {@tool snippet}
69///
70/// This sample extends the same code shown for [RadialGradient] to create a
71/// custom painter that paints a sky.
72///
73/// ```dart
74/// class Sky extends CustomPainter {
75/// @override
76/// void paint(Canvas canvas, Size size) {
77/// final Rect rect = Offset.zero & size;
78/// const RadialGradient gradient = RadialGradient(
79/// center: Alignment(0.7, -0.6),
80/// radius: 0.2,
81/// colors: <Color>[Color(0xFFFFFF00), Color(0xFF0099FF)],
82/// stops: <double>[0.4, 1.0],
83/// );
84/// canvas.drawRect(
85/// rect,
86/// Paint()..shader = gradient.createShader(rect),
87/// );
88/// }
89///
90/// @override
91/// SemanticsBuilderCallback get semanticsBuilder {
92/// return (Size size) {
93/// // Annotate a rectangle containing the picture of the sun
94/// // with the label "Sun". When text to speech feature is enabled on the
95/// // device, a user will be able to locate the sun on this picture by
96/// // touch.
97/// Rect rect = Offset.zero & size;
98/// final double width = size.shortestSide * 0.4;
99/// rect = const Alignment(0.8, -0.9).inscribe(Size(width, width), rect);
100/// return <CustomPainterSemantics>[
101/// CustomPainterSemantics(
102/// rect: rect,
103/// properties: const SemanticsProperties(
104/// label: 'Sun',
105/// textDirection: TextDirection.ltr,
106/// ),
107/// ),
108/// ];
109/// };
110/// }
111///
112/// // Since this Sky painter has no fields, it always paints
113/// // the same thing and semantics information is the same.
114/// // Therefore we return false here. If we had fields (set
115/// // from the constructor) then we would return true if any
116/// // of them differed from the same fields on the oldDelegate.
117/// @override
118/// bool shouldRepaint(Sky oldDelegate) => false;
119/// @override
120/// bool shouldRebuildSemantics(Sky oldDelegate) => false;
121/// }
122/// ```
123/// {@end-tool}
124///
125/// ## Composition and the sharing of canvases
126///
127/// Widgets (or rather, render objects) are composited together using a minimum
128/// number of [Canvas]es, for performance reasons. As a result, a
129/// [CustomPainter]'s [Canvas] may be the same as that used by other widgets
130/// (including other [CustomPaint] widgets).
131///
132/// This is mostly unnoticeable, except when using unusual [BlendMode]s. For
133/// example, trying to use [BlendMode.dstOut] to "punch a hole" through a
134/// previously-drawn image may erase more than was intended, because previous
135/// widgets will have been painted onto the same canvas.
136///
137/// To avoid this issue, consider using [Canvas.saveLayer] and
138/// [Canvas.restore] when using such blend modes. Creating new layers is
139/// relatively expensive, however, and should be done sparingly to avoid
140/// introducing jank.
141///
142/// See also:
143///
144/// * [Canvas], the class that a custom painter uses to paint.
145/// * [CustomPaint], the widget that uses [CustomPainter], and whose sample
146/// code shows how to use the above `Sky` class.
147/// * [RadialGradient], whose sample code section shows a different take
148/// on the sample code above.
149abstract class CustomPainter extends Listenable {
150 /// Creates a custom painter.
151 ///
152 /// The painter will repaint whenever `repaint` notifies its listeners.
153 const CustomPainter({ Listenable? repaint }) : _repaint = repaint;
154
155 final Listenable? _repaint;
156
157 /// Register a closure to be notified when it is time to repaint.
158 ///
159 /// The [CustomPainter] implementation merely forwards to the same method on
160 /// the [Listenable] provided to the constructor in the `repaint` argument, if
161 /// it was not null.
162 @override
163 void addListener(VoidCallback listener) => _repaint?.addListener(listener);
164
165 /// Remove a previously registered closure from the list of closures that the
166 /// object notifies when it is time to repaint.
167 ///
168 /// The [CustomPainter] implementation merely forwards to the same method on
169 /// the [Listenable] provided to the constructor in the `repaint` argument, if
170 /// it was not null.
171 @override
172 void removeListener(VoidCallback listener) => _repaint?.removeListener(listener);
173
174 /// Called whenever the object needs to paint. The given [Canvas] has its
175 /// coordinate space configured such that the origin is at the top left of the
176 /// box. The area of the box is the size of the [size] argument.
177 ///
178 /// Paint operations should remain inside the given area. Graphical
179 /// operations outside the bounds may be silently ignored, clipped, or not
180 /// clipped. It may sometimes be difficult to guarantee that a certain
181 /// operation is inside the bounds (e.g., drawing a rectangle whose size is
182 /// determined by user inputs). In that case, consider calling
183 /// [Canvas.clipRect] at the beginning of [paint] so everything that follows
184 /// will be guaranteed to only draw within the clipped area.
185 ///
186 /// Implementations should be wary of correctly pairing any calls to
187 /// [Canvas.save]/[Canvas.saveLayer] and [Canvas.restore], otherwise all
188 /// subsequent painting on this canvas may be affected, with potentially
189 /// hilarious but confusing results.
190 ///
191 /// To paint text on a [Canvas], use a [TextPainter].
192 ///
193 /// To paint an image on a [Canvas]:
194 ///
195 /// 1. Obtain an [ImageStream], for example by calling [ImageProvider.resolve]
196 /// on an [AssetImage] or [NetworkImage] object.
197 ///
198 /// 2. Whenever the [ImageStream]'s underlying [ImageInfo] object changes
199 /// (see [ImageStream.addListener]), create a new instance of your custom
200 /// paint delegate, giving it the new [ImageInfo] object.
201 ///
202 /// 3. In your delegate's [paint] method, call the [Canvas.drawImage],
203 /// [Canvas.drawImageRect], or [Canvas.drawImageNine] methods to paint the
204 /// [ImageInfo.image] object, applying the [ImageInfo.scale] value to
205 /// obtain the correct rendering size.
206 void paint(Canvas canvas, Size size);
207
208 /// Returns a function that builds semantic information for the picture drawn
209 /// by this painter.
210 ///
211 /// If the returned function is null, this painter will not contribute new
212 /// [SemanticsNode]s to the semantics tree and the [CustomPaint] corresponding
213 /// to this painter will not create a semantics boundary. However, if the
214 /// child of a [CustomPaint] is not null, the child may contribute
215 /// [SemanticsNode]s to the tree.
216 ///
217 /// See also:
218 ///
219 /// * [SemanticsConfiguration.isSemanticBoundary], which causes new
220 /// [SemanticsNode]s to be added to the semantics tree.
221 /// * [RenderCustomPaint], which uses this getter to build semantics.
222 SemanticsBuilderCallback? get semanticsBuilder => null;
223
224 /// Called whenever a new instance of the custom painter delegate class is
225 /// provided to the [RenderCustomPaint] object, or any time that a new
226 /// [CustomPaint] object is created with a new instance of the custom painter
227 /// delegate class (which amounts to the same thing, because the latter is
228 /// implemented in terms of the former).
229 ///
230 /// If the new instance would cause [semanticsBuilder] to create different
231 /// semantics information, then this method should return true, otherwise it
232 /// should return false.
233 ///
234 /// If the method returns false, then the [semanticsBuilder] call might be
235 /// optimized away.
236 ///
237 /// It's possible that the [semanticsBuilder] will get called even if
238 /// [shouldRebuildSemantics] would return false. For example, it is called
239 /// when the [CustomPaint] is rendered for the very first time, or when the
240 /// box changes its size.
241 ///
242 /// By default this method delegates to [shouldRepaint] under the assumption
243 /// that in most cases semantics change when something new is drawn.
244 bool shouldRebuildSemantics(covariant CustomPainter oldDelegate) => shouldRepaint(oldDelegate);
245
246 /// Called whenever a new instance of the custom painter delegate class is
247 /// provided to the [RenderCustomPaint] object, or any time that a new
248 /// [CustomPaint] object is created with a new instance of the custom painter
249 /// delegate class (which amounts to the same thing, because the latter is
250 /// implemented in terms of the former).
251 ///
252 /// If the new instance represents different information than the old
253 /// instance, then the method should return true, otherwise it should return
254 /// false.
255 ///
256 /// If the method returns false, then the [paint] call might be optimized
257 /// away.
258 ///
259 /// It's possible that the [paint] method will get called even if
260 /// [shouldRepaint] returns false (e.g. if an ancestor or descendant needed to
261 /// be repainted). It's also possible that the [paint] method will get called
262 /// without [shouldRepaint] being called at all (e.g. if the box changes
263 /// size).
264 ///
265 /// If a custom delegate has a particularly expensive paint function such that
266 /// repaints should be avoided as much as possible, a [RepaintBoundary] or
267 /// [RenderRepaintBoundary] (or other render object with
268 /// [RenderObject.isRepaintBoundary] set to true) might be helpful.
269 ///
270 /// The `oldDelegate` argument will never be null.
271 bool shouldRepaint(covariant CustomPainter oldDelegate);
272
273 /// Called whenever a hit test is being performed on an object that is using
274 /// this custom paint delegate.
275 ///
276 /// The given point is relative to the same coordinate space as the last
277 /// [paint] call.
278 ///
279 /// The default behavior is to consider all points to be hits for
280 /// background painters, and no points to be hits for foreground painters.
281 ///
282 /// Return true if the given position corresponds to a point on the drawn
283 /// image that should be considered a "hit", false if it corresponds to a
284 /// point that should be considered outside the painted image, and null to use
285 /// the default behavior.
286 bool? hitTest(Offset position) => null;
287
288 @override
289 String toString() => '${describeIdentity(this)}(${ _repaint?.toString() ?? "" })';
290}
291
292/// Contains properties describing information drawn in a rectangle contained by
293/// the [Canvas] used by a [CustomPaint].
294///
295/// This information is used, for example, by assistive technologies to improve
296/// the accessibility of applications.
297///
298/// Implement [CustomPainter.semanticsBuilder] to build the semantic
299/// description of the whole picture drawn by a [CustomPaint], rather than one
300/// particular rectangle.
301///
302/// See also:
303///
304/// * [SemanticsNode], which is created using the properties of this class.
305/// * [CustomPainter], which creates instances of this class.
306@immutable
307class CustomPainterSemantics {
308 /// Creates semantics information describing a rectangle on a canvas.
309 const CustomPainterSemantics({
310 this.key,
311 required this.rect,
312 required this.properties,
313 this.transform,
314 this.tags,
315 });
316
317 /// Identifies this object in a list of siblings.
318 ///
319 /// [SemanticsNode] inherits this key, so that when the list of nodes is
320 /// updated, its nodes are updated from [CustomPainterSemantics] with matching
321 /// keys.
322 ///
323 /// If this is null, the update algorithm does not guarantee which
324 /// [SemanticsNode] will be updated using this instance.
325 ///
326 /// This value is assigned to [SemanticsNode.key] during update.
327 final Key? key;
328
329 /// The location and size of the box on the canvas where this piece of semantic
330 /// information applies.
331 ///
332 /// This value is assigned to [SemanticsNode.rect] during update.
333 final Rect rect;
334
335 /// The transform from the canvas' coordinate system to its parent's
336 /// coordinate system.
337 ///
338 /// This value is assigned to [SemanticsNode.transform] during update.
339 final Matrix4? transform;
340
341 /// Contains properties that are assigned to the [SemanticsNode] created or
342 /// updated from this object.
343 ///
344 /// See also:
345 ///
346 /// * [Semantics], which is a widget that also uses [SemanticsProperties] to
347 /// annotate.
348 final SemanticsProperties properties;
349
350 /// Tags used by the parent [SemanticsNode] to determine the layout of the
351 /// semantics tree.
352 ///
353 /// This value is assigned to [SemanticsNode.tags] during update.
354 final Set<SemanticsTag>? tags;
355}
356
357/// Provides a canvas on which to draw during the paint phase.
358///
359/// When asked to paint, [RenderCustomPaint] first asks its [painter] to paint
360/// on the current canvas, then it paints its child, and then, after painting
361/// its child, it asks its [foregroundPainter] to paint. The coordinate system of
362/// the canvas matches the coordinate system of the [CustomPaint] object. The
363/// painters are expected to paint within a rectangle starting at the origin and
364/// encompassing a region of the given size. (If the painters paint outside
365/// those bounds, there might be insufficient memory allocated to rasterize the
366/// painting commands and the resulting behavior is undefined.)
367///
368/// Painters are implemented by subclassing or implementing [CustomPainter].
369///
370/// Because custom paint calls its painters during paint, you cannot mark the
371/// tree as needing a new layout during the callback (the layout for this frame
372/// has already happened).
373///
374/// Custom painters normally size themselves to their child. If they do not have
375/// a child, they attempt to size themselves to the [preferredSize], which
376/// defaults to [Size.zero].
377///
378/// See also:
379///
380/// * [CustomPainter], the class that custom painter delegates should extend.
381/// * [Canvas], the API provided to custom painter delegates.
382class RenderCustomPaint extends RenderProxyBox {
383 /// Creates a render object that delegates its painting.
384 RenderCustomPaint({
385 CustomPainter? painter,
386 CustomPainter? foregroundPainter,
387 Size preferredSize = Size.zero,
388 this.isComplex = false,
389 this.willChange = false,
390 RenderBox? child,
391 }) : _painter = painter,
392 _foregroundPainter = foregroundPainter,
393 _preferredSize = preferredSize,
394 super(child);
395
396 /// The background custom paint delegate.
397 ///
398 /// This painter, if non-null, is called to paint behind the children.
399 CustomPainter? get painter => _painter;
400 CustomPainter? _painter;
401 /// Set a new background custom paint delegate.
402 ///
403 /// If the new delegate is the same as the previous one, this does nothing.
404 ///
405 /// If the new delegate is the same class as the previous one, then the new
406 /// delegate has its [CustomPainter.shouldRepaint] called; if the result is
407 /// true, then the delegate will be called.
408 ///
409 /// If the new delegate is a different class than the previous one, then the
410 /// delegate will be called.
411 ///
412 /// If the new value is null, then there is no background custom painter.
413 set painter(CustomPainter? value) {
414 if (_painter == value) {
415 return;
416 }
417 final CustomPainter? oldPainter = _painter;
418 _painter = value;
419 _didUpdatePainter(_painter, oldPainter);
420 }
421
422 /// The foreground custom paint delegate.
423 ///
424 /// This painter, if non-null, is called to paint in front of the children.
425 CustomPainter? get foregroundPainter => _foregroundPainter;
426 CustomPainter? _foregroundPainter;
427 /// Set a new foreground custom paint delegate.
428 ///
429 /// If the new delegate is the same as the previous one, this does nothing.
430 ///
431 /// If the new delegate is the same class as the previous one, then the new
432 /// delegate has its [CustomPainter.shouldRepaint] called; if the result is
433 /// true, then the delegate will be called.
434 ///
435 /// If the new delegate is a different class than the previous one, then the
436 /// delegate will be called.
437 ///
438 /// If the new value is null, then there is no foreground custom painter.
439 set foregroundPainter(CustomPainter? value) {
440 if (_foregroundPainter == value) {
441 return;
442 }
443 final CustomPainter? oldPainter = _foregroundPainter;
444 _foregroundPainter = value;
445 _didUpdatePainter(_foregroundPainter, oldPainter);
446 }
447
448 void _didUpdatePainter(CustomPainter? newPainter, CustomPainter? oldPainter) {
449 // Check if we need to repaint.
450 if (newPainter == null) {
451 assert(oldPainter != null); // We should be called only for changes.
452 markNeedsPaint();
453 } else if (oldPainter == null ||
454 newPainter.runtimeType != oldPainter.runtimeType ||
455 newPainter.shouldRepaint(oldPainter)) {
456 markNeedsPaint();
457 }
458 if (attached) {
459 oldPainter?.removeListener(markNeedsPaint);
460 newPainter?.addListener(markNeedsPaint);
461 }
462
463 // Check if we need to rebuild semantics.
464 if (newPainter == null) {
465 assert(oldPainter != null); // We should be called only for changes.
466 if (attached) {
467 markNeedsSemanticsUpdate();
468 }
469 } else if (oldPainter == null ||
470 newPainter.runtimeType != oldPainter.runtimeType ||
471 newPainter.shouldRebuildSemantics(oldPainter)) {
472 markNeedsSemanticsUpdate();
473 }
474 }
475
476 /// The size that this [RenderCustomPaint] should aim for, given the layout
477 /// constraints, if there is no child.
478 ///
479 /// Defaults to [Size.zero].
480 ///
481 /// If there's a child, this is ignored, and the size of the child is used
482 /// instead.
483 Size get preferredSize => _preferredSize;
484 Size _preferredSize;
485 set preferredSize(Size value) {
486 if (preferredSize == value) {
487 return;
488 }
489 _preferredSize = value;
490 markNeedsLayout();
491 }
492
493 /// Whether to hint that this layer's painting should be cached.
494 ///
495 /// The compositor contains a raster cache that holds bitmaps of layers in
496 /// order to avoid the cost of repeatedly rendering those layers on each
497 /// frame. If this flag is not set, then the compositor will apply its own
498 /// heuristics to decide whether the layer containing this render object is
499 /// complex enough to benefit from caching.
500 bool isComplex;
501
502 /// Whether the raster cache should be told that this painting is likely
503 /// to change in the next frame.
504 ///
505 /// This hint tells the compositor not to cache the layer containing this
506 /// render object because the cache will not be used in the future. If this
507 /// hint is not set, the compositor will apply its own heuristics to decide
508 /// whether this layer is likely to be reused in the future.
509 bool willChange;
510
511 @override
512 double computeMinIntrinsicWidth(double height) {
513 if (child == null) {
514 return preferredSize.width.isFinite ? preferredSize.width : 0;
515 }
516 return super.computeMinIntrinsicWidth(height);
517 }
518
519 @override
520 double computeMaxIntrinsicWidth(double height) {
521 if (child == null) {
522 return preferredSize.width.isFinite ? preferredSize.width : 0;
523 }
524 return super.computeMaxIntrinsicWidth(height);
525 }
526
527 @override
528 double computeMinIntrinsicHeight(double width) {
529 if (child == null) {
530 return preferredSize.height.isFinite ? preferredSize.height : 0;
531 }
532 return super.computeMinIntrinsicHeight(width);
533 }
534
535 @override
536 double computeMaxIntrinsicHeight(double width) {
537 if (child == null) {
538 return preferredSize.height.isFinite ? preferredSize.height : 0;
539 }
540 return super.computeMaxIntrinsicHeight(width);
541 }
542
543 @override
544 void attach(PipelineOwner owner) {
545 super.attach(owner);
546 _painter?.addListener(markNeedsPaint);
547 _foregroundPainter?.addListener(markNeedsPaint);
548 }
549
550 @override
551 void detach() {
552 _painter?.removeListener(markNeedsPaint);
553 _foregroundPainter?.removeListener(markNeedsPaint);
554 super.detach();
555 }
556
557 @override
558 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
559 if (_foregroundPainter != null && (_foregroundPainter!.hitTest(position) ?? false)) {
560 return true;
561 }
562 return super.hitTestChildren(result, position: position);
563 }
564
565 @override
566 bool hitTestSelf(Offset position) {
567 return _painter != null && (_painter!.hitTest(position) ?? true);
568 }
569
570 @override
571 void performLayout() {
572 super.performLayout();
573 markNeedsSemanticsUpdate();
574 }
575
576 @override
577 Size computeSizeForNoChild(BoxConstraints constraints) {
578 return constraints.constrain(preferredSize);
579 }
580
581 void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) {
582 late int debugPreviousCanvasSaveCount;
583 canvas.save();
584 assert(() {
585 debugPreviousCanvasSaveCount = canvas.getSaveCount();
586 return true;
587 }());
588 if (offset != Offset.zero) {
589 canvas.translate(offset.dx, offset.dy);
590 }
591 painter.paint(canvas, size);
592 assert(() {
593 // This isn't perfect. For example, we can't catch the case of
594 // someone first restoring, then setting a transform or whatnot,
595 // then saving.
596 // If this becomes a real problem, we could add logic to the
597 // Canvas class to lock the canvas at a particular save count
598 // such that restore() fails if it would take the lock count
599 // below that number.
600 final int debugNewCanvasSaveCount = canvas.getSaveCount();
601 if (debugNewCanvasSaveCount > debugPreviousCanvasSaveCount) {
602 throw FlutterError.fromParts(<DiagnosticsNode>[
603 ErrorSummary(
604 'The $painter custom painter called canvas.save() or canvas.saveLayer() at least '
605 '${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount} more '
606 'time${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount == 1 ? '' : 's' } '
607 'than it called canvas.restore().',
608 ),
609 ErrorDescription('This leaves the canvas in an inconsistent state and will probably result in a broken display.'),
610 ErrorHint('You must pair each call to save()/saveLayer() with a later matching call to restore().'),
611 ]);
612 }
613 if (debugNewCanvasSaveCount < debugPreviousCanvasSaveCount) {
614 throw FlutterError.fromParts(<DiagnosticsNode>[
615 ErrorSummary(
616 'The $painter custom painter called canvas.restore() '
617 '${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount} more '
618 'time${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount == 1 ? '' : 's' } '
619 'than it called canvas.save() or canvas.saveLayer().',
620 ),
621 ErrorDescription('This leaves the canvas in an inconsistent state and will result in a broken display.'),
622 ErrorHint('You should only call restore() if you first called save() or saveLayer().'),
623 ]);
624 }
625 return debugNewCanvasSaveCount == debugPreviousCanvasSaveCount;
626 }());
627 canvas.restore();
628 }
629
630 @override
631 void paint(PaintingContext context, Offset offset) {
632 if (_painter != null) {
633 _paintWithPainter(context.canvas, offset, _painter!);
634 _setRasterCacheHints(context);
635 }
636 super.paint(context, offset);
637 if (_foregroundPainter != null) {
638 _paintWithPainter(context.canvas, offset, _foregroundPainter!);
639 _setRasterCacheHints(context);
640 }
641 }
642
643 void _setRasterCacheHints(PaintingContext context) {
644 if (isComplex) {
645 context.setIsComplexHint();
646 }
647 if (willChange) {
648 context.setWillChangeHint();
649 }
650 }
651
652 /// Builds semantics for the picture drawn by [painter].
653 SemanticsBuilderCallback? _backgroundSemanticsBuilder;
654
655 /// Builds semantics for the picture drawn by [foregroundPainter].
656 SemanticsBuilderCallback? _foregroundSemanticsBuilder;
657
658 @override
659 void describeSemanticsConfiguration(SemanticsConfiguration config) {
660 super.describeSemanticsConfiguration(config);
661 _backgroundSemanticsBuilder = painter?.semanticsBuilder;
662 _foregroundSemanticsBuilder = foregroundPainter?.semanticsBuilder;
663 config.isSemanticBoundary = _backgroundSemanticsBuilder != null || _foregroundSemanticsBuilder != null;
664 }
665
666 /// Describe the semantics of the picture painted by the [painter].
667 List<SemanticsNode>? _backgroundSemanticsNodes;
668
669 /// Describe the semantics of the picture painted by the [foregroundPainter].
670 List<SemanticsNode>? _foregroundSemanticsNodes;
671
672 @override
673 void assembleSemanticsNode(
674 SemanticsNode node,
675 SemanticsConfiguration config,
676 Iterable<SemanticsNode> children,
677 ) {
678 assert(() {
679 if (child == null && children.isNotEmpty) {
680 throw FlutterError.fromParts(<DiagnosticsNode>[
681 ErrorSummary(
682 '$runtimeType does not have a child widget but received a non-empty list of child SemanticsNode:\n'
683 '${children.join('\n')}',
684 ),
685 ]);
686 }
687 return true;
688 }());
689
690 final List<CustomPainterSemantics> backgroundSemantics = _backgroundSemanticsBuilder != null
691 ? _backgroundSemanticsBuilder!(size)
692 : const <CustomPainterSemantics>[];
693 _backgroundSemanticsNodes = _updateSemanticsChildren(_backgroundSemanticsNodes, backgroundSemantics);
694
695 final List<CustomPainterSemantics> foregroundSemantics = _foregroundSemanticsBuilder != null
696 ? _foregroundSemanticsBuilder!(size)
697 : const <CustomPainterSemantics>[];
698 _foregroundSemanticsNodes = _updateSemanticsChildren(_foregroundSemanticsNodes, foregroundSemantics);
699
700 final bool hasBackgroundSemantics = _backgroundSemanticsNodes != null && _backgroundSemanticsNodes!.isNotEmpty;
701 final bool hasForegroundSemantics = _foregroundSemanticsNodes != null && _foregroundSemanticsNodes!.isNotEmpty;
702 final List<SemanticsNode> finalChildren = <SemanticsNode>[
703 if (hasBackgroundSemantics) ..._backgroundSemanticsNodes!,
704 ...children,
705 if (hasForegroundSemantics) ..._foregroundSemanticsNodes!,
706 ];
707 super.assembleSemanticsNode(node, config, finalChildren);
708 }
709
710 @override
711 void clearSemantics() {
712 super.clearSemantics();
713 _backgroundSemanticsNodes = null;
714 _foregroundSemanticsNodes = null;
715 }
716
717 /// Updates the nodes of `oldSemantics` using data in `newChildSemantics`, and
718 /// returns a new list containing child nodes sorted according to the order
719 /// specified by `newChildSemantics`.
720 ///
721 /// [SemanticsNode]s that match [CustomPainterSemantics] by [Key]s preserve
722 /// their [SemanticsNode.key] field. If a node with the same key appears in
723 /// a different position in the list, it is moved to the new position, but the
724 /// same object is reused.
725 ///
726 /// [SemanticsNode]s whose `key` is null may be updated from
727 /// [CustomPainterSemantics] whose `key` is also null. However, the algorithm
728 /// does not guarantee it. If your semantics require that specific nodes are
729 /// updated from specific [CustomPainterSemantics], it is recommended to match
730 /// them by specifying non-null keys.
731 ///
732 /// The algorithm tries to be as close to [RenderObjectElement.updateChildren]
733 /// as possible, deviating only where the concepts diverge between widgets and
734 /// semantics. For example, a [SemanticsNode] can be updated from a
735 /// [CustomPainterSemantics] based on `Key` alone; their types are not
736 /// considered because there is only one type of [SemanticsNode]. There is no
737 /// concept of a "forgotten" node in semantics, deactivated nodes, or global
738 /// keys.
739 static List<SemanticsNode> _updateSemanticsChildren(
740 List<SemanticsNode>? oldSemantics,
741 List<CustomPainterSemantics>? newChildSemantics,
742 ) {
743 oldSemantics = oldSemantics ?? const <SemanticsNode>[];
744 newChildSemantics = newChildSemantics ?? const <CustomPainterSemantics>[];
745
746 assert(() {
747 final Map<Key, int> keys = HashMap<Key, int>();
748 final List<DiagnosticsNode> information = <DiagnosticsNode>[];
749 for (int i = 0; i < newChildSemantics!.length; i += 1) {
750 final CustomPainterSemantics child = newChildSemantics[i];
751 if (child.key != null) {
752 if (keys.containsKey(child.key)) {
753 information.add(ErrorDescription('- duplicate key ${child.key} found at position $i'));
754 }
755 keys[child.key!] = i;
756 }
757 }
758
759 if (information.isNotEmpty) {
760 information.insert(0, ErrorSummary('Failed to update the list of CustomPainterSemantics:'));
761 throw FlutterError.fromParts(information);
762 }
763
764 return true;
765 }());
766
767 int newChildrenTop = 0;
768 int oldChildrenTop = 0;
769 int newChildrenBottom = newChildSemantics.length - 1;
770 int oldChildrenBottom = oldSemantics.length - 1;
771
772 final List<SemanticsNode?> newChildren = List<SemanticsNode?>.filled(newChildSemantics.length, null);
773
774 // Update the top of the list.
775 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
776 final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
777 final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
778 if (!_canUpdateSemanticsChild(oldChild, newSemantics)) {
779 break;
780 }
781 final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
782 newChildren[newChildrenTop] = newChild;
783 newChildrenTop += 1;
784 oldChildrenTop += 1;
785 }
786
787 // Scan the bottom of the list.
788 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
789 final SemanticsNode oldChild = oldSemantics[oldChildrenBottom];
790 final CustomPainterSemantics newChild = newChildSemantics[newChildrenBottom];
791 if (!_canUpdateSemanticsChild(oldChild, newChild)) {
792 break;
793 }
794 oldChildrenBottom -= 1;
795 newChildrenBottom -= 1;
796 }
797
798 // Scan the old children in the middle of the list.
799 final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
800 late final Map<Key, SemanticsNode> oldKeyedChildren;
801 if (haveOldChildren) {
802 oldKeyedChildren = <Key, SemanticsNode>{};
803 while (oldChildrenTop <= oldChildrenBottom) {
804 final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
805 if (oldChild.key != null) {
806 oldKeyedChildren[oldChild.key!] = oldChild;
807 }
808 oldChildrenTop += 1;
809 }
810 }
811
812 // Update the middle of the list.
813 while (newChildrenTop <= newChildrenBottom) {
814 SemanticsNode? oldChild;
815 final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
816 if (haveOldChildren) {
817 final Key? key = newSemantics.key;
818 if (key != null) {
819 oldChild = oldKeyedChildren[key];
820 if (oldChild != null) {
821 if (_canUpdateSemanticsChild(oldChild, newSemantics)) {
822 // we found a match!
823 // remove it from oldKeyedChildren so we don't unsync it later
824 oldKeyedChildren.remove(key);
825 } else {
826 // Not a match, let's pretend we didn't see it for now.
827 oldChild = null;
828 }
829 }
830 }
831 }
832 assert(oldChild == null || _canUpdateSemanticsChild(oldChild, newSemantics));
833 final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
834 assert(oldChild == newChild || oldChild == null);
835 newChildren[newChildrenTop] = newChild;
836 newChildrenTop += 1;
837 }
838
839 // We've scanned the whole list.
840 assert(oldChildrenTop == oldChildrenBottom + 1);
841 assert(newChildrenTop == newChildrenBottom + 1);
842 assert(newChildSemantics.length - newChildrenTop == oldSemantics.length - oldChildrenTop);
843 newChildrenBottom = newChildSemantics.length - 1;
844 oldChildrenBottom = oldSemantics.length - 1;
845
846 // Update the bottom of the list.
847 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
848 final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
849 final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
850 assert(_canUpdateSemanticsChild(oldChild, newSemantics));
851 final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
852 assert(oldChild == newChild);
853 newChildren[newChildrenTop] = newChild;
854 newChildrenTop += 1;
855 oldChildrenTop += 1;
856 }
857
858 assert(() {
859 for (final SemanticsNode? node in newChildren) {
860 assert(node != null);
861 }
862 return true;
863 }());
864
865 return newChildren.cast<SemanticsNode>();
866 }
867
868 /// Whether `oldChild` can be updated with properties from `newSemantics`.
869 ///
870 /// If `oldChild` can be updated, it is updated using [_updateSemanticsChild].
871 /// Otherwise, the node is replaced by a new instance of [SemanticsNode].
872 static bool _canUpdateSemanticsChild(SemanticsNode oldChild, CustomPainterSemantics newSemantics) {
873 return oldChild.key == newSemantics.key;
874 }
875
876 /// Updates `oldChild` using the properties of `newSemantics`.
877 ///
878 /// This method requires that `_canUpdateSemanticsChild(oldChild, newSemantics)`
879 /// is true prior to calling it.
880 static SemanticsNode _updateSemanticsChild(SemanticsNode? oldChild, CustomPainterSemantics newSemantics) {
881 assert(oldChild == null || _canUpdateSemanticsChild(oldChild, newSemantics));
882
883 final SemanticsNode newChild = oldChild ?? SemanticsNode(
884 key: newSemantics.key,
885 );
886
887 final SemanticsProperties properties = newSemantics.properties;
888 final SemanticsConfiguration config = SemanticsConfiguration();
889 if (properties.sortKey != null) {
890 config.sortKey = properties.sortKey;
891 }
892 if (properties.checked != null) {
893 config.isChecked = properties.checked;
894 }
895 if (properties.mixed != null) {
896 config.isCheckStateMixed = properties.mixed;
897 }
898 if (properties.selected != null) {
899 config.isSelected = properties.selected!;
900 }
901 if (properties.button != null) {
902 config.isButton = properties.button!;
903 }
904 if (properties.expanded != null) {
905 config.isExpanded = properties.expanded;
906 }
907 if (properties.link != null) {
908 config.isLink = properties.link!;
909 }
910 if (properties.linkUrl != null) {
911 config.linkUrl = properties.linkUrl;
912 }
913 if (properties.textField != null) {
914 config.isTextField = properties.textField!;
915 }
916 if (properties.slider != null) {
917 config.isSlider = properties.slider!;
918 }
919 if (properties.keyboardKey != null) {
920 config.isKeyboardKey = properties.keyboardKey!;
921 }
922 if (properties.readOnly != null) {
923 config.isReadOnly = properties.readOnly!;
924 }
925 if (properties.focusable != null) {
926 config.isFocusable = properties.focusable!;
927 }
928 if (properties.focused != null) {
929 config.isFocused = properties.focused!;
930 }
931 if (properties.enabled != null) {
932 config.isEnabled = properties.enabled;
933 }
934 if (properties.inMutuallyExclusiveGroup != null) {
935 config.isInMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup!;
936 }
937 if (properties.obscured != null) {
938 config.isObscured = properties.obscured!;
939 }
940 if (properties.multiline != null) {
941 config.isMultiline = properties.multiline!;
942 }
943 if (properties.hidden != null) {
944 config.isHidden = properties.hidden!;
945 }
946 if (properties.header != null) {
947 config.isHeader = properties.header!;
948 }
949 if (properties.headingLevel != null) {
950 config.headingLevel = properties.headingLevel!;
951 }
952 if (properties.scopesRoute != null) {
953 config.scopesRoute = properties.scopesRoute!;
954 }
955 if (properties.namesRoute != null) {
956 config.namesRoute = properties.namesRoute!;
957 }
958 if (properties.liveRegion != null) {
959 config.liveRegion = properties.liveRegion!;
960 }
961 if (properties.maxValueLength != null) {
962 config.maxValueLength = properties.maxValueLength;
963 }
964 if (properties.currentValueLength != null) {
965 config.currentValueLength = properties.currentValueLength;
966 }
967 if (properties.toggled != null) {
968 config.isToggled = properties.toggled;
969 }
970 if (properties.image != null) {
971 config.isImage = properties.image!;
972 }
973 if (properties.label != null) {
974 config.label = properties.label!;
975 }
976 if (properties.value != null) {
977 config.value = properties.value!;
978 }
979 if (properties.increasedValue != null) {
980 config.increasedValue = properties.increasedValue!;
981 }
982 if (properties.decreasedValue != null) {
983 config.decreasedValue = properties.decreasedValue!;
984 }
985 if (properties.hint != null) {
986 config.hint = properties.hint!;
987 }
988 if (properties.textDirection != null) {
989 config.textDirection = properties.textDirection;
990 }
991 if (properties.onTap != null) {
992 config.onTap = properties.onTap;
993 }
994 if (properties.onLongPress != null) {
995 config.onLongPress = properties.onLongPress;
996 }
997 if (properties.onScrollLeft != null) {
998 config.onScrollLeft = properties.onScrollLeft;
999 }
1000 if (properties.onScrollRight != null) {
1001 config.onScrollRight = properties.onScrollRight;
1002 }
1003 if (properties.onScrollUp != null) {
1004 config.onScrollUp = properties.onScrollUp;
1005 }
1006 if (properties.onScrollDown != null) {
1007 config.onScrollDown = properties.onScrollDown;
1008 }
1009 if (properties.onIncrease != null) {
1010 config.onIncrease = properties.onIncrease;
1011 }
1012 if (properties.onDecrease != null) {
1013 config.onDecrease = properties.onDecrease;
1014 }
1015 if (properties.onCopy != null) {
1016 config.onCopy = properties.onCopy;
1017 }
1018 if (properties.onCut != null) {
1019 config.onCut = properties.onCut;
1020 }
1021 if (properties.onPaste != null) {
1022 config.onPaste = properties.onPaste;
1023 }
1024 if (properties.onMoveCursorForwardByCharacter != null) {
1025 config.onMoveCursorForwardByCharacter = properties.onMoveCursorForwardByCharacter;
1026 }
1027 if (properties.onMoveCursorBackwardByCharacter != null) {
1028 config.onMoveCursorBackwardByCharacter = properties.onMoveCursorBackwardByCharacter;
1029 }
1030 if (properties.onMoveCursorForwardByWord != null) {
1031 config.onMoveCursorForwardByWord = properties.onMoveCursorForwardByWord;
1032 }
1033 if (properties.onMoveCursorBackwardByWord != null) {
1034 config.onMoveCursorBackwardByWord = properties.onMoveCursorBackwardByWord;
1035 }
1036 if (properties.onSetSelection != null) {
1037 config.onSetSelection = properties.onSetSelection;
1038 }
1039 if (properties.onSetText != null) {
1040 config.onSetText = properties.onSetText;
1041 }
1042 if (properties.onDidGainAccessibilityFocus != null) {
1043 config.onDidGainAccessibilityFocus = properties.onDidGainAccessibilityFocus;
1044 }
1045 if (properties.onDidLoseAccessibilityFocus != null) {
1046 config.onDidLoseAccessibilityFocus = properties.onDidLoseAccessibilityFocus;
1047 }
1048 if (properties.onFocus != null) {
1049 config.onFocus = properties.onFocus;
1050 }
1051 if (properties.onDismiss != null) {
1052 config.onDismiss = properties.onDismiss;
1053 }
1054
1055 newChild.updateWith(
1056 config: config,
1057 // As of now CustomPainter does not support multiple tree levels.
1058 childrenInInversePaintOrder: const <SemanticsNode>[],
1059 );
1060
1061 newChild
1062 ..rect = newSemantics.rect
1063 ..transform = newSemantics.transform
1064 ..tags = newSemantics.tags;
1065
1066 return newChild;
1067 }
1068
1069 @override
1070 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1071 super.debugFillProperties(properties);
1072 properties.add(MessageProperty('painter', '$painter'));
1073 properties.add(MessageProperty('foregroundPainter', '$foregroundPainter', level: foregroundPainter != null ? DiagnosticLevel.info : DiagnosticLevel.fine));
1074 properties.add(DiagnosticsProperty<Size>('preferredSize', preferredSize, defaultValue: Size.zero));
1075 properties.add(DiagnosticsProperty<bool>('isComplex', isComplex, defaultValue: false));
1076 properties.add(DiagnosticsProperty<bool>('willChange', willChange, defaultValue: false));
1077 }
1078}
1079