From 47c4f2045c9a81867c8588570b16aec891ff517f Mon Sep 17 00:00:00 2001 From: asrael Date: Mon, 6 Oct 2025 18:14:07 -0500 Subject: [PATCH] add proper fnl modules to demo --- demo/cube3d.fnl | 151 ------------------------------ demo/main.fnl | 2 +- demo/mod/cube3d.fnl | 213 ++++++++++++++++++++++++++++++++++++++++++ demo/mod/debug_ui.fnl | 32 +++++++ demo/ui_demo.fnl | 20 ---- src/lua/pxl8.lua | 58 +++++++++--- src/pxl8.c | 63 +++++-------- src/pxl8_io.c | 49 +++++++++- src/pxl8_io.h | 10 +- src/pxl8_script.c | 24 ++++- src/pxl8_types.h | 6 +- src/pxl8_ui.c | 94 ++++++++++++++++++- src/pxl8_ui.h | 20 ++++ src/pxl8_vfx.h | 8 ++ 14 files changed, 510 insertions(+), 240 deletions(-) delete mode 100644 demo/cube3d.fnl create mode 100644 demo/mod/cube3d.fnl create mode 100644 demo/mod/debug_ui.fnl delete mode 100644 demo/ui_demo.fnl diff --git a/demo/cube3d.fnl b/demo/cube3d.fnl deleted file mode 100644 index e9d5650..0000000 --- a/demo/cube3d.fnl +++ /dev/null @@ -1,151 +0,0 @@ -(local pxl8 (require :pxl8)) - -(var angle-x 0) -(var angle-y 0) -(var angle-z 0) -(var auto-rotate true) -(var orthographic true) -(var wireframe true) -(var time 0) -(var zoom 5.0) -(var texture-id nil) -(var use-texture false) -(var affine false) -(var texture-initialized false) - -(fn init-texture [] - (when (not texture-initialized) - (pxl8.load_palette "sprites/pxl8_logo.ase") - (set texture-id (pxl8.load_sprite "sprites/pxl8_logo.ase")) - (pxl8.upload_atlas) - (set texture-initialized true))) - -(fn make-cube-vertices [] - [[-1 -1 -1] [1 -1 -1] [1 1 -1] [-1 1 -1] - [-1 -1 1] [1 -1 1] [1 1 1] [-1 1 1]]) - -(fn make-cube-faces [] - [[0 1 2] [0 2 3] - [1 5 6] [1 6 2] - [5 4 7] [5 7 6] - [4 0 3] [4 3 7] - [3 2 6] [3 6 7] - [4 5 1] [4 1 0]]) - -(fn make-cube-faces-with-uvs [] - [{:tri [0 1 2] :uvs [[0 0] [1 0] [1 1]]} - {:tri [0 2 3] :uvs [[0 0] [1 1] [0 1]]} - {:tri [1 5 6] :uvs [[0 0] [1 0] [1 1]]} - {:tri [1 6 2] :uvs [[0 0] [1 1] [0 1]]} - {:tri [5 4 7] :uvs [[0 0] [1 0] [1 1]]} - {:tri [5 7 6] :uvs [[0 0] [1 1] [0 1]]} - {:tri [4 0 3] :uvs [[0 0] [1 0] [1 1]]} - {:tri [4 3 7] :uvs [[0 0] [1 1] [0 1]]} - {:tri [3 2 6] :uvs [[0 0] [1 0] [1 1]]} - {:tri [3 6 7] :uvs [[0 0] [1 1] [0 1]]} - {:tri [4 5 1] :uvs [[0 0] [1 0] [1 1]]} - {:tri [4 1 0] :uvs [[0 0] [1 1] [0 1]]}]) - -(fn get-face-color [face-idx] - (let [colors [12 22 30 16 28 20]] - (. colors (+ 1 (% face-idx 6))))) - -(fn cube-update [dt] - (set time (+ time dt)) - - (when (pxl8.key_down "w") - (set angle-x (- angle-x (* dt 2.0)))) - (when (pxl8.key_down "s") - (set angle-x (+ angle-x (* dt 2.0)))) - (when (pxl8.key_down "a") - (set angle-y (- angle-y (* dt 2.0)))) - (when (pxl8.key_down "d") - (set angle-y (+ angle-y (* dt 2.0)))) - (when (pxl8.key_down "q") - (set angle-z (- angle-z (* dt 2.0)))) - (when (pxl8.key_down "e") - (set angle-z (+ angle-z (* dt 2.0)))) - - (when (pxl8.key_pressed " ") - (set wireframe (not wireframe))) - (when (pxl8.key_pressed "f") - (set affine (not affine))) - (when (pxl8.key_pressed "p") - (set orthographic (not orthographic))) - (when (pxl8.key_pressed "r") - (set auto-rotate (not auto-rotate))) - (when (pxl8.key_pressed "t") - (set use-texture (not use-texture)) - (when use-texture - (init-texture))) - - (when (pxl8.key_down "=") - (set zoom (- zoom (* dt 2.0)))) - (when (pxl8.key_down "-") - (set zoom (+ zoom (* dt 2.0)))) - (set zoom (math.max 1.0 (math.min zoom 20.0))) - - (when auto-rotate - (set angle-x (+ angle-x (* dt 0.7))) - (set angle-y (+ angle-y (* dt 0.5))) - (set angle-z (+ angle-z (* dt 0.3))))) - -(fn cube-frame [] - (pxl8.clr 0) - - (pxl8.clear_zbuffer) - (pxl8.set_affine_textures affine) - (pxl8.set_backface_culling true) - (pxl8.set_wireframe wireframe) - - (if orthographic - (let [size (* 2.5 (/ zoom 5.0)) - aspect (/ (pxl8.get_width) (pxl8.get_height)) - w (* size aspect) - h size] - (pxl8.set_projection (pxl8.mat4_ortho (- w) w (- h) h 1.0 50.0))) - (let [aspect (/ (pxl8.get_width) (pxl8.get_height)) - fov (/ 3.14159 (+ 2.0 (/ zoom 5.0)))] - (pxl8.set_projection (pxl8.mat4_perspective fov aspect 1.0 50.0)))) - - (pxl8.set_view (pxl8.mat4_lookat [0 0 zoom] [0 0 0] [0 1 0])) - - (let [model (-> (pxl8.mat4_identity) - (pxl8.mat4_multiply (pxl8.mat4_rotate_x angle-x)) - (pxl8.mat4_multiply (pxl8.mat4_rotate_y angle-y)) - (pxl8.mat4_multiply (pxl8.mat4_rotate_z angle-z)))] - (pxl8.set_model model)) - - (let [vertices (make-cube-vertices)] - (if (and use-texture texture-id) - (let [faces (make-cube-faces-with-uvs)] - (each [i face-data (ipairs faces)] - (let [tri-indices face-data.tri - tri-uvs face-data.uvs - v0 (. vertices (+ 1 (. tri-indices 1))) - v1 (. vertices (+ 1 (. tri-indices 2))) - v2 (. vertices (+ 1 (. tri-indices 3))) - uv0 (. tri-uvs 1) - uv1 (. tri-uvs 2) - uv2 (. tri-uvs 3)] - (pxl8.draw_triangle_3d_textured - v0 v1 v2 - uv0 uv1 uv2 - texture-id)))) - (let [faces (make-cube-faces)] - (each [i face (ipairs faces)] - (let [[i0 i1 i2] face - v0 (. vertices (+ 1 i0)) - v1 (. vertices (+ 1 i1)) - v2 (. vertices (+ 1 i2)) - color (get-face-color (math.floor (/ (- i 1) 2)))] - (pxl8.draw_triangle_3d v0 v1 v2 color)))))) - - (pxl8.text "WASD/QE: Rotate | +/-: Zoom | Space: Wire | R: Auto | P: Proj" 5 5 15) - (pxl8.text (.. "T: Texture | F: Affine | Mode: " - (if wireframe "Wire" "Fill") - " | Texture: " (if use-texture "On" "Off") - " | Mapping: " (if affine "Affine" "Persp")) 5 15 15)) - -{:update cube-update - :frame cube-frame} diff --git a/demo/main.fnl b/demo/main.fnl index 87655c1..6dc7541 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -1,5 +1,5 @@ (local pxl8 (require :pxl8)) -(local cube3d (fennel.dofile "cube3d.fnl")) +(local cube3d (require :mod.cube3d)) (var time 0) (var current-effect 1) diff --git a/demo/mod/cube3d.fnl b/demo/mod/cube3d.fnl new file mode 100644 index 0000000..606e29b --- /dev/null +++ b/demo/mod/cube3d.fnl @@ -0,0 +1,213 @@ +(local pxl8 (require :pxl8)) +(local debug-ui (require :mod.debug_ui)) + +(var angle-x 0) +(var angle-y 0) +(var angle-z 0) +(var auto-rotate true) +(var orthographic true) +(var wireframe true) +(var time 0) +(var zoom 5.0) +(var texture-id nil) +(var use-texture false) +(var affine false) +(var texture-initialized false) +(var cam-x 0) +(var cam-y 2) +(var cam-z 12) +(var cam-yaw 0) +(var cam-pitch -0.2) +(var show-debug-ui false) +(var fps 0) +(var fps-timer 0) +(var fps-accumulator 0) +(var fps-frame-count 0) + +(fn init-texture [] + (when (not texture-initialized) + (pxl8.load_palette "sprites/pxl8_logo.ase") + (set texture-id (pxl8.load_sprite "sprites/pxl8_logo.ase")) + (pxl8.upload_atlas) + (set texture-initialized true))) + +(fn make-cube-vertices [] + [[-1 -1 -1] [1 -1 -1] [1 1 -1] [-1 1 -1] + [-1 -1 1] [1 -1 1] [1 1 1] [-1 1 1]]) + +(fn make-cube-faces [] + [[0 1 2] [0 2 3] + [1 5 6] [1 6 2] + [5 4 7] [5 7 6] + [4 0 3] [4 3 7] + [3 2 6] [3 6 7] + [4 5 1] [4 1 0]]) + +(fn make-cube-faces-with-uvs [] + [{:tri [0 1 2] :uvs [[0 0] [1 0] [1 1]]} + {:tri [0 2 3] :uvs [[0 0] [1 1] [0 1]]} + {:tri [1 5 6] :uvs [[0 0] [1 0] [1 1]]} + {:tri [1 6 2] :uvs [[0 0] [1 1] [0 1]]} + {:tri [5 4 7] :uvs [[0 0] [1 0] [1 1]]} + {:tri [5 7 6] :uvs [[0 0] [1 1] [0 1]]} + {:tri [4 0 3] :uvs [[0 0] [1 0] [1 1]]} + {:tri [4 3 7] :uvs [[0 0] [1 1] [0 1]]} + {:tri [3 2 6] :uvs [[0 0] [1 0] [1 1]]} + {:tri [3 6 7] :uvs [[0 0] [1 1] [0 1]]} + {:tri [4 5 1] :uvs [[0 0] [1 0] [1 1]]} + {:tri [4 1 0] :uvs [[0 0] [1 1] [0 1]]}]) + +(fn get-face-color [face-idx] + (let [colors [12 22 30 16 28 20]] + (. colors (+ 1 (% face-idx 6))))) + +(fn update [dt] + (set time (+ time dt)) + (set fps-accumulator (+ fps-accumulator dt)) + (set fps-frame-count (+ fps-frame-count 1)) + + (when (>= fps-accumulator 0.25) + (set fps (/ fps-frame-count fps-accumulator)) + (set fps-accumulator 0) + (set fps-frame-count 0)) + + (let [wheel-y (pxl8.mouse_wheel_y) + zoom-speed 0.5] + (when (not= wheel-y 0) + (let [forward-x (* (math.sin cam-yaw) wheel-y zoom-speed) + forward-z (* (math.cos cam-yaw) wheel-y zoom-speed)] + (set cam-x (+ cam-x forward-x)) + (set cam-z (- cam-z forward-z))))) + + (let [move-speed 5.0 + rot-speed 2.0 + forward-x (* (math.sin cam-yaw) move-speed dt) + forward-z (* (math.cos cam-yaw) move-speed dt) + right-x (* (math.cos cam-yaw) move-speed dt) + right-z (* (- (math.sin cam-yaw)) move-speed dt)] + + (when (pxl8.key_down "w") + (set cam-x (+ cam-x forward-x)) + (set cam-z (- cam-z forward-z))) + (when (pxl8.key_down "s") + (set cam-x (- cam-x forward-x)) + (set cam-z (+ cam-z forward-z))) + (when (pxl8.key_down "a") + (set cam-x (- cam-x right-x)) + (set cam-z (- cam-z right-z))) + (when (pxl8.key_down "d") + (set cam-x (+ cam-x right-x)) + (set cam-z (+ cam-z right-z))) + (when (pxl8.key_down "q") + (set cam-y (- cam-y (* move-speed dt)))) + (when (pxl8.key_down "e") + (set cam-y (+ cam-y (* move-speed dt)))) + + (when (pxl8.key_down "left") + (set cam-yaw (- cam-yaw (* rot-speed dt)))) + (when (pxl8.key_down "right") + (set cam-yaw (+ cam-yaw (* rot-speed dt)))) + (when (pxl8.key_down "up") + (set cam-pitch (+ cam-pitch (* rot-speed dt)))) + (when (pxl8.key_down "down") + (set cam-pitch (- cam-pitch (* rot-speed dt)))) + + (set cam-pitch (math.max -1.5 (math.min cam-pitch 1.5)))) + + (when (pxl8.key_pressed " ") + (set wireframe (not wireframe))) + (when (pxl8.key_pressed "f") + (set affine (not affine))) + (when (pxl8.key_pressed "p") + (set orthographic (not orthographic))) + (when (pxl8.key_pressed "r") + (set auto-rotate (not auto-rotate))) + (when (pxl8.key_pressed "t") + (set use-texture (not use-texture)) + (when use-texture + (init-texture))) + (when (pxl8.key_pressed "F8") + (set show-debug-ui (not show-debug-ui)) + (pxl8.ui_window_set_open "Debug Menu (F8)" show-debug-ui)) + + (when auto-rotate + (set angle-x (+ angle-x (* dt 0.7))) + (set angle-y (+ angle-y (* dt 0.5))) + (set angle-z (+ angle-z (* dt 0.3))))) + +(fn frame [] + (pxl8.clr 0) + + (pxl8.clear_zbuffer) + (pxl8.set_affine_textures affine) + (pxl8.set_backface_culling true) + (pxl8.set_wireframe wireframe) + + (if orthographic + (let [size 2.5 + aspect (/ (pxl8.get_width) (pxl8.get_height)) + w (* size aspect) + h size] + (pxl8.set_projection (pxl8.mat4_ortho (- w) w (- h) h 1.0 100.0))) + (let [aspect (/ (pxl8.get_width) (pxl8.get_height)) + fov (* (/ 60.0 180.0) 3.14159)] + (pxl8.set_projection (pxl8.mat4_perspective fov aspect 0.1 100.0)))) + + (let [target-x (* (math.sin cam-yaw) (math.cos cam-pitch)) + target-y (* (math.sin cam-pitch)) + target-z (* (- (math.cos cam-yaw)) (math.cos cam-pitch)) + look-x (+ cam-x target-x) + look-y (+ cam-y target-y) + look-z (+ cam-z target-z)] + (pxl8.set_view (pxl8.mat4_lookat [cam-x cam-y cam-z] [look-x look-y look-z] [0 1 0]))) + + (let [model (-> (pxl8.mat4_identity) + (pxl8.mat4_multiply (pxl8.mat4_rotate_x angle-x)) + (pxl8.mat4_multiply (pxl8.mat4_rotate_y angle-y)) + (pxl8.mat4_multiply (pxl8.mat4_rotate_z angle-z)))] + (pxl8.set_model model)) + + (let [vertices (make-cube-vertices)] + (if (and use-texture texture-id) + (let [faces (make-cube-faces-with-uvs)] + (each [_i face-data (ipairs faces)] + (let [tri-indices face-data.tri + tri-uvs face-data.uvs + v0 (. vertices (+ 1 (. tri-indices 1))) + v1 (. vertices (+ 1 (. tri-indices 2))) + v2 (. vertices (+ 1 (. tri-indices 3))) + uv0 (. tri-uvs 1) + uv1 (. tri-uvs 2) + uv2 (. tri-uvs 3)] + (pxl8.draw_triangle_3d_textured + v0 v1 v2 + uv0 uv1 uv2 + texture-id)))) + (let [faces (make-cube-faces)] + (each [i face (ipairs faces)] + (let [[i0 i1 i2] face + v0 (. vertices (+ 1 i0)) + v1 (. vertices (+ 1 i1)) + v2 (. vertices (+ 1 i2)) + color (get-face-color (math.floor (/ (- i 1) 2)))] + (pxl8.draw_triangle_3d v0 v1 v2 color)))))) + + (let [new-state (debug-ui.render {:show-debug-ui show-debug-ui + :fps fps + :wireframe wireframe + :auto-rotate auto-rotate + :orthographic orthographic + :use-texture use-texture + :affine affine + :init-texture init-texture})] + (when (not= new-state.show-debug-ui nil) (set show-debug-ui new-state.show-debug-ui)) + (when (not= new-state.wireframe nil) (set wireframe new-state.wireframe)) + (when (not= new-state.auto-rotate nil) (set auto-rotate new-state.auto-rotate)) + (when (not= new-state.orthographic nil) (set orthographic new-state.orthographic)) + (when (not= new-state.use-texture nil) + (set use-texture new-state.use-texture) + (when use-texture (init-texture))) + (when (not= new-state.affine nil) (set affine new-state.affine)))) + +{: update + : frame} diff --git a/demo/mod/debug_ui.fnl b/demo/mod/debug_ui.fnl new file mode 100644 index 0000000..eadc6f8 --- /dev/null +++ b/demo/mod/debug_ui.fnl @@ -0,0 +1,32 @@ +(local pxl8 (require :pxl8)) + +(fn render [state] + (var new-state {}) + (when state.show-debug-ui + (let [window-h (if state.use-texture 210 180) + window-open (pxl8.ui_window_begin "Debug Menu (F8)" 10 10 250 window-h)] + (when window-open + (pxl8.ui_layout_row 1 0 0) + (pxl8.ui_label (string.format "FPS: %.0f" (or state.fps 0))) + (let [(changed new-val) (pxl8.ui_checkbox "Wireframe" state.wireframe)] + (when changed (set new-state.wireframe new-val))) + (let [(changed new-val) (pxl8.ui_checkbox "Auto-rotate" state.auto-rotate)] + (when changed (set new-state.auto-rotate new-val))) + (let [(changed new-val) (pxl8.ui_checkbox "Orthographic" state.orthographic)] + (when changed (set new-state.orthographic new-val))) + (let [(changed new-val) (pxl8.ui_checkbox "Texture" state.use-texture)] + (when changed + (set new-state.use-texture new-val) + (when (and new-val state.init-texture) + (state.init-texture)))) + (when state.use-texture + (pxl8.ui_indent 20) + (let [(changed new-val) (pxl8.ui_checkbox "Affine mapping" state.affine)] + (when changed (set new-state.affine new-val))) + (pxl8.ui_indent -20)) + (pxl8.ui_window_end)) + (when (not window-open) + (set new-state.show-debug-ui false)))) + new-state) + +{: render} diff --git a/demo/ui_demo.fnl b/demo/ui_demo.fnl deleted file mode 100644 index cd4e9e3..0000000 --- a/demo/ui_demo.fnl +++ /dev/null @@ -1,20 +0,0 @@ -(local pxl8 (require :pxl8)) - -(var button-clicks 0) - -(global init (fn [] - (pxl8.load_palette "palettes/gruvbox.ase"))) - -(global update (fn [_dt])) - -(global frame (fn [] - (pxl8.clr 1) - - (when pxl8.ui - (when (pxl8.ui_window_begin "UI Demo" 20 20 280 150) - (pxl8.ui_layout_row 1 0 0) - (pxl8.ui_label "Welcome to some window UI!") - (pxl8.ui_label (.. "Clicks: " button-clicks)) - (when (pxl8.ui_button "Click me!") - (set button-clicks (+ button-clicks 1))) - (pxl8.ui_window_end))))) diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 786f711..92d043f 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -107,19 +107,33 @@ function pxl8.trace(msg) end function pxl8.key_down(key) - if type(key) == "string" then - key = string.byte(key) - end return C.pxl8_key_down(input, key) end function pxl8.key_pressed(key) - if type(key) == "string" then - key = string.byte(key) - end return C.pxl8_key_pressed(input, key) end +function pxl8.key_released(key) + return C.pxl8_key_released(input, key) +end + +function pxl8.mouse_wheel_x() + return C.pxl8_mouse_wheel_x(input) +end + +function pxl8.mouse_wheel_y() + return C.pxl8_mouse_wheel_y(input) +end + +function pxl8.mouse_x() + return C.pxl8_mouse_x(input) +end + +function pxl8.mouse_y() + return C.pxl8_mouse_y(input) +end + function pxl8.vfx_raster_bars(bars, time) local c_bars = ffi.new("pxl8_raster_bar[?]", #bars) for i, bar in ipairs(bars) do @@ -363,19 +377,20 @@ function pxl8.bounds(x, y, w, h) return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h}) end -function pxl8.ui_window_begin(title, x, y, w, h, options) - local rect = ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h}) - return C.pxl8_ui_window_begin(_pxl8_ui, title, rect, options or 0) -end - -function pxl8.ui_window_end() - C.pxl8_ui_window_end(_pxl8_ui) -end - function pxl8.ui_button(label) return C.pxl8_ui_button(_pxl8_ui, label) end +function pxl8.ui_checkbox(label, state) + local state_ptr = ffi.new("bool[1]", state) + local changed = C.pxl8_ui_checkbox(_pxl8_ui, label, state_ptr) + return changed, state_ptr[0] +end + +function pxl8.ui_indent(amount) + C.pxl8_ui_indent(_pxl8_ui, amount) +end + function pxl8.ui_label(text) C.pxl8_ui_label(_pxl8_ui, text) end @@ -390,4 +405,17 @@ function pxl8.ui_layout_row(item_count, widths, height) C.pxl8_ui_layout_row(_pxl8_ui, item_count, widths_array, height) end +function pxl8.ui_window_begin(title, x, y, w, h, options) + local rect = ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h}) + return C.pxl8_ui_window_begin(_pxl8_ui, title, rect, options or 0) +end + +function pxl8.ui_window_end() + C.pxl8_ui_window_end(_pxl8_ui) +end + +function pxl8.ui_window_set_open(title, open) + C.pxl8_ui_window_set_open(_pxl8_ui, title, open) +end + return pxl8 diff --git a/src/pxl8.c b/src/pxl8.c index 34f69da..47ce57c 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -44,8 +44,6 @@ typedef struct pxl8_state { pxl8_script* script; pxl8_ui* ui; - f32 current_fps; - f32 fps_timer; i32 frame_count; u64 last_time; f32 time; @@ -53,7 +51,6 @@ typedef struct pxl8_state { bool repl_mode; bool running; bool script_loaded; - bool show_fps; char script_path[256]; pxl8_input_state input; @@ -319,16 +316,8 @@ SDL_AppResult SDL_AppIterate(void* appstate) { u64 current_time = SDL_GetTicksNS(); f32 dt = (f32)(current_time - app->last_time) / 1000000000.0f; - app->frame_count++; - app->fps_timer += dt; app->last_time = current_time; app->time += dt; - - if (app->fps_timer >= 1.0f) { - app->current_fps = app->frame_count / app->fps_timer; - app->frame_count = 0; - app->fps_timer = 0.0f; - } pxl8_script_check_reload(app->script); @@ -344,23 +333,24 @@ SDL_AppResult SDL_AppIterate(void* appstate) { } if (app->ui) { + pxl8_ui_input_mousemove(app->ui, app->input.mouse_x, app->input.mouse_y); + pxl8_ui_frame_begin(app->ui); for (i32 i = 0; i < 3; i++) { - if (app->input.mouse_buttons[i] && app->input.mouse_buttons_pressed[i]) { + if (app->input.mouse_buttons_pressed[i]) { pxl8_ui_input_mousedown(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1); } - if (!app->input.mouse_buttons[i]) { + if (app->input.mouse_buttons_released[i]) { pxl8_ui_input_mouseup(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1); } } - pxl8_ui_input_mousemove(app->ui, app->input.mouse_x, app->input.mouse_y); for (i32 key = 0; key < 256; key++) { if (app->input.keys_pressed[key]) { pxl8_ui_input_keydown(app->ui, key); } - if (!app->input.keys[key] && app->input.keys_pressed[key]) { + if (!app->input.keys_down[key] && app->input.keys_pressed[key]) { pxl8_ui_input_keyup(app->ui, key); } } @@ -390,12 +380,6 @@ SDL_AppResult SDL_AppIterate(void* appstate) { i32 render_width, render_height; pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height); - if (app->show_fps && app->current_fps > 0.0f) { - char fps_text[32]; - SDL_snprintf(fps_text, sizeof(fps_text), "FPS: %d", (i32)(app->current_fps + 0.5f)); - pxl8_text(app->gfx, fps_text, render_width - 80, 10, 15); - } - if (app->ui) { pxl8_ui_frame_end(app->ui); } @@ -406,7 +390,9 @@ SDL_AppResult SDL_AppIterate(void* appstate) { pxl8_gfx_present(app->gfx); SDL_memset(app->input.keys_pressed, 0, sizeof(app->input.keys_pressed)); + SDL_memset(app->input.keys_released, 0, sizeof(app->input.keys_released)); SDL_memset(app->input.mouse_buttons_pressed, 0, sizeof(app->input.mouse_buttons_pressed)); + SDL_memset(app->input.mouse_buttons_released, 0, sizeof(app->input.mouse_buttons_released)); app->input.mouse_wheel_x = 0; app->input.mouse_wheel_y = 0; @@ -427,25 +413,23 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { return SDL_APP_SUCCESS; } - if (event->key.key == SDLK_F3) { - app->show_fps = !app->show_fps; - } - - SDL_Keycode key = event->key.key; - if (key < 256) { - if (!app->input.keys[key]) { - app->input.keys_pressed[key] = true; + SDL_Scancode scancode = event->key.scancode; + if (scancode < 256) { + if (!app->input.keys_down[scancode]) { + app->input.keys_pressed[scancode] = true; } - app->input.keys[key] = true; + app->input.keys_down[scancode] = true; + app->input.keys_released[scancode] = false; } break; } - + case SDL_EVENT_KEY_UP: { - SDL_Keycode key = event->key.key; - if (key < 256) { - app->input.keys[key] = false; - app->input.keys_pressed[key] = false; + SDL_Scancode scancode = event->key.scancode; + if (scancode < 256) { + app->input.keys_down[scancode] = false; + app->input.keys_pressed[scancode] = false; + app->input.keys_released[scancode] = true; } break; } @@ -453,10 +437,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { case SDL_EVENT_MOUSE_BUTTON_DOWN: { u8 button = event->button.button - 1; if (button < 3) { - if (!app->input.mouse_buttons[button]) { + if (!app->input.mouse_buttons_down[button]) { app->input.mouse_buttons_pressed[button] = true; } - app->input.mouse_buttons[button] = true; + app->input.mouse_buttons_down[button] = true; + app->input.mouse_buttons_released[button] = false; } break; } @@ -464,7 +449,9 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { case SDL_EVENT_MOUSE_BUTTON_UP: { u8 button = event->button.button - 1; if (button < 3) { - app->input.mouse_buttons[button] = false; + app->input.mouse_buttons_down[button] = false; + app->input.mouse_buttons_pressed[button] = false; + app->input.mouse_buttons_released[button] = true; } break; } diff --git a/src/pxl8_io.c b/src/pxl8_io.c index 8292c17..6822913 100644 --- a/src/pxl8_io.c +++ b/src/pxl8_io.c @@ -1,3 +1,4 @@ +#include #include #include "pxl8_io.h" @@ -97,12 +98,50 @@ void pxl8_io_free_binary_data(u8* data) { } } -bool pxl8_key_down(const pxl8_input_state* input, i32 key) { - if (!input || key < 0 || key >= 256) return false; - return input->keys[key]; +static i32 pxl8_key_code(const char* key_name) { + if (!key_name || !key_name[0]) return 0; + + SDL_Scancode scancode = SDL_GetScancodeFromName(key_name); + return (i32)scancode; } -bool pxl8_key_pressed(const pxl8_input_state* input, i32 key) { - if (!input || key < 0 || key >= 256) return false; +bool pxl8_key_down(const pxl8_input_state* input, const char* key_name) { + if (!input) return false; + i32 key = pxl8_key_code(key_name); + if (key < 0 || key >= 256) return false; + return input->keys_down[key]; +} + +bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name) { + if (!input) return false; + i32 key = pxl8_key_code(key_name); + if (key < 0 || key >= 256) return false; return input->keys_pressed[key]; } + +bool pxl8_key_released(const pxl8_input_state* input, const char* key_name) { + if (!input) return false; + i32 key = pxl8_key_code(key_name); + if (key < 0 || key >= 256) return false; + return input->keys_released[key]; +} + +i32 pxl8_mouse_wheel_x(const pxl8_input_state* input) { + if (!input) return 0; + return input->mouse_wheel_x; +} + +i32 pxl8_mouse_wheel_y(const pxl8_input_state* input) { + if (!input) return 0; + return input->mouse_wheel_y; +} + +i32 pxl8_mouse_x(const pxl8_input_state* input) { + if (!input) return 0; + return input->mouse_x; +} + +i32 pxl8_mouse_y(const pxl8_input_state* input) { + if (!input) return 0; + return input->mouse_y; +} diff --git a/src/pxl8_io.h b/src/pxl8_io.h index 4c0aca4..fcf0afb 100644 --- a/src/pxl8_io.h +++ b/src/pxl8_io.h @@ -19,8 +19,14 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size); pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size); pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size); -bool pxl8_key_down(const pxl8_input_state* input, i32 key); -bool pxl8_key_pressed(const pxl8_input_state* input, i32 key); +bool pxl8_key_down(const pxl8_input_state* input, const char* key_name); +bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name); +bool pxl8_key_released(const pxl8_input_state* input, const char* key_name); + +i32 pxl8_mouse_wheel_x(const pxl8_input_state* input); +i32 pxl8_mouse_wheel_y(const pxl8_input_state* input); +i32 pxl8_mouse_x(const pxl8_input_state* input); +i32 pxl8_mouse_y(const pxl8_input_state* input); #ifdef __cplusplus } diff --git a/src/pxl8_script.c b/src/pxl8_script.c index ba62dfb..91f684f 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -59,8 +59,13 @@ static const char* pxl8_ffi_cdefs = "i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n" "void pxl8_gfx_upload_atlas(pxl8_gfx* ctx);\n" "typedef struct pxl8_input_state pxl8_input_state;\n" -"bool pxl8_key_down(const pxl8_input_state* input, i32 key);\n" -"bool pxl8_key_pressed(const pxl8_input_state* input, i32 key);\n" +"bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n" +"bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n" +"bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);\n" +"int pxl8_mouse_wheel_x(const pxl8_input_state* input);\n" +"int pxl8_mouse_wheel_y(const pxl8_input_state* input);\n" +"int pxl8_mouse_x(const pxl8_input_state* input);\n" +"int pxl8_mouse_y(const pxl8_input_state* input);\n" "void pxl8_lua_debug(const char* msg);\n" "void pxl8_lua_error(const char* msg);\n" "void pxl8_lua_info(const char* msg);\n" @@ -168,12 +173,15 @@ static const char* pxl8_ffi_cdefs = "void pxl8_ui_input_scroll(pxl8_ui* ui, int x, int y);\n" "void pxl8_ui_input_text(pxl8_ui* ui, const char* text);\n" "bool pxl8_ui_button(pxl8_ui* ui, const char* label);\n" +"bool pxl8_ui_checkbox(pxl8_ui* ui, const char* label, bool* state);\n" +"void pxl8_ui_indent(pxl8_ui* ui, int amount);\n" "void pxl8_ui_label(pxl8_ui* ui, const char* text);\n" "void pxl8_ui_layout_row(pxl8_ui* ui, int item_count, const int* widths, int height);\n" "int pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, int item_count);\n" "void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme);\n" "bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, int options);\n" "void pxl8_ui_window_end(pxl8_ui* ui);\n" +"void pxl8_ui_window_set_open(pxl8_ui* ui, const char* title, bool open);\n" "pxl8_frame_theme pxl8_ui_theme_default(void);\n"; void pxl8_lua_info(const char* msg) { @@ -246,6 +254,18 @@ pxl8_script* pxl8_script_create(void) { if (luaL_dofile(script->L, "lib/fennel/fennel.lua") == 0) { lua_setglobal(script->L, "fennel"); + + lua_getglobal(script->L, "fennel"); + lua_getfield(script->L, -1, "install"); + if (lua_isfunction(script->L, -1)) { + if (lua_pcall(script->L, 0, 0, 0) != 0) { + pxl8_warn("Failed to install fennel searcher: %s", lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + } + } else { + lua_pop(script->L, 1); + } + lua_pop(script->L, 1); } script->last_error[0] = '\0'; diff --git a/src/pxl8_types.h b/src/pxl8_types.h index 1eb0c4f..60094e1 100644 --- a/src/pxl8_types.h +++ b/src/pxl8_types.h @@ -64,11 +64,13 @@ typedef struct pxl8_bounds { } pxl8_bounds; typedef struct pxl8_input_state { - bool keys[256]; + bool keys_down[256]; bool keys_pressed[256]; + bool keys_released[256]; - bool mouse_buttons[3]; + bool mouse_buttons_down[3]; bool mouse_buttons_pressed[3]; + bool mouse_buttons_released[3]; i32 mouse_wheel_x; i32 mouse_wheel_y; i32 mouse_x; diff --git a/src/pxl8_ui.c b/src/pxl8_ui.c index 6f58623..0813a17 100644 --- a/src/pxl8_ui.c +++ b/src/pxl8_ui.c @@ -38,26 +38,47 @@ static int mu_text_height(mu_Font font) { return f->default_height; } +static void pxl8_ui_render_icon(pxl8_gfx* gfx, i32 id, i32 x, i32 y, i32 w, i32 h, u8 color) { + switch (id) { + case 2: { + i32 cx = x + w / 2; + i32 cy = y + h / 2; + i32 size = (w < h ? w : h) / 3; + pxl8_line(gfx, cx - size, cy, cx, cy + size, color); + pxl8_line(gfx, cx, cy + size, cx + size, cy - size, color); + break; + } + case 1: { + i32 cx = x + w / 2; + i32 cy = y + h / 2; + i32 size = (w < h ? w : h) / 4; + pxl8_line(gfx, cx - size, cy - size, cx + size, cy + size, color); + pxl8_line(gfx, cx + size, cy - size, cx - size, cy + size, color); + break; + } + } +} + static void pxl8_ui_render_commands(pxl8_ui* ui) { mu_Command* cmd = NULL; while (mu_next_command(&ui->mu_ctx, &cmd)) { switch (cmd->type) { case MU_COMMAND_RECT: { mu_RectCommand* rc = (mu_RectCommand*)cmd; - u8 color = rc->color.r; - pxl8_rect_fill(ui->gfx, rc->rect.x, rc->rect.y, rc->rect.w, rc->rect.h, color); + pxl8_rect_fill(ui->gfx, rc->rect.x, rc->rect.y, rc->rect.w, rc->rect.h, rc->color.r); break; } case MU_COMMAND_TEXT: { mu_TextCommand* tc = (mu_TextCommand*)cmd; - u8 color = tc->color.r; - pxl8_text(ui->gfx, tc->str, tc->pos.x, tc->pos.y, color); + pxl8_text(ui->gfx, tc->str, tc->pos.x, tc->pos.y, tc->color.r); break; } case MU_COMMAND_CLIP: { break; } case MU_COMMAND_ICON: { + mu_IconCommand* ic = (mu_IconCommand*)cmd; + pxl8_ui_render_icon(ui->gfx, ic->id, ic->rect.x, ic->rect.y, ic->rect.w, ic->rect.h, ic->color.r); break; } } @@ -104,6 +125,21 @@ pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx) { ui->mu_ctx.text_height = mu_text_height; ui->mu_ctx.text_width = mu_text_width; + ui->mu_ctx.style->colors[0] = (mu_Color){15, 0, 0, 255}; + ui->mu_ctx.style->colors[1] = (mu_Color){8, 0, 0, 255}; + ui->mu_ctx.style->colors[2] = (mu_Color){1, 0, 0, 255}; + ui->mu_ctx.style->colors[3] = (mu_Color){2, 0, 0, 255}; + ui->mu_ctx.style->colors[4] = (mu_Color){15, 0, 0, 255}; + ui->mu_ctx.style->colors[5] = (mu_Color){0, 0, 0, 0}; + ui->mu_ctx.style->colors[6] = (mu_Color){7, 0, 0, 255}; + ui->mu_ctx.style->colors[7] = (mu_Color){8, 0, 0, 255}; + ui->mu_ctx.style->colors[8] = (mu_Color){10, 0, 0, 255}; + ui->mu_ctx.style->colors[9] = (mu_Color){2, 0, 0, 255}; + ui->mu_ctx.style->colors[10] = (mu_Color){3, 0, 0, 255}; + ui->mu_ctx.style->colors[11] = (mu_Color){10, 0, 0, 255}; + ui->mu_ctx.style->colors[12] = (mu_Color){7, 0, 0, 255}; + ui->mu_ctx.style->colors[13] = (mu_Color){8, 0, 0, 255}; + return ui; } @@ -163,6 +199,48 @@ bool pxl8_ui_button(pxl8_ui* ui, const char* label) { return mu_button(&ui->mu_ctx, label) & MU_RES_SUBMIT; } +bool pxl8_ui_checkbox(pxl8_ui* ui, const char* label, bool* state) { + if (!ui || !state || !label) return false; + + mu_Context* ctx = &ui->mu_ctx; + mu_push_id(ctx, label, (int)strlen(label)); + + mu_Id id = mu_get_id(ctx, label, (int)strlen(label)); + mu_Rect r = mu_layout_next(ctx); + mu_Rect box = mu_rect(r.x, r.y, r.h, r.h); + + int had_focus_before = (ctx->focus == id); + mu_update_control(ctx, id, r, 0); + int has_focus_after = (ctx->focus == id); + int mouseover = mu_mouse_over(ctx, r); + + int res = 0; + int int_state = *state ? 1 : 0; + + if (had_focus_before && !has_focus_after && !ctx->mouse_down && mouseover) { + res |= MU_RES_CHANGE; + int_state = !int_state; + } + + mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0); + if (int_state) { + mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx->style->colors[MU_COLOR_TEXT]); + } + r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h); + mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0); + + mu_pop_id(ctx); + + *state = int_state != 0; + return res & MU_RES_CHANGE; +} + +void pxl8_ui_indent(pxl8_ui* ui, i32 amount) { + if (!ui) return; + mu_Layout* layout = &ui->mu_ctx.layout_stack.items[ui->mu_ctx.layout_stack.idx - 1]; + layout->indent += amount; +} + void pxl8_ui_label(pxl8_ui* ui, const char* text) { if (!ui || !text) return; mu_label(&ui->mu_ctx, text); @@ -205,6 +283,14 @@ void pxl8_ui_window_end(pxl8_ui* ui) { mu_end_window(&ui->mu_ctx); } +void pxl8_ui_window_set_open(pxl8_ui* ui, const char* title, bool open) { + if (!ui || !title) return; + mu_Container* win = mu_get_container(&ui->mu_ctx, title); + if (win) { + win->open = open ? 1 : 0; + } +} + pxl8_frame_theme pxl8_ui_theme_default(void) { pxl8_frame_theme theme = {0}; theme.bg_color = 0; diff --git a/src/pxl8_ui.h b/src/pxl8_ui.h index c2fc749..51acc52 100644 --- a/src/pxl8_ui.h +++ b/src/pxl8_ui.h @@ -5,6 +5,23 @@ typedef struct pxl8_ui pxl8_ui; +typedef struct pxl8_ui_theme { + u8 text; + u8 border; + u8 window_bg; + u8 title_bg; + u8 title_text; + u8 panel_bg; + u8 button; + u8 button_hover; + u8 button_focus; + u8 base; + u8 base_hover; + u8 base_focus; + u8 scroll_base; + u8 scroll_thumb; +} pxl8_ui_theme; + typedef struct pxl8_frame_theme { u8 bg_color; u32 sprite_id; @@ -44,12 +61,15 @@ void pxl8_ui_input_scroll(pxl8_ui* ui, i32 x, i32 y); void pxl8_ui_input_text(pxl8_ui* ui, const char* text); bool pxl8_ui_button(pxl8_ui* ui, const char* label); +bool pxl8_ui_checkbox(pxl8_ui* ui, const char* label, bool* state); +void pxl8_ui_indent(pxl8_ui* ui, i32 amount); void pxl8_ui_label(pxl8_ui* ui, const char* text); void pxl8_ui_layout_row(pxl8_ui* ui, i32 item_count, const i32* widths, i32 height); i32 pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, i32 item_count); void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme); bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, i32 options); void pxl8_ui_window_end(pxl8_ui* ui); +void pxl8_ui_window_set_open(pxl8_ui* ui, const char* title, bool open); pxl8_frame_theme pxl8_ui_theme_default(void); diff --git a/src/pxl8_vfx.h b/src/pxl8_vfx.h index cbfe98b..14db362 100644 --- a/src/pxl8_vfx.h +++ b/src/pxl8_vfx.h @@ -30,6 +30,10 @@ typedef struct pxl8_raster_bar { f32 speed; } pxl8_raster_bar; +#ifdef __cplusplus +extern "C" { +#endif + pxl8_particles* pxl8_particles_create(u32 max_count); void pxl8_particles_destroy(pxl8_particles* particles); @@ -51,3 +55,7 @@ void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy); void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist); void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping); + +#ifdef __cplusplus +} +#endif