From 8baf5f06ea43df61eae52d0637cff6f4a46be461 Mon Sep 17 00:00:00 2001 From: asrael Date: Fri, 21 Nov 2025 11:51:23 -0600 Subject: [PATCH] implement our own gui module, drop microui --- README.md | 4 +- demo/main.fnl | 95 ++++++++-------- demo/mod/cube3d.fnl | 251 ----------------------------------------- demo/mod/debug_ui.fnl | 28 ----- demo/mod/menu.fnl | 51 +++++++++ demo/mod/worldgen.fnl | 5 - pxl8.sh | 24 +--- src/lua/pxl8.lua | 31 +++-- src/lua/pxl8/core.lua | 7 +- src/lua/pxl8/gui.lua | 59 ++++++++++ src/lua/pxl8/input.lua | 28 +++++ src/lua/pxl8/ui.lua | 52 --------- src/pxl8.c | 53 ++------- src/pxl8_game.h | 2 - src/pxl8_gui.c | 131 +++++++++++++++++++++ src/pxl8_gui.h | 38 +++++++ src/pxl8_hal.h | 2 + src/pxl8_io.c | 10 ++ src/pxl8_io.h | 2 + src/pxl8_script.c | 85 +++++++------- src/pxl8_script.h | 2 - src/pxl8_sdl3.c | 34 ++++++ src/pxl8_sys.h | 2 + src/pxl8_types.h | 5 + src/pxl8_world.c | 1 - 25 files changed, 495 insertions(+), 507 deletions(-) delete mode 100644 demo/mod/cube3d.fnl delete mode 100644 demo/mod/debug_ui.fnl create mode 100644 demo/mod/menu.fnl create mode 100644 src/lua/pxl8/gui.lua delete mode 100644 src/lua/pxl8/ui.lua create mode 100644 src/pxl8_gui.c create mode 100644 src/pxl8_gui.h diff --git a/README.md b/README.md index 759fb75..c7cba97 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ./pxl8.sh run [game.cart | project_dir] # Run pxl8 demo (or a specific game) ./pxl8.sh run [game.cart | project_dir] --repl # Run pxl8 demo (or a specific game) with a REPL ``` -**Note** The demo has keys 1-9 bound for different examples. +**Note** The demo has keys 1-8 bound for different examples. > [!WARNING] > Heavy development. So... here be dragons :3 @@ -38,4 +38,4 @@ pxl8 is free and open source! All code in this repository is licensed under: - Mozilla Public License, Version 2.0 ([LICENSE](LICENSE) or https://mozilla.org/MPL/2.0/) -Third-party dependencies (Fennel, LuaJIT, linenoise, microui, miniz, SDL3) retain their original licenses. +Third-party dependencies (Fennel, linenoise, log.c, LuaJIT, miniz, SDL3) retain their original licenses. diff --git a/demo/main.fnl b/demo/main.fnl index 7c025af..282a22a 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -1,5 +1,5 @@ (local pxl8 (require :pxl8)) -(local cube3d (require :mod.cube3d)) +(local menu (require :mod.menu)) (local worldgen (require :mod.worldgen)) (var time 0) @@ -29,53 +29,59 @@ (pxl8.load_palette "res/sprites/pxl8_logo.ase") (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) (set particles (pxl8.particles_new 1000)) - (cube3d.init) (worldgen.init))) (global update (fn [dt] - (set time (+ time dt)) + (when (pxl8.key_pressed "escape") + (menu.toggle) + (when (= active-demo :worldgen) + (pxl8.set_relative_mouse_mode (not (menu.is-paused))))) - (when transition - (pxl8.transition_update transition dt) - (when (pxl8.transition_is_complete transition) - (when transition-pending - (when (and (= active-demo :worldgen) (not= transition-pending :worldgen)) - (pxl8.set_relative_mouse_mode false)) - (set active-demo transition-pending) - (set transition-pending nil) - (when (= active-demo :fire) (set fire-init? false)) - (when (= active-demo :rain) (set rain-init? false)) - (when (= active-demo :snow) (set snow-init? false))) - (pxl8.transition_destroy transition) - (set transition nil))) + (when (not (menu.is-paused)) + (set time (+ time dt)) - (when (pxl8.key_pressed "1") (switch-demo :logo)) - (when (pxl8.key_pressed "2") (switch-demo :plasma)) - (when (pxl8.key_pressed "3") (switch-demo :tunnel)) - (when (pxl8.key_pressed "4") (switch-demo :raster)) - (when (pxl8.key_pressed "5") (switch-demo :fire)) - (when (pxl8.key_pressed "6") (switch-demo :rain)) - (when (pxl8.key_pressed "7") (switch-demo :snow)) - (when (pxl8.key_pressed "8") (switch-demo :cube3d)) - (when (pxl8.key_pressed "9") (switch-demo :worldgen)) - (when (pxl8.key_pressed "=") - (set use-famicube-palette? (not use-famicube-palette?)) - (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) - (pxl8.load_palette palette-path)) + (when transition + (pxl8.transition_update transition dt) + (when (pxl8.transition_is_complete transition) + (when transition-pending + (when (and (= active-demo :worldgen) (not= transition-pending :worldgen)) + (pxl8.set_relative_mouse_mode false)) + (set active-demo transition-pending) + (set transition-pending nil) + (when (= active-demo :fire) (set fire-init? false)) + (when (= active-demo :rain) (set rain-init? false)) + (when (= active-demo :snow) (set snow-init? false))) + (pxl8.transition_destroy transition) + (set transition nil))) - (case active-demo - :logo (do - (set logo-x (+ logo-x (* logo-dx dt))) - (set logo-y (+ logo-y (* logo-dy dt))) - (when (or (< logo-x 0) (> logo-x 512)) - (set logo-dx (- logo-dx))) - (when (or (< logo-y 0) (> logo-y 296)) - (set logo-dy (- logo-dy)))) - :cube3d (cube3d.update dt) - :worldgen (worldgen.update dt)) + (when (pxl8.key_pressed "1") (switch-demo :logo)) + (when (pxl8.key_pressed "2") (switch-demo :plasma)) + (when (pxl8.key_pressed "3") (switch-demo :tunnel)) + (when (pxl8.key_pressed "4") (switch-demo :raster)) + (when (pxl8.key_pressed "5") (switch-demo :fire)) + (when (pxl8.key_pressed "6") (switch-demo :rain)) + (when (pxl8.key_pressed "7") (switch-demo :snow)) + (when (pxl8.key_pressed "8") (switch-demo :worldgen)) + (when (pxl8.key_pressed "=") + (set use-famicube-palette? (not use-famicube-palette?)) + (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) + (pxl8.load_palette palette-path)) - (when particles - (pxl8.particles_update particles dt)))) + (case active-demo + :logo (do + (set logo-x (+ logo-x (* logo-dx dt))) + (set logo-y (+ logo-y (* logo-dy dt))) + (when (or (< logo-x 0) (> logo-x 512)) + (set logo-dx (- logo-dx))) + (when (or (< logo-y 0) (> logo-y 296)) + (set logo-dy (- logo-dy)))) + :worldgen (worldgen.update dt)) + + (when particles + (pxl8.particles_update particles dt))) + + (when (menu.is-paused) + (menu.update)))) (global frame (fn [] (case active-demo @@ -122,11 +128,12 @@ (set snow-init? true)) (pxl8.particles_render particles))) - :cube3d (cube3d.frame) - :worldgen (worldgen.frame) _ (pxl8.clear 0)) (when transition - (pxl8.transition_render transition)))) + (pxl8.transition_render transition)) + + (when (menu.is-paused) + (menu.draw)))) diff --git a/demo/mod/cube3d.fnl b/demo/mod/cube3d.fnl deleted file mode 100644 index a6ec01e..0000000 --- a/demo/mod/cube3d.fnl +++ /dev/null @@ -1,251 +0,0 @@ -(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? false) -(var wireframe? true) -(var time 0) -(var zoom 5.0) -(var texture-id nil) -(var use-texture? false) -(var affine? 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-accumulator 0) -(var fps-frame-count 0) - -(fn init [] - (set angle-x 0) - (set angle-y 0) - (set angle-z 0) - (set auto-rotate? true) - (set orthographic? false) - (set wireframe? true) - (set time 0) - (set zoom 5.0) - (set use-texture? false) - (set affine? false) - (set cam-x 0) - (set cam-y 2) - (set cam-z 12) - (set cam-yaw 0) - (set cam-pitch -0.2) - (set show-debug-ui? false) - (set fps 0) - (set fps-accumulator 0) - (set fps-frame-count 0) - (set texture-id (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) - (pxl8.upload_atlas)) - -(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)] - (when (and (not= wheel-y 0) (not (pxl8.ui_has_mouse_focus))) - (if orthographic? - (set zoom (math.max 0.5 (math.min (- zoom (* wheel-y 0.2)) 10.0))) - (let [zoom-speed 0.5 - 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 - zoom-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)] - - (if orthographic? - (do - (when (pxl8.key_down "w") - (set zoom (math.max 0.5 (- zoom (* zoom-speed dt))))) - (when (pxl8.key_down "s") - (set zoom (math.min 10.0 (+ zoom (* zoom-speed dt))))) - (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))))) - (do - (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 (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 draw-cube [pos scale rotation-offset] - (let [[x y z] pos - model (-> (pxl8.mat4_translate x y z) - (pxl8.mat4_multiply (pxl8.mat4_rotate_z (+ angle-z (* rotation-offset 0.7)))) - (pxl8.mat4_multiply (pxl8.mat4_rotate_y (+ angle-y (* rotation-offset 1.3)))) - (pxl8.mat4_multiply (pxl8.mat4_rotate_x (+ angle-x rotation-offset))) - (pxl8.mat4_multiply (pxl8.mat4_scale scale scale scale)))] - (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))))))) - -(fn frame [] - (pxl8.clear 0) - - (pxl8.clear_zbuffer) - (pxl8.set_affine_textures affine?) - (pxl8.set_backface_culling true) - (pxl8.set_wireframe wireframe?) - - (if orthographic? - (let [size zoom - 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]))) - - (draw-cube [0 0 0] 1.0 0) - (draw-cube [-3 0 -4] 0.8 0.5) - (draw-cube [3 1 -7] 0.9 1.2) - (draw-cube [0 -2 -10] 1.1 -0.7) - - (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?})] - (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 (not= new-state.affine nil) (set affine? new-state.affine)))) - -{:init init - :update update - :frame frame} diff --git a/demo/mod/debug_ui.fnl b/demo/mod/debug_ui.fnl deleted file mode 100644 index 424edd4..0000000 --- a/demo/mod/debug_ui.fnl +++ /dev/null @@ -1,28 +0,0 @@ -(local pxl8 (require :pxl8)) - -(fn render [state] - (var new-state {}) - (when state.show-debug-ui - (let [window-open (pxl8.ui_window_begin "Debug Menu (F8)" 10 10 256 128)] - (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 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/mod/menu.fnl b/demo/mod/menu.fnl new file mode 100644 index 0000000..09ea01d --- /dev/null +++ b/demo/mod/menu.fnl @@ -0,0 +1,51 @@ +(local pxl8 (require :pxl8)) + +(var paused false) + +(fn show [] + (set paused true) + (pxl8.set_relative_mouse_mode false) + (pxl8.center_cursor)) + +(fn hide [] + (set paused false) + (pxl8.set_relative_mouse_mode true)) + +(fn toggle [] + (if paused + (hide) + (show))) + +(fn update [] + (let [(mx my) (pxl8.get_mouse_pos)] + (pxl8.gui_cursor_move mx my)) + + (when (pxl8.mouse_pressed 1) + (pxl8.gui_cursor_down)) + + (when (pxl8.mouse_released 1) + (pxl8.gui_cursor_up))) + +(fn draw [] + (pxl8.gui_begin_frame) + + (pxl8.gui_window 200 100 240 140 "pxl8 demo") + + (when (pxl8.gui_button 1 215 145 210 32 "Resume") + (hide)) + + (when (pxl8.gui_button 2 215 185 210 32 "Quit") + (pxl8.quit)) + + (if (pxl8.gui_is_hovering) + (pxl8.set_cursor :hand) + (pxl8.set_cursor :arrow)) + + (pxl8.gui_end_frame)) + +{:is-paused (fn [] paused) + :toggle toggle + :show show + :hide hide + :update update + :draw draw} diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index b05d013..43a447a 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -70,11 +70,6 @@ (pxl8.error (.. "Failed to apply textures - result: " result)))))))) (fn update [dt] - (pxl8.set_relative_mouse_mode mouse-look?) - - (when (pxl8.key_pressed "escape") - (set mouse-look? (not mouse-look?))) - (when (pxl8.key_pressed "`") (set auto-run? (not auto-run?))) diff --git a/pxl8.sh b/pxl8.sh index adb1612..4380b92 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -82,7 +82,7 @@ compile_source_file() { make_lib_dirs() { - mkdir -p lib/linenoise lib/fennel lib/microui/src lib/miniz + mkdir -p lib/linenoise lib/fennel lib/miniz } print_error() { @@ -216,17 +216,6 @@ update_luajit() { print_info "Updated LuaJIT (${version})" } -update_microui() { - print_info "Fetching microui" - - if curl -sL --max-time 5 -o lib/microui/src/microui.c https://raw.githubusercontent.com/rxi/microui/master/src/microui.c 2>/dev/null && \ - curl -sL --max-time 5 -o lib/microui/src/microui.h https://raw.githubusercontent.com/rxi/microui/master/src/microui.h 2>/dev/null; then - print_info "Updated microui" - else - print_error "Failed to download microui" - return 1 - fi -} update_miniz() { print_info "Fetching miniz" @@ -291,8 +280,7 @@ case "$COMMAND" in mkdir -p "$BUILDDIR" mkdir -p "$BINDIR" - if [[ ! -f "lib/microui/src/microui.c" ]] || \ - [[ ! -d "lib/luajit" ]] || \ + if [[ ! -d "lib/luajit" ]] || \ [[ ! -f "lib/linenoise/linenoise.c" ]] || \ [[ ! -f "lib/miniz/miniz.c" ]] || \ [[ ! -f "lib/fennel/fennel.lua" ]]; then @@ -302,7 +290,6 @@ case "$COMMAND" in update_fennel update_linenoise update_luajit - update_microui update_miniz fi @@ -333,13 +320,13 @@ case "$COMMAND" in print_info "Compiler cache: ccache enabled" fi - INCLUDES="-Isrc -Ilib -Ilib/microui/src -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" + INCLUDES="-Isrc -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" COMPILE_FLAGS="$CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" EXECUTABLE="$BINDIR/pxl8" - LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/microui/src/microui.c lib/miniz/miniz.c" + LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c" PXL8_SOURCE_FILES=" src/pxl8.c @@ -359,7 +346,7 @@ case "$COMMAND" in src/pxl8_tilemap.c src/pxl8_tilesheet.c src/pxl8_transition.c - src/pxl8_ui.c + src/pxl8_gui.c src/pxl8_vfx.c src/pxl8_world.c " @@ -473,7 +460,6 @@ case "$COMMAND" in update_fennel update_linenoise update_luajit - update_microui update_miniz print_info "All dependencies updated" ;; diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index c1174a2..e2fa1f8 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -8,12 +8,12 @@ local particles = require("pxl8.particles") local tilemap = require("pxl8.tilemap") local gfx3d = require("pxl8.gfx3d") local math3d = require("pxl8.math") -local ui = require("pxl8.ui") +local gui = require("pxl8.gui") local world = require("pxl8.world") local transition = require("pxl8.transition") local anim = require("pxl8.anim") -core.init(_pxl8_gfx, _pxl8_input, _pxl8_sys, _pxl8_ui) +core.init(_pxl8_gfx, _pxl8_input, _pxl8_sys) local pxl8 = {} @@ -25,6 +25,7 @@ pxl8.warn = core.warn pxl8.error = core.error pxl8.debug = core.debug pxl8.trace = core.trace +pxl8.quit = core.quit pxl8.clear = gfx2d.clear pxl8.pixel = gfx2d.pixel @@ -51,6 +52,11 @@ pxl8.mouse_wheel_x = input.mouse_wheel_x pxl8.mouse_wheel_y = input.mouse_wheel_y pxl8.mouse_x = input.mouse_x pxl8.mouse_y = input.mouse_y +pxl8.get_mouse_pos = input.get_mouse_pos +pxl8.mouse_pressed = input.mouse_pressed +pxl8.mouse_released = input.mouse_released +pxl8.center_cursor = input.center_cursor +pxl8.set_cursor = input.set_cursor pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode pxl8.vfx_raster_bars = vfx.raster_bars @@ -116,15 +122,18 @@ pxl8.mat4_perspective = math3d.mat4_perspective pxl8.mat4_lookat = math3d.mat4_lookat pxl8.bounds = math3d.bounds -pxl8.ui_button = ui.button -pxl8.ui_checkbox = ui.checkbox -pxl8.ui_has_mouse_focus = ui.has_mouse_focus -pxl8.ui_indent = ui.indent -pxl8.ui_label = ui.label -pxl8.ui_layout_row = ui.layout_row -pxl8.ui_window_begin = ui.window_begin -pxl8.ui_window_end = ui.window_end -pxl8.ui_window_set_open = ui.window_set_open +pxl8.gui_state_create = gui.state_create +pxl8.gui_state_destroy = gui.state_destroy +pxl8.gui_begin_frame = gui.begin_frame +pxl8.gui_end_frame = gui.end_frame +pxl8.gui_cursor_move = gui.cursor_move +pxl8.gui_cursor_down = gui.cursor_down +pxl8.gui_cursor_up = gui.cursor_up +pxl8.gui_button = gui.button +pxl8.gui_window = gui.window +pxl8.gui_label = gui.label +pxl8.gui_is_hovering = gui.is_hovering +pxl8.gui_get_cursor_pos = gui.get_cursor_pos pxl8.world_new = world.new pxl8.world_destroy = world.destroy diff --git a/src/lua/pxl8/core.lua b/src/lua/pxl8/core.lua index d80e434..1ca9d9e 100644 --- a/src/lua/pxl8/core.lua +++ b/src/lua/pxl8/core.lua @@ -3,11 +3,10 @@ local C = ffi.C local core = {} -function core.init(gfx_ptr, input_ptr, sys_ptr, ui_ptr) +function core.init(gfx_ptr, input_ptr, sys_ptr) core.gfx = gfx_ptr core.input = input_ptr core.sys = sys_ptr - core.ui = ui_ptr end function core.get_fps() @@ -42,4 +41,8 @@ function core.trace(msg) C.pxl8_lua_trace(msg) end +function core.quit() + C.pxl8_set_running(core.sys, false) +end + return core diff --git a/src/lua/pxl8/gui.lua b/src/lua/pxl8/gui.lua new file mode 100644 index 0000000..5e1dcb9 --- /dev/null +++ b/src/lua/pxl8/gui.lua @@ -0,0 +1,59 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local gui = {} + +local state = nil + +local function gui_state() + if not state then + state = ffi.gc(C.pxl8_gui_state_create(), C.pxl8_gui_state_destroy) + end + return state +end + +function gui.begin_frame() + C.pxl8_gui_begin_frame(gui_state()) +end + +function gui.end_frame() + C.pxl8_gui_end_frame(gui_state()) +end + +function gui.cursor_move(x, y) + C.pxl8_gui_cursor_move(gui_state(), x, y) +end + +function gui.cursor_down() + C.pxl8_gui_cursor_down(gui_state()) +end + +function gui.cursor_up() + C.pxl8_gui_cursor_up(gui_state()) +end + +function gui.button(id, x, y, w, h, label) + return C.pxl8_gui_button(gui_state(), core.gfx, id, x, y, w, h, label) +end + +function gui.window(x, y, w, h, title) + C.pxl8_gui_window(core.gfx, x, y, w, h, title) +end + +function gui.label(x, y, text, color) + C.pxl8_gui_label(core.gfx, x, y, text, color) +end + +function gui.is_hovering() + return C.pxl8_gui_is_hovering(gui_state()) +end + +function gui.get_cursor_pos() + local x = ffi.new("i32[1]") + local y = ffi.new("i32[1]") + C.pxl8_gui_get_cursor_pos(gui_state(), x, y) + return x[0], y[0] +end + +return gui diff --git a/src/lua/pxl8/input.lua b/src/lua/pxl8/input.lua index 416c86b..855e1aa 100644 --- a/src/lua/pxl8/input.lua +++ b/src/lua/pxl8/input.lua @@ -40,8 +40,36 @@ function input.mouse_dy() return C.pxl8_mouse_dy(core.input) end +function input.get_mouse_pos() + return C.pxl8_mouse_x(core.input), C.pxl8_mouse_y(core.input) +end + +function input.mouse_pressed(button) + return C.pxl8_mouse_pressed(core.input, button) +end + +function input.mouse_released(button) + return C.pxl8_mouse_released(core.input, button) +end + function input.set_relative_mouse_mode(enabled) C.pxl8_set_relative_mouse_mode(core.sys, enabled) end +function input.center_cursor() + C.pxl8_center_cursor(core.sys) +end + +function input.set_cursor(cursor_type) + local cursor_enum + if cursor_type == "arrow" then + cursor_enum = C.PXL8_CURSOR_ARROW + elseif cursor_type == "hand" then + cursor_enum = C.PXL8_CURSOR_HAND + else + cursor_enum = C.PXL8_CURSOR_ARROW + end + C.pxl8_set_cursor(core.sys, cursor_enum) +end + return input diff --git a/src/lua/pxl8/ui.lua b/src/lua/pxl8/ui.lua deleted file mode 100644 index 63ce2a3..0000000 --- a/src/lua/pxl8/ui.lua +++ /dev/null @@ -1,52 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local ui = {} - -function ui.button(label) - return C.pxl8_ui_button(core.ui, label) -end - -function ui.checkbox(label, state) - local state_ptr = ffi.new("bool[1]", state) - local changed = C.pxl8_ui_checkbox(core.ui, label, state_ptr) - return changed, state_ptr[0] -end - -function ui.has_mouse_focus() - return C.pxl8_ui_has_mouse_focus(core.ui) -end - -function ui.indent(amount) - C.pxl8_ui_indent(core.ui, amount) -end - -function ui.label(text) - C.pxl8_ui_label(core.ui, text) -end - -function ui.layout_row(item_count, widths, height) - local widths_array = widths - if type(widths) == "table" then - widths_array = ffi.new("int[?]", #widths, widths) - elseif type(widths) == "number" then - widths_array = ffi.new("int[1]", widths) - end - C.pxl8_ui_layout_row(core.ui, item_count, widths_array, height) -end - -function 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(core.ui, title, rect, options or 0) -end - -function ui.window_end() - C.pxl8_ui_window_end(core.ui) -end - -function ui.window_set_open(title, open) - C.pxl8_ui_window_set_open(core.ui, title, open) -end - -return ui diff --git a/src/pxl8.c b/src/pxl8.c index 94cbf13..d8b92f4 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -16,7 +16,6 @@ #include "pxl8_script.h" #include "pxl8_sys.h" #include "pxl8_types.h" -#include "pxl8_ui.h" struct pxl8 { pxl8_cart* cart; @@ -127,12 +126,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return PXL8_ERROR_INITIALIZATION_FAILED; } - game->ui = pxl8_ui_create(game->gfx); - if (!game->ui) { - pxl8_error("Failed to create UI"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - game->script = pxl8_script_create(); if (!game->script) { pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(game->script)); @@ -170,7 +163,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { pxl8_script_set_gfx(game->script, game->gfx); pxl8_script_set_input(game->script, &game->input); pxl8_script_set_sys(game->script, sys); - pxl8_script_set_ui(game->script, game->ui); if (game->script_path[0] != '\0') { pxl8_result result = pxl8_script_load_main(game->script, game->script_path); @@ -252,34 +244,6 @@ pxl8_result pxl8_update(pxl8* sys) { } } - if (game->ui) { - pxl8_ui_input_mousemove(game->ui, game->input.mouse_x, game->input.mouse_y); - - if (game->input.mouse_wheel_x != 0 || game->input.mouse_wheel_y != 0) { - pxl8_ui_input_scroll(game->ui, game->input.mouse_wheel_x * 10, -game->input.mouse_wheel_y * 10); - } - - for (i32 i = 0; i < 3; i++) { - if (game->input.mouse_buttons_pressed[i]) { - pxl8_ui_input_mousedown(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1); - } - if (game->input.mouse_buttons_released[i]) { - pxl8_ui_input_mouseup(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1); - } - } - - for (i32 key = 0; key < 256; key++) { - if (game->input.keys_pressed[key]) { - pxl8_ui_input_keydown(game->ui, key); - } - if (!game->input.keys_down[key] && game->input.keys_pressed[key]) { - pxl8_ui_input_keyup(game->ui, key); - } - } - - pxl8_ui_frame_begin(game->ui); - } - if (game->script_loaded) { pxl8_script_call_function_f32(game->script, "update", dt); } @@ -315,10 +279,6 @@ pxl8_result pxl8_frame(pxl8* sys) { pxl8_size render_size = pxl8_get_resolution_dimensions(game->resolution); - if (game->ui) { - pxl8_ui_frame_end(game->ui); - } - pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_size.w, render_size.h)); pxl8_gfx_upload_framebuffer(game->gfx); pxl8_gfx_upload_atlas(game->gfx); @@ -332,8 +292,6 @@ pxl8_result pxl8_frame(pxl8* sys) { game->input.mouse_dy = 0; game->input.mouse_wheel_x = 0; game->input.mouse_wheel_y = 0; - game->input.mouse_x = 0; - game->input.mouse_y = 0; game->frame_count++; @@ -360,7 +318,6 @@ void pxl8_quit(pxl8* sys) { pxl8_gfx_destroy(game->gfx); pxl8_script_destroy(game->script); - if (game->ui) pxl8_ui_destroy(game->ui); } bool pxl8_is_running(const pxl8* sys) { @@ -389,6 +346,16 @@ pxl8_resolution pxl8_get_resolution(const pxl8* sys) { return (sys && sys->game) ? sys->game->resolution : PXL8_RESOLUTION_640x360; } +void pxl8_center_cursor(pxl8* sys) { + if (!sys || !sys->hal || !sys->hal->center_cursor) return; + sys->hal->center_cursor(sys->platform_data); +} + +void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor) { + if (!sys || !sys->hal || !sys->hal->set_cursor) return; + sys->hal->set_cursor(sys->platform_data, cursor); +} + void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled) { if (!sys || !sys->hal || !sys->hal->set_relative_mouse_mode) return; sys->hal->set_relative_mouse_mode(sys->platform_data, enabled); diff --git a/src/pxl8_game.h b/src/pxl8_game.h index c2fb2ff..28d5732 100644 --- a/src/pxl8_game.h +++ b/src/pxl8_game.h @@ -3,14 +3,12 @@ #include "pxl8_gfx.h" #include "pxl8_script.h" #include "pxl8_types.h" -#include "pxl8_ui.h" typedef struct pxl8_game { pxl8_color_mode color_mode; pxl8_gfx* gfx; pxl8_resolution resolution; pxl8_script* script; - pxl8_ui* ui; i32 frame_count; u64 last_time; diff --git a/src/pxl8_gui.c b/src/pxl8_gui.c new file mode 100644 index 0000000..fb691a1 --- /dev/null +++ b/src/pxl8_gui.c @@ -0,0 +1,131 @@ +#include "pxl8_gui.h" + +#include +#include + +pxl8_gui_state* pxl8_gui_state_create(void) { + pxl8_gui_state* state = (pxl8_gui_state*)malloc(sizeof(pxl8_gui_state)); + if (!state) return NULL; + + memset(state, 0, sizeof(pxl8_gui_state)); + return state; +} + +void pxl8_gui_state_destroy(pxl8_gui_state* state) { + if (!state) return; + free(state); +} + +void pxl8_gui_begin_frame(pxl8_gui_state* state) { + if (!state) return; + state->hot_id = 0; +} + +void pxl8_gui_end_frame(pxl8_gui_state* state) { + if (!state) return; + + if (!state->cursor_down) { + state->active_id = 0; + } + state->cursor_clicked = false; +} + +void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) { + if (!state) return; + state->cursor_x = x; + state->cursor_y = y; +} + +void pxl8_gui_cursor_down(pxl8_gui_state* state) { + if (!state) return; + state->cursor_down = true; +} + +void pxl8_gui_cursor_up(pxl8_gui_state* state) { + if (!state) return; + state->cursor_down = false; + state->cursor_clicked = true; +} + +static bool is_cursor_over(const pxl8_gui_state* state, i32 x, i32 y, i32 w, i32 h) { + return state->cursor_x >= x && state->cursor_x < (x + w) && + state->cursor_y >= y && state->cursor_y < (y + h); +} + +bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label) { + if (!state || !gfx || !label) return false; + + bool cursor_over = is_cursor_over(state, x, y, w, h); + bool is_hot = (state->hot_id == id); + bool is_active = (state->active_id == id); + + if (cursor_over) { + state->hot_id = id; + } + + if (cursor_over && state->cursor_down && state->active_id == 0) { + state->active_id = id; + } + + bool clicked = is_active && state->cursor_clicked && cursor_over; + if (clicked) { + state->active_id = 0; + } + + u8 bg_color; + u8 border_color; + i32 offset_x = 0; + i32 offset_y = 0; + + if (is_active) { + bg_color = 4; + border_color = 3; + offset_x = 1; + offset_y = 1; + } else if (is_hot || cursor_over) { + bg_color = 4; + border_color = 8; + } else { + bg_color = 3; + border_color = 4; + } + + pxl8_rect_fill(gfx, x, y, w, h, bg_color); + pxl8_rect(gfx, x, y, w, h, border_color); + + i32 text_len = (i32)strlen(label); + i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x; + i32 text_y = y + (h / 2) - 5 + offset_y; + pxl8_text(gfx, label, text_x, text_y, 6); + + return clicked; +} + +void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) { + if (!gfx || !title) return; + + pxl8_rect_fill(gfx, x, y, w, 28, 1); + pxl8_rect_fill(gfx, x, y + 28, w, h - 28, 2); + pxl8_rect(gfx, x, y, w, h, 4); + pxl8_rect_fill(gfx, x, y + 28, w, 1, 4); + + i32 title_x = x + 10; + i32 title_y = y + (28 / 2) - 5; + pxl8_text(gfx, title, title_x, title_y, 8); +} + +void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) { + if (!gfx || !text) return; + pxl8_text(gfx, text, x, y, color); +} + +bool pxl8_gui_is_hovering(const pxl8_gui_state* state) { + if (!state) return false; + return state->hot_id != 0; +} + +void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y) { + if (!state) return; + if (x) *x = state->cursor_x; + if (y) *y = state->cursor_y; +} diff --git a/src/pxl8_gui.h b/src/pxl8_gui.h new file mode 100644 index 0000000..31af817 --- /dev/null +++ b/src/pxl8_gui.h @@ -0,0 +1,38 @@ +#pragma once + +#include "pxl8_gfx.h" +#include "pxl8_types.h" + +typedef struct { + i32 cursor_x; + i32 cursor_y; + bool cursor_down; + bool cursor_clicked; + u32 hot_id; + u32 active_id; +} pxl8_gui_state; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_gui_state* pxl8_gui_state_create(void); +void pxl8_gui_state_destroy(pxl8_gui_state* state); + +void pxl8_gui_begin_frame(pxl8_gui_state* state); +void pxl8_gui_end_frame(pxl8_gui_state* state); + +void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y); +void pxl8_gui_cursor_down(pxl8_gui_state* state); +void pxl8_gui_cursor_up(pxl8_gui_state* state); + +bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label); +void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title); +void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color); + +bool pxl8_gui_is_hovering(const pxl8_gui_state* state); +void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y); + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_hal.h b/src/pxl8_hal.h index 9006116..2b6363a 100644 --- a/src/pxl8_hal.h +++ b/src/pxl8_hal.h @@ -12,7 +12,9 @@ typedef struct pxl8_hal { u64 (*get_ticks)(void); + void (*center_cursor)(void* platform_data); void (*present)(void* platform_data); + void (*set_cursor)(void* platform_data, pxl8_cursor cursor); void (*set_relative_mouse_mode)(void* platform_data, bool enabled); void (*upload_atlas)(void* platform_data, const pxl8_atlas* atlas, const u32* palette, pxl8_color_mode mode); diff --git a/src/pxl8_io.c b/src/pxl8_io.c index 75d515c..1353f5b 100644 --- a/src/pxl8_io.c +++ b/src/pxl8_io.c @@ -170,6 +170,16 @@ i32 pxl8_mouse_wheel_y(const pxl8_input_state* input) { return input->mouse_wheel_y; } +bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button) { + if (!input || button < 1 || button > 3) return false; + return input->mouse_buttons_pressed[button - 1]; +} + +bool pxl8_mouse_released(const pxl8_input_state* input, i32 button) { + if (!input || button < 1 || button > 3) return false; + return input->mouse_buttons_released[button - 1]; +} + i32 pxl8_mouse_x(const pxl8_input_state* input) { if (!input) return 0; return input->mouse_x; diff --git a/src/pxl8_io.h b/src/pxl8_io.h index e11efdc..c0a2cd3 100644 --- a/src/pxl8_io.h +++ b/src/pxl8_io.h @@ -100,6 +100,8 @@ 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); +bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button); +bool pxl8_mouse_released(const pxl8_input_state* input, i32 button); 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); diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 260a268..158c01a 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -13,13 +13,12 @@ #include #include "pxl8_macros.h" -#include "pxl8_ui.h" +#include "pxl8_gui.h" struct pxl8_script { lua_State* L; pxl8_gfx* gfx; pxl8_input_state* input; - pxl8_ui* ui; char last_error[2048]; char main_path[256]; char watch_dir[256]; @@ -109,13 +108,19 @@ static const char* pxl8_ffi_cdefs = "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" +"bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);\n" +"bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);\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" "int pxl8_mouse_dx(const pxl8_input_state* input);\n" "int pxl8_mouse_dy(const pxl8_input_state* input);\n" +"typedef enum { PXL8_CURSOR_ARROW = 0, PXL8_CURSOR_HAND = 1 } pxl8_cursor;\n" +"void pxl8_center_cursor(pxl8* sys);\n" +"void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);\n" "void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);\n" +"void pxl8_set_running(pxl8* sys, bool running);\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" @@ -303,32 +308,19 @@ static const char* pxl8_ffi_cdefs = "void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" "void pxl8_world_unload(pxl8_world* world);\n" "\n" -"typedef struct pxl8_ui pxl8_ui;\n" -"typedef struct { unsigned char bg_color; unsigned int sprite_id; int corner_size; int edge_size; int padding; } pxl8_frame_theme;\n" -"typedef struct { bool enabled; const char* label; } pxl8_menu_item;\n" -"pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx);\n" -"void pxl8_ui_destroy(pxl8_ui* ui);\n" -"void pxl8_ui_frame_begin(pxl8_ui* ui);\n" -"void pxl8_ui_frame_end(pxl8_ui* ui);\n" -"void pxl8_ui_input_keydown(pxl8_ui* ui, int key);\n" -"void pxl8_ui_input_keyup(pxl8_ui* ui, int key);\n" -"void pxl8_ui_input_mousedown(pxl8_ui* ui, int x, int y, int button);\n" -"void pxl8_ui_input_mousemove(pxl8_ui* ui, int x, int y);\n" -"void pxl8_ui_input_mouseup(pxl8_ui* ui, int x, int y, int button);\n" -"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" -"bool pxl8_ui_has_mouse_focus(pxl8_ui* ui);\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"; +"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" +"pxl8_gui_state* pxl8_gui_state_create(void);\n" +"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n" +"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n" +"void pxl8_gui_end_frame(pxl8_gui_state* state);\n" +"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n" +"void pxl8_gui_cursor_down(pxl8_gui_state* state);\n" +"void pxl8_gui_cursor_up(pxl8_gui_state* state);\n" +"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n" +"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n" +"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n" +"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n" +"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"; void pxl8_lua_info(const char* msg) { pxl8_info("%s", msg); @@ -444,14 +436,6 @@ void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input) { } } -void pxl8_script_set_ui(pxl8_script* script, pxl8_ui* ui) { - if (!script) return; - script->ui = ui; - if (script->L && ui) { - lua_pushlightuserdata(script->L, ui); - lua_setglobal(script->L, "_pxl8_ui"); - } -} void pxl8_script_set_sys(pxl8_script* script, void* sys) { if (!script) return; @@ -719,16 +703,27 @@ static time_t get_latest_script_mod_time(const char* dir_path) { while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; - size_t len = strlen(entry->d_name); - bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) || - (len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0); + char full_path[512]; + snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name); - if (is_script) { - char full_path[512]; - snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name); - time_t mod_time = get_file_mod_time(full_path); - if (mod_time > latest) { - latest = mod_time; + struct stat st; + if (stat(full_path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + time_t subdir_time = get_latest_script_mod_time(full_path); + if (subdir_time > latest) { + latest = subdir_time; + } + } else { + size_t len = strlen(entry->d_name); + bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) || + (len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0); + + if (is_script) { + time_t mod_time = get_file_mod_time(full_path); + if (mod_time > latest) { + latest = mod_time; + } + } } } } diff --git a/src/pxl8_script.h b/src/pxl8_script.h index 813d756..40bd82d 100644 --- a/src/pxl8_script.h +++ b/src/pxl8_script.h @@ -2,7 +2,6 @@ #include "pxl8_gfx.h" #include "pxl8_types.h" -#include "pxl8_ui.h" typedef struct pxl8_script pxl8_script; typedef struct pxl8_script_repl pxl8_script_repl; @@ -20,7 +19,6 @@ void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx); void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input); void pxl8_script_set_sys(pxl8_script* script, void* sys); -void pxl8_script_set_ui(pxl8_script* script, pxl8_ui* ui); pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name); pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, f32 arg); diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c index 9550bc6..f51b617 100644 --- a/src/pxl8_sdl3.c +++ b/src/pxl8_sdl3.c @@ -367,11 +367,45 @@ static void sdl3_set_relative_mouse_mode(void* platform_data, bool enabled) { } } +static void sdl3_set_cursor(void* platform_data, pxl8_cursor cursor) { + if (!platform_data) return; + + SDL_SystemCursor sdl_cursor; + switch (cursor) { + case PXL8_CURSOR_ARROW: + sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT; + break; + case PXL8_CURSOR_HAND: + sdl_cursor = SDL_SYSTEM_CURSOR_POINTER; + break; + default: + sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT; + break; + } + + SDL_Cursor* cursor_obj = SDL_CreateSystemCursor(sdl_cursor); + if (cursor_obj) { + SDL_SetCursor(cursor_obj); + } +} + +static void sdl3_center_cursor(void* platform_data) { + if (!platform_data) return; + + pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; + i32 w, h; + if (SDL_GetWindowSize(ctx->window, &w, &h)) { + SDL_WarpMouseInWindow(ctx->window, w / 2, h / 2); + } +} + const pxl8_hal pxl8_hal_sdl3 = { .create = sdl3_create, .destroy = sdl3_destroy, .get_ticks = sdl3_get_ticks, + .center_cursor = sdl3_center_cursor, .present = sdl3_present, + .set_cursor = sdl3_set_cursor, .upload_atlas = sdl3_upload_atlas, .upload_framebuffer = sdl3_upload_framebuffer, .set_relative_mouse_mode = sdl3_set_relative_mouse_mode, diff --git a/src/pxl8_sys.h b/src/pxl8_sys.h index fb27ec0..f93c314 100644 --- a/src/pxl8_sys.h +++ b/src/pxl8_sys.h @@ -18,6 +18,8 @@ pxl8_gfx* pxl8_get_gfx(const pxl8* sys); pxl8_input_state* pxl8_get_input(const pxl8* sys); pxl8_resolution pxl8_get_resolution(const pxl8* sys); +void pxl8_center_cursor(pxl8* sys); +void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor); void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled); pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]); diff --git a/src/pxl8_types.h b/src/pxl8_types.h index 302aa1d..5aa6a38 100644 --- a/src/pxl8_types.h +++ b/src/pxl8_types.h @@ -28,6 +28,11 @@ typedef enum pxl8_color_mode { PXL8_COLOR_MODE_SNES } pxl8_color_mode; +typedef enum pxl8_cursor { + PXL8_CURSOR_ARROW, + PXL8_CURSOR_HAND +} pxl8_cursor; + typedef enum pxl8_resolution { PXL8_RESOLUTION_240x160, PXL8_RESOLUTION_320x180, diff --git a/src/pxl8_world.c b/src/pxl8_world.c index 38047f2..d34d081 100644 --- a/src/pxl8_world.c +++ b/src/pxl8_world.c @@ -59,7 +59,6 @@ pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_pro return result; } - pxl8_debug("World generation succeeded, setting loaded=true"); world->loaded = true; return PXL8_OK; }