11import 'dart:math' as math;
22import 'dart:ui' as ui;
33
4+ import 'package:flutter/foundation.dart' ; // For kDebugMode
45import 'package:flutter/rendering.dart' ;
56import 'package:flutter/widgets.dart' ;
67// For testing, you might need:
7- // import 'package:flutter/material.dart';
8+ // import 'package:flutter/material.dart'; // Example: for Color
89
910// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
1011
@@ -25,108 +26,97 @@ typedef Picture = ui.Picture;
2526/// The re-compositing logic for complex layer trees is a simplification and might not
2627/// capture all advanced effects or deeply nested non-transformed offsets perfectly.
2728Picture ? captureWidgetPictureSync (BuildContext context) {
28- if (! context.mounted) {
29- debugPrint ('[captureWidgetPictureSync] Context is not mounted at call time.' );
30- return null ;
31- }
29+ try {
30+ if (! context.mounted) {
31+ debugPrint ('[captureWidgetPictureSync] Context is not mounted at call time.' );
32+ return null ;
33+ }
3234
33- final renderObject = context.findRenderObject ();
35+ final renderObject = context.findRenderObject ();
3436
35- if (renderObject == null ) {
36- debugPrint ('[captureWidgetPictureSync] RenderObject is null.' );
37- return null ;
38- }
37+ if (renderObject == null ) {
38+ debugPrint ('[captureWidgetPictureSync] RenderObject is null.' );
39+ return null ;
40+ }
3941
40- if (renderObject is ! RenderRepaintBoundary ) {
41- debugPrint (
42- '[captureWidgetPictureSync] RenderObject is not a RenderRepaintBoundary. It is a ${renderObject .runtimeType }.' ,
43- );
44- return null ;
45- }
42+ if (renderObject is ! RenderRepaintBoundary ) {
43+ debugPrint (
44+ '[captureWidgetPictureSync] RenderObject is not a RenderRepaintBoundary. It is a ${renderObject .runtimeType }.' ,
45+ );
46+ return null ;
47+ }
4648
47- final rrb = renderObject;
49+ final rrb = renderObject;
50+
51+ // --- kDebugMode guarded section for robust release mode operation ---
52+ bool logAsPotentiallyNotReady = ! rrb.hasSize;
53+ String layoutDebugInfoForLog = '' ;
54+
55+ if (kDebugMode) {
56+ // Only in debug mode, check debugNeedsLayout if rrb has size
57+ if (rrb.hasSize) {
58+ // Only check layout if it has size
59+ if (rrb.debugNeedsLayout) {
60+ logAsPotentiallyNotReady = true ; // Mark for logging that it might not be ideal
61+ layoutDebugInfoForLog = ', NeedsLayout: true (debug check)' ;
62+ } else {
63+ layoutDebugInfoForLog = ', NeedsLayout: false (debug check)' ;
64+ }
65+ }
66+ // If !rrb.hasSize, logAsPotentiallyNotReady is already true, no need to check layout.
67+ }
68+ // In release mode, logAsPotentiallyNotReady is solely based on !rrb.hasSize,
69+ // and layoutDebugInfoForLog remains empty. No access to rrb.debugNeedsLayout occurs.
4870
49- // Check if the RepaintBoundary has a size and has been painted.
50- // The layer check is crucial.
51- if (! rrb.hasSize || rrb.debugNeedsLayout) {
52- debugPrint (
53- '[captureWidgetPictureSync] RenderRepaintBoundary not ready (no size or needs layout). Size: ${rrb .size }, NeedsLayout: ${rrb .debugNeedsLayout }' ,
54- );
55- // It's possible it has size but no layer if it's empty or just became visible.
56- }
71+ if (logAsPotentiallyNotReady) {
72+ // This debugPrint is primarily for debugging. In release, layoutDebugInfoForLog will be empty.
73+ debugPrint (
74+ '[captureWidgetPictureSync] RenderRepaintBoundary status check: Size: ${rrb .size }$layoutDebugInfoForLog . Proceeding with capture.' ,
75+ );
76+ }
77+ // --- End of kDebugMode guarded section ---
5778
58- // ignore: invalid_use_of_protected_member
59- final Layer ? rrbRootLayer = rrb.layer;
79+ // ignore: invalid_use_of_protected_member
80+ final Layer ? rrbRootLayer = rrb.layer;
6081
61- if (rrbRootLayer == null && rrb.hasSize && rrb.size != Size .zero) {
62- // It has a size, but no layer. This implies it hasn't been painted yet or is truly empty.
63- // If it's supposed to have content, this is where things go wrong for lazy items.
64- debugPrint (
65- '[captureWidgetPictureSync] RenderRepaintBoundary has size ${rrb .size } but no layer. Has it been painted? Or is it genuinely empty?' ,
66- );
67- // Proceeding will result in a blank picture of these bounds.
68- }
82+ if (rrbRootLayer == null && rrb.hasSize && rrb.size != Size .zero) {
83+ debugPrint (
84+ '[captureWidgetPictureSync] RenderRepaintBoundary has size ${rrb .size } but no layer. Has it been painted as empty? Or is it genuinely empty?' ,
85+ );
86+ }
6987
70- final bounds = Offset .zero & rrb.size;
71- // It's valid for a RepaintBoundary to have zero size.
72- // If bounds are empty, the resulting picture will also be empty, which is correct.
88+ final bounds = Offset .zero & rrb.size;
89+ final pictureRecorder = ui. PictureRecorder ();
90+ final canvas = ui. Canvas (pictureRecorder, bounds);
7391
74- final pictureRecorder = ui.PictureRecorder ();
75- final canvas = ui.Canvas (pictureRecorder, bounds); // Use RRB's bounds for the new picture
92+ if (rrbRootLayer != null ) {
93+ _compositeLayerTreeToCanvas (canvas, rrbRootLayer, Offset .zero);
94+ } else {
95+ debugPrint (
96+ '[captureWidgetPictureSync] RRB has no root layer (or size is zero). Canvas will reflect this (e.g., empty picture).' ,
97+ );
98+ }
7699
77- if (rrbRootLayer != null ) {
78- debugPrint (
79- '[captureWidgetPictureSync] RRB root layer is ${rrbRootLayer .runtimeType }. Attempting to re-composite.' ,
80- );
81- // The root layer of the RepaintBoundary (often an OffsetLayer) is already at the correct
82- // position relative to the RepaintBoundary's origin (0,0). So, we start compositing
83- // it with an initial offset of Offset.zero onto our new canvas.
84- _compositeLayerTreeToCanvas (canvas, rrbRootLayer, Offset .zero);
85- } else {
86- debugPrint (
87- '[captureWidgetPictureSync] RRB has no root layer. Canvas will be empty (or background if one was set).' ,
88- );
89- // This means the RepaintBoundary was either empty or not painted.
90- // The canvas created with `bounds` will result in an appropriately sized (possibly empty) picture.
100+ return pictureRecorder.endRecording ();
101+ } catch (e, stackTrace) {
102+ debugPrint ('[captureWidgetPictureSync] Error capturing widget picture: $e \n $stackTrace ' );
103+ return null ;
91104 }
92-
93- return pictureRecorder.endRecording ();
94105}
95106
107+ // ... (Rest of your _compositeLayerTreeToCanvas, LayerDebugging, PicturePainter, PictureWidget code remains the same) ...
108+
96109/// Recursively re-draws layers onto the provided canvas.
97- /// `canvas` : The canvas to draw onto.
98- /// `layer` : The current layer to process.
99- /// `parentAccumulatedOffset` : The offset of this layer's parent relative to the
100- /// RepaintBoundary's origin. This is NOT the layer's own offset.
101- /// For the initial root layer, this is Offset.zero.
102110void _compositeLayerTreeToCanvas (ui.Canvas canvas, Layer layer, Offset parentAccumulatedOffset) {
103111 canvas.save ();
104-
105112 var currentGlobalOffset = parentAccumulatedOffset;
106113
107- // Apply layer-specific effects and transformations.
108- // The order of these checks can matter if layers inherit from each other.
109- // Generally, check for more specific types before more general types if there's overlap.
110-
111114 if (layer is OffsetLayer ) {
112- // OffsetLayer has an `offset` property. This offset is relative to its parent.
113- // We need to translate the canvas by this amount.
114115 canvas.translate (layer.offset.dx, layer.offset.dy);
115116 currentGlobalOffset = currentGlobalOffset + layer.offset;
116117 }
117- // Note: TransformLayer, OpacityLayer, etc., often extend OffsetLayer.
118- // Their specific properties are applied *after* their intrinsic offset.
119118
120119 if (layer is TransformLayer ) {
121- // If it's a TransformLayer, it might also be an OffsetLayer.
122- // Its `offset` would have been handled by the `OffsetLayer` check if it came first.
123- // Here we apply its specific `transform`.
124- // The engine logic is roughly: apply offset, then apply transform.
125- // If `OffsetLayer` was already handled, `layer.offset` is already part of the CTM.
126- // If `TransformLayer` is handled *before* `OffsetLayer` (if it's a direct subclass, not via OffsetLayer),
127- // then its own offset would need to be handled here too.
128- // Given `TransformLayer extends OffsetLayer`, the OffsetLayer case handles its `offset`.
129- // Now, apply its `transform` property.
130120 if (layer.transform != null ) {
131121 canvas.transform (layer.transform! .storage);
132122 }
@@ -142,22 +132,15 @@ void _compositeLayerTreeToCanvas(ui.Canvas canvas, Layer layer, Offset parentAcc
142132 if (layer.clipPath != null ) {
143133 canvas.clipPath (layer.clipPath! , doAntiAlias: layer.clipBehavior != Clip .hardEdge);
144134 }
145- }
146- // OpacityLayer extends OffsetLayer, so its offset is handled by the `is OffsetLayer` block.
147- // Here, we handle the opacity itself.
148- else if (layer is OpacityLayer ) {
135+ } else if (layer is OpacityLayer ) {
149136 final alpha = layer.alpha;
150137 if (alpha != null ) {
151138 if (alpha == 0 ) {
152- canvas.restore (); // Pop the save for this layer's state (including its offset)
139+ canvas.restore (); // Pop state for this layer
153140 return ; // Nothing more to draw for this layer or its children
154141 }
155142 if (alpha != 255 ) {
156- // The bounds for saveLayer should be relative to the current canvas transform.
157- // OpacityLayer.describeClipBounds() can be null.
158- // If null, it applies to the current clip.
159143 var layerBounds = layer.describeClipBounds ();
160- // DescribeClipBounds are in parent coords. Canvas is already there.
161144 canvas.saveLayer (layerBounds, ui.Paint ()..color = ui.Color .fromARGB (alpha, 0 , 0 , 0 ));
162145 // Children drawn into this temp layer, then opacity applied on restore.
163146 }
@@ -167,17 +150,13 @@ void _compositeLayerTreeToCanvas(ui.Canvas canvas, Layer layer, Offset parentAcc
167150 canvas.saveLayer (layer.describeClipBounds (), ui.Paint ()..colorFilter = layer.colorFilter);
168151 }
169152 } else if (layer is ImageFilterLayer ) {
170- // ImageFilterLayer extends OffsetLayer. Its offset is handled.
171153 if (layer.imageFilter != null ) {
172154 canvas.saveLayer (layer.describeClipBounds (), ui.Paint ()..imageFilter = layer.imageFilter);
173155 }
174156 }
175- // Add other specific layer types like ShaderMaskLayer if needed.
176157
177- // Draw content of this layer or recurse for its children.
158+ // Draw content or recurse
178159 if (layer is PictureLayer ) {
179- // PictureLayer is painted at Offset.zero in its coordinate system.
180- // Its position on canvas is determined by ancestor transforms/offsets.
181160 if (layer.picture != null ) {
182161 canvas.drawPicture (layer.picture! );
183162 } else {
@@ -186,104 +165,53 @@ void _compositeLayerTreeToCanvas(ui.Canvas canvas, Layer layer, Offset parentAcc
186165 );
187166 }
188167 } else if (layer is TextureLayer ) {
189- // TextureLayer has its own rect.
190- // We cannot directly draw a texture to a ui.Picture. Draw a placeholder.
191168 debugPrint (
192- '[_compositeLayerTreeToCanvas] Encountered TextureLayer (id: ${layer .textureId }). Cannot draw to ui.Picture. Drawing placeholder.' ,
169+ '[_compositeLayerTreeToCanvas] Encountered TextureLayer (id: ${layer .textureId }). Drawing placeholder.' ,
193170 );
194171 final placeholderPaint =
195172 Paint ()
196- ..color = const Color (0xFFFF00FF ). withValues (alpha : 0.5 ) // Bright pink placeholder
173+ ..color = const Color (0x80FF00FF ) // Bright pink with 50% alpha
197174 ..style = PaintingStyle .fill;
198- // TextureLayer.rect is in its parent's coordinate system.
199- // Since we've already applied parent transforms (like OffsetLayer),
200- // we draw rect directly.
201175 canvas.drawRect (layer.rect, placeholderPaint);
202176 } else if (layer is PlatformViewLayer ) {
203- debugPrint (
204- '[_compositeLayerTreeToCanvas] Encountered PlatformViewLayer. Cannot draw platform views to ui.Picture. Skipping.' ,
205- );
206- }
207- // ContainerLayer is the base for layers that have children.
208- // OffsetLayer, Clip*, Opacity*, etc., are all ContainerLayers.
209- else if (layer is ContainerLayer ) {
210- var child = layer.firstChild;
211- while (child != null ) {
212- // The child's position is relative to this ContainerLayer.
213- // If this ContainerLayer was an OffsetLayer, its offset was already applied to the canvas.
214- // So, the child starts from the new origin of the canvas.
215- // The `currentGlobalOffset` is the global position of *this* layer's origin.
216- _compositeLayerTreeToCanvas (canvas, child, currentGlobalOffset);
217- child = child.nextSibling;
218- }
219- } else if (layer is AnnotatedRegionLayer ) {
220- // AnnotatedRegionLayer is a ContainerLayer. It doesn't paint itself but its children.
221- // This case might be hit if it's not caught by `is ContainerLayer` first,
222- // but ContainerLayer should catch it. Handle children:
177+ debugPrint ('[_compositeLayerTreeToCanvas] Encountered PlatformViewLayer. Skipping.' );
178+ } else if (layer is ContainerLayer ) {
179+ // Base for layers with children
223180 var child = layer.firstChild;
224181 while (child != null ) {
225182 _compositeLayerTreeToCanvas (canvas, child, currentGlobalOffset);
226183 child = child.nextSibling;
227184 }
228- } else if (layer is LeaderLayer ) {
229- // LeaderLayer extends ContainerLayer
230- // Its offset is handled by OffsetLayer case if it's also an OffsetLayer
231- // (LeaderLayer does extend OffsetLayer via ContainerLayer -> OffsetLayer).
232- // Then recurse for children.
233- var child = layer.firstChild;
234- while (child != null ) {
235- _compositeLayerTreeToCanvas (canvas, child, currentGlobalOffset); // Pass current global offset
236- child = child.nextSibling;
237- }
238- } else if (layer is FollowerLayer ) {
239- // FollowerLayer extends ContainerLayer
240- // FollowerLayer calculates its own transform based on its linked LeaderLayer.
241- // This is complex to replicate perfectly without engine internals.
242- // A simple approach is to draw its children at its current position.
243- // Its `_lastTransform` is internal.
244- // For a synchronous capture, we assume its current painted state is what we get.
245- // It's also an OffsetLayer effectively, if its _lastTransform is applied.
246- // This is a known difficult case for simple layer tree traversal.
247- // We will effectively draw its children as if it's a regular ContainerLayer
248- // at its last painted offset/transform.
249- // The `canvas.save()` and `canvas.restore()` at the start/end of this function
250- // handle the local transform scope. If `FollowerLayer` has a complex transform
251- // not captured by `TransformLayer` type, it might not be perfect.
252- var child = layer.firstChild;
253- while (child != null ) {
254- _compositeLayerTreeToCanvas (canvas, child, currentGlobalOffset);
255- child = child.nextSibling;
256- }
257- } else {
185+ }
186+ // Specific container types like AnnotatedRegionLayer, LeaderLayer, FollowerLayer
187+ // are already handled by 'is ContainerLayer' if they don't have specific drawing logic
188+ // beyond their children. If they do, they'd need their own 'else if'.
189+ // The provided code had them as separate else-ifs, which is fine for clarity
190+ // or if they might get special handling later.
191+ // For this example, assuming ContainerLayer handles their children traversal.
192+ else if (layer is ! PictureLayer && layer is ! TextureLayer && layer is ! PlatformViewLayer ) {
193+ // Catch-all for unhandled non-ContainerLayer
258194 debugPrint ('[_compositeLayerTreeToCanvas] Unhandled layer type: ${layer .runtimeType }' );
259195 }
260196
261- // Restore for layers that used saveLayer (e.g. Opacity, ColorFilter, ImageFilter)
197+ // Restore for layers that used saveLayer
262198 if ((layer is OpacityLayer && layer.alpha != null && layer.alpha != 0 && layer.alpha != 255 ) ||
263199 (layer is ColorFilterLayer && layer.colorFilter != null ) ||
264200 (layer is ImageFilterLayer && layer.imageFilter != null )) {
265- canvas.restore ();
201+ canvas.restore (); // Matches the saveLayer
266202 }
267203
268- canvas.restore (); // Pop the save for this layer's transformations/clips
204+ canvas.restore (); // Matches the initial canvas.save()
269205}
270206
271- // Helper for debugging layer path
272207extension LayerDebugging on Layer {
273208 String get debugCreatorPathToLayer {
274209 final path = < String > [];
275210 Layer ? current = this ;
276211 while (current != null ) {
277212 var layerDesc = current.runtimeType.toString ();
278213 if (current.debugCreator != null ) {
279- // debugCreator is often a RenderObject.
280- // We might want a shorter description of it.
281214 var creatorDesc = current.debugCreator.runtimeType.toString ();
282- if (current.debugCreator is RenderObject ) {
283- // This can be verbose
284- //final ro = current.debugCreator as RenderObject;
285- // creatorDesc = ro.toStringShort();
286- }
287215 layerDesc += '(creator: $creatorDesc )' ;
288216 }
289217 path.add (layerDesc);
0 commit comments