Skip to content

Commit 759e27b

Browse files
committed
update
1 parent 25a0b18 commit 759e27b

File tree

2 files changed

+91
-162
lines changed

2 files changed

+91
-162
lines changed

lib/src/picture.dart

Lines changed: 89 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import 'dart:math' as math;
22
import 'dart:ui' as ui;
33

4+
import 'package:flutter/foundation.dart'; // For kDebugMode
45
import 'package:flutter/rendering.dart';
56
import '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.
2728
Picture? 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.
102110
void _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
272207
extension 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);

lib/src/route_controller.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class RouteController {
154154

155155
void _maybeCapture() {
156156
if (shouldCapture && _captureContext != null && _captureContext!.mounted) {
157-
_prevSnapshotPicture = captureWidgetPictureSync(_captureContext!)!;
157+
_prevSnapshotPicture = captureWidgetPictureSync(_captureContext!);
158158
}
159159
}
160160

@@ -226,6 +226,7 @@ class RouteController {
226226
_controller.reset();
227227
});
228228
}
229+
print(1);
229230
}
230231

231232
//

0 commit comments

Comments
 (0)