aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.zig
diff options
context:
space:
mode:
authorkj_sh6042026-06-05 16:08:47 -0400
committerkj_sh6042026-06-05 16:08:47 -0400
commitffb0182d90d5607ccccff5210a2e711d6af35458 (patch)
tree3bb0cee0f2917a66d735b1e08797da0dd9666f45 /src/app.zig
parent8c3af04bf55c334500252faca56fae61429fb770 (diff)
refactor: zig re-implementation
Diffstat (limited to 'src/app.zig')
-rw-r--r--src/app.zig208
1 files changed, 208 insertions, 0 deletions
diff --git a/src/app.zig b/src/app.zig
new file mode 100644
index 0000000..51d55c4
--- /dev/null
+++ b/src/app.zig
@@ -0,0 +1,208 @@
+const std = @import("std");
+const math = @import("math.zig");
+const config = @import("config.zig");
+const c = @import("c.zig").c;
+
+const Vec2f = math.Vec2f;
+const Config = config.Config;
+
+pub const Camera = struct {
+ position: Vec2f = .{},
+ velocity: Vec2f = .{},
+ scale: f32 = 1.0,
+ delta_scale: f32 = 0.0,
+ scale_pivot: Vec2f = .{},
+};
+
+pub const Mouse = struct {
+ curr: Vec2f = .{},
+ prev: Vec2f = .{},
+ drag: bool = false,
+};
+
+pub const Flashlight = struct {
+ enabled: bool = false,
+ shadow: f32 = 0.0,
+ radius: f32 = 0.0,
+ delta_radius: f32 = 0.0,
+};
+
+pub const State = struct {
+ camera: Camera = .{},
+ mouse: Mouse = .{},
+ flashlight: Flashlight = .{},
+ dt: f32 = 0.0,
+ running: bool = true,
+ mirror: bool = false,
+};
+
+pub const App = struct {
+ config: Config = Config.default(),
+ state: State = .{},
+ config_path: ?[]const u8 = null,
+ allocator: std.mem.Allocator,
+
+ pub fn init(alloc: std.mem.Allocator, cfg_path: ?[]const u8) !App {
+ var app = App{
+ .allocator = alloc,
+ .config = Config.default(),
+ };
+
+ if (cfg_path) |p| {
+ app.config_path = try alloc.dupe(u8, p);
+ app.config.loadFromFile(p);
+ }
+
+ app.state.flashlight.radius = app.config.initial_radius;
+
+ return app;
+ }
+
+ pub fn deinit(self: *App) void {
+ if (self.config_path) |p| {
+ self.allocator.free(p);
+ }
+ }
+
+ pub fn cameraUpdate(self: *App, ws: Vec2f) void {
+ const cfg = &self.config;
+ const cam = &self.state.camera;
+ const m = &self.state.mouse;
+ const dt = self.state.dt;
+
+ if (@abs(cam.delta_scale) > cfg.scale_change_threshold) {
+ const half = ws.mul(0.5);
+ const sub = cam.scale_pivot.sub(half);
+ const p0 = sub.div(cam.scale);
+
+ cam.scale += cam.delta_scale * dt;
+ if (cam.scale < cfg.min_scale) cam.scale = cfg.min_scale;
+
+ const p1 = sub.div(cam.scale);
+ cam.position = cam.position.add(p0.sub(p1));
+ cam.delta_scale -= cam.delta_scale * dt * cfg.scale_friction;
+ }
+
+ if (!m.drag and cam.velocity.length() > cfg.velocity_threshold) {
+ cam.position = cam.position.add(cam.velocity.mul(dt));
+ cam.velocity = cam.velocity.sub(cam.velocity.mul(dt * cfg.drag_friction));
+ }
+ }
+
+ pub fn flashlightUpdate(self: *App) void {
+ const fl = &self.state.flashlight;
+ const dt = self.state.dt;
+ const cfg = &self.config;
+
+ fl.shadow = if (fl.enabled)
+ @min(fl.shadow + cfg.fade_speed * dt, cfg.max_shadow_opacity)
+ else
+ @max(fl.shadow - cfg.fade_speed * dt, 0.0);
+
+ if (@abs(fl.delta_radius) > cfg.radius_change_threshold) {
+ fl.radius = @max(0.0, fl.radius + fl.delta_radius * dt);
+ fl.delta_radius -= fl.delta_radius * cfg.radius_damping * dt;
+ }
+ }
+
+ fn worldPosition(camera: *Camera, pos: Vec2f) Vec2f {
+ return pos.div(camera.scale);
+ }
+
+ pub fn processEvents(self: *App, x11: anytype) void {
+ var ev: c.XEvent = undefined;
+ while (c.XPending(x11.display) != 0) {
+ _ = c.XNextEvent(x11.display, &ev);
+
+ switch (ev.type) {
+ c.KeyPress => self.handleKeypress(&ev.xkey),
+ c.MotionNotify => self.handleMousemove(&ev.xmotion, x11.refresh_rate),
+ c.ButtonPress => self.handleButtonpress(&ev.xbutton),
+ c.ButtonRelease => self.handleButtonrelease(&ev.xbutton),
+ c.ClientMessage => {
+ if (@as(c.Atom, @bitCast(ev.xclient.data.l[0])) == x11.wm_delete_window)
+ self.state.running = false;
+ },
+ else => {},
+ }
+ }
+ }
+
+ fn handleKeypress(self: *App, ke: *c.XKeyEvent) void {
+ const key = c.XLookupKeysym(ke, 0);
+
+ if (key == self.config.key_escape or key == c.XK_q)
+ self.state.running = false;
+
+ if (key == c.XK_r) {
+ if (self.config_path) |p| {
+ self.config.loadFromFile(p);
+ }
+ }
+
+ if (key == self.config.key_flashlight)
+ self.state.flashlight.enabled = !self.state.flashlight.enabled;
+
+ if (key == self.config.key_reset) {
+ self.state.camera = .{ .scale = 1.0 };
+ self.state.flashlight.shadow = 0.0;
+ self.state.flashlight.radius = self.config.initial_radius;
+ self.state.flashlight.delta_radius = 0.0;
+ }
+
+ if (key == self.config.key_mirror)
+ self.state.mirror = !self.state.mirror;
+
+ if (key == self.config.key_zoom_in) {
+ self.state.camera.delta_scale += self.config.scroll_speed;
+ self.state.camera.scale_pivot = self.state.mouse.curr;
+ }
+
+ if (key == self.config.key_zoom_out) {
+ self.state.camera.delta_scale -= self.config.scroll_speed;
+ self.state.camera.scale_pivot = self.state.mouse.curr;
+ }
+ }
+
+ fn handleMousemove(self: *App, motion: *c.XMotionEvent, rr: i32) void {
+ self.state.mouse.curr = .{ .x = @floatFromInt(motion.x), .y = @floatFromInt(motion.y) };
+
+ if (self.state.mouse.drag) {
+ const prev = worldPosition(&self.state.camera, self.state.mouse.prev);
+ const cur = worldPosition(&self.state.camera, self.state.mouse.curr);
+ self.state.camera.position = self.state.camera.position.add(prev.sub(cur));
+ self.state.camera.velocity = prev.sub(cur).mul(@floatFromInt(rr));
+ }
+
+ self.state.mouse.prev = self.state.mouse.curr;
+ }
+
+ fn handleButtonpress(self: *App, be: *c.XButtonEvent) void {
+ const ctrl_pressed = (be.state & self.config.modifier_flashlight) != 0;
+
+ if (be.button == self.config.button_drag) {
+ self.state.mouse.prev = self.state.mouse.curr;
+ self.state.mouse.drag = true;
+ self.state.camera.velocity = .{};
+ } else if (be.button == self.config.button_zoom_in) {
+ if (ctrl_pressed and self.state.flashlight.enabled) {
+ self.state.flashlight.delta_radius += self.config.initial_delta_radius;
+ } else {
+ self.state.camera.delta_scale += self.config.scroll_speed;
+ self.state.camera.scale_pivot = self.state.mouse.curr;
+ }
+ } else if (be.button == self.config.button_zoom_out) {
+ if (ctrl_pressed and self.state.flashlight.enabled) {
+ self.state.flashlight.delta_radius -= self.config.initial_delta_radius;
+ } else {
+ self.state.camera.delta_scale -= self.config.scroll_speed;
+ self.state.camera.scale_pivot = self.state.mouse.curr;
+ }
+ }
+ }
+
+ fn handleButtonrelease(self: *App, be: *c.XButtonEvent) void {
+ if (be.button == self.config.button_drag)
+ self.state.mouse.drag = false;
+ }
+}; \ No newline at end of file