Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
1 change: 1 addition & 0 deletions lib/ui/dart_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ typedef CanvasPath Path;
V(Canvas, getSaveCount, 1) \
V(Canvas, getTransform, 2) \
V(Canvas, restore, 1) \
V(Canvas, restoreToCount, 2) \
V(Canvas, rotate, 2) \
V(Canvas, save, 1) \
V(Canvas, saveLayer, 7) \
Expand Down
10 changes: 10 additions & 0 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4691,6 +4691,16 @@ class Canvas extends NativeFieldWrapperClass1 {
@FfiNative<Void Function(Pointer<Void>)>('Canvas::restore', isLeaf: true)
external void restore();

/// Pops the save stack until the count layer, if there is anything to pop.
/// Otherwise, does nothing.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment about how this relates to getSaveCount(). Something along the lines of "the count layer that was previously retrieved with [getSaveCount]" or "After this method completes, the [count] should match the return value of [getSaveCount]".

Also, describe what happens if count > getSaveCount(). I believe it just gets ignored, but we should document that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

/// Restores the save stack to a previous level as might be obtained from [getSaveCount].
/// If [count] is less than 1, the stack is restored to its initial state. If [count] is
/// greater than the current [getSaveCount] then nothing happens.

I hinted at what happens with a negative count and also implied that the initial save count is 1 (is that true?) - perhaps we should add a test that verifies these behaviors. I believe that one of the web implementations may not be protected against restoring too far which means that behavior will end up blowing up compared to the other implementations (check that restore protects against an empty stack and also that restoreToCount of values less than 1 all behave the same on all platforms).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get that, I have added test to cover those corner cases.

///
/// Use [save] and [saveLayer] to push state onto the stack.
///
/// If the state was pushed with [saveLayer], then this call will also
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - this paragraph is worded as if there is only one stack level being restored. Please reword it:

/// If any of the state stack levels restored by this call were pushed with
/// [saveLayer], then this call will also cause those layers to be composited
/// into their previous layers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get, it's done.

/// cause the new layer to be composited into the previous layer.
@FfiNative<Void Function(Pointer<Void>, Int32)>('Canvas::restoreToCount', isLeaf: true)
external void restoreToCount(int count);

/// Returns the number of items on the save stack, including the
/// initial state. This means it returns 1 for a clean canvas, and
/// that each call to [save] and [saveLayer] increments it, and that
Expand Down
6 changes: 6 additions & 0 deletions lib/ui/painting/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ int Canvas::getSaveCount() {
}
}

void Canvas::restoreToCount(int count) {
if (display_list_recorder_) {
builder()->restoreToCount(count);
}
}

void Canvas::translate(double dx, double dy) {
if (display_list_recorder_) {
builder()->translate(dx, dy);
Expand Down
1 change: 1 addition & 0 deletions lib/ui/painting/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Canvas : public RefCountedDartWrappable<Canvas>, DisplayListOpFlags {

void restore();
int getSaveCount();
void restoreToCount(int count);

void translate(double dx, double dy);
void scale(double sx, double sy);
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ abstract class Canvas {
void saveLayer(Rect? bounds, Paint paint);
void restore();
int getSaveCount();
void restoreToCount(int count);
void translate(double dx, double dy);
void scale(double sx, [double? sy]);
void rotate(double radians);
Expand Down
5 changes: 5 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ class CanvasKitCanvas implements ui.Canvas {
_canvas.restore();
}

@override
void restoreToCount(int count) {
_canvas.restoreToCount(count);
}

@override
int getSaveCount() {
return _canvas.saveCount!;
Expand Down
5 changes: 5 additions & 0 deletions lib/web_ui/lib/src/engine/html/canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class SurfaceCanvas implements ui.Canvas {
_canvas.restore();
}

@override
void restoreToCount(int count) {
_canvas.restoreToCount(count);
}

@override
int getSaveCount() => _canvas.saveCount;

Expand Down
6 changes: 6 additions & 0 deletions lib/web_ui/lib/src/engine/html/recording_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ class RecordingCanvas {
_saveCount--;
}

void restoreToCount(int count) {
while (count < _saveCount) {
restore();
}
}

void translate(double dx, double dy) {
assert(!_recordingEnded);
_paintBounds.translate(dx, dy);
Expand Down
15 changes: 15 additions & 0 deletions lib/web_ui/test/engine/canvas_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -276,5 +276,20 @@ void runCanvasTests({required bool deviceClipRoundsOut}) {
expect(canvas.getLocalClipBounds(), initialLocalBounds);
expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
});

test('RestoreToCount can work', () async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder);
canvas.save();
canvas.save();
canvas.save();
canvas.save();
canvas.save();
expect(canvas.getSaveCount(), 6);
canvas.restoreToCount(2);
expect(canvas.getSaveCount(), 2);
canvas.restore();
expect(canvas.getSaveCount(), 1);
});
});
}
15 changes: 15 additions & 0 deletions testing/dart/canvas_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,21 @@ void main() {
expect(canvas.getLocalClipBounds(), initialLocalBounds);
expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
});

test('RestoreToCount can work', () async {
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.save();
canvas.save();
canvas.save();
canvas.save();
canvas.save();
expect(canvas.getSaveCount(), equals(6));
canvas.restoreToCount(2);
expect(canvas.getSaveCount(), equals(2));
canvas.restore();
expect(canvas.getSaveCount(), equals(1));
});
}

Matcher listEquals(ByteData expected) => (dynamic v) {
Expand Down