From 8d2793e9ef7cf7f742ec23e77c44ac21a624d01f Mon Sep 17 00:00:00 2001 From: iamcheeseman Date: Wed, 13 May 2026 21:24:21 -0400 Subject: start ui --- dc/dc.c | 20 +++- platform/gl/gl.c | 56 ++++++---- teensy/teensy.h | 22 +++- teensy/teensy_context.c | 5 + teensy/teensy_list.h | 32 +++--- teensy/teensy_math.c | 19 ++++ teensy/teensy_mem.c | 2 +- teensy/teensy_renderer.c | 4 +- teensy/teensy_ui.c | 277 +++++++++++++++++++++++++++++++++++++++++++++++ teensy/teensy_ui.h | 42 +++++++ 10 files changed, 426 insertions(+), 53 deletions(-) create mode 100644 teensy/teensy_math.c create mode 100644 teensy/teensy_ui.c create mode 100644 teensy/teensy_ui.h diff --git a/dc/dc.c b/dc/dc.c index b90cca7..863c06b 100644 --- a/dc/dc.c +++ b/dc/dc.c @@ -2,6 +2,7 @@ #include #include +#include #include #define QOI_IMPLEMENTATION @@ -14,6 +15,7 @@ ty_Image img; ty_Font font = {}; +tyui_Id winid = 0; double last_frame = 0; @@ -115,13 +117,19 @@ void tick(void) "^_`{}|~:;<>=? " ); y += 8; - ty_draw_text(&font, ty_vec2i(5, y), - "THe_value = MY_arr[15]" - ); - y += 8; - ty_draw_end(); + if (tyui_begin_window("Test Window", ty_recti(5, 5, 100, 100), &winid)) { + for (int i = 0; i < 20; i++) + tyui_text("super duper awesome example text"); + tyui_end_window(); + } + + tyui_draw(); + ty_Vec2i mouse_pos = ty_mouse_pos(); + ty_draw_rect(ty_recti(mouse_pos.x, mouse_pos.y, 1, 1), TY_COLOR_RED); + + ty_draw_end(); } ty_Image load_qoi_image(const char *path) @@ -145,6 +153,7 @@ int main(void) .ticrate = 60, }; ty_init(hints); + tyui_init(&font); img = load_qoi_image("test_img.qoi"); @@ -191,6 +200,7 @@ int main(void) ty_free_image(glyph); } + tyui_deinit(); ty_deinit(); return 0; } diff --git a/platform/gl/gl.c b/platform/gl/gl.c index 30ea49d..c6b8194 100644 --- a/platform/gl/gl.c +++ b/platform/gl/gl.c @@ -220,32 +220,44 @@ ty_Vec2i ty_platform_get_mouse(void) { double x, y; glfwGetCursorPos(p.win, &x, &y); - return ty_vec2i(x, y); + + + int win_width, win_height; + glfwGetWindowSize(p.win, &win_width, &win_height); + + double scalex = win_width / ctx.hints.scr_width; + double scaley = win_height / ctx.hints.scr_height; + return ty_vec2i(x / scalex, y / scaley); } -static -int button_to_glfw_key(ty_Button btn) +bool ty_platform_is_button_down(ty_Button btn) { + int res = -1; switch (btn) { - case TY_BTN_LEFT_UP: return GLFW_KEY_W; - case TY_BTN_LEFT_DOWN: return GLFW_KEY_S; - case TY_BTN_LEFT_LEFT: return GLFW_KEY_A; - case TY_BTN_LEFT_RIGHT: return GLFW_KEY_D; - case TY_BTN_RIGHT_UP: return GLFW_KEY_I; - case TY_BTN_RIGHT_DOWN: return GLFW_KEY_K; - case TY_BTN_RIGHT_LEFT: return GLFW_KEY_J; - case TY_BTN_RIGHT_RIGHT: return GLFW_KEY_L; - case TY_BTN_ACTION_1: return GLFW_KEY_Q; - case TY_BTN_ACTION_2: return GLFW_KEY_E; - case TY_BTN_ACTION_3: return GLFW_KEY_U; - case TY_BTN_ACTION_4: return GLFW_KEY_O; - case TY_BTN_DB_CTRL: return GLFW_KEY_LEFT_CONTROL; - case TY_BTN_DB_SHIFT: return GLFW_KEY_LEFT_SHIFT; + case TY_BTN_LEFT_UP: res = glfwGetKey(p.win, GLFW_KEY_W); break; + case TY_BTN_LEFT_DOWN: res = glfwGetKey(p.win, GLFW_KEY_S); break; + case TY_BTN_LEFT_LEFT: res = glfwGetKey(p.win, GLFW_KEY_A); break; + case TY_BTN_LEFT_RIGHT: res = glfwGetKey(p.win, GLFW_KEY_D); break; + case TY_BTN_RIGHT_UP: res = glfwGetKey(p.win, GLFW_KEY_I); break; + case TY_BTN_RIGHT_DOWN: res = glfwGetKey(p.win, GLFW_KEY_K); break; + case TY_BTN_RIGHT_LEFT: res = glfwGetKey(p.win, GLFW_KEY_J); break; + case TY_BTN_RIGHT_RIGHT: res = glfwGetKey(p.win, GLFW_KEY_L); break; + case TY_BTN_ACTION_1: res = glfwGetKey(p.win, GLFW_KEY_Q); break; + case TY_BTN_ACTION_2: res = glfwGetKey(p.win, GLFW_KEY_E); break; + case TY_BTN_ACTION_3: res = glfwGetKey(p.win, GLFW_KEY_U); break; + case TY_BTN_ACTION_4: res = glfwGetKey(p.win, GLFW_KEY_O); break; + case TY_BTN_DB_CTRL: res = glfwGetKey(p.win, GLFW_KEY_LEFT_CONTROL); break; + case TY_BTN_DB_SHIFT: res = glfwGetKey(p.win, GLFW_KEY_LEFT_SHIFT); break; + case TY_BTN_DB_LMB: + res = glfwGetMouseButton(p.win, GLFW_MOUSE_BUTTON_LEFT); + break; + case TY_BTN_DB_RMB: + res = glfwGetMouseButton(p.win, GLFW_MOUSE_BUTTON_RIGHT); + break; + case TY_BTN_DB_MMB: + res = glfwGetMouseButton(p.win, GLFW_MOUSE_BUTTON_MIDDLE); + break; } - return 0; -} -bool ty_platform_is_button_down(ty_Button btn) -{ - return glfwGetKey(p.win, button_to_glfw_key(btn)) == GLFW_PRESS; + return res == GLFW_PRESS; } diff --git a/teensy/teensy.h b/teensy/teensy.h index c272664..0e36a6e 100644 --- a/teensy/teensy.h +++ b/teensy/teensy.h @@ -6,10 +6,12 @@ #include "teensy_common.h" #include "teensy_list.h" -#define ty_vec2(x, y) ((ty_Vec2){x, y}) -#define ty_vec2i(x, y) ((ty_Vec2i){x, y}) -#define ty_color(R, G, B) ((ty_Color){R, G, B}) -#define ty_img_full(img) ((ty_Recti){0, 0, (img).width, (img).height}) +#define ty_vec2(x, y) ((ty_Vec2){x, y}) +#define ty_vec2i(x, y) ((ty_Vec2i){x, y}) +#define ty_color(R, G, B) ((ty_Color){R, G, B}) +#define ty_rect(x, y, w, h) ((ty_Rect){x, y, w, h}) +#define ty_recti(x, y, w, h) ((ty_Recti){x, y, w, h}) +#define ty_img_full(img) ((ty_Recti){0, 0, (img).width, (img).height}) #define ty_rect_start(rect) (ty_vec2(rect.x, rect.y)) #define ty_recti_start(rect) (ty_vec2i(rect.x, rect.y)) @@ -48,6 +50,9 @@ enum { // for use in the editor TY_BTN_DB_CTRL, TY_BTN_DB_SHIFT, + TY_BTN_DB_LMB, + TY_BTN_DB_RMB, + TY_BTN_DB_MMB, TY_BTN_COUNT, }; @@ -107,6 +112,8 @@ void ty_deinit(void); bool ty_button_down(ty_Button btn); // If the button was pressed just now bool ty_button_pressed(ty_Button btn); +// Gets the mouse position; should only be used as debug or dev tools +ty_Vec2i ty_mouse_pos(void); // Whether or not the main loop should continue executing. bool ty_is_game_running(void); @@ -116,6 +123,9 @@ double ty_get_time(void); // tic. int ty_tick(void); +bool ty_pointi_in_recti(ty_Vec2i point, ty_Recti rect); +bool ty_point_in_rect(ty_Vec2 point, ty_Rect rect); + ty_Image ty_create_image(int w, int h, const ty_Color* data); void ty_free_image(ty_Image img); ty_Color ty_img_get_pixel(ty_Image img, ty_Vec2i pos); @@ -149,9 +159,9 @@ void ty_draw_line( ty_Vec2i end, ty_Color color ); -void ty_draw_text(ty_Font *font, ty_Vec2i pos, const char *text); +void ty_draw_text(const ty_Font *font, ty_Vec2i pos, const char *text); void ty_draw_text_fmt( - ty_Font *font, + const ty_Font *font, ty_Vec2i pos, const char *fmt, ... diff --git a/teensy/teensy_context.c b/teensy/teensy_context.c index 547a5dd..91b9b90 100644 --- a/teensy/teensy_context.c +++ b/teensy/teensy_context.c @@ -46,6 +46,11 @@ bool ty_button_pressed(ty_Button btn) return ctx.pressed[btn]; } +ty_Vec2i ty_mouse_pos(void) +{ + return ty_platform_get_mouse(); +} + bool ty_is_game_running(void) { assert(is_init()); diff --git a/teensy/teensy_list.h b/teensy/teensy_list.h index 6265e6a..12da8e0 100644 --- a/teensy/teensy_list.h +++ b/teensy/teensy_list.h @@ -16,26 +16,24 @@ typedef struct { #define ty_list_cap(arr) (ty_list_get_header(arr)->cap) #define ty_list_len(arr) (ty_list_get_header(arr)->len) -#define ty_list_reserve(arr, amt) \ - do { \ - ty_List_Header *header = ty_list_get_header(arr); \ - if (amt > header->cap) { \ - header->cap = header->cap < TY_LIST_MIN_CAP \ - ? TY_LIST_MIN_CAP \ - : header->cap * TY_LIST_GROW_RATE; \ - header = ty_realloc( \ - header, \ - (sizeof(*(arr)) * amt) + sizeof(ty_List_Header) \ - ); \ - (arr) = (void*)(header + 1); \ - } \ +#define ty_list_reserve(arr, amt) \ + do { \ + ty_List_Header *header = ty_list_get_header(arr); \ + if ((amt) > header->cap) { \ + header->cap = (amt) < TY_LIST_MIN_CAP ? TY_LIST_MIN_CAP : (amt); \ + header = ty_realloc( \ + header, \ + (sizeof(*(arr)) * (amt)) + sizeof(ty_List_Header) \ + ); \ + (arr) = (void*)(header + 1); \ + } \ } while (0) -#define ty_list_append(arr, elem) \ - do { \ +#define ty_list_append(arr, elem) \ + do { \ ty_List_Header *header = ty_list_get_header(arr); \ - ty_list_reserve(arr, header->len + 1); \ - (arr)[header->len++] = (elem); \ + ty_list_reserve(arr, header->len + 1); \ + (arr)[header->len++] = (elem); \ } while (0) #define ty_list_clear(arr) \ diff --git a/teensy/teensy_math.c b/teensy/teensy_math.c new file mode 100644 index 0000000..79d89c1 --- /dev/null +++ b/teensy/teensy_math.c @@ -0,0 +1,19 @@ +#include "teensy.h" + +bool ty_pointi_in_recti(ty_Vec2i point, ty_Recti rect) +{ + return + point.x > rect.x && + point.x < rect.x + rect.w && + point.y > rect.y && + point.y < rect.y + rect.h; +} + +bool ty_point_in_rect(ty_Vec2 point, ty_Rect rect) +{ + return + point.x > rect.x && + point.x < rect.x + rect.w && + point.y > rect.y && + point.y < rect.y + rect.h; +} diff --git a/teensy/teensy_mem.c b/teensy/teensy_mem.c index 7e8dd58..40882a7 100644 --- a/teensy/teensy_mem.c +++ b/teensy/teensy_mem.c @@ -101,7 +101,7 @@ void *ty_alloc(size_t size) allocations_cap *= 2; allocations = realloc( allocations, - sizeof(struct alloc) * allocations_cap + sizeof(Alloc) * allocations_cap ); if (!allocations) ty_log_fatal( diff --git a/teensy/teensy_renderer.c b/teensy/teensy_renderer.c index 173fbed..1e24248 100644 --- a/teensy/teensy_renderer.c +++ b/teensy/teensy_renderer.c @@ -293,7 +293,7 @@ void ty_draw_line( } } -void ty_draw_text(ty_Font *font, ty_Vec2i pos, const char *text) +void ty_draw_text(const ty_Font *font, ty_Vec2i pos, const char *text) { for (const uint8_t *c = (const uint8_t*)text; *c != '\0'; c++) { ty_Image glyph = font->glyphs[*c]; @@ -303,7 +303,7 @@ void ty_draw_text(ty_Font *font, ty_Vec2i pos, const char *text) } void ty_draw_text_fmt( - ty_Font *font, + const ty_Font *font, ty_Vec2i pos, const char *fmt, ... diff --git a/teensy/teensy_ui.c b/teensy/teensy_ui.c new file mode 100644 index 0000000..af3e78f --- /dev/null +++ b/teensy/teensy_ui.c @@ -0,0 +1,277 @@ +#include "teensy_ui.h" + +#include + +#include "teensy_list.h" + +#define TITLE_BAR_HEIGHT (8) +#define TEXT_HEIGHT (8) + +enum { + CMD_RECT, + CMD_TEXT, + CMD_TARGET, + CMD_IMAGE, +}; +typedef uint8_t Cmd_Kind; + +typedef struct { + const char *text; + ty_Recti rect; + ty_Color color; + const ty_Image *img; + Cmd_Kind kind; +} Cmd; + +typedef struct { + const char *title; + ty_Recti rect; + ty_Image content; + uint32_t flags; + + ty_Vec2i next_pos; +} Window; + +typedef struct { + Window *active; + Window root; + Window windows[255]; + int window_count; + tyui_Style style; + const ty_Font *font; + Cmd *cmds; + + ty_Vec2i prev_mouse_pos; +} Ctx; + +static +tyui_Style default_style = { + .fg_normal = ty_color(200, 200, 200), + .bg_normal = ty_color(64, 64, 64), + .fg_hover = ty_color(255, 255, 255), + .bg_hover = ty_color(100, 100, 100), + .win_bg = ty_color(48, 48, 48), + .win_title = ty_color(64, 128, 128), +}; + +static +Ctx uictx; + +static +void rect_cmd(ty_Recti rect, ty_Color color) +{ + Cmd cmd = {}; + cmd.kind = CMD_RECT; + cmd.rect = rect; + cmd.color = color; + + ty_list_append(uictx.cmds, cmd); +} + +static +void text_cmd(const char *text, ty_Vec2i pos) +{ + Cmd cmd = {}; + cmd.kind = CMD_TEXT; + cmd.text = text; + cmd.rect.x = pos.x; + cmd.rect.y = pos.y; + + ty_list_append(uictx.cmds, cmd); +} + +static +void image_cmd(const ty_Image *img, ty_Vec2i pos) +{ + Cmd cmd = {}; + cmd.kind = CMD_IMAGE; + cmd.img = img; + cmd.rect.x = pos.x; + cmd.rect.y = pos.y; + ty_list_append(uictx.cmds, cmd); +} + +static +void target_cmd(const ty_Image *target) +{ + Cmd cmd = {}; + cmd.kind = CMD_TARGET; + cmd.img = target; + ty_list_append(uictx.cmds, cmd); +} + +static +ty_Vec2i next_pos(int height) +{ + ty_Vec2i next = uictx.active->next_pos; + uictx.active->next_pos.y += height; + return next; +} + +static +ty_Vec2i mouse_delta(void) +{ + ty_Vec2i cur = ty_mouse_pos(); + return ty_vec2i( + cur.x - uictx.prev_mouse_pos.x, + cur.y - uictx.prev_mouse_pos.y + ); +} + +static +Window create_window(const char *title, ty_Recti rect, uint32_t flags) +{ + Window win = {}; + win.title = title; + win.rect = rect; + win.flags = flags; + win.content = ty_create_image(rect.w, rect.h, NULL); + return win; +} + +void tyui_init(const ty_Font *font) +{ + assert(font); + + uictx.cmds = ty_list_create(); + ty_list_reserve(uictx.cmds, 256); + + uint32_t root_flags = 0; + root_flags |= TYUI_WIN_NORESIZE; + root_flags |= TYUI_WIN_NOCLOSE; + root_flags |= TYUI_WIN_NOMOVE; + root_flags |= TYUI_WIN_INVISIBLE; + + uictx.root = create_window( + "__ROOT__", + ty_recti(0, 0, 256, 256), + root_flags + ); + + uictx.style = default_style; + uictx.font = font; + + uictx.active = &uictx.root; +} + +void tyui_deinit(void) +{ + ty_list_free(uictx.cmds); + + ty_free_image(uictx.root.content); + for (int i = 0; i < uictx.window_count; i++) { + ty_free_image(uictx.windows[i].content); + } +} + +void tyui_draw(void) +{ + for (size_t i = 0; i < ty_list_len(uictx.cmds); i++) { + Cmd cmd = uictx.cmds[i]; + switch (cmd.kind) { + case CMD_RECT: + ty_draw_rect(cmd.rect, cmd.color); + break; + case CMD_TEXT: + ty_draw_text(uictx.font, ty_recti_start(cmd.rect), cmd.text); + break; + case CMD_IMAGE: + ty_draw_image(*cmd.img, ty_recti_start(cmd.rect)); + break; + case CMD_TARGET: + ty_draw_set_target(cmd.img); + if (cmd.img != NULL) ty_draw_clear(TY_COLOR_MAGENTA); + break; + } + } + ty_list_clear(uictx.cmds); + + uictx.prev_mouse_pos = ty_mouse_pos(); +} + +bool tyui_begin_window_ex( + const char *title, + ty_Recti rect, + tyui_Id *id, + uint32_t flags, + bool *closed_ptr +) { + assert(id); + + bool closed = false; + + if (closed_ptr) + closed = *closed_ptr; + + if (uictx.active != &uictx.root) { + ty_log_err("cannot create window within another window"); + return false; + } + + if (closed) + return !closed; + + if (*id == 0) { + *id = ++uictx.window_count; + uictx.windows[*id - 1] = create_window(title, rect, flags); + } + + Window *win = &uictx.windows[*id - 1]; + uictx.active = win; + + win->next_pos = ty_vec2i(0, 0); + + if (win->flags & TYUI_WIN_INVISIBLE) + goto done; + + ty_Recti title_rect = win->rect; + title_rect.h = TEXT_HEIGHT; + + ty_Vec2i mouse_pos = ty_mouse_pos(); + ty_Vec2i md = mouse_delta(); + mouse_pos.x -= md.x; + mouse_pos.y -= md.y; + + if (ty_button_down(TY_BTN_DB_LMB) && + ty_pointi_in_recti(mouse_pos, title_rect) + ) { + ty_Vec2i md = mouse_delta(); + win->rect.x += md.x; + win->rect.y += md.y; + + title_rect = win->rect; + title_rect.h = TEXT_HEIGHT; + } + + rect_cmd(title_rect, uictx.style.win_title); + text_cmd(win->title, ty_recti_start(title_rect)); + + ty_Recti body_rect = win->rect; + body_rect.y += TEXT_HEIGHT; + rect_cmd(body_rect, uictx.style.win_bg); + + target_cmd(&win->content); + +done: + return !closed; +} + +void tyui_end_window(void) +{ + if (uictx.active == &uictx.root) { + ty_log_err("ending window with no window open"); + return; + } + + target_cmd(NULL); + ty_Vec2i content_pos = ty_recti_start(uictx.active->rect); + content_pos.y += TITLE_BAR_HEIGHT; + image_cmd(&uictx.active->content, content_pos); + + uictx.active = &uictx.root; +} + +void tyui_text(const char *text) +{ + text_cmd(text, next_pos(TEXT_HEIGHT)); +} diff --git a/teensy/teensy_ui.h b/teensy/teensy_ui.h new file mode 100644 index 0000000..9a1e33a --- /dev/null +++ b/teensy/teensy_ui.h @@ -0,0 +1,42 @@ +#ifndef TEENSY_UI_H_ +#define TEENSY_UI_H_ + +#include "teensy_common.h" +#include "teensy.h" + +#define TYUI_WIN_NORESIZE (1 << 0) +#define TYUI_WIN_NOCLOSE (1 << 1) +#define TYUI_WIN_NOMOVE (1 << 2) +#define TYUI_WIN_INVISIBLE (1 << 3) + +#define tyui_begin_window(title, rect, id) \ + tyui_begin_window_ex(title, rect, id, 0, NULL) + +typedef struct { + ty_Color fg_normal; + ty_Color bg_normal; + ty_Color fg_hover; + ty_Color bg_hover; + ty_Color win_bg; + ty_Color win_title; +} tyui_Style; + +typedef uint8_t tyui_Id; + +void tyui_init(const ty_Font *font); +void tyui_deinit(void); + +void tyui_draw(void); + +bool tyui_begin_window_ex( + const char *title, + ty_Recti rect, + tyui_Id *id, + uint32_t flags, + bool *closed +); +void tyui_end_window(void); + +void tyui_text(const char *text); + +#endif // TEENSY_UI_H_ -- cgit v1.3-2-g0d8e