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