From efec93031f1f13eaac34485f1d895a3bb578afff Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Mon, 13 Jul 2026 16:21:52 -0600 Subject: [PATCH] Added lua for UI and fixed compile_commands.json in Makefile --- client/Makefile | 20 +- client/include/ui.h | 76 +----- client/include/ui_lua.h | 74 ++++++ client/script/color_picker.lua | 204 +++++++++++++++ client/src/editor.c | 460 +-------------------------------- client/src/engine.c | 123 ++++----- client/src/gpu.c | 115 +++++---- client/src/ui.c | 63 +++-- client/src/ui_lua.c | 390 ++++++++++++++++++++++++++++ 9 files changed, 853 insertions(+), 672 deletions(-) create mode 100644 client/include/ui_lua.h create mode 100644 client/script/color_picker.lua create mode 100644 client/src/ui_lua.c diff --git a/client/Makefile b/client/Makefile index 007be3f..18875b8 100644 --- a/client/Makefile +++ b/client/Makefile @@ -1,8 +1,11 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -CFLAGS = -I $(ROOT_DIR)/include -I/usr/local/include -O0 -g -Wall -Wextra -MMD -MP +CFLAGS = -I $(ROOT_DIR)/include -I/usr/local/include -I/opt/homebrew/include -O0 -g -Wall -Wextra -MMD -MP LDFLAGS = -lfreetype -lz -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/homebrew/lib -ENGINE_SOURCES = src/engine.c src/draw.c src/ui.c src/gpu.c src/hex.c src/hsv.c lib/spng.c lib/vma.cpp +CFLAGS += $(shell pkg-config --cflags lua) +LDFLAGS += $(shell pkg-config --libs lua) + +ENGINE_SOURCES = src/engine.c src/draw.c src/ui.c src/ui_lua.c src/gpu.c src/hex.c src/hsv.c lib/spng.c lib/vma.cpp APP_SOURCES = src/main.c $(ENGINE_SOURCES) EDITOR_SOURCES = src/editor.c $(ENGINE_SOURCES) TEST_SOURCES = test/hsv.c $(ENGINE_SOURCES) @@ -40,10 +43,10 @@ CXX ?= clang++ .PHONY: all all: roleplay $(SPV_FILES) -roleplay: $(APP_OBJECTS) +roleplay: $(APP_OBJECTS) | $(SPV_FILES) $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^ -editor: $(EDITOR_OBJECTS) +editor: $(EDITOR_OBJECTS) | $(SPV_FILES) $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^ test/test_hsv: $(TEST_OBJECTS) @@ -59,6 +62,13 @@ test: test/test_hsv %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< +.PHONY: compdb +compdb: compile_commands.json + +compile_commands.json: Makefile + rm -f $(ALL_OBJECTS) + bear --output $@ -- $(MAKE) $(ALL_OBJECTS) + .PHONY: clean clean_compdb run run_editor clean: @@ -69,7 +79,7 @@ clean: clean_compdb: rm -rf .compdb - rm compile_commands.json + rm -f compile_commands.json run: roleplay $(SPV_FILES) ./roleplay diff --git a/client/include/ui.h b/client/include/ui.h index 08508f3..4556d58 100644 --- a/client/include/ui.h +++ b/client/include/ui.h @@ -9,6 +9,10 @@ #include "ft2build.h" #include FT_FREETYPE_H +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + #define ANCHOR_TOP_LEFT 0 #define ANCHOR_TOP_RIGHT 1 #define ANCHOR_BOTTOM_LEFT 2 @@ -146,68 +150,6 @@ typedef struct GPUContainerStruct { typedef struct UIContextStruct UIContext; typedef struct ContainerStruct Container; -typedef bool (*ui_text_callback)( - Container* container, - void* data, - UIContext* ui, - RenderContext* gpu, - unsigned int codepoint); - -typedef bool (*ui_key_callback)( - Container* container, - void* data, - UIContext* ui, - RenderContext* gpu, - int key, - int action, - int mods); - -typedef bool (*ui_button_callback)( - Container* container, - void* data, - UIContext* ui, - RenderContext* gpu, - float x, - float y, - int button, - int action, - int mods); - -typedef bool (*ui_scroll_callback)( - Container* container, - void* data, - UIContext* ui, - RenderContext* gpu, - double x, - double y); - -typedef bool (*ui_cursor_callback)( - Container* container, - void* data, - UIContext* ui, - RenderContext* gpu, - float x, - float y); - -typedef void (*ui_deselect_callback)( - Container* container, - void* data, - UIContext* ui, - RenderContext* gpu); - -typedef struct UICallbacksStruct { - uint32_t layer; - uint32_t element; - void* data; - - ui_text_callback text; - ui_key_callback key; - ui_button_callback button; - ui_scroll_callback scroll; - ui_cursor_callback cursor; - ui_deselect_callback deselect; -} UICallbacks; - struct ContainerStruct { VkBuffer container[MAX_FRAMES_IN_FLIGHT]; @@ -221,8 +163,8 @@ struct ContainerStruct { uint32_t layer_count; Layer* layers; - uint32_t callback_count; - UICallbacks* callbacks; + int script_env; // Lua registry ref for this container's environment table, 0 = no script + char* script_path; }; typedef struct ContainerInputStruct { @@ -234,8 +176,7 @@ typedef struct ContainerInputStruct { uint32_t layer_count; LayerInput* layers; - uint32_t callback_count; - UICallbacks* callbacks; + const char* script_path; // path to .lua file, NULL for containers with no script } ContainerInput; typedef struct GPUUIContextStruct { @@ -280,7 +221,8 @@ struct UIContextStruct { FT_Library freetype; - UICallbacks* active_callbacks; + lua_State* lua; + Container* active_container; uint32_t active_layer; uint32_t active_element; diff --git a/client/include/ui_lua.h b/client/include/ui_lua.h new file mode 100644 index 0000000..b958ad9 --- /dev/null +++ b/client/include/ui_lua.h @@ -0,0 +1,74 @@ +#ifndef UI_LUA_H +#define UI_LUA_H + +#include "ui.h" + +// Registers the `ui` global table and input constants in the Lua state +void ui_lua_register(lua_State* L); + +// Loads a script into a sandboxed environment, stores the env ref in c->script_env +VkResult ui_lua_load_container( + lua_State* L, + Container* c, + UIContext* ui, + RenderContext* gpu, + const char* path); + +// Called by the engine input path. Each returns true if the container's script +// defined the handler and it returned a truthy value (event consumed). +bool ui_lua_dispatch_button( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + float x, + float y, + int button, + int action, + int mods); + +bool ui_lua_dispatch_cursor( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + float x, + float y); + +bool ui_lua_dispatch_scroll( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + double x, + double y); + +bool ui_lua_dispatch_key( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + int key, + int action, + int mods); + +bool ui_lua_dispatch_text( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + unsigned int codepoint); + +bool ui_lua_dispatch_deselect( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element); + +#endif diff --git a/client/script/color_picker.lua b/client/script/color_picker.lua new file mode 100644 index 0000000..2573134 --- /dev/null +++ b/client/script/color_picker.lua @@ -0,0 +1,204 @@ +-- Color picker logic, ported from the C callbacks that used to live in editor.c +-- +-- Layer 0 elements: +-- 0 background +-- 1 sv square (HSV rect, hue in color channel 0) +-- 2 hue bar (HSV rect) +-- 3 sv select outline +-- 4 sv select (HSV rect) +-- 5 hex string input area +-- 6 hue select bar +-- 7-18 saved color slots + +local state = { + hsv = {0, 0, 0}, + rgb = {0, 0, 0, 1}, + saved = {}, + hex_string = "#000000FF", + hex_len = 8, +} +for i = 1, 12 do state.saved[i] = {0, 0, 0, 0} end + +local function update_hex_string() + state.hex_string = string.format("#%02X%02X%02X%02X", + math.floor(state.rgb[1]*255 + 0.5), + math.floor(state.rgb[2]*255 + 0.5), + math.floor(state.rgb[3]*255 + 0.5), + math.floor(state.rgb[4]*255 + 0.5)) + state.hex_len = 8 + ui.set_string(0, 0, state.hex_string) +end + +local function sv_pick(s, v) + s = math.max(0, math.min(1, s)) + v = math.max(0, math.min(1, v)) + state.hsv[2] = s + state.hsv[3] = v + + ui.set_pos(0, 4, s*130 - 2, 130 - v*130 - 2) + for corner = 0, 3 do + ui.set_corner_color(0, 4, corner, state.hsv[1], s, v, 1) + end + ui.set_pos(0, 3, s*130 - 3, 130 - v*130 - 3) + + state.rgb[1], state.rgb[2], state.rgb[3] = + ui.hsv_to_rgb(state.hsv[1], state.hsv[2], state.hsv[3]) + update_hex_string() +end + +local function hue_set(h) + h = math.max(0, math.min(1, h)) + state.hsv[1] = h + + -- sv square and sv select keep their per-corner s/v, only hue changes + for _, drawable in ipairs({1, 4}) do + for corner = 0, 3 do + local _, s, v, a = ui.get_corner_color(0, drawable, corner) + ui.set_corner_color(0, drawable, corner, h, s, v, a) + end + end + ui.set_pos(0, 6, 134, 2 + h*130) + + state.rgb[1], state.rgb[2], state.rgb[3] = + ui.hsv_to_rgb(state.hsv[1], state.hsv[2], state.hsv[3]) + update_hex_string() +end + +local function hex_string_highlight(value) + for corner = 0, 3 do + local r, g, _, _ = ui.get_corner_color(0, 5, corner) + ui.set_corner_color(0, 5, corner, r, g, value, value) + end +end + +local function set_saved(index, color) + state.saved[index] = {color[1], color[2], color[3], color[4]} + local element = 6 + index + for corner = 0, 3 do + local _, _, _, a = ui.get_corner_color(0, element, corner) + ui.set_corner_color(0, element, corner, color[1], color[2], color[3], a) + end +end + +function on_button(layer, element, x, y, button, action, mods) + if element == 1 then -- sv square + if action == PRESS and button == MOUSE_LEFT then + ui.focus(layer, element) + sv_pick(x, 1 - y) + elseif action == RELEASE and button == MOUSE_LEFT then + ui.blur() + end + return true + + elseif element == 2 then -- hue bar + if action == PRESS and button == MOUSE_LEFT then + ui.focus(layer, element) + hue_set(y) + elseif action == RELEASE and button == MOUSE_LEFT then + ui.blur() + end + return true + + elseif element == 5 then -- hex string input + if action == PRESS and button == MOUSE_LEFT then + ui.focus(layer, element) + hex_string_highlight(1) + end + return true + + elseif element >= 7 and element <= 18 then -- saved color slots + local index = element - 6 + if action == PRESS then + if button == MOUSE_LEFT then + local saved = state.saved[index] + state.rgb = {saved[1], saved[2], saved[3], saved[4]} + state.hsv[1], state.hsv[2], state.hsv[3] = + ui.rgb_to_hsv(state.rgb[1], state.rgb[2], state.rgb[3]) + hue_set(state.hsv[1]) + sv_pick(state.hsv[2], state.hsv[3]) + elseif button == MOUSE_RIGHT then + set_saved(index, state.rgb) + elseif button == MOUSE_MIDDLE then + set_saved(index, {0, 0, 0, 0}) + end + end + return true + end + + return false +end + +function on_cursor(layer, element, x, y) + if ui.is_focused(0, 1) then -- dragging the sv square + sv_pick(x, 1 - y) + return true + elseif ui.is_focused(0, 2) then -- dragging the hue bar + hue_set(y) + return true + end + return false +end + +function on_scroll(layer, element, x, y) + if element == 2 then -- hue bar + hue_set(state.hsv[1] + y*0.01) + return true + end + return false +end + +function on_key(layer, element, key, action, mods) + if element ~= 5 then return false end + + if action == PRESS then + if key == KEY_ESCAPE then + state.rgb[1], state.rgb[2], state.rgb[3] = + ui.hsv_to_rgb(state.hsv[1], state.hsv[2], state.hsv[3]) + update_hex_string() + ui.blur() + elseif key == KEY_ENTER then + local s = state.hex_string + state.rgb[1] = (tonumber(s:sub(2, 3), 16) or 0) / 255 + state.rgb[2] = (tonumber(s:sub(4, 5), 16) or 0) / 255 + state.rgb[3] = (tonumber(s:sub(6, 7), 16) or 0) / 255 + state.rgb[4] = (tonumber(s:sub(8, 9), 16) or 0) / 255 + state.hsv[1], state.hsv[2], state.hsv[3] = + ui.rgb_to_hsv(state.rgb[1], state.rgb[2], state.rgb[3]) + hue_set(state.hsv[1]) + sv_pick(state.hsv[2], state.hsv[3]) + ui.blur() + elseif key == KEY_BACKSPACE then + if state.hex_len > 0 then + state.hex_string = state.hex_string:sub(1, state.hex_len) + state.hex_len = state.hex_len - 1 + ui.set_string(0, 0, state.hex_string) + end + end + end + + -- consume all keys while the hex input is focused + return true +end + +function on_text(layer, element, codepoint) + if element ~= 5 then return false end + + if codepoint < 128 then + local ch = string.char(codepoint):upper() + if ch:match("[0-9A-F]") and state.hex_len < 8 then + state.hex_string = state.hex_string .. ch + state.hex_len = state.hex_len + 1 + ui.set_string(0, 0, state.hex_string) + end + end + return true +end + +function on_deselect(layer, element) + if element == 5 then + hex_string_highlight(0) + end +end + +-- runs once at container load +ui.set_string(0, 0, state.hex_string) diff --git a/client/src/editor.c b/client/src/editor.c index c4366ce..3276693 100644 --- a/client/src/editor.c +++ b/client/src/editor.c @@ -1,6 +1,5 @@ #include #include "hex.h" -#include "hsv.h" #include "engine.h" #include "vulkan/vulkan_core.h" @@ -48,12 +47,6 @@ struct EditorDataStruct { uint32_t* selected_regions; uint32_t* selected_hexes; uint32_t* selected_vertices; - - double current_hsv[3]; - vec4 current; - vec4 saved[12]; - char string[10]; - int string_len; }; @@ -127,385 +120,6 @@ uint32_t add_hex_region(ClientContext* context) { return i; } -void update_hex_string(Container* container, UIContext* ui, RenderContext* gpu, EditorData* data) { - snprintf(data->string, 10, "#%02X%02X%02X%02X", - (uint)rintf(data->current[0]*255), - (uint)rintf(data->current[1]*255), - (uint)rintf(data->current[2]*255), - (uint)rintf(data->current[3]*255)); - data->string_len = 8; - update_ui_string(data->string, container, 0, 0, ui, gpu); -} - -void sv_square_pick(Container* container, UIContext* ui, RenderContext* gpu, EditorData* data, float s, float v) { - if(s < 0) s = 0; - if(s > 1) s = 1; - - if(v < 0) v = 0; - if(v > 1) v = 1; - - data->current_hsv[1] = s; - data->current_hsv[2] = v; - - Layer* layer = &container->layers[0]; - GPUDrawable* select_outline = &layer->drawables_buffer[3]; - GPUDrawable* select = &layer->drawables_buffer[4]; - - select->pos[0] = s*130 - 2; - select->pos[1] = 130 - v*130 - 2; - - select->color[0][1] = s; - select->color[0][2] = v; - - select->color[1][1] = s; - select->color[1][2] = v; - - select->color[2][1] = s; - select->color[2][2] = v; - - select->color[3][1] = s; - select->color[3][2] = v; - - select_outline->pos[0] = s*130 - 3; - select_outline->pos[1] = 130 - v*130 - 3; - - add_transfers( - &select_outline->pos[0], - layer->drawables, - 3*sizeof(GPUDrawable) + offsetof(GPUDrawable, pos), - 1*sizeof(vec2), - gpu); - - add_transfers( - &select->pos[0], - layer->drawables, - 4*sizeof(GPUDrawable) + offsetof(GPUDrawable, pos), - 1*sizeof(vec2), - gpu); - - add_transfers( - &select->color[0], - layer->drawables, - 4*sizeof(GPUDrawable) + offsetof(GPUDrawable, color), - 4*sizeof(vec4), - gpu); - - hsv_to_rgb(data->current_hsv, data->current); - update_hex_string(container, ui, gpu, data); -} - -bool sv_square_button_callback(Container* container, void* data, UIContext* ui, RenderContext* gpu, float x, float y, int button, int action, int mods) { - (void)mods; - (void)x; - - if(action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_LEFT) { - set_active_element(COLOR_PICK_CONTAINER_ID, 0, 1, ui); - sv_square_pick(container, ui, gpu, data, x, 1-y); - } else if(action == GLFW_RELEASE && button == GLFW_MOUSE_BUTTON_LEFT) { - clear_active_element(ui, gpu); - } - - return true; -} - -bool sv_square_cursor_callback(Container* container, void* data, UIContext* ui, RenderContext* gpu, float x, float y) { - if(ui->active_element == 1 - && ui->active_layer == 0 - && ui->active_container == container) { - sv_square_pick(container, ui, gpu, data, x, 1-y); - } - - return true; -} - -void hue_bar_set(Container* container, UIContext* ui, RenderContext* gpu, EditorData* data, float y) { - if(y < 0) y = 0; - if(y > 1) y = 1; - - data->current_hsv[0] = y; - - Layer* layer = &container->layers[0]; - GPUDrawable* sv_square = &layer->drawables_buffer[1]; - GPUDrawable* sv_select = &layer->drawables_buffer[4]; - GPUDrawable* hue_select = &layer->drawables_buffer[6]; - - sv_square->color[0][0] = y; - sv_square->color[1][0] = y; - sv_square->color[2][0] = y; - sv_square->color[3][0] = y; - - sv_select->color[0][0] = y; - sv_select->color[1][0] = y; - sv_select->color[2][0] = y; - sv_select->color[3][0] = y; - - hue_select->pos[1] = 2 + y*130; - - add_transfers( - &sv_square->color[0], - layer->drawables, - 1*sizeof(GPUDrawable) + offsetof(GPUDrawable, color), - 4*sizeof(vec4), - gpu); - - add_transfers( - &sv_select->color[0], - layer->drawables, - 4*sizeof(GPUDrawable) + offsetof(GPUDrawable, color), - 4*sizeof(vec4), - gpu); - - add_transfers( - &hue_select->pos[1], - layer->drawables, - 6*sizeof(GPUDrawable) + offsetof(GPUDrawable, pos) + sizeof(float), - 1*sizeof(float), - gpu); - - hsv_to_rgb(data->current_hsv, data->current); - update_hex_string(container, ui, gpu, data); -} - -bool hue_bar_scroll_callback(Container* container, void* data, UIContext* ui, RenderContext* gpu, double x, double y) { - (void)x; - - hue_bar_set(container, ui, gpu, data, y*0.01 + container->layers[0].drawables_buffer[1].color[0][0]); - - return true; -} - -bool hue_bar_cursor_callback(Container* container, void*data, UIContext* ui, RenderContext* gpu, float x, float y) { - (void)x; - - if(ui->active_element == 2 - && ui->active_layer == 0 - && ui->active_container == container) { - hue_bar_set(container, ui, gpu, data, y); - } - - return true; -} - -bool hue_bar_button_callback(Container* container, void* data, UIContext* ui, RenderContext* gpu, float x, float y, int button, int action, int mods) { - (void)mods; - (void)x; - - if(action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_LEFT) { - set_active_element(COLOR_PICK_CONTAINER_ID, 0, 2, ui); - hue_bar_set(container, ui, gpu, data, y); - } else if(action == GLFW_RELEASE && button == GLFW_MOUSE_BUTTON_LEFT) { - clear_active_element(ui, gpu); - } - - return true; -} - -void hex_string_set_color(Container* container, RenderContext* gpu, float color) { - container->layers[0].drawables_buffer[5].color[0][2] = color; - container->layers[0].drawables_buffer[5].color[0][3] = color; - - container->layers[0].drawables_buffer[5].color[1][2] = color; - container->layers[0].drawables_buffer[5].color[1][3] = color; - - container->layers[0].drawables_buffer[5].color[2][2] = color; - container->layers[0].drawables_buffer[5].color[2][3] = color; - - container->layers[0].drawables_buffer[5].color[3][2] = color; - container->layers[0].drawables_buffer[5].color[3][3] = color; - - add_transfers( - &container->layers[0].drawables_buffer[5].color[0], - container->layers[0].drawables, - 5*sizeof(GPUDrawable) + offsetof(GPUDrawable, color), - 4*sizeof(vec4), - gpu); -} - -bool hex_string_text_callback(Container* container, void* ptr, UIContext* ui, RenderContext* gpu, unsigned int codepoint) { - EditorData* data = ptr; - - if(codepoint >= 'a' && codepoint <= 'f') { - codepoint += 'A' - 'a'; - } else if(!((codepoint >= 'A' && codepoint <= 'F') || (codepoint >= '0' && codepoint <= '9'))) { - return true; - } - - if(data->string_len < 8) { - data->string_len += 1; - data->string[data->string_len] = codepoint; - update_ui_string(data->string, container, 0, 0, ui, gpu); - } - - return true; -} - -bool hex_string_key_callback(Container* container, void* ptr, UIContext* ui, RenderContext* gpu, int key, int action, int mods) { - (void)mods; - EditorData* data = ptr; - char tmp[3]; - - if(action == GLFW_PRESS) { - switch(key) { - case GLFW_KEY_ESCAPE: - hsv_to_rgb(data->current_hsv, data->current); - update_hex_string(container, ui, gpu, data); - clear_active_element(ui, gpu); - break; - case GLFW_KEY_ENTER: - // TODO: validate hex string and reset or set hsv - tmp[0] = data->string[1]; - tmp[1] = data->string[2]; - tmp[2] = 0; - data->current[0] = strtol(tmp, NULL, 16)/255.0; - - tmp[0] = data->string[3]; - tmp[1] = data->string[4]; - tmp[2] = 0; - data->current[1] = strtol(tmp, NULL, 16)/255.0; - - tmp[0] = data->string[5]; - tmp[1] = data->string[6]; - tmp[2] = 0; - data->current[2] = strtol(tmp, NULL, 16)/255.0; - - tmp[0] = data->string[7]; - tmp[1] = data->string[8]; - tmp[2] = 0; - data->current[3] = strtol(tmp, NULL, 16)/255.0; - - rgb_to_hsv(data->current, data->current_hsv); - hue_bar_set(container, ui, gpu, data, data->current_hsv[0]); - sv_square_pick(container, ui, gpu, data, data->current_hsv[1], data->current_hsv[2]); - - clear_active_element(ui, gpu); - break; - case GLFW_KEY_BACKSPACE: - if(data->string_len > 0) { - data->string[data->string_len] = '\0'; - data->string_len -= 1; - update_ui_string(data->string, container, 0, 0, ui, gpu); - } - break; - } - } - - return true; -} - -bool hex_string_button_callback(Container* container, void* data, UIContext* ui, RenderContext* gpu, float x, float y, int button, int action, int mods) { - (void)data; - (void)mods; - (void)x; - (void)y; - - if(action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_LEFT) { - set_active_element(COLOR_PICK_CONTAINER_ID, 0, 5, ui); - hex_string_set_color(container, gpu, 1); - } - - return true; -} - -void hex_string_deselect_callback(Container* container, void* data, UIContext* ui, RenderContext* gpu) { - (void)data; - (void)ui; - hex_string_set_color(container, gpu, 0); -} - -void set_saved_color(Container* container, RenderContext* gpu, EditorData* data, uint32_t index, vec4 color) { - data->saved[index][0] = color[0]; - data->saved[index][1] = color[1]; - data->saved[index][2] = color[2]; - data->saved[index][3] = color[3]; - - Layer* layer = &container->layers[0]; - GPUDrawable* saved_color = &layer->drawables_buffer[7 + index]; - - saved_color->color[0][0] = data->current[0]; - saved_color->color[0][1] = data->current[1]; - saved_color->color[0][2] = data->current[2]; - - saved_color->color[1][0] = data->current[0]; - saved_color->color[1][1] = data->current[1]; - saved_color->color[1][2] = data->current[2]; - - saved_color->color[2][0] = data->current[0]; - saved_color->color[2][1] = data->current[1]; - saved_color->color[2][2] = data->current[2]; - - saved_color->color[3][0] = data->current[0]; - saved_color->color[3][1] = data->current[1]; - saved_color->color[3][2] = data->current[2]; - - add_transfers( - &saved_color->color[0], - layer->drawables, - (7+index)*sizeof(GPUDrawable) + offsetof(GPUDrawable, color), - 4*sizeof(vec4), - gpu); -} - -bool saved_color_button_callback( - Container* container, - void* ptr, - UIContext* ui, - RenderContext* gpu, - float x, float y, - int button, int action, int mods, - uint32_t index) { - (void)x; - (void)y; - (void)mods; - - EditorData* data = ptr; - - if(action == GLFW_PRESS) { - if(button == GLFW_MOUSE_BUTTON_LEFT) { - data->current[0] = data->saved[index][0]; - data->current[1] = data->saved[index][1]; - data->current[2] = data->saved[index][2]; - data->current[3] = data->saved[index][3]; - rgb_to_hsv(data->current, data->current_hsv); - hue_bar_set(container, ui, gpu, data, data->current_hsv[0]); - sv_square_pick(container, ui, gpu, data, data->current_hsv[1], data->current_hsv[2]); - update_hex_string(container, ui, gpu, data); - } else if(button == GLFW_MOUSE_BUTTON_RIGHT) { - set_saved_color(container, gpu, data, index, data->current); - } else if (button == GLFW_MOUSE_BUTTON_MIDDLE) { - vec4 clear = {0, 0, 0, 0}; - set_saved_color(container, gpu, data, index, clear); - } - } - - return true; -} - -#define SAVED_COLOR_BUTTON_CALLBACK(n) \ -bool saved_color_button_callback_##n(Container* container, void* data, UIContext* ui, RenderContext* gpu, float x, float y, int button, int action, int mods) { \ - return saved_color_button_callback(container, data, ui, gpu, x, y, button, action, mods, n); \ -} - -SAVED_COLOR_BUTTON_CALLBACK( 0); -SAVED_COLOR_BUTTON_CALLBACK( 1); -SAVED_COLOR_BUTTON_CALLBACK( 2); -SAVED_COLOR_BUTTON_CALLBACK( 3); -SAVED_COLOR_BUTTON_CALLBACK( 4); -SAVED_COLOR_BUTTON_CALLBACK( 5); -SAVED_COLOR_BUTTON_CALLBACK( 6); -SAVED_COLOR_BUTTON_CALLBACK( 7); -SAVED_COLOR_BUTTON_CALLBACK( 8); -SAVED_COLOR_BUTTON_CALLBACK( 9); -SAVED_COLOR_BUTTON_CALLBACK(10); -SAVED_COLOR_BUTTON_CALLBACK(11); - -#define SAVED_COLOR_CALLBACK_ENTRY(n) \ - { \ - .layer = 0, \ - .element = 7 + n, \ - .data = data, \ - .button = saved_color_button_callback_##n, \ - } - // TODO: Make load from current state instead of having a default state VkResult color_ui(ClientContext* context) { if(context_container(COLOR_PICK_CONTAINER_ID, &context->ui) != NULL) { @@ -637,8 +251,6 @@ VkResult color_ui(ClientContext* context) { }, }; - VkResult result; - LayerInput layer = { .strings = strings, .num_strings = sizeof(strings)/sizeof(GPUString), @@ -646,71 +258,6 @@ VkResult color_ui(ClientContext* context) { .num_drawables = sizeof(drawables)/sizeof(GPUDrawable), }; - EditorData* data = context->app_data; - data->string_len = 8; - data->string[0] = '#'; - data->string[1] = '0'; - data->string[2] = '0'; - data->string[3] = '0'; - data->string[4] = '0'; - data->string[5] = '0'; - data->string[6] = '0'; - data->string[7] = 'F'; - data->string[8] = 'F'; - data->string[9] = '\0'; - - data->current[0] = 0; - data->current[1] = 0; - data->current[2] = 0; - data->current[3] = 1; - - for(uint32_t i = 0; i < 12; i++) { - data->saved[i][0] = 0; - data->saved[i][1] = 0; - data->saved[i][2] = 0; - data->saved[i][3] = 1; - } - - - UICallbacks callbacks[] = { - { - .layer = 0, - .element = 1, - .data = context->app_data, - .button = sv_square_button_callback, - .cursor = sv_square_cursor_callback, - }, - { - .layer = 0, - .element = 2, - .data = context->app_data, - .button = hue_bar_button_callback, - .cursor = hue_bar_cursor_callback, - .scroll = hue_bar_scroll_callback, - }, - { - .layer = 0, - .element = 5, - .data = context->app_data, - .button = hex_string_button_callback, - .key = hex_string_key_callback, - .text = hex_string_text_callback, - .deselect = hex_string_deselect_callback, - }, - SAVED_COLOR_CALLBACK_ENTRY( 0), - SAVED_COLOR_CALLBACK_ENTRY( 1), - SAVED_COLOR_CALLBACK_ENTRY( 2), - SAVED_COLOR_CALLBACK_ENTRY( 3), - SAVED_COLOR_CALLBACK_ENTRY( 4), - SAVED_COLOR_CALLBACK_ENTRY( 5), - SAVED_COLOR_CALLBACK_ENTRY( 6), - SAVED_COLOR_CALLBACK_ENTRY( 7), - SAVED_COLOR_CALLBACK_ENTRY( 8), - SAVED_COLOR_CALLBACK_ENTRY( 9), - SAVED_COLOR_CALLBACK_ENTRY(10), - SAVED_COLOR_CALLBACK_ENTRY(11), - }; - ContainerInput container = { .layers = &layer, .layer_count = 1, @@ -718,13 +265,10 @@ VkResult color_ui(ClientContext* context) { .id = COLOR_PICK_CONTAINER_ID, .offset = {0, 0}, .size = {190, 150}, - .callbacks = callbacks, - .callback_count = sizeof(callbacks)/sizeof(UICallbacks), + .script_path = "script/color_picker.lua", }; - VK_RESULT(load_container(&container, &context->render, &context->ui)); - Container* c = context_container(COLOR_PICK_CONTAINER_ID, &context->ui); - return update_ui_string(data->string, c, 0, 0, &context->ui, &context->render); + return load_container(&container, &context->render, &context->ui); } VkResult mode_string_ui(ClientContext* context) { diff --git a/client/src/engine.c b/client/src/engine.c index f1a9094..c547b13 100644 --- a/client/src/engine.c +++ b/client/src/engine.c @@ -1,16 +1,17 @@ #include "engine.h" +#include "ui_lua.h" #include "gpu.h" #include "draw.h" void text_callback(GLFWwindow* window, unsigned int codepoint) { ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window); - if(context->ui.active_callbacks != NULL - && context->ui.active_callbacks->text != NULL - && context->ui.active_callbacks->text( - context->ui.active_container, - context->ui.active_callbacks->data, + if(context->ui.active_container != NULL + && ui_lua_dispatch_text( &context->ui, &context->render, + context->ui.active_container, + context->ui.active_layer, + context->ui.active_element, codepoint)) return; if(context->app_text != NULL) context->app_text(context, codepoint); @@ -22,13 +23,13 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window); - if(context->ui.active_callbacks != NULL - && context->ui.active_callbacks->key != NULL - && context->ui.active_callbacks->key( - context->ui.active_container, - context->ui.active_callbacks->data, + if(context->ui.active_container != NULL + && ui_lua_dispatch_key( &context->ui, &context->render, + context->ui.active_container, + context->ui.active_layer, + context->ui.active_element, key, action, mods)) return; if(context->app_key != NULL) context->app_key(context, key, action, mods); @@ -45,7 +46,7 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) { glfwGetCursorPos(window, &cursor[0], &cursor[1]); - if(context->ui.active_callbacks != NULL && context->ui.active_callbacks->button != NULL) { + if(context->ui.active_container != NULL && context->ui.active_container->script_env != 0) { Container* container_ptr = context->ui.active_container; GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element]; vec2 element_pos = { @@ -62,13 +63,14 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) { (cursor[0] - element_pos[0])/element_size[0], (cursor[1] - element_pos[1])/element_size[1], }; - + if((point[0] <= 1 && point[0] >= 0 && point[1] <= 1 && point[1] >= 0) || action == GLFW_RELEASE) { - context->ui.active_callbacks->button( - context->ui.active_container, - context->ui.active_callbacks->data, + ui_lua_dispatch_button( &context->ui, &context->render, + container_ptr, + context->ui.active_layer, + context->ui.active_element, point[0], point[1], button, @@ -82,23 +84,17 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) { if(ui_intersect(cursor, &context->render, &context->ui, UI_EVENT_BUTTON, &container, &layer, &element, position)) { Container* container_ptr = context_container(container, &context->ui); - for(uint32_t c = 0; c < container_ptr->callback_count; c++) { - if(container_ptr->callbacks[c].element == element) { - if(container_ptr->callbacks[c].button != NULL) { - if(container_ptr->callbacks[c].button( - container_ptr, - container_ptr->callbacks[c].data, - &context->ui, - &context->render, - position[0], - position[1], - button, - action, - mods)) return; - } - break; - } - } + if(ui_lua_dispatch_button( + &context->ui, + &context->render, + container_ptr, + layer, + element, + position[0], + position[1], + button, + action, + mods)) return; } if(context->app_button != NULL) context->app_button(context, cursor[0], cursor[1], button, action, mods); @@ -112,13 +108,13 @@ void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { vec2 position; ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window); - if(context->ui.active_callbacks != NULL - && context->ui.active_callbacks->scroll != NULL - && context->ui.active_callbacks->scroll( - context->ui.active_container, - context->ui.active_callbacks->data, + if(context->ui.active_container != NULL + && ui_lua_dispatch_scroll( &context->ui, &context->render, + context->ui.active_container, + context->ui.active_layer, + context->ui.active_element, xoffset, yoffset)) return; if(ui_intersect( @@ -131,20 +127,14 @@ void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { &element, position)) { Container* container_ptr = context_container(container, &context->ui); - for(uint32_t c = 0; c < container_ptr->callback_count; c++) { - if(container_ptr->callbacks[c].element == element) { - if(container_ptr->callbacks[c].scroll != NULL) { - container_ptr->callbacks[c].scroll( - container_ptr, - container_ptr->callbacks[c].data, - &context->ui, - &context->render, - xoffset, - yoffset); - } - break; - } - } + ui_lua_dispatch_scroll( + &context->ui, + &context->render, + container_ptr, + layer, + element, + xoffset, + yoffset); } if(context->app_scroll != NULL) context->app_scroll(context, xoffset, yoffset); @@ -160,7 +150,7 @@ void cursor_callback(GLFWwindow* window, double xpos, double ypos) { context->ui.cursor[0] = xpos; context->ui.cursor[1] = ypos; - if(context->ui.active_callbacks != NULL && context->ui.active_callbacks->cursor != NULL) { + if(context->ui.active_container != NULL && context->ui.active_container->script_env != 0) { Container* container_ptr = context->ui.active_container; GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element]; vec2 element_pos = { @@ -172,31 +162,26 @@ void cursor_callback(GLFWwindow* window, double xpos, double ypos) { drawable_ptr->size[0], drawable_ptr->size[1], }; - if(context->ui.active_callbacks->cursor( - context->ui.active_container, - context->ui.active_callbacks->data, + if(ui_lua_dispatch_cursor( &context->ui, &context->render, + container_ptr, + context->ui.active_layer, + context->ui.active_element, (context->ui.cursor[0] - element_pos[0])/element_size[0], (context->ui.cursor[1] - element_pos[1])/element_size[1])) return; } if(ui_intersect(context->ui.cursor, &context->render, &context->ui, UI_EVENT_CURSOR, &container, &layer, &element, position)) { Container* container_ptr = context_container(container, &context->ui); - for(uint32_t c = 0; c < container_ptr->callback_count; c++) { - if(container_ptr->callbacks[c].element == element) { - if(container_ptr->callbacks[c].cursor != NULL) { - container_ptr->callbacks[c].cursor( - container_ptr, - container_ptr->callbacks[c].data, - &context->ui, - &context->render, - position[0], - position[1]); - } - break; - } - } + ui_lua_dispatch_cursor( + &context->ui, + &context->render, + container_ptr, + layer, + element, + position[0], + position[1]); } if(context->app_cursor != NULL) context->app_cursor(context, xpos, ypos); diff --git a/client/src/gpu.c b/client/src/gpu.c index c8a9f7a..d826cc0 100644 --- a/client/src/gpu.c +++ b/client/src/gpu.c @@ -218,56 +218,22 @@ VkResult create_debug_messenger( return VK_SUCCESS; } -VkResult get_best_physical_device( - VkInstance instance, - VkPhysicalDevice* device) { - uint32_t device_count = 0; - VkResult result; - VK_RESULT(vkEnumeratePhysicalDevices(instance, &device_count, 0)); - - VkPhysicalDevice* devices = malloc(sizeof(VkPhysicalDevice)*device_count); - result = vkEnumeratePhysicalDevices(instance, &device_count, devices); - if(result != VK_SUCCESS) { - free(devices); - return result; - } - - int top_score = -1; - for(uint32_t i = 0; i < device_count; i++) { - int score = 0; - - VkPhysicalDeviceProperties properties; - vkGetPhysicalDeviceProperties(devices[i], &properties); - - VkPhysicalDeviceFeatures features; - vkGetPhysicalDeviceFeatures(devices[i], &features); - - switch(properties.deviceType) { - case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: - score += 100; - break; - case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: - score += 50; - break; - case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: - score += 25; - break; - case VK_PHYSICAL_DEVICE_TYPE_CPU: - score += 0; - break; - default: - continue; - } - - if(score > top_score) { - top_score = score; - *device = devices[i]; - } +int physical_device_score(VkPhysicalDevice device) { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device, &properties); + + switch(properties.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + return 100; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + return 50; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + return 25; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + return 0; + default: + return -1; } - - free(devices); - - return VK_SUCCESS; } VkResult create_logical_device( @@ -443,6 +409,53 @@ VkResult create_logical_device( return VK_SUCCESS; } +// Tries candidates from highest to lowest score, since a higher-scoring +// device (e.g. another ICD for the same GPU) may not satisfy the queue or +// feature requirements of create_logical_device +VkResult create_best_device( + VkInstance instance, + VkSurfaceKHR surface, + VkPhysicalDevice* physical_device, + GPUQueue* graphics_queue, + GPUQueue* present_queue, + GPUQueue* transfer_queue, + VkDevice* device) { + uint32_t device_count = 0; + VkResult result; + VK_RESULT(vkEnumeratePhysicalDevices(instance, &device_count, 0)); + + VkPhysicalDevice* devices = malloc(sizeof(VkPhysicalDevice)*device_count); + result = vkEnumeratePhysicalDevices(instance, &device_count, devices); + if(result != VK_SUCCESS) { + free(devices); + return result; + } + + while(1) { + int top_score = -1; + uint32_t best = 0xFFFFFFFF; + for(uint32_t i = 0; i < device_count; i++) { + if(devices[i] == VK_NULL_HANDLE) continue; + int score = physical_device_score(devices[i]); + if(score > top_score) { + top_score = score; + best = i; + } + } + if(best == 0xFFFFFFFF) break; + + if(create_logical_device(devices[best], surface, graphics_queue, present_queue, transfer_queue, device) == VK_SUCCESS) { + *physical_device = devices[best]; + free(devices); + return VK_SUCCESS; + } + devices[best] = VK_NULL_HANDLE; + } + + free(devices); + return VK_ERROR_INITIALIZATION_FAILED; +} + VkResult create_memory_allocator( VkInstance instance, VkPhysicalDevice physical_device, @@ -859,11 +872,9 @@ VkResult init_vulkan(GLFWwindow* window, RenderContext* context) { VK_RESULT(create_debug_messenger(context->instance, &context->debug_messenger)); - VK_RESULT(get_best_physical_device(context->instance, &context->physical_device)); - VK_RESULT(glfwCreateWindowSurface(context->instance, window, 0, &context->surface)); - VK_RESULT(create_logical_device(context->physical_device, context->surface, &context->graphics_queue, &context->present_queue, &context->transfer_queue, &context->device)); + VK_RESULT(create_best_device(context->instance, context->surface, &context->physical_device, &context->graphics_queue, &context->present_queue, &context->transfer_queue, &context->device)); VK_RESULT(create_memory_allocator(context->instance, context->physical_device, context->device, &context->allocator)); diff --git a/client/src/ui.c b/client/src/ui.c index 1243e7c..4f2668e 100644 --- a/client/src/ui.c +++ b/client/src/ui.c @@ -1,4 +1,5 @@ #include "ui.h" +#include "ui_lua.h" #include "gpu.h" #include "cglm/affine.h" @@ -258,6 +259,15 @@ VkResult unload_container( context->containers[index].layer_count = 0; + if(context->containers[index].script_env != 0) { + luaL_unref(context->lua, LUA_REGISTRYINDEX, context->containers[index].script_env); + context->containers[index].script_env = 0; + } + if(context->containers[index].script_path != NULL) { + free(context->containers[index].script_path); + context->containers[index].script_path = NULL; + } + return VK_SUCCESS; } @@ -304,12 +314,18 @@ VkResult load_container( } context->containers[index].layer_count = container->layer_count; - context->containers[index].callbacks = malloc(sizeof(UICallbacks)*container->callback_count); - context->containers[index].callback_count = container->callback_count; - memcpy( - context->containers[index].callbacks, - container->callbacks, - sizeof(UICallbacks)*container->callback_count); + + context->containers[index].script_env = 0; + context->containers[index].script_path = NULL; + if(container->script_path != NULL) { + context->containers[index].script_path = strdup(container->script_path); + VK_RESULT(ui_lua_load_container( + context->lua, + &context->containers[index], + context, + gpu, + container->script_path)); + } return VK_SUCCESS; } @@ -1178,6 +1194,13 @@ VkResult create_ui_context( context->containers = malloc(max_containers*sizeof(Container)); memset(context->containers, 0, max_containers*sizeof(Container)); + context->lua = luaL_newstate(); + if(context->lua == NULL) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + luaL_openlibs(context->lua); + ui_lua_register(context->lua); + VK_RESULT(load_font(0, "fonts/runescape.ttf", 16, VK_FALSE, gpu, context)); return VK_SUCCESS; @@ -1317,32 +1340,30 @@ bool ui_intersect( } void clear_active_element(UIContext* ui, RenderContext* gpu) { - if(ui->active_callbacks != NULL && ui->active_callbacks->deselect != NULL) { - ui->active_callbacks->deselect(ui->active_container, ui->active_callbacks->data, ui, gpu); - } - ui->active_callbacks = NULL; + // Clear the active fields before dispatching so a script calling + // ui.blur() inside on_deselect can't recurse forever + Container* container = ui->active_container; + uint32_t layer = ui->active_layer; + uint32_t element = ui->active_element; + ui->active_container = NULL; ui->active_element = 0; + + if(container != NULL) { + ui_lua_dispatch_deselect(ui, gpu, container, layer, element); + } } void set_active_element(uint32_t container, uint32_t layer, uint32_t element, UIContext* ui) { - ui->active_element = element; - ui->active_layer = layer; - ui->active_callbacks = NULL; - Container* container_ptr = context_container(container, ui); if(container_ptr == NULL) { fprintf(stderr, "set_active_element passed invalid container id"); return; } + + ui->active_element = element; + ui->active_layer = layer; ui->active_container = container_ptr; - - for(uint32_t c = 0; c < container_ptr->callback_count; c++) { - if(container_ptr->callbacks[c].element == element && container_ptr->callbacks[c].layer == layer) { - ui->active_callbacks = &container_ptr->callbacks[c]; - break; - } - } } void anchor_offset(RenderContext* gpu, Container* container, vec2 offset) { diff --git a/client/src/ui_lua.c b/client/src/ui_lua.c new file mode 100644 index 0000000..e8139ea --- /dev/null +++ b/client/src/ui_lua.c @@ -0,0 +1,390 @@ +#include "ui_lua.h" +#include "gpu.h" +#include "hsv.h" + +#include "GLFW/glfw3.h" + +#include +#include + +#define UI_LUA_CONTAINER "ui_container" +#define UI_LUA_CONTEXT "ui_context" +#define UI_LUA_GPU "ui_gpu" + +static void* registry_pointer(lua_State* L, const char* key) { + lua_getfield(L, LUA_REGISTRYINDEX, key); + void* ptr = lua_touserdata(L, -1); + lua_pop(L, 1); + return ptr; +} + +static void set_registry_pointers(lua_State* L, Container* c, UIContext* ui, RenderContext* gpu) { + lua_pushlightuserdata(L, c); + lua_setfield(L, LUA_REGISTRYINDEX, UI_LUA_CONTAINER); + lua_pushlightuserdata(L, ui); + lua_setfield(L, LUA_REGISTRYINDEX, UI_LUA_CONTEXT); + lua_pushlightuserdata(L, gpu); + lua_setfield(L, LUA_REGISTRYINDEX, UI_LUA_GPU); +} + +static GPUDrawable* check_drawable(lua_State* L, Container* c, uint32_t layer, uint32_t drawable) { + if(layer >= c->layer_count) { + luaL_error(L, "layer %d out of range", layer); + } + if(drawable >= c->layers[layer].data.num_drawables) { + luaL_error(L, "drawable %d out of range in layer %d", drawable, layer); + } + return &c->layers[layer].drawables_buffer[drawable]; +} + +// ui.set_pos(layer, drawable, x, y) +static int lua_ui_set_pos(lua_State* L) { + Container* container = registry_pointer(L, UI_LUA_CONTAINER); + RenderContext* gpu = registry_pointer(L, UI_LUA_GPU); + uint32_t layer = luaL_checkinteger(L, 1); + uint32_t drawable = luaL_checkinteger(L, 2); + GPUDrawable* d = check_drawable(L, container, layer, drawable); + + d->pos[0] = luaL_checknumber(L, 3); + d->pos[1] = luaL_checknumber(L, 4); + + add_transfers( + &d->pos, + container->layers[layer].drawables, + drawable*sizeof(GPUDrawable) + offsetof(GPUDrawable, pos), + sizeof(vec2), + gpu); + return 0; +} + +// ui.set_corner_color(layer, drawable, corner, r, g, b, a) +static int lua_ui_set_corner_color(lua_State* L) { + Container* container = registry_pointer(L, UI_LUA_CONTAINER); + RenderContext* gpu = registry_pointer(L, UI_LUA_GPU); + uint32_t layer = luaL_checkinteger(L, 1); + uint32_t drawable = luaL_checkinteger(L, 2); + uint32_t corner = luaL_checkinteger(L, 3); + if(corner > 3) { + return luaL_error(L, "corner %d out of range", corner); + } + GPUDrawable* d = check_drawable(L, container, layer, drawable); + + d->color[corner][0] = luaL_checknumber(L, 4); + d->color[corner][1] = luaL_checknumber(L, 5); + d->color[corner][2] = luaL_checknumber(L, 6); + d->color[corner][3] = luaL_checknumber(L, 7); + + add_transfers( + &d->color[corner], + container->layers[layer].drawables, + drawable*sizeof(GPUDrawable) + offsetof(GPUDrawable, color) + corner*sizeof(vec4), + sizeof(vec4), + gpu); + return 0; +} + +// ui.get_corner_color(layer, drawable, corner) -> r, g, b, a +static int lua_ui_get_corner_color(lua_State* L) { + Container* container = registry_pointer(L, UI_LUA_CONTAINER); + uint32_t layer = luaL_checkinteger(L, 1); + uint32_t drawable = luaL_checkinteger(L, 2); + uint32_t corner = luaL_checkinteger(L, 3); + if(corner > 3) { + return luaL_error(L, "corner %d out of range", corner); + } + GPUDrawable* d = check_drawable(L, container, layer, drawable); + + lua_pushnumber(L, d->color[corner][0]); + lua_pushnumber(L, d->color[corner][1]); + lua_pushnumber(L, d->color[corner][2]); + lua_pushnumber(L, d->color[corner][3]); + return 4; +} + +// ui.set_string(layer, string_index, text) +static int lua_ui_set_string(lua_State* L) { + Container* container = registry_pointer(L, UI_LUA_CONTAINER); + UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT); + RenderContext* gpu = registry_pointer(L, UI_LUA_GPU); + uint32_t layer = luaL_checkinteger(L, 1); + uint32_t string_index = luaL_checkinteger(L, 2); + const char* text = luaL_checkstring(L, 3); + if(layer >= container->layer_count) { + return luaL_error(L, "layer %d out of range", layer); + } + if(string_index >= container->layers[layer].data.max_strings) { + return luaL_error(L, "string %d out of range in layer %d", string_index, layer); + } + + update_ui_string(text, container, layer, string_index, ui, gpu); + return 0; +} + +// ui.focus(layer, element) +static int lua_ui_focus(lua_State* L) { + Container* container = registry_pointer(L, UI_LUA_CONTAINER); + UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT); + uint32_t layer = luaL_checkinteger(L, 1); + uint32_t element = luaL_checkinteger(L, 2); + + set_active_element(container->id, layer, element, ui); + return 0; +} + +// ui.blur() +static int lua_ui_blur(lua_State* L) { + UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT); + RenderContext* gpu = registry_pointer(L, UI_LUA_GPU); + + clear_active_element(ui, gpu); + return 0; +} + +// ui.is_focused(layer, element) -> bool +static int lua_ui_is_focused(lua_State* L) { + Container* container = registry_pointer(L, UI_LUA_CONTAINER); + UIContext* ui = registry_pointer(L, UI_LUA_CONTEXT); + uint32_t layer = luaL_checkinteger(L, 1); + uint32_t element = luaL_checkinteger(L, 2); + + lua_pushboolean(L, + ui->active_container == container + && ui->active_layer == layer + && ui->active_element == element); + return 1; +} + +// ui.hsv_to_rgb(h, s, v) -> r, g, b +static int lua_ui_hsv_to_rgb(lua_State* L) { + double hsv[3] = { + luaL_checknumber(L, 1), + luaL_checknumber(L, 2), + luaL_checknumber(L, 3), + }; + vec3 rgb; + hsv_to_rgb(hsv, rgb); + lua_pushnumber(L, rgb[0]); + lua_pushnumber(L, rgb[1]); + lua_pushnumber(L, rgb[2]); + return 3; +} + +// ui.rgb_to_hsv(r, g, b) -> h, s, v +static int lua_ui_rgb_to_hsv(lua_State* L) { + vec3 rgb = { + luaL_checknumber(L, 1), + luaL_checknumber(L, 2), + luaL_checknumber(L, 3), + }; + double hsv[3]; + rgb_to_hsv(rgb, hsv); + lua_pushnumber(L, hsv[0]); + lua_pushnumber(L, hsv[1]); + lua_pushnumber(L, hsv[2]); + return 3; +} + +void ui_lua_register(lua_State* L) { + static const luaL_Reg ui_funcs[] = { + {"set_pos", lua_ui_set_pos}, + {"set_corner_color", lua_ui_set_corner_color}, + {"get_corner_color", lua_ui_get_corner_color}, + {"set_string", lua_ui_set_string}, + {"focus", lua_ui_focus}, + {"blur", lua_ui_blur}, + {"is_focused", lua_ui_is_focused}, + {"hsv_to_rgb", lua_ui_hsv_to_rgb}, + {"rgb_to_hsv", lua_ui_rgb_to_hsv}, + {NULL, NULL}, + }; + luaL_newlib(L, ui_funcs); + lua_setglobal(L, "ui"); + + lua_pushinteger(L, GLFW_PRESS); lua_setglobal(L, "PRESS"); + lua_pushinteger(L, GLFW_RELEASE); lua_setglobal(L, "RELEASE"); + lua_pushinteger(L, GLFW_MOUSE_BUTTON_LEFT); lua_setglobal(L, "MOUSE_LEFT"); + lua_pushinteger(L, GLFW_MOUSE_BUTTON_RIGHT); lua_setglobal(L, "MOUSE_RIGHT"); + lua_pushinteger(L, GLFW_MOUSE_BUTTON_MIDDLE); lua_setglobal(L, "MOUSE_MIDDLE"); + lua_pushinteger(L, GLFW_KEY_ESCAPE); lua_setglobal(L, "KEY_ESCAPE"); + lua_pushinteger(L, GLFW_KEY_ENTER); lua_setglobal(L, "KEY_ENTER"); + lua_pushinteger(L, GLFW_KEY_BACKSPACE); lua_setglobal(L, "KEY_BACKSPACE"); +} + +VkResult ui_lua_load_container( + lua_State* L, + Container* c, + UIContext* ui, + RenderContext* gpu, + const char* path) { + + if(luaL_loadfile(L, path) != LUA_OK) { + fprintf(stderr, "ui_lua_load_container: %s\n", lua_tostring(L, -1)); + lua_pop(L, 1); + return VK_ERROR_UNKNOWN; + } + + // Sandboxed environment table: env = setmetatable({}, {__index = _G}), + // set as the chunk's _ENV so script globals land in env, not _G + lua_newtable(L); + lua_newtable(L); + lua_getglobal(L, "_G"); + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); + + lua_pushvalue(L, -1); + lua_setupvalue(L, -3, 1); + + // Ref the env first so the chunk (now at the top) can be called + c->script_env = luaL_ref(L, LUA_REGISTRYINDEX); + + // Top-level script code may call ui.* during execution + set_registry_pointers(L, c, ui, gpu); + + if(lua_pcall(L, 0, 0, 0) != LUA_OK) { + fprintf(stderr, "ui_lua_load_container: %s\n", lua_tostring(L, -1)); + lua_pop(L, 1); + luaL_unref(L, LUA_REGISTRYINDEX, c->script_env); + c->script_env = 0; + return VK_ERROR_UNKNOWN; + } + + return VK_SUCCESS; +} + +// Pushes the container's handler onto the stack, ready for args. +// Returns false if the container has no script or no such handler. +static bool ui_lua_begin_dispatch( + lua_State* L, + UIContext* ui, + RenderContext* gpu, + Container* c, + const char* handler) { + if(c == NULL || c->script_env == 0) return false; + + set_registry_pointers(L, c, ui, gpu); + + lua_rawgeti(L, LUA_REGISTRYINDEX, c->script_env); + lua_getfield(L, -1, handler); + lua_remove(L, -2); + if(!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + return true; +} + +// Calls the handler with nargs already on the stack, returns its boolean result +static bool ui_lua_finish_dispatch(lua_State* L, const char* handler, int nargs) { + if(lua_pcall(L, nargs, 1, 0) != LUA_OK) { + fprintf(stderr, "%s: %s\n", handler, lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + bool consumed = lua_toboolean(L, -1); + lua_pop(L, 1); + return consumed; +} + +bool ui_lua_dispatch_button( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + float x, + float y, + int button, + int action, + int mods) { + lua_State* L = ui->lua; + if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_button")) return false; + lua_pushinteger(L, layer); + lua_pushinteger(L, element); + lua_pushnumber(L, x); + lua_pushnumber(L, y); + lua_pushinteger(L, button); + lua_pushinteger(L, action); + lua_pushinteger(L, mods); + return ui_lua_finish_dispatch(L, "on_button", 7); +} + +bool ui_lua_dispatch_cursor( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + float x, + float y) { + lua_State* L = ui->lua; + if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_cursor")) return false; + lua_pushinteger(L, layer); + lua_pushinteger(L, element); + lua_pushnumber(L, x); + lua_pushnumber(L, y); + return ui_lua_finish_dispatch(L, "on_cursor", 4); +} + +bool ui_lua_dispatch_scroll( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + double x, + double y) { + lua_State* L = ui->lua; + if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_scroll")) return false; + lua_pushinteger(L, layer); + lua_pushinteger(L, element); + lua_pushnumber(L, x); + lua_pushnumber(L, y); + return ui_lua_finish_dispatch(L, "on_scroll", 4); +} + +bool ui_lua_dispatch_key( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + int key, + int action, + int mods) { + lua_State* L = ui->lua; + if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_key")) return false; + lua_pushinteger(L, layer); + lua_pushinteger(L, element); + lua_pushinteger(L, key); + lua_pushinteger(L, action); + lua_pushinteger(L, mods); + return ui_lua_finish_dispatch(L, "on_key", 5); +} + +bool ui_lua_dispatch_text( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element, + unsigned int codepoint) { + lua_State* L = ui->lua; + if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_text")) return false; + lua_pushinteger(L, layer); + lua_pushinteger(L, element); + lua_pushinteger(L, codepoint); + return ui_lua_finish_dispatch(L, "on_text", 3); +} + +bool ui_lua_dispatch_deselect( + UIContext* ui, + RenderContext* gpu, + Container* c, + uint32_t layer, + uint32_t element) { + lua_State* L = ui->lua; + if(!ui_lua_begin_dispatch(L, ui, gpu, c, "on_deselect")) return false; + lua_pushinteger(L, layer); + lua_pushinteger(L, element); + return ui_lua_finish_dispatch(L, "on_deselect", 2); +}