Added lua for UI and fixed compile_commands.json in Makefile

main
noah metz 2026-07-13 16:21:52 -06:00
parent 62d3de16f7
commit efec93031f
9 changed files with 853 additions and 672 deletions

@ -1,8 +1,11 @@
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 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 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) APP_SOURCES = src/main.c $(ENGINE_SOURCES)
EDITOR_SOURCES = src/editor.c $(ENGINE_SOURCES) EDITOR_SOURCES = src/editor.c $(ENGINE_SOURCES)
TEST_SOURCES = test/hsv.c $(ENGINE_SOURCES) TEST_SOURCES = test/hsv.c $(ENGINE_SOURCES)
@ -40,10 +43,10 @@ CXX ?= clang++
.PHONY: all .PHONY: all
all: roleplay $(SPV_FILES) all: roleplay $(SPV_FILES)
roleplay: $(APP_OBJECTS) roleplay: $(APP_OBJECTS) | $(SPV_FILES)
$(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^
editor: $(EDITOR_OBJECTS) editor: $(EDITOR_OBJECTS) | $(SPV_FILES)
$(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^
test/test_hsv: $(TEST_OBJECTS) test/test_hsv: $(TEST_OBJECTS)
@ -59,6 +62,13 @@ test: test/test_hsv
%.o: %.c %.o: %.c
$(CC) $(CFLAGS) -c -o $@ $< $(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 .PHONY: clean clean_compdb run run_editor
clean: clean:
@ -69,7 +79,7 @@ clean:
clean_compdb: clean_compdb:
rm -rf .compdb rm -rf .compdb
rm compile_commands.json rm -f compile_commands.json
run: roleplay $(SPV_FILES) run: roleplay $(SPV_FILES)
./roleplay ./roleplay

@ -9,6 +9,10 @@
#include "ft2build.h" #include "ft2build.h"
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#define ANCHOR_TOP_LEFT 0 #define ANCHOR_TOP_LEFT 0
#define ANCHOR_TOP_RIGHT 1 #define ANCHOR_TOP_RIGHT 1
#define ANCHOR_BOTTOM_LEFT 2 #define ANCHOR_BOTTOM_LEFT 2
@ -146,68 +150,6 @@ typedef struct GPUContainerStruct {
typedef struct UIContextStruct UIContext; typedef struct UIContextStruct UIContext;
typedef struct ContainerStruct Container; 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 { struct ContainerStruct {
VkBuffer container[MAX_FRAMES_IN_FLIGHT]; VkBuffer container[MAX_FRAMES_IN_FLIGHT];
@ -221,8 +163,8 @@ struct ContainerStruct {
uint32_t layer_count; uint32_t layer_count;
Layer* layers; Layer* layers;
uint32_t callback_count; int script_env; // Lua registry ref for this container's environment table, 0 = no script
UICallbacks* callbacks; char* script_path;
}; };
typedef struct ContainerInputStruct { typedef struct ContainerInputStruct {
@ -234,8 +176,7 @@ typedef struct ContainerInputStruct {
uint32_t layer_count; uint32_t layer_count;
LayerInput* layers; LayerInput* layers;
uint32_t callback_count; const char* script_path; // path to .lua file, NULL for containers with no script
UICallbacks* callbacks;
} ContainerInput; } ContainerInput;
typedef struct GPUUIContextStruct { typedef struct GPUUIContextStruct {
@ -280,7 +221,8 @@ struct UIContextStruct {
FT_Library freetype; FT_Library freetype;
UICallbacks* active_callbacks; lua_State* lua;
Container* active_container; Container* active_container;
uint32_t active_layer; uint32_t active_layer;
uint32_t active_element; uint32_t active_element;

@ -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

@ -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)

@ -1,6 +1,5 @@
#include <stdio.h> #include <stdio.h>
#include "hex.h" #include "hex.h"
#include "hsv.h"
#include "engine.h" #include "engine.h"
#include "vulkan/vulkan_core.h" #include "vulkan/vulkan_core.h"
@ -48,12 +47,6 @@ struct EditorDataStruct {
uint32_t* selected_regions; uint32_t* selected_regions;
uint32_t* selected_hexes; uint32_t* selected_hexes;
uint32_t* selected_vertices; 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; 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 // TODO: Make load from current state instead of having a default state
VkResult color_ui(ClientContext* context) { VkResult color_ui(ClientContext* context) {
if(context_container(COLOR_PICK_CONTAINER_ID, &context->ui) != NULL) { if(context_container(COLOR_PICK_CONTAINER_ID, &context->ui) != NULL) {
@ -637,8 +251,6 @@ VkResult color_ui(ClientContext* context) {
}, },
}; };
VkResult result;
LayerInput layer = { LayerInput layer = {
.strings = strings, .strings = strings,
.num_strings = sizeof(strings)/sizeof(GPUString), .num_strings = sizeof(strings)/sizeof(GPUString),
@ -646,71 +258,6 @@ VkResult color_ui(ClientContext* context) {
.num_drawables = sizeof(drawables)/sizeof(GPUDrawable), .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 = { ContainerInput container = {
.layers = &layer, .layers = &layer,
.layer_count = 1, .layer_count = 1,
@ -718,13 +265,10 @@ VkResult color_ui(ClientContext* context) {
.id = COLOR_PICK_CONTAINER_ID, .id = COLOR_PICK_CONTAINER_ID,
.offset = {0, 0}, .offset = {0, 0},
.size = {190, 150}, .size = {190, 150},
.callbacks = callbacks, .script_path = "script/color_picker.lua",
.callback_count = sizeof(callbacks)/sizeof(UICallbacks),
}; };
VK_RESULT(load_container(&container, &context->render, &context->ui)); return 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);
} }
VkResult mode_string_ui(ClientContext* context) { VkResult mode_string_ui(ClientContext* context) {

@ -1,16 +1,17 @@
#include "engine.h" #include "engine.h"
#include "ui_lua.h"
#include "gpu.h" #include "gpu.h"
#include "draw.h" #include "draw.h"
void text_callback(GLFWwindow* window, unsigned int codepoint) { void text_callback(GLFWwindow* window, unsigned int codepoint) {
ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window); ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window);
if(context->ui.active_callbacks != NULL if(context->ui.active_container != NULL
&& context->ui.active_callbacks->text != NULL && ui_lua_dispatch_text(
&& context->ui.active_callbacks->text(
context->ui.active_container,
context->ui.active_callbacks->data,
&context->ui, &context->ui,
&context->render, &context->render,
context->ui.active_container,
context->ui.active_layer,
context->ui.active_element,
codepoint)) return; codepoint)) return;
if(context->app_text != NULL) context->app_text(context, codepoint); 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); ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window);
if(context->ui.active_callbacks != NULL if(context->ui.active_container != NULL
&& context->ui.active_callbacks->key != NULL && ui_lua_dispatch_key(
&& context->ui.active_callbacks->key(
context->ui.active_container,
context->ui.active_callbacks->data,
&context->ui, &context->ui,
&context->render, &context->render,
context->ui.active_container,
context->ui.active_layer,
context->ui.active_element,
key, action, mods)) return; key, action, mods)) return;
if(context->app_key != NULL) context->app_key(context, key, action, mods); 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]); 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; Container* container_ptr = context->ui.active_container;
GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element]; GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element];
vec2 element_pos = { vec2 element_pos = {
@ -64,11 +65,12 @@ void button_callback(GLFWwindow* window, int button, int action, int mods) {
}; };
if((point[0] <= 1 && point[0] >= 0 && point[1] <= 1 && point[1] >= 0) || action == GLFW_RELEASE) { if((point[0] <= 1 && point[0] >= 0 && point[1] <= 1 && point[1] >= 0) || action == GLFW_RELEASE) {
context->ui.active_callbacks->button( ui_lua_dispatch_button(
context->ui.active_container,
context->ui.active_callbacks->data,
&context->ui, &context->ui,
&context->render, &context->render,
container_ptr,
context->ui.active_layer,
context->ui.active_element,
point[0], point[0],
point[1], point[1],
button, button,
@ -82,24 +84,18 @@ 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)) { if(ui_intersect(cursor, &context->render, &context->ui, UI_EVENT_BUTTON, &container, &layer, &element, position)) {
Container* container_ptr = context_container(container, &context->ui); Container* container_ptr = context_container(container, &context->ui);
for(uint32_t c = 0; c < container_ptr->callback_count; c++) { if(ui_lua_dispatch_button(
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->ui,
&context->render, &context->render,
container_ptr,
layer,
element,
position[0], position[0],
position[1], position[1],
button, button,
action, action,
mods)) return; mods)) return;
} }
break;
}
}
}
if(context->app_button != NULL) context->app_button(context, cursor[0], cursor[1], button, action, mods); 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; vec2 position;
ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window); ClientContext* context = (ClientContext*)glfwGetWindowUserPointer(window);
if(context->ui.active_callbacks != NULL if(context->ui.active_container != NULL
&& context->ui.active_callbacks->scroll != NULL && ui_lua_dispatch_scroll(
&& context->ui.active_callbacks->scroll(
context->ui.active_container,
context->ui.active_callbacks->data,
&context->ui, &context->ui,
&context->render, &context->render,
context->ui.active_container,
context->ui.active_layer,
context->ui.active_element,
xoffset, yoffset)) return; xoffset, yoffset)) return;
if(ui_intersect( if(ui_intersect(
@ -131,21 +127,15 @@ void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
&element, &element,
position)) { position)) {
Container* container_ptr = context_container(container, &context->ui); Container* container_ptr = context_container(container, &context->ui);
for(uint32_t c = 0; c < container_ptr->callback_count; c++) { ui_lua_dispatch_scroll(
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->ui,
&context->render, &context->render,
container_ptr,
layer,
element,
xoffset, xoffset,
yoffset); yoffset);
} }
break;
}
}
}
if(context->app_scroll != NULL) context->app_scroll(context, 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[0] = xpos;
context->ui.cursor[1] = ypos; 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; Container* container_ptr = context->ui.active_container;
GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element]; GPUDrawable* drawable_ptr = &container_ptr->layers[context->ui.active_layer].drawables_buffer[context->ui.active_element];
vec2 element_pos = { vec2 element_pos = {
@ -172,32 +162,27 @@ void cursor_callback(GLFWwindow* window, double xpos, double ypos) {
drawable_ptr->size[0], drawable_ptr->size[0],
drawable_ptr->size[1], drawable_ptr->size[1],
}; };
if(context->ui.active_callbacks->cursor( if(ui_lua_dispatch_cursor(
context->ui.active_container,
context->ui.active_callbacks->data,
&context->ui, &context->ui,
&context->render, &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[0] - element_pos[0])/element_size[0],
(context->ui.cursor[1] - element_pos[1])/element_size[1])) return; (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)) { 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); Container* container_ptr = context_container(container, &context->ui);
for(uint32_t c = 0; c < container_ptr->callback_count; c++) { ui_lua_dispatch_cursor(
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->ui,
&context->render, &context->render,
container_ptr,
layer,
element,
position[0], position[0],
position[1]); position[1]);
} }
break;
}
}
}
if(context->app_cursor != NULL) context->app_cursor(context, xpos, ypos); if(context->app_cursor != NULL) context->app_cursor(context, xpos, ypos);
} }

@ -218,56 +218,22 @@ VkResult create_debug_messenger(
return VK_SUCCESS; return VK_SUCCESS;
} }
VkResult get_best_physical_device( int physical_device_score(VkPhysicalDevice 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; VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(devices[i], &properties); vkGetPhysicalDeviceProperties(device, &properties);
VkPhysicalDeviceFeatures features;
vkGetPhysicalDeviceFeatures(devices[i], &features);
switch(properties.deviceType) { switch(properties.deviceType) {
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
score += 100; return 100;
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
score += 50; return 50;
break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
score += 25; return 25;
break;
case VK_PHYSICAL_DEVICE_TYPE_CPU: case VK_PHYSICAL_DEVICE_TYPE_CPU:
score += 0; return 0;
break;
default: default:
continue; return -1;
}
if(score > top_score) {
top_score = score;
*device = devices[i];
}
} }
free(devices);
return VK_SUCCESS;
} }
VkResult create_logical_device( VkResult create_logical_device(
@ -443,6 +409,53 @@ VkResult create_logical_device(
return VK_SUCCESS; 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( VkResult create_memory_allocator(
VkInstance instance, VkInstance instance,
VkPhysicalDevice physical_device, 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(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(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)); VK_RESULT(create_memory_allocator(context->instance, context->physical_device, context->device, &context->allocator));

@ -1,4 +1,5 @@
#include "ui.h" #include "ui.h"
#include "ui_lua.h"
#include "gpu.h" #include "gpu.h"
#include "cglm/affine.h" #include "cglm/affine.h"
@ -258,6 +259,15 @@ VkResult unload_container(
context->containers[index].layer_count = 0; 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; return VK_SUCCESS;
} }
@ -304,12 +314,18 @@ VkResult load_container(
} }
context->containers[index].layer_count = container->layer_count; 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; context->containers[index].script_env = 0;
memcpy( context->containers[index].script_path = NULL;
context->containers[index].callbacks, if(container->script_path != NULL) {
container->callbacks, context->containers[index].script_path = strdup(container->script_path);
sizeof(UICallbacks)*container->callback_count); VK_RESULT(ui_lua_load_container(
context->lua,
&context->containers[index],
context,
gpu,
container->script_path));
}
return VK_SUCCESS; return VK_SUCCESS;
} }
@ -1178,6 +1194,13 @@ VkResult create_ui_context(
context->containers = malloc(max_containers*sizeof(Container)); context->containers = malloc(max_containers*sizeof(Container));
memset(context->containers, 0, 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)); VK_RESULT(load_font(0, "fonts/runescape.ttf", 16, VK_FALSE, gpu, context));
return VK_SUCCESS; return VK_SUCCESS;
@ -1317,32 +1340,30 @@ bool ui_intersect(
} }
void clear_active_element(UIContext* ui, RenderContext* gpu) { void clear_active_element(UIContext* ui, RenderContext* gpu) {
if(ui->active_callbacks != NULL && ui->active_callbacks->deselect != NULL) { // Clear the active fields before dispatching so a script calling
ui->active_callbacks->deselect(ui->active_container, ui->active_callbacks->data, ui, gpu); // ui.blur() inside on_deselect can't recurse forever
} Container* container = ui->active_container;
ui->active_callbacks = NULL; uint32_t layer = ui->active_layer;
uint32_t element = ui->active_element;
ui->active_container = NULL; ui->active_container = NULL;
ui->active_element = 0; 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) { 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); Container* container_ptr = context_container(container, ui);
if(container_ptr == NULL) { if(container_ptr == NULL) {
fprintf(stderr, "set_active_element passed invalid container id"); fprintf(stderr, "set_active_element passed invalid container id");
return; return;
} }
ui->active_container = container_ptr;
for(uint32_t c = 0; c < container_ptr->callback_count; c++) { ui->active_element = element;
if(container_ptr->callbacks[c].element == element && container_ptr->callbacks[c].layer == layer) { ui->active_layer = layer;
ui->active_callbacks = &container_ptr->callbacks[c]; ui->active_container = container_ptr;
break;
}
}
} }
void anchor_offset(RenderContext* gpu, Container* container, vec2 offset) { void anchor_offset(RenderContext* gpu, Container* container, vec2 offset) {

@ -0,0 +1,390 @@
#include "ui_lua.h"
#include "gpu.h"
#include "hsv.h"
#include "GLFW/glfw3.h"
#include <stddef.h>
#include <stdio.h>
#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);
}