diff options
| author | kj_sh604 | 2026-06-05 16:08:47 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-06-05 16:08:47 -0400 |
| commit | ffb0182d90d5607ccccff5210a2e711d6af35458 (patch) | |
| tree | 3bb0cee0f2917a66d735b1e08797da0dd9666f45 /src/app.zig | |
| parent | 8c3af04bf55c334500252faca56fae61429fb770 (diff) | |
refactor: zig re-implementation
Diffstat (limited to 'src/app.zig')
| -rw-r--r-- | src/app.zig | 208 |
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 |
