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