diff --git a/src/browser/tests/animation/animation.html b/src/browser/tests/animation/animation.html
index 27e562a0..97bfe077 100644
--- a/src/browser/tests/animation/animation.html
+++ b/src/browser/tests/animation/animation.html
@@ -3,13 +3,67 @@
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/animation/Animation.zig b/src/browser/webapi/animation/Animation.zig
index 2b6855ac..a9443d90 100644
--- a/src/browser/webapi/animation/Animation.zig
+++ b/src/browser/webapi/animation/Animation.zig
@@ -16,40 +16,113 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+const std = @import("std");
+const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
+const Allocator = std.mem.Allocator;
+
const Animation = @This();
+const PlayState = enum {
+ idle,
+ running,
+ paused,
+ finished,
+};
+
+_page: *Page,
+_arena: Allocator,
+
_effect: ?js.Object.Global = null,
_timeline: ?js.Object.Global = null,
_ready_resolver: ?js.PromiseResolver.Global = null,
_finished_resolver: ?js.PromiseResolver.Global = null,
+_startTime: ?f64 = null,
+_onFinish: ?js.Function.Temp = null,
+_playState: PlayState = .idle,
+// Fake the animation by passing the states:
+// .idle => .running once play() is called.
+// .running => .finished after 10ms when update() is callback.
+//
+// TODO add support for effect and timeline
pub fn init(page: *Page) !*Animation {
- return page._factory.create(Animation{});
+ const arena = try page.getArena(.{ .debug = "Animation" });
+ errdefer page.releaseArena(arena);
+
+ const self = try page._factory.create(Animation{
+ ._page = page,
+ ._arena = arena,
+ });
+
+ return self;
}
-pub fn play(_: *Animation) void {}
-pub fn pause(_: *Animation) void {}
-pub fn cancel(_: *Animation) void {}
-pub fn finish(_: *Animation) void {}
-pub fn reverse(_: *Animation) void {}
+pub fn play(self: *Animation, page: *Page) !void {
+ if (self._playState == .running) {
+ return;
+ }
+
+ // transition to running.
+ self._playState = .running;
+
+ // Schedule the transition from .idle => .running in 10ms.
+ page.js.strongRef(self);
+ try page.js.scheduler.add(
+ self,
+ Animation.update,
+ 10,
+ .{ .name = "animation.update" },
+ );
+}
+
+pub fn pause(self: *Animation) void {
+ self._playState = .paused;
+}
+
+pub fn cancel(_: *Animation) void {
+ log.warn(.not_implemented, "Animation.cancel", .{});
+}
+
+pub fn finish(self: *Animation, page: *Page) void {
+ if (self._playState == .finished) {
+ return;
+ }
+
+ self._playState = .finished;
+
+ // resolve finished
+ if (self._finished_resolver) |resolver| {
+ page.js.local.?.toLocal(resolver).resolve("Animation.getFinished", self);
+ }
+ // call onfinish
+ if (self._onFinish) |func| {
+ page.js.local.?.toLocal(func).call(void, .{}) catch |err| {
+ log.warn(.js, "Animation._onFinish", .{ .err = err });
+ };
+ }
+}
+
+pub fn reverse(_: *Animation) void {
+ log.warn(.not_implemented, "Animation.reverse", .{});
+}
pub fn getFinished(self: *Animation, page: *Page) !js.Promise {
if (self._finished_resolver == null) {
const resolver = page.js.local.?.createPromiseResolver();
- resolver.resolve("Animation.getFinished", self);
self._finished_resolver = try resolver.persist();
return resolver.promise();
}
return page.js.toLocal(self._finished_resolver).?.promise();
}
+// The ready promise is immediately resolved.
pub fn getReady(self: *Animation, page: *Page) !js.Promise {
- // never resolved, because we're always "finished"
if (self._ready_resolver == null) {
const resolver = page.js.local.?.createPromiseResolver();
+ resolver.resolve("Animation.getReady", self);
self._ready_resolver = try resolver.persist();
return resolver.promise();
}
@@ -72,6 +145,63 @@ pub fn setTimeline(self: *Animation, timeline: ?js.Object.Global) !void {
self._timeline = timeline;
}
+pub fn getStartTime(self: *const Animation) ?f64 {
+ return self._startTime;
+}
+
+pub fn setStartTime(self: *Animation, value: ?f64, page: *Page) !void {
+ self._startTime = value;
+ return self.play(page);
+}
+
+pub fn getOnFinish(self: *const Animation) ?js.Function.Temp {
+ return self._onFinish;
+}
+
+pub fn deinit(self: *Animation, _: bool) void {
+ self._page.releaseArena(self._arena);
+}
+
+// callback function transitionning from a state to another
+fn update(ctx: *anyopaque) anyerror!?u32 {
+ const self: *Animation = @ptrCast(@alignCast(ctx));
+
+ switch (self._playState) {
+ .running => {
+ // transition to finished.
+ self._playState = .finished;
+
+ var ls: js.Local.Scope = undefined;
+ self._page.js.localScope(&ls);
+ defer ls.deinit();
+
+ // resolve finished
+ if (self._finished_resolver) |resolver| {
+ ls.toLocal(resolver).resolve("Animation.getFinished", self);
+ }
+ // call onfinish
+ if (self._onFinish) |func| {
+ ls.toLocal(func).call(void, .{}) catch |err| {
+ log.warn(.js, "Animation._onFinish", .{ .err = err });
+ };
+ }
+ },
+ .idle, .paused, .finished => {},
+ }
+
+ // No future change scheduled, set the object weak for garbage collection.
+ self._page.js.weakRef(self);
+ return null;
+}
+
+pub fn setOnFinish(self: *Animation, cb: ?js.Function.Temp) !void {
+ self._onFinish = cb;
+}
+
+pub fn playState(self: *const Animation) []const u8 {
+ return @tagName(self._playState);
+}
+
pub const JsApi = struct {
pub const bridge = js.Bridge(Animation);
@@ -79,6 +209,8 @@ pub const JsApi = struct {
pub const name = "Animation";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
+ pub const weak = true;
+ pub const finalizer = bridge.finalizer(Animation.deinit);
};
pub const play = bridge.function(Animation.play, .{});
@@ -86,12 +218,14 @@ pub const JsApi = struct {
pub const cancel = bridge.function(Animation.cancel, .{});
pub const finish = bridge.function(Animation.finish, .{});
pub const reverse = bridge.function(Animation.reverse, .{});
- pub const playState = bridge.property("finished", .{ .template = false });
+ pub const playState = bridge.accessor(Animation.playState, null, .{});
pub const pending = bridge.property(false, .{ .template = false });
pub const finished = bridge.accessor(Animation.getFinished, null, .{});
pub const ready = bridge.accessor(Animation.getReady, null, .{});
pub const effect = bridge.accessor(Animation.getEffect, Animation.setEffect, .{});
pub const timeline = bridge.accessor(Animation.getTimeline, Animation.setTimeline, .{});
+ pub const startTime = bridge.accessor(Animation.getStartTime, Animation.setStartTime, .{});
+ pub const onfinish = bridge.accessor(Animation.getOnFinish, Animation.setOnFinish, .{});
};
const testing = @import("../../../testing.zig");