Skip to content

Commit

Permalink
feat: add Window.on_frame
Browse files Browse the repository at this point in the history
This event source is called whenever a frame can be drawn (so,
basically, on the monitor's vsync)
  • Loading branch information
zenith391 committed Jul 4, 2024
1 parent a3e362a commit 34b28d4
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 20 deletions.
41 changes: 28 additions & 13 deletions examples/graph.zig
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,32 @@ pub fn main() !void {
window.setPreferredSize(800, 600);
window.show();

var animStart: i64 = 0;
while (capy.stepEventLoop(.Asynchronous)) {
var dt = std.time.milliTimestamp() - animStart;
if (dt > 1500) {
animStart = std.time.milliTimestamp();
continue;
} else if (dt > 1000) {
dt = 1000;
}
const t = @as(f32, @floatFromInt(dt)) / 1000;
rectangleX.set(graph.dataFn.get()(t * 10.0));
std.time.sleep(30);
}
const ListenerData = struct {
rectangleX: *capy.Atom(f32),
animStart: i64,
};
var listener_data = ListenerData{
.rectangleX = &rectangleX,
.animStart = std.time.milliTimestamp(),
};

// Listeners are always deinitialized when the event source is destroyed.
// In this case, this means it will be deinitialized when the window is destroyed.
// Therefore, as long as we don't want to remove it mid-program, we don't have to deinit this listener manually
_ = try window.on_frame.listen(.{
.callback = struct {
fn callback(userdata: ?*anyopaque) void {
const data: *ListenerData = @ptrCast(@alignCast(userdata.?));
var dt = std.time.milliTimestamp() - data.animStart;
if (dt > 1500)
data.animStart = std.time.milliTimestamp();
if (dt > 1000)
dt = 1000;
const t = @as(f32, @floatFromInt(dt)) / 1000;
data.rectangleX.set(graph.dataFn.get()(t * 10.0));
}
}.callback,
.userdata = &listener_data,
});
capy.runEventLoop();
}
25 changes: 25 additions & 0 deletions src/backends/gtk/backend.zig
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,31 @@ pub const Window = struct {
_ = activeWindows.fetchAdd(1, .release);
}

pub fn registerTickCallback(self: *Window) void {
_ = c.gtk_widget_add_tick_callback(
self.peer,
&tickCallback,
null,
null,
);
}

/// Callback called by GTK on each frame (tied to the monitor's sync rate)
fn tickCallback(
widget: ?*c.GtkWidget,
frame_clock: ?*c.GdkFrameClock,
user_data: ?*anyopaque,
) callconv(.C) c.gboolean {
_ = frame_clock;
_ = user_data;
const data = getEventUserData(widget.?);
if (data.user.propertyChangeHandler) |handler| {
const id: u64 = 0;
handler("tick_id", &id, data.userdata);
}
return @intFromBool(c.G_SOURCE_CONTINUE);
}

pub fn close(self: *Window) void {
c.gtk_window_close(@as(*c.GtkWindow, @ptrCast(self.peer)));
}
Expand Down
4 changes: 3 additions & 1 deletion src/listener.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const lasting_allocator = @import("internal.zig").lasting_allocator;
const Atom = @import("data.zig").Atom;

pub const EventSource = struct {
// TODO: use ListAtom(*Listener)
listeners: std.ArrayList(*Listener),

pub fn init(allocator: std.mem.Allocator) EventSource {
Expand Down Expand Up @@ -33,6 +34,7 @@ pub const EventSource = struct {
}
}

/// Returns true if there is atleast one listener listening to this event source
pub fn hasEnabledListeners(self: *const EventSource) bool {
var result = false;

Expand Down Expand Up @@ -60,7 +62,7 @@ pub const Listener = struct {
listened: *EventSource,
callback: *const fn (userdata: ?*anyopaque) void,
userdata: ?*anyopaque = null,
/// The listener is called only if enabled is set to true.
/// The listener is called only when it is enabled.
enabled: Atom(bool) = Atom(bool).of(true),

pub const Config = struct {
Expand Down
10 changes: 5 additions & 5 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ pub fn deinit() void {
}
}

// /// Posts an empty event to finish the current step started in zgt.stepEventLoop
/// Posts an empty event to finish the current step started in capy.stepEventLoop
pub fn wakeEventLoop() void {
backend.postEmptyEvent();
}

// /// Returns false if the last window has been closed.
// /// Even if the wanted step type is Blocking, zgt has the right
// /// to request an asynchronous step to the backend in order to animate
// /// data wrappers.
/// Returns false if the last window has been closed.
/// Even if the wanted step type is Blocking, capy has the right
/// to request an asynchronous step to the backend in order to animate
/// data wrappers.
pub fn stepEventLoop(stepType: EventLoopStep) bool {
eventStep.callListeners();

Expand Down
23 changes: 22 additions & 1 deletion src/window.zig
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const std = @import("std");
const backend = @import("backend.zig");
const internal = @import("internal.zig");
const listener = @import("listener.zig");
const Widget = @import("widget.zig").Widget;
// const ImageData = @import("image.zig").ImageData;
const MenuBar = @import("components/Menu.zig").MenuBar;
const Size = @import("data.zig").Size;
const Atom = @import("data.zig").Atom;
const EventSource = listener.EventSource;

const Display = struct { resolution: Size, dpi: u32 };

Expand All @@ -26,6 +28,9 @@ pub const Window = struct {
/// The maximum refresh rate of the screen the window is atleast partially in.
/// For instance, if a window is on both screen A (60Hz) and B (144Hz) then the value of screenRefreshRate will be 144Hz.
screenRefreshRate: Atom(f32) = Atom(f32).of(60),
/// Event source called whenever a frame would be drawn.
/// This can be used for synchronizing animations to the window's monitor's sync rate.
on_frame: EventSource,

pub const Feature = enum {
Title,
Expand All @@ -35,10 +40,17 @@ pub const Window = struct {

pub fn init() !Window {
const peer = try backend.Window.create();
var window = Window{ .peer = peer };
var window = Window{
.peer = peer,
.on_frame = EventSource.init(internal.lasting_allocator),
};
window.setSourceDpi(96);
window.setPreferredSize(640, 480);
try window.peer.setCallback(.Resize, sizeChanged);
try window.peer.setCallback(.PropertyChange, propertyChanged);

// TODO: call only when there is at least one listener on EventSource
window.peer.registerTickCallback();

return window;
}
Expand Down Expand Up @@ -107,6 +119,14 @@ pub const Window = struct {
self.size.set(.{ .width = width, .height = height });
}

pub fn propertyChanged(name: []const u8, value: *const anyopaque, data: usize) void {
const self: *Window = @ptrFromInt(data);
if (std.mem.eql(u8, name, "tick_id")) {
_ = value;
self.on_frame.callListeners();
}
}

// TODO: minimumSize and maximumSize

pub fn hasFeature(self: *Window, feature: Window.Feature) bool {
Expand Down Expand Up @@ -137,6 +157,7 @@ pub const Window = struct {
if (self._child) |child| {
child.unref();
}
self.on_frame.deinitAllListeners();
self.peer.deinit();
}
};

0 comments on commit 34b28d4

Please sign in to comment.