From 84e0301ce53d3227e25581cd7514b1f4f03f1178 Mon Sep 17 00:00:00 2001 From: dlannan Date: Fri, 17 Oct 2025 19:11:10 +1030 Subject: [PATCH] Added nicer window handling at startup. Initial gui protocol working well. --- data/gui/actions/panels.lua | 13 ++ data/gui/panels/panels.lua | 29 ++++ data/gui/widgets/panels.lua | 27 ---- editor/editor.lua | 49 ++++--- editor/states/mainGui.lua | 160 ++++++++++++++++++++- engine/app/state_main.lua | 2 +- engine/state_engine.lua | 36 +++-- projects/examples/samples/nuklear_sapp.lua | 2 +- 8 files changed, 256 insertions(+), 62 deletions(-) create mode 100644 data/gui/actions/panels.lua create mode 100644 data/gui/panels/panels.lua delete mode 100644 data/gui/widgets/panels.lua diff --git a/data/gui/actions/panels.lua b/data/gui/actions/panels.lua new file mode 100644 index 0000000..fad3d65 --- /dev/null +++ b/data/gui/actions/panels.lua @@ -0,0 +1,13 @@ +-- Add your panel actions here. +local sapp = require("sokol_app") + + +local panel_actions = {} + +panel_actions["quit_app"] = function(event, element) + + sapp.sapp_quit() +end + + +return panel_actions \ No newline at end of file diff --git a/data/gui/panels/panels.lua b/data/gui/panels/panels.lua new file mode 100644 index 0000000..15d4ffa --- /dev/null +++ b/data/gui/panels/panels.lua @@ -0,0 +1,29 @@ +-- All gui ui elements will follow a std format (under development) +--- Primary goal: A formal gui protocol like svg or html that allows for clear gui creation and control. +local nk = sg +local panels = {} + +panels.windows = {} + +panels.windows.popup = { + type = "panel", + title = "Main", + size = {600, "auto"}, + pos = {"left", "top"}, -- Can be pixels or strings + color = { background = 0x000000ff, text = 0xffffffff, border = 0x888888ff }, + window_flags = bit.bor(nk.NK_WINDOW_TITLE, nk.NK_WINDOW_MOVABLE), + + layout = { + { + type = "row", height = 24, layout = { + { type = "label", text = "Some text", width = 0.1 }, + { type = "button", text = "Build Project", action = "build_project", width = "auto" }, + { type = "button", text = "Export", action = "export_assets", width = 200 }, + { type = "button", text = "Quit", action = "quit_app", width = "auto" }, + } + }, + } +} + + +return panels \ No newline at end of file diff --git a/data/gui/widgets/panels.lua b/data/gui/widgets/panels.lua deleted file mode 100644 index a7a0ca4..0000000 --- a/data/gui/widgets/panels.lua +++ /dev/null @@ -1,27 +0,0 @@ --- All gui ui elements will follow a std format (under development) ---- Primary goal: A formal gui protocol like svg or html that allows for clear gui creation and control. - -local panels = {} - -panels.windows = {} - -panels.windows.popup = { - title = "", - size = {500, 300}, - pos = { "center", "center"}, -- Can be pixels or strings - color = { background = 0x000000ff, text = 0xffffffff, border = 0x888888ff }, - - layout = { - { - type = "row", height = 100, children = { - { type = "label", text = "Some text in the row", width = "auto" }, - { type = "button", label = "Build Project", action = "build_project", width = "auto" }, - { type = "button", label = "Export", action = "export_assets", width = 200 }, - { type = "button", label = "Quit", action = "quit_app", width = "auto" }, - } - }, - } -} - - -return panels \ No newline at end of file diff --git a/editor/editor.lua b/editor/editor.lua index b8d8720..afc6a0c 100644 --- a/editor/editor.lua +++ b/editor/editor.lua @@ -18,8 +18,9 @@ local utils = require("utils") local ffi = require("ffi") -gSmgr = require("engine.utils.statemanager") +-- -------------------------------------------------------------------------------------- +local engineState = require("engine.state_engine") -- -------------------------------------------------------------------------------------- local shell32 = ffi.load("shell32") @@ -31,9 +32,6 @@ ffi.cdef[[ ]] local SW_MAXIMIZE = 3 --- -------------------------------------------------------------------------------------- - -local engineState = require("engine.state_engine") -- -------------------------------------------------------------------------------------- local enabled_profile = arg[1] == "-profile" @@ -49,6 +47,7 @@ end -- -------------------------------------------------------------------------------------- -- Grab some gui elements for testing local SmainGui = require("editor.states.mainGui") +engineState.queue( "mainGui", SmainGui, true ) -- -------------------------------------------------------------------------------------- @@ -95,25 +94,25 @@ local function init() print("Sokol Is Valid: "..tostring(sg.sg_isvalid())) - local hwnd = sapp.sapp_win32_get_hwnd() - ffi.C.ShowWindow(hwnd, SW_MAXIMIZE) + engineState.ready = nil + mainState.ctx = nil sapp.sapp_show_mouse(true) - sapp.sapp_set_window_title("Thunc Editor v") + sapp.sapp_set_window_title("Thunc Editor v"..PROJECT_VERSION) --- io.popen(cmd, "r") - ffi.C.Sleep(500) - -- Add the state to the statemanager and jump to it - gSmgr:Init() - gSmgr:CreateState("MainGui", SmainGui) - gSmgr:ChangeState("MainGui") + local hwnd = sapp.sapp_win32_get_hwnd() + ffi.C.ShowWindow(hwnd, SW_MAXIMIZE) end - -- -------------------------------------------------------------------------------------- local function input(event) + -- Only kick things off once the window is ready. + if(event.type == sapp.SAPP_EVENTTYPE_RESIZED) then + engineState.ready = true + end + engineState.input(event, {}) end @@ -128,18 +127,27 @@ local function frame() local dt = sapp.sapp_frame_duration() local ctx = nk.snk_new_frame() - - if(mainState.ctx == nil) then + if(engineState.ready == true and mainState.ctx == nil) then mainState.ctx = ctx - ErrorCheck( pcall( engineState.init ) ) - print("----------->>> Init") - nk.nk_style_show_cursor(ctx) - else + ErrorCheck( pcall( engineState.init, w, h ) ) + nk.nk_style_show_cursor(ctx) + elseif(mainState.ctx) then mainState.ctx = ctx end ErrorCheck( pcall( engineState.update, dt ) ) + -- // the sokol_gfx draw pass + local pass = ffi.new("sg_pass[1]") + pass[0].action.colors[0].load_action = sg.SG_LOADACTION_CLEAR + pass[0].action.colors[0].clear_value = { 0.25, 0.5, 0.7, 1.0 } + pass[0].swapchain = slib.sglue_swapchain() + sg.sg_begin_pass(pass) + + nk.snk_render(sapp.sapp_width(), sapp.sapp_height()) + sg.sg_end_pass() + sg.sg_commit() + -- Display frame stats in console. -- hutils.show_stats() end @@ -160,6 +168,7 @@ app_desc[0].cleanup_cb = cleanup app_desc[0].event_cb = input app_desc[0].width = width app_desc[0].height = height +app_desc[0].high_dpi = true app_desc[0].window_title = "editor - sokol" app_desc[0].fullscreen = false -- app_desc[0].icon.sokol_default = true diff --git a/editor/states/mainGui.lua b/editor/states/mainGui.lua index d2cce05..60e81ee 100644 --- a/editor/states/mainGui.lua +++ b/editor/states/mainGui.lua @@ -4,6 +4,7 @@ -- Decription: -- Runs most of the scene. +local sapp = require("sokol_app") local smgr = require("engine.utils.statemanager") local nk = sg local hmm = require("hmm") @@ -32,7 +33,8 @@ local follow = require("engine.libs.camera.follow") local socket = require("socket.core") local copas = require("copas") -local panels = require("data.gui.widgets.panels") +local panels = require("data.gui.panels.panels") +local panelactions = require("data.gui.actions.panels") ------------------------------------------------------------------------------------------------------------ @@ -49,6 +51,8 @@ function SmainGui:Init(wwidth, wheight) -- SmenuMain:Init(wwidth, wheight) -- SenvHandler:Init(wwidth, wheight) + print("Display: "..wwidth.." x "..wheight) + panels.windows.popup.parent = { size = { wwidth, wheight } } end ------------------------------------------------------------------------------------------------------------ @@ -93,6 +97,156 @@ end ------------------------------------------------------------------------------------------------------------ +local function get_parent_size( ele ) + + local size = { 1920, 1440 } + -- Calc size first since this may be used in positioning (like center etc) + if(ele.size) then + if(ele.size[1]) then + if(type(ele.size[1]) == "string") then + if(ele.size[1] == "auto") then + size[1] = ele.parent.size[1] or 1920 + end + else + size[1] = tonumber(ele.size[1]) or 100 + end + end + if(ele.size[2]) then + if(type(ele.size[2]) == "string") then + if(ele.size[2] == "auto") then + size[2] = ele.parent.size[2] or 1440 + end + else + size[2] = tonumber(ele.size[2]) or 100 + end + end + end + return size +end +------------------------------------------------------------------------------------------------------------ + +local function calc_row_geometry( ele ) + local esizes = {} + local autosize = 0 + local pwidth = get_parent_size(ele)[1] + local remainwidth = pwidth + for i,v in ipairs(ele.layout) do + if(v.width and type(v.width) == "number") then + if(v.width > 0.99) then + esizes[i] = v.width / pwidth + remainwidth = remainwidth - v.width + else + esizes[i] = v.width + remainwidth = remainwidth - v.width * pwidth + end + else + esizes[i] = 0 + autosize = autosize + 1 + end + end + local autosize = (remainwidth / pwidth) / autosize + for i,v in ipairs(ele.layout) do + if(esizes[i] == 0) then esizes[i] = autosize end + end + return esizes +end + +------------------------------------------------------------------------------------------------------------ + +local function get_geometry( ele, winrect ) + + local size = get_parent_size( ele ) + winrect[0].w, winrect[0].h = size[1], size[2] + if(ele.pos) then + if(ele.pos[1]) then + if(type(ele.pos[1]) == "string") then + if(ele.pos[1] == "center") then + local pwidth = ele.parent.size[1] or 1920 + winrect[0].x = pwidth * 0.5 - winrect[0].w * 0.5 + elseif(ele.pos[1] == "left") then + winrect[0].x = 0.0 + elseif(ele.pos[1] == "right") then + local pwidth = ele.parent.size[1] or 1920 + winrect[0].x = pwidth - winrect[0].w + end + + else + winrect[0].x = tonumber(ele.pos[1]) or 0 + end + end + if(ele.pos[2]) then + if(type(ele.pos[2]) == "string") then + if(ele.pos[2] == "center") then + local pheight = ele.parent.size[2] or 1440 + winrect[0].y = pheight * 0.5 - winrect[0].h * 0.5 + elseif(ele.pos[2] == "top") then + winrect[0].y = 0.0 + elseif(ele.pos[2] == "bottom") then + local pheight = ele.parent.size[2] or 1440 + winrect[0].y = pheight - winrect[0].h + end + else + winrect[0].y = tonumber(ele.pos[2]) or 0 + end + end + end +end + +------------------------------------------------------------------------------------------------------------ + +local function render_element( ctx, element ) + + if(element.type == "panel") then + element.winrect = element.winrect or ffi.new("struct nk_rect[1]", {{0, 0, 400, 600}}) + get_geometry(element, element.winrect) + if (nk.nk_begin(ctx, element.title or "", element.winrect[0], element.window_flags or 0) == true) then + if(element.layout) then + for i,v in ipairs(element.layout) do + v.parent = element + render_element(ctx, v) + end + end + end + nk.nk_end(ctx) + + elseif(element.type == "row") then + local row_height = 30 + local children = 1 + if(element.height) then row_height = tonumber(element.height) end + if(element.layout) then children = #element.layout end + nk.nk_layout_row_begin(ctx, nk.NK_DYNAMIC, row_height, children) + if(element.layout) then + local esizes = calc_row_geometry(element) + for i,v in ipairs(element.layout) do + v.parent = element + v.row_height = row_height + nk.nk_layout_row_push(ctx, esizes[i]) + render_element(ctx, v) + end + end + nk.nk_layout_row_end(ctx) + + elseif(element.type == "label") then + local alignment = nk.NK_TEXT_LEFT + if(element.align) then alignment = ALIGNMENT[element.align] or alignment end + nk.nk_label(ctx, element.text or "", alignment) + + elseif(element.type == "button") then + local styled_button = ffi.new("struct nk_style_button[1]", { ctx[0].style.button}) + -- Override rounded corners (can do other things here too - can also make your own button style) + styled_button[0].rounding = 0.0 + if( nk.nk_button_label_styled(ctx, styled_button, element.text or "Missing Text")) then + -- if (nk.nk_button_label(ctx, element.text or "Missing Text")) then + if(element.action) then + local action = panelactions[element.action] + if(action) then action("button", element) end + end + end + end +end + +------------------------------------------------------------------------------------------------------------ + function SmainGui:Update(mxi, myi, buttons) --nkgui:update() @@ -100,10 +254,10 @@ function SmainGui:Update(mxi, myi, buttons) if(self.do_screenshot) then self:TakeScreenshot() end - + -- SmenuMain:Update(mxi, myi, buttons) -- SenvHandler:Update(mxi, myi, buttons) - + render_element(mainState.ctx, panels.windows.popup) --nkgui:render() self.prevmxi = mxi diff --git a/engine/app/state_main.lua b/engine/app/state_main.lua index 7b5fc7b..c4a7662 100644 --- a/engine/app/state_main.lua +++ b/engine/app/state_main.lua @@ -45,7 +45,7 @@ end ------------------------------------------------------------------------------------------------------------ function mainState:Begin() - Sassets:Begin() + -- Sassets:Begin() -- bins.bin_add_func(bins.BTYPE_OPAQUE, RenderNuklear) end diff --git a/engine/state_engine.lua b/engine/state_engine.lua index 11b7e83..e5197f7 100644 --- a/engine/state_engine.lua +++ b/engine/state_engine.lua @@ -12,6 +12,7 @@ print("PROJECT_VERSION: "..PROJECT_VERSION) ------------------------------------------------------------------------------------------------------------ local sapp = require("sokol_app") +local tinsert = table.insert ------------------------------------------------------------------------------------------------------------ -- Global because states need to use it themselves @@ -26,6 +27,7 @@ local defaults = { } engineState = {} +engineState.queued = {} engineState.events = defaults engineState.sm = require("engine.utils.statemanager") @@ -35,31 +37,45 @@ engineState.sm = require("engine.utils.statemanager") local mainState = require("engine.app.state_main") local SengineRunner = require("engine.app.states.engineRunner") --- local SgeomModels = require("engine.app.states.geometry.models") +------------------------------------------------------------------------------------------------------------ +-- Queue a state for execution from beginning ----- States --- local SmainBG = require("engine.app.states.mainBG") +engineState.queue = function( name, state, startup ) + + if(name == nil or state == nil) then return end + tinsert(engineState.queued, { name = name, state = state, startup = startup } ) +end ------------------------------------------------------------------------------------------------------------ -- Register every state with the statemanager. -engineState.init = function( ) +engineState.init = function( w, h ) engineState.sm:Init() - -- engineState.sm:CreateState("MainBG", SmainBG) engineState.sm:CreateState("EngineRunner", SengineRunner) - -- engineState.sm:CreateState("MainState", mainState) + engineState.sm:CreateState("MainState", mainState) - -- engineState.sm:CreateState("GeomModels", SgeomModels) + engineState.sm:AddSibling("MainState", "EngineRunner") - -- engineState.sm:AddSibling("MainState", "EngineRunner") + -- Add any queued states + local startup = nil + for i, v in ipairs(engineState.queued) do + engineState.sm:CreateState(v.name, v.state) + if(v.startup) then startup = v.name end + + v.state:Init(w, h) + end -- Init some modules ready for kick off. - mainState:Init(sapp.sapp_widthf(), sapp.sapp_heightf()) - SengineRunner:Init(sapp.sapp_widthf(), sapp.sapp_heightf()) + mainState:Init(w, h) + SengineRunner:Init(w, h) -- This kicks things off! engineState.sm:ChangeState("EngineRunner") + + if(startup) then + engineState.sm:AddSibling(startup, "EngineRunner") + end end ------------------------------------------------------------------------------------------------------------ diff --git a/projects/examples/samples/nuklear_sapp.lua b/projects/examples/samples/nuklear_sapp.lua index ef772ce..310c7dc 100644 --- a/projects/examples/samples/nuklear_sapp.lua +++ b/projects/examples/samples/nuklear_sapp.lua @@ -1448,8 +1448,8 @@ local function draw_demo_ui(ctx) end nk.nk_tree_pop(ctx) end + nk.nk_end(ctx) end - nk.nk_end(ctx) return not nk.nk_window_is_closed(ctx, "Overview") end