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 | import 'dart:collection'; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | import 'package:flutter/semantics.dart'; |
9 | |
10 | import 'box.dart'; |
11 | import 'object.dart'; |
12 | import '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. |
23 | typedef 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. |
146 | abstract 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 |
304 | class 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. |
379 | class 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 | |