Skip to content

Commit

Permalink
update examples and add safe-guards for capy init
Browse files Browse the repository at this point in the history
  • Loading branch information
zenith391 committed Oct 20, 2024
1 parent 971357b commit 83f875a
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 31 deletions.
4 changes: 3 additions & 1 deletion examples/graph.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pub const LineGraph = struct {
dataFn: capy.Atom(*const fn (x: f32) f32),

pub fn init(config: LineGraph.Config) LineGraph {
var line_graph = LineGraph.init_events(LineGraph{ .dataFn = capy.Atom(*const fn (x: f32) f32).of(config.dataFn) });
var line_graph = LineGraph.init_events(LineGraph{
.dataFn = capy.Atom(*const fn (x: f32) f32).of(config.dataFn),
});
line_graph.addDrawHandler(&LineGraph.draw) catch unreachable;
return line_graph;
}
Expand Down
25 changes: 17 additions & 8 deletions examples/notepad.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ pub fn main() !void {

var window = try capy.Window.init();

var monospace = capy.Atom(bool).of(false);
var text = capy.Atom([]const u8).of("");
// Whether the font is monospaced
var monospace = capy.Atom(bool).alloc(false);
defer monospace.deinit();

const text_length = try capy.Atom(usize).derived(.{&text}, &struct {
// The context of the text area
var text = capy.Atom([]const u8).alloc("");
defer text.deinit();

const text_length = try capy.Atom(usize).derived(.{text}, &struct {
fn callback(txt: []const u8) usize {
return txt.len;
}
Expand All @@ -21,14 +26,18 @@ pub fn main() !void {
defer label_text.deinit();

try window.set(capy.column(.{ .spacing = 0 }, .{
capy.expanded(capy.textArea(.{})
.bind("monospace", &monospace)
.bind("text", &text)),
capy.label(.{ .text = "TODO: cursor info" })
// By binding the atoms, we know the text area's text and the 'text' variable are always 'synchronized',
// or bound. Same goes for the 'monospace' property.
capy.expanded(
capy.textArea(.{})
.bind("monospace", monospace)
.bind("text", text),
),
capy.label(.{ .text = "" })
.bind("text", label_text),
// TODO: move into menu
capy.checkBox(.{ .label = "Monospaced" })
.bind("checked", &monospace),
.bind("checked", monospace),
}));

// TODO: hotkeys for actions (Ctrl+S, Ctrl+C) plus corresponding Cmd+C on macOS
Expand Down
49 changes: 32 additions & 17 deletions examples/osm-viewer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ pub const MapViewer = struct {
// .Handlers and .Atoms are implemented by `capy.internal.All(MapViewer)`
widget_data: MapViewer.WidgetData = .{},

// Our own component state.
// State specific to this component.
tileCache: std.AutoHashMap(TilePosition, Tile),
pendingRequests: std.AutoHashMap(TilePosition, capy.http.HttpResponse),
pendingSearchRequest: ?capy.http.HttpResponse = null,
centerX: f32 = 0,
centerY: f32 = 0,
centerX: Atom(f32) = Atom(f32).of(0),
centerY: Atom(f32) = Atom(f32).of(0),
targetCenterX: Atom(f32) = Atom(f32).of(0),
targetCenterY: Atom(f32) = Atom(f32).of(0),
camZoom: u5 = 4,
isDragging: bool = false,
lastMouseX: i32 = 0,
Expand Down Expand Up @@ -57,6 +59,8 @@ pub const MapViewer = struct {
.pendingRequests = std.AutoHashMap(TilePosition, capy.http.HttpResponse).init(config.allocator),
.allocator = Atom(std.mem.Allocator).of(config.allocator),
});
capy.internal.applyConfigStruct(&viewer, config);

viewer.centerTo(2.3200, 48.8589);
viewer.setName(config.name);
_ = viewer.addDrawHandler(&MapViewer.draw) catch unreachable;
Expand Down Expand Up @@ -93,8 +97,8 @@ pub const MapViewer = struct {
const x = n * ((lon + 180) / 360);
const lat_rad = deg2rad(lat);
const y = n * (1 - (std.math.log(f32, std.math.e, std.math.tan(lat_rad) + (1.0 / std.math.cos(lat_rad))) / std.math.pi)) / 2;
self.centerX = x * 256;
self.centerY = y * 256;
self.targetCenterX.set(x * 256);
self.targetCenterX.set(y * 256);
}

pub fn search(self: *MapViewer, query: []const u8) !void {
Expand Down Expand Up @@ -165,8 +169,8 @@ pub const MapViewer = struct {
const height = self.getHeight();
ctx.clear(0, 0, width, height);

const camX = @as(i32, @intFromFloat(self.centerX)) - @as(i32, @intCast(width / 2));
const camY = @as(i32, @intFromFloat(self.centerY)) - @as(i32, @intCast(height / 2));
const camX = @as(i32, @intFromFloat(self.centerX.get())) - @as(i32, @intCast(width / 2));
const camY = @as(i32, @intFromFloat(self.centerY.get())) - @as(i32, @intCast(height / 2));
var x: i32 = @divFloor(camX, 256);
while (x < @divFloor(camX + @as(i32, @intCast(width)) + 255, 256)) : (x += 1) {
var y: i32 = @divFloor(camY, 256);
Expand Down Expand Up @@ -200,30 +204,29 @@ pub const MapViewer = struct {
fn mouseMoved(self: *MapViewer, x: i32, y: i32) !void {
if (self.isDragging) {
// TODO: smooth move
self.centerX -= @as(f32, @floatFromInt(x - self.lastMouseX));
self.centerY -= @as(f32, @floatFromInt(y - self.lastMouseY));
self.targetCenterX.set(self.targetCenterX.get() - @as(f32, @floatFromInt(x - self.lastMouseX)));
self.targetCenterY.set(self.targetCenterY.get() - @as(f32, @floatFromInt(y - self.lastMouseY)));

self.lastMouseX = x;
self.lastMouseY = y;
self.requestDraw() catch unreachable;
}
}

fn mouseScroll(self: *MapViewer, dx: f32, dy: f32) !void {
_ = dx;
if (dy > 0 and self.camZoom > 0) {
self.camZoom -|= 2 * @as(u5, @intFromFloat(dy));
self.centerX /= 4 * dy;
self.centerY /= 4 * dy;
self.targetCenterX.set(self.targetCenterX.get() / (4 * dy));
self.targetCenterY.set(self.targetCenterY.get() / (4 * dy));
} else if (dy < 0 and self.camZoom < 18) {
self.camZoom +|= 2 * @as(u5, @intFromFloat(-dy));
self.centerX *= 4 * -dy;
self.centerY *= 4 * -dy;
self.targetCenterX.set(self.targetCenterX.get() * (4 * -dy));
self.targetCenterY.set(self.targetCenterY.get() * (4 * -dy));
}
if (self.camZoom > 18) {
self.camZoom = 18;
}
std.log.info("zoom: {d}, pos: {d}, {d}", .{ self.camZoom, self.centerX, self.centerY });
std.log.info("zoom: {d}, pos: {d}, {d}", .{ self.camZoom, self.centerX.get(), self.centerY.get() });
self.requestDraw() catch unreachable;
}

Expand All @@ -234,6 +237,16 @@ pub const MapViewer = struct {
self.peer = try capy.backend.Canvas.create();
try self.setupEvents();
}
try self.centerX.implicitlyAnimate(&self.targetCenterX, capy.Easings.InOut, 50);
try self.centerY.implicitlyAnimate(&self.targetCenterY, capy.Easings.InOut, 50);

_ = try self.centerX.addChangeListener(.{ .function = onCenterChange, .userdata = self });
_ = try self.centerY.addChangeListener(.{ .function = onCenterChange, .userdata = self });
}

fn onCenterChange(_: f32, userdata: ?*anyopaque) void {
const self: *MapViewer = @ptrCast(@alignCast(userdata));
self.requestDraw() catch {};
}

pub fn getPreferredSize(self: *MapViewer, available: capy.Size) capy.Size {
Expand All @@ -248,7 +261,7 @@ pub fn mapViewer(config: MapViewer.Config) *MapViewer {
}

pub fn main() !void {
try capy.backend.init();
try capy.init();

var window = try capy.Window.init();
try window.set(
Expand All @@ -257,7 +270,9 @@ pub fn main() !void {
capy.expanded(capy.textField(.{ .name = "location-input" })),
capy.button(.{ .label = "Go!", .onclick = onGo }),
}),
capy.expanded(mapViewer(.{ .name = "map-viewer" })),
capy.expanded(mapViewer(.{
.name = "map-viewer",
})),
}),
);
window.setTitle("OpenStreetMap Viewer");
Expand Down
2 changes: 1 addition & 1 deletion examples/test-backend.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const capy = @import("capy");
pub usingnamespace capy.cross_platform;

pub fn main() !void {
try capy.backend.init();
try capy.init();

var window = try capy.Window.init();
try window.set(
Expand Down
4 changes: 3 additions & 1 deletion src/data.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ pub fn Atom(comptime T: type) type {
const animate_fn = struct {
fn a(new_value: T, uncast: ?*anyopaque) void {
const ptr: *AnimationParameters = @ptrCast(@alignCast(uncast));
std.log.info("animate", .{});
ptr.self_ptr.animate(ptr.easing, new_value, ptr.duration);
}
}.a;
Expand Down Expand Up @@ -620,8 +619,11 @@ pub fn Atom(comptime T: type) type {

a.set(5);
// now c is equal to 10
try std.testing.expectEqual(10, c.get());

b.set("no");
// and now c is equal to 7
try std.testing.expectEqual(7, c.get());
}

fn callHandlers(self: *Self) void {
Expand Down
30 changes: 27 additions & 3 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ else
pub const EventLoopStep = @import("backends/shared.zig").EventLoopStep;
pub const MouseButton = @import("backends/shared.zig").MouseButton;

// This is a private global variable used for safety.
var isCapyInitialized: bool = false;
pub fn init() !void {
try backend.init();
if (ENABLE_DEV_TOOLS) {
Expand All @@ -72,15 +74,17 @@ pub fn init() !void {
}.a) catch unreachable;

var timerListener = eventStep.listen(.{ .callback = @import("timer.zig").handleTimersTick }) catch unreachable;
// The listener is enabled only if there is at least 1 atom currently being animated
// The listener is enabled only if there is at least 1 timer is running
timerListener.enabled.dependOn(.{&@import("timer.zig").runningTimers.length}, &struct {
fn a(num: usize) bool {
return num >= 1;
}
}.a) catch unreachable;
isCapyInitialized = true;
}

pub fn deinit() void {
isCapyInitialized = false;
Monitors.deinit();

@import("data.zig")._animatedAtoms.deinit();
Expand All @@ -103,6 +107,7 @@ pub fn wakeEventLoop() void {
/// to request an asynchronous step to the backend in order to animate
/// data wrappers.
pub fn stepEventLoop(stepType: EventLoopStep) bool {
std.debug.assert(isCapyInitialized);
eventStep.callListeners();

// const timer = @import("timer.zig");
Expand Down Expand Up @@ -138,12 +143,31 @@ fn animateAtoms(_: ?*anyopaque) void {
data._animatedAtomsMutex.lock();
defer data._animatedAtomsMutex.unlock();

// List of atoms that are no longer animated and that need to be removed from the list
var toRemove = std.BoundedArray(usize, 64).init(0) catch unreachable;
for (data._animatedAtoms.items, 0..) |item, i| {
if (item.fnPtr(item.userdata) == false) { // animation ended
_ = data._animatedAtoms.swapRemove(i);
data._animatedAtomsLength.set(data._animatedAtoms.items.len);
toRemove.append(i) catch |err| switch (err) {
error.Overflow => {}, // It can be removed on the next call to animateAtoms()
};
}
}

// The index list is ordered in increasing index order
const indexList = toRemove.constSlice();
// So we iterate it backward in order to avoid indices being invalidated
if (indexList.len > 0) {
var i: usize = indexList.len - 1;
while (i >= 0) {
_ = data._animatedAtoms.swapRemove(indexList[i]);
if (i == 0) {
break;
} else {
i -= 1;
}
}
}
data._animatedAtomsLength.set(data._animatedAtoms.items.len);
}

pub fn runEventLoop() void {
Expand Down

0 comments on commit 83f875a

Please sign in to comment.