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
402 /// Set a new background custom paint delegate.
403 ///
404 /// If the new delegate is the same as the previous one, this does nothing.
405 ///
406 /// If the new delegate is the same class as the previous one, then the new
407 /// delegate has its [CustomPainter.shouldRepaint] called; if the result is
408 /// true, then the delegate will be called.
409 ///
410 /// If the new delegate is a different class than the previous one, then the
411 /// delegate will be called.
412 ///
413 /// If the new value is null, then there is no background custom painter.
414 set painter(CustomPainter? value) {
415 if (_painter == value) {
416 return;
417 }
418 final CustomPainter? oldPainter = _painter;
419 _painter = value;
420 _didUpdatePainter(_painter, oldPainter);
421 }
422
423 /// The foreground custom paint delegate.
424 ///
425 /// This painter, if non-null, is called to paint in front of the children.
426 CustomPainter? get foregroundPainter => _foregroundPainter;
427 CustomPainter? _foregroundPainter;
428
429 /// Set a new foreground custom paint delegate.
430 ///
431 /// If the new delegate is the same as the previous one, this does nothing.
432 ///
433 /// If the new delegate is the same class as the previous one, then the new
434 /// delegate has its [CustomPainter.shouldRepaint] called; if the result is
435 /// true, then the delegate will be called.
436 ///
437 /// If the new delegate is a different class than the previous one, then the
438 /// delegate will be called.
439 ///
440 /// If the new value is null, then there is no foreground custom painter.
441 set foregroundPainter(CustomPainter? value) {
442 if (_foregroundPainter == value) {
443 return;
444 }
445 final CustomPainter? oldPainter = _foregroundPainter;
446 _foregroundPainter = value;
447 _didUpdatePainter(_foregroundPainter, oldPainter);
448 }
449
450 void _didUpdatePainter(CustomPainter? newPainter, CustomPainter? oldPainter) {
451 // Check if we need to repaint.
452 if (newPainter == null) {
453 assert(oldPainter != null); // We should be called only for changes.
454 markNeedsPaint();
455 } else if (oldPainter == null ||
456 newPainter.runtimeType != oldPainter.runtimeType ||
457 newPainter.shouldRepaint(oldPainter)) {
458 markNeedsPaint();
459 }
460 if (attached) {
461 oldPainter?.removeListener(markNeedsPaint);
462 newPainter?.addListener(markNeedsPaint);
463 }
464
465 // Check if we need to rebuild semantics.
466 if (newPainter == null) {
467 assert(oldPainter != null); // We should be called only for changes.
468 if (attached) {
469 markNeedsSemanticsUpdate();
470 }
471 } else if (oldPainter == null ||
472 newPainter.runtimeType != oldPainter.runtimeType ||
473 newPainter.shouldRebuildSemantics(oldPainter)) {
474 markNeedsSemanticsUpdate();
475 }
476 }
477
478 /// The size that this [RenderCustomPaint] should aim for, given the layout
479 /// constraints, if there is no child.
480 ///
481 /// Defaults to [Size.zero].
482 ///
483 /// If there's a child, this is ignored, and the size of the child is used
484 /// instead.
485 Size get preferredSize => _preferredSize;
486 Size _preferredSize;
487 set preferredSize(Size value) {
488 if (preferredSize == value) {
489 return;
490 }
491 _preferredSize = value;
492 markNeedsLayout();
493 }
494
495 /// Whether to hint that this layer's painting should be cached.
496 ///
497 /// The compositor contains a raster cache that holds bitmaps of layers in
498 /// order to avoid the cost of repeatedly rendering those layers on each
499 /// frame. If this flag is not set, then the compositor will apply its own
500 /// heuristics to decide whether the layer containing this render object is
501 /// complex enough to benefit from caching.
502 bool isComplex;
503
504 /// Whether the raster cache should be told that this painting is likely
505 /// to change in the next frame.
506 ///
507 /// This hint tells the compositor not to cache the layer containing this
508 /// render object because the cache will not be used in the future. If this
509 /// hint is not set, the compositor will apply its own heuristics to decide
510 /// whether this layer is likely to be reused in the future.
511 bool willChange;
512
513 @override
514 double computeMinIntrinsicWidth(double height) {
515 if (child == null) {
516 return preferredSize.width.isFinite ? preferredSize.width : 0;
517 }
518 return super.computeMinIntrinsicWidth(height);
519 }
520
521 @override
522 double computeMaxIntrinsicWidth(double height) {
523 if (child == null) {
524 return preferredSize.width.isFinite ? preferredSize.width : 0;
525 }
526 return super.computeMaxIntrinsicWidth(height);
527 }
528
529 @override
530 double computeMinIntrinsicHeight(double width) {
531 if (child == null) {
532 return preferredSize.height.isFinite ? preferredSize.height : 0;
533 }
534 return super.computeMinIntrinsicHeight(width);
535 }
536
537 @override
538 double computeMaxIntrinsicHeight(double width) {
539 if (child == null) {
540 return preferredSize.height.isFinite ? preferredSize.height : 0;
541 }
542 return super.computeMaxIntrinsicHeight(width);
543 }
544
545 @override
546 void attach(PipelineOwner owner) {
547 super.attach(owner);
548 _painter?.addListener(markNeedsPaint);
549 _foregroundPainter?.addListener(markNeedsPaint);
550 }
551
552 @override
553 void detach() {
554 _painter?.removeListener(markNeedsPaint);
555 _foregroundPainter?.removeListener(markNeedsPaint);
556 super.detach();
557 }
558
559 @override
560 bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
561 if (_foregroundPainter != null && (_foregroundPainter!.hitTest(position) ?? false)) {
562 return true;
563 }
564 return super.hitTestChildren(result, position: position);
565 }
566
567 @override
568 bool hitTestSelf(Offset position) {
569 return _painter != null && (_painter!.hitTest(position) ?? true);
570 }
571
572 @override
573 void performLayout() {
574 super.performLayout();
575 markNeedsSemanticsUpdate();
576 }
577
578 @override
579 Size computeSizeForNoChild(BoxConstraints constraints) {
580 return constraints.constrain(preferredSize);
581 }
582
583 void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) {
584 late int debugPreviousCanvasSaveCount;
585 canvas.save();
586 assert(() {
587 debugPreviousCanvasSaveCount = canvas.getSaveCount();
588 return true;
589 }());
590 if (offset != Offset.zero) {
591 canvas.translate(offset.dx, offset.dy);
592 }
593 painter.paint(canvas, size);
594 assert(() {
595 // This isn't perfect. For example, we can't catch the case of
596 // someone first restoring, then setting a transform or whatnot,
597 // then saving.
598 // If this becomes a real problem, we could add logic to the
599 // Canvas class to lock the canvas at a particular save count
600 // such that restore() fails if it would take the lock count
601 // below that number.
602 final int debugNewCanvasSaveCount = canvas.getSaveCount();
603 if (debugNewCanvasSaveCount > debugPreviousCanvasSaveCount) {
604 throw FlutterError.fromParts(<DiagnosticsNode>[
605 ErrorSummary(
606 'The $painter custom painter called canvas.save() or canvas.saveLayer() at least '
607 '${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount} more '
608 'time${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount == 1 ? '' : 's'} '
609 'than it called canvas.restore().',
610 ),
611 ErrorDescription(
612 'This leaves the canvas in an inconsistent state and will probably result in a broken display.',
613 ),
614 ErrorHint(
615 'You must pair each call to save()/saveLayer() with a later matching call to restore().',
616 ),
617 ]);
618 }
619 if (debugNewCanvasSaveCount < debugPreviousCanvasSaveCount) {
620 throw FlutterError.fromParts(<DiagnosticsNode>[
621 ErrorSummary(
622 'The $painter custom painter called canvas.restore() '
623 '${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount} more '
624 'time${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount == 1 ? '' : 's'} '
625 'than it called canvas.save() or canvas.saveLayer().',
626 ),
627 ErrorDescription(
628 'This leaves the canvas in an inconsistent state and will result in a broken display.',
629 ),
630 ErrorHint('You should only call restore() if you first called save() or saveLayer().'),
631 ]);
632 }
633 return debugNewCanvasSaveCount == debugPreviousCanvasSaveCount;
634 }());
635 canvas.restore();
636 }
637
638 @override
639 void paint(PaintingContext context, Offset offset) {
640 if (_painter != null) {
641 _paintWithPainter(context.canvas, offset, _painter!);
642 _setRasterCacheHints(context);
643 }
644 super.paint(context, offset);
645 if (_foregroundPainter != null) {
646 _paintWithPainter(context.canvas, offset, _foregroundPainter!);
647 _setRasterCacheHints(context);
648 }
649 }
650
651 void _setRasterCacheHints(PaintingContext context) {
652 if (isComplex) {
653 context.setIsComplexHint();
654 }
655 if (willChange) {
656 context.setWillChangeHint();
657 }
658 }
659
660 /// Builds semantics for the picture drawn by [painter].
661 SemanticsBuilderCallback? _backgroundSemanticsBuilder;
662
663 /// Builds semantics for the picture drawn by [foregroundPainter].
664 SemanticsBuilderCallback? _foregroundSemanticsBuilder;
665
666 @override
667 void describeSemanticsConfiguration(SemanticsConfiguration config) {
668 super.describeSemanticsConfiguration(config);
669 _backgroundSemanticsBuilder = painter?.semanticsBuilder;
670 _foregroundSemanticsBuilder = foregroundPainter?.semanticsBuilder;
671 config.isSemanticBoundary =
672 _backgroundSemanticsBuilder != null || _foregroundSemanticsBuilder != null;
673 }
674
675 /// Describe the semantics of the picture painted by the [painter].
676 List<SemanticsNode>? _backgroundSemanticsNodes;
677
678 /// Describe the semantics of the picture painted by the [foregroundPainter].
679 List<SemanticsNode>? _foregroundSemanticsNodes;
680
681 @override
682 void assembleSemanticsNode(
683 SemanticsNode node,
684 SemanticsConfiguration config,
685 Iterable<SemanticsNode> children,
686 ) {
687 assert(() {
688 if (child == null && children.isNotEmpty) {
689 throw FlutterError.fromParts(<DiagnosticsNode>[
690 ErrorSummary(
691 '$runtimeType does not have a child widget but received a non-empty list of child SemanticsNode:\n'
692 '${children.join('\n')}',
693 ),
694 ]);
695 }
696 return true;
697 }());
698
699 final List<CustomPainterSemantics> backgroundSemantics =
700 _backgroundSemanticsBuilder != null
701 ? _backgroundSemanticsBuilder!(size)
702 : const <CustomPainterSemantics>[];
703 _backgroundSemanticsNodes = _updateSemanticsChildren(
704 _backgroundSemanticsNodes,
705 backgroundSemantics,
706 );
707
708 final List<CustomPainterSemantics> foregroundSemantics =
709 _foregroundSemanticsBuilder != null
710 ? _foregroundSemanticsBuilder!(size)
711 : const <CustomPainterSemantics>[];
712 _foregroundSemanticsNodes = _updateSemanticsChildren(
713 _foregroundSemanticsNodes,
714 foregroundSemantics,
715 );
716
717 final bool hasBackgroundSemantics =
718 _backgroundSemanticsNodes != null && _backgroundSemanticsNodes!.isNotEmpty;
719 final bool hasForegroundSemantics =
720 _foregroundSemanticsNodes != null && _foregroundSemanticsNodes!.isNotEmpty;
721 final List<SemanticsNode> finalChildren = <SemanticsNode>[
722 if (hasBackgroundSemantics) ..._backgroundSemanticsNodes!,
723 ...children,
724 if (hasForegroundSemantics) ..._foregroundSemanticsNodes!,
725 ];
726 super.assembleSemanticsNode(node, config, finalChildren);
727 }
728
729 @override
730 void clearSemantics() {
731 super.clearSemantics();
732 _backgroundSemanticsNodes = null;
733 _foregroundSemanticsNodes = null;
734 }
735
736 /// Updates the nodes of `oldSemantics` using data in `newChildSemantics`, and
737 /// returns a new list containing child nodes sorted according to the order
738 /// specified by `newChildSemantics`.
739 ///
740 /// [SemanticsNode]s that match [CustomPainterSemantics] by [Key]s preserve
741 /// their [SemanticsNode.key] field. If a node with the same key appears in
742 /// a different position in the list, it is moved to the new position, but the
743 /// same object is reused.
744 ///
745 /// [SemanticsNode]s whose `key` is null may be updated from
746 /// [CustomPainterSemantics] whose `key` is also null. However, the algorithm
747 /// does not guarantee it. If your semantics require that specific nodes are
748 /// updated from specific [CustomPainterSemantics], it is recommended to match
749 /// them by specifying non-null keys.
750 ///
751 /// The algorithm tries to be as close to [RenderObjectElement.updateChildren]
752 /// as possible, deviating only where the concepts diverge between widgets and
753 /// semantics. For example, a [SemanticsNode] can be updated from a
754 /// [CustomPainterSemantics] based on `Key` alone; their types are not
755 /// considered because there is only one type of [SemanticsNode]. There is no
756 /// concept of a "forgotten" node in semantics, deactivated nodes, or global
757 /// keys.
758 static List<SemanticsNode> _updateSemanticsChildren(
759 List<SemanticsNode>? oldSemantics,
760 List<CustomPainterSemantics>? newChildSemantics,
761 ) {
762 oldSemantics = oldSemantics ?? const <SemanticsNode>[];
763 newChildSemantics = newChildSemantics ?? const <CustomPainterSemantics>[];
764
765 assert(() {
766 final Map<Key, int> keys = HashMap<Key, int>();
767 final List<DiagnosticsNode> information = <DiagnosticsNode>[];
768 for (int i = 0; i < newChildSemantics!.length; i += 1) {
769 final CustomPainterSemantics child = newChildSemantics[i];
770 if (child.key != null) {
771 if (keys.containsKey(child.key)) {
772 information.add(ErrorDescription('- duplicate key ${child.key} found at position $i'));
773 }
774 keys[child.key!] = i;
775 }
776 }
777
778 if (information.isNotEmpty) {
779 information.insert(0, ErrorSummary('Failed to update the list of CustomPainterSemantics:'));
780 throw FlutterError.fromParts(information);
781 }
782
783 return true;
784 }());
785
786 int newChildrenTop = 0;
787 int oldChildrenTop = 0;
788 int newChildrenBottom = newChildSemantics.length - 1;
789 int oldChildrenBottom = oldSemantics.length - 1;
790
791 final List<SemanticsNode?> newChildren = List<SemanticsNode?>.filled(
792 newChildSemantics.length,
793 null,
794 );
795
796 // Update the top of the list.
797 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
798 final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
799 final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
800 if (!_canUpdateSemanticsChild(oldChild, newSemantics)) {
801 break;
802 }
803 final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
804 newChildren[newChildrenTop] = newChild;
805 newChildrenTop += 1;
806 oldChildrenTop += 1;
807 }
808
809 // Scan the bottom of the list.
810 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
811 final SemanticsNode oldChild = oldSemantics[oldChildrenBottom];
812 final CustomPainterSemantics newChild = newChildSemantics[newChildrenBottom];
813 if (!_canUpdateSemanticsChild(oldChild, newChild)) {
814 break;
815 }
816 oldChildrenBottom -= 1;
817 newChildrenBottom -= 1;
818 }
819
820 // Scan the old children in the middle of the list.
821 final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
822 late final Map<Key, SemanticsNode> oldKeyedChildren;
823 if (haveOldChildren) {
824 oldKeyedChildren = <Key, SemanticsNode>{};
825 while (oldChildrenTop <= oldChildrenBottom) {
826 final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
827 if (oldChild.key != null) {
828 oldKeyedChildren[oldChild.key!] = oldChild;
829 }
830 oldChildrenTop += 1;
831 }
832 }
833
834 // Update the middle of the list.
835 while (newChildrenTop <= newChildrenBottom) {
836 SemanticsNode? oldChild;
837 final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
838 if (haveOldChildren) {
839 final Key? key = newSemantics.key;
840 if (key != null) {
841 oldChild = oldKeyedChildren[key];
842 if (oldChild != null) {
843 if (_canUpdateSemanticsChild(oldChild, newSemantics)) {
844 // we found a match!
845 // remove it from oldKeyedChildren so we don't unsync it later
846 oldKeyedChildren.remove(key);
847 } else {
848 // Not a match, let's pretend we didn't see it for now.
849 oldChild = null;
850 }
851 }
852 }
853 }
854 assert(oldChild == null || _canUpdateSemanticsChild(oldChild, newSemantics));
855 final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
856 assert(oldChild == newChild || oldChild == null);
857 newChildren[newChildrenTop] = newChild;
858 newChildrenTop += 1;
859 }
860
861 // We've scanned the whole list.
862 assert(oldChildrenTop == oldChildrenBottom + 1);
863 assert(newChildrenTop == newChildrenBottom + 1);
864 assert(newChildSemantics.length - newChildrenTop == oldSemantics.length - oldChildrenTop);
865 newChildrenBottom = newChildSemantics.length - 1;
866 oldChildrenBottom = oldSemantics.length - 1;
867
868 // Update the bottom of the list.
869 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
870 final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
871 final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
872 assert(_canUpdateSemanticsChild(oldChild, newSemantics));
873 final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
874 assert(oldChild == newChild);
875 newChildren[newChildrenTop] = newChild;
876 newChildrenTop += 1;
877 oldChildrenTop += 1;
878 }
879
880 assert(() {
881 for (final SemanticsNode? node in newChildren) {
882 assert(node != null);
883 }
884 return true;
885 }());
886
887 return newChildren.cast<SemanticsNode>();
888 }
889
890 /// Whether `oldChild` can be updated with properties from `newSemantics`.
891 ///
892 /// If `oldChild` can be updated, it is updated using [_updateSemanticsChild].
893 /// Otherwise, the node is replaced by a new instance of [SemanticsNode].
894 static bool _canUpdateSemanticsChild(
895 SemanticsNode oldChild,
896 CustomPainterSemantics newSemantics,
897 ) {
898 return oldChild.key == newSemantics.key;
899 }
900
901 /// Updates `oldChild` using the properties of `newSemantics`.
902 ///
903 /// This method requires that `_canUpdateSemanticsChild(oldChild, newSemantics)`
904 /// is true prior to calling it.
905 static SemanticsNode _updateSemanticsChild(
906 SemanticsNode? oldChild,
907 CustomPainterSemantics newSemantics,
908 ) {
909 assert(oldChild == null || _canUpdateSemanticsChild(oldChild, newSemantics));
910
911 final SemanticsNode newChild = oldChild ?? SemanticsNode(key: newSemantics.key);
912
913 final SemanticsProperties properties = newSemantics.properties;
914 final SemanticsConfiguration config = SemanticsConfiguration();
915 if (properties.role != null) {
916 config.role = properties.role!;
917 }
918 if (properties.sortKey != null) {
919 config.sortKey = properties.sortKey;
920 }
921 if (properties.checked != null) {
922 config.isChecked = properties.checked;
923 }
924 if (properties.mixed != null) {
925 config.isCheckStateMixed = properties.mixed;
926 }
927 if (properties.selected != null) {
928 config.isSelected = properties.selected!;
929 }
930 if (properties.button != null) {
931 config.isButton = properties.button!;
932 }
933 if (properties.expanded != null) {
934 config.isExpanded = properties.expanded;
935 }
936 if (properties.link != null) {
937 config.isLink = properties.link!;
938 }
939 if (properties.linkUrl != null) {
940 config.linkUrl = properties.linkUrl;
941 }
942 if (properties.textField != null) {
943 config.isTextField = properties.textField!;
944 }
945 if (properties.slider != null) {
946 config.isSlider = properties.slider!;
947 }
948 if (properties.keyboardKey != null) {
949 config.isKeyboardKey = properties.keyboardKey!;
950 }
951 if (properties.readOnly != null) {
952 config.isReadOnly = properties.readOnly!;
953 }
954 if (properties.focusable != null) {
955 config.isFocusable = properties.focusable!;
956 }
957 if (properties.focused != null) {
958 config.isFocused = properties.focused!;
959 }
960 if (properties.enabled != null) {
961 config.isEnabled = properties.enabled;
962 }
963 if (properties.inMutuallyExclusiveGroup != null) {
964 config.isInMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup!;
965 }
966 if (properties.obscured != null) {
967 config.isObscured = properties.obscured!;
968 }
969 if (properties.multiline != null) {
970 config.isMultiline = properties.multiline!;
971 }
972 if (properties.hidden != null) {
973 config.isHidden = properties.hidden!;
974 }
975 if (properties.header != null) {
976 config.isHeader = properties.header!;
977 }
978 if (properties.headingLevel != null) {
979 config.headingLevel = properties.headingLevel!;
980 }
981 if (properties.scopesRoute != null) {
982 config.scopesRoute = properties.scopesRoute!;
983 }
984 if (properties.namesRoute != null) {
985 config.namesRoute = properties.namesRoute!;
986 }
987 if (properties.liveRegion != null) {
988 config.liveRegion = properties.liveRegion!;
989 }
990 if (properties.isRequired != null) {
991 config.isRequired = properties.isRequired;
992 }
993 if (properties.maxValueLength != null) {
994 config.maxValueLength = properties.maxValueLength;
995 }
996 if (properties.currentValueLength != null) {
997 config.currentValueLength = properties.currentValueLength;
998 }
999 if (properties.toggled != null) {
1000 config.isToggled = properties.toggled;
1001 }
1002 if (properties.image != null) {
1003 config.isImage = properties.image!;
1004 }
1005 if (properties.label != null) {
1006 config.label = properties.label!;
1007 }
1008 if (properties.value != null) {
1009 config.value = properties.value!;
1010 }
1011 if (properties.increasedValue != null) {
1012 config.increasedValue = properties.increasedValue!;
1013 }
1014 if (properties.decreasedValue != null) {
1015 config.decreasedValue = properties.decreasedValue!;
1016 }
1017 if (properties.hint != null) {
1018 config.hint = properties.hint!;
1019 }
1020 if (properties.textDirection != null) {
1021 config.textDirection = properties.textDirection;
1022 }
1023 if (config.validationResult != properties.validationResult) {
1024 config.validationResult = properties.validationResult;
1025 }
1026 if (properties.onTap != null) {
1027 config.onTap = properties.onTap;
1028 }
1029 if (properties.onLongPress != null) {
1030 config.onLongPress = properties.onLongPress;
1031 }
1032 if (properties.onScrollLeft != null) {
1033 config.onScrollLeft = properties.onScrollLeft;
1034 }
1035 if (properties.onScrollRight != null) {
1036 config.onScrollRight = properties.onScrollRight;
1037 }
1038 if (properties.onScrollUp != null) {
1039 config.onScrollUp = properties.onScrollUp;
1040 }
1041 if (properties.onScrollDown != null) {
1042 config.onScrollDown = properties.onScrollDown;
1043 }
1044 if (properties.onIncrease != null) {
1045 config.onIncrease = properties.onIncrease;
1046 }
1047 if (properties.onDecrease != null) {
1048 config.onDecrease = properties.onDecrease;
1049 }
1050 if (properties.onCopy != null) {
1051 config.onCopy = properties.onCopy;
1052 }
1053 if (properties.onCut != null) {
1054 config.onCut = properties.onCut;
1055 }
1056 if (properties.onPaste != null) {
1057 config.onPaste = properties.onPaste;
1058 }
1059 if (properties.onMoveCursorForwardByCharacter != null) {
1060 config.onMoveCursorForwardByCharacter = properties.onMoveCursorForwardByCharacter;
1061 }
1062 if (properties.onMoveCursorBackwardByCharacter != null) {
1063 config.onMoveCursorBackwardByCharacter = properties.onMoveCursorBackwardByCharacter;
1064 }
1065 if (properties.onMoveCursorForwardByWord != null) {
1066 config.onMoveCursorForwardByWord = properties.onMoveCursorForwardByWord;
1067 }
1068 if (properties.onMoveCursorBackwardByWord != null) {
1069 config.onMoveCursorBackwardByWord = properties.onMoveCursorBackwardByWord;
1070 }
1071 if (properties.onSetSelection != null) {
1072 config.onSetSelection = properties.onSetSelection;
1073 }
1074 if (properties.onSetText != null) {
1075 config.onSetText = properties.onSetText;
1076 }
1077 if (properties.onDidGainAccessibilityFocus != null) {
1078 config.onDidGainAccessibilityFocus = properties.onDidGainAccessibilityFocus;
1079 }
1080 if (properties.onDidLoseAccessibilityFocus != null) {
1081 config.onDidLoseAccessibilityFocus = properties.onDidLoseAccessibilityFocus;
1082 }
1083 if (properties.onFocus != null) {
1084 config.onFocus = properties.onFocus;
1085 }
1086 if (properties.onDismiss != null) {
1087 config.onDismiss = properties.onDismiss;
1088 }
1089
1090 newChild.updateWith(
1091 config: config,
1092 // As of now CustomPainter does not support multiple tree levels.
1093 childrenInInversePaintOrder: const <SemanticsNode>[],
1094 );
1095
1096 newChild
1097 ..rect = newSemantics.rect
1098 ..transform = newSemantics.transform
1099 ..tags = newSemantics.tags;
1100
1101 return newChild;
1102 }
1103
1104 @override
1105 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1106 super.debugFillProperties(properties);
1107 properties.add(MessageProperty('painter', '$painter'));
1108 properties.add(
1109 MessageProperty(
1110 'foregroundPainter',
1111 '$foregroundPainter',
1112 level: foregroundPainter != null ? DiagnosticLevel.info : DiagnosticLevel.fine,
1113 ),
1114 );
1115 properties.add(
1116 DiagnosticsProperty<Size>('preferredSize', preferredSize, defaultValue: Size.zero),
1117 );
1118 properties.add(DiagnosticsProperty<bool>('isComplex', isComplex, defaultValue: false));
1119 properties.add(DiagnosticsProperty<bool>('willChange', willChange, defaultValue: false));
1120 }
1121}
1122

Provided by KDAB

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