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