#include "teensy_ui.h" #include #include #include #include "teensy_list.h" #define TITLE_BAR_HEIGHT (8) #define TEXT_HEIGHT (8) #define PADDING (1) 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; int layout_idx; int layout_size; tyui_Style style; const ty_Font *font; const float *layout; Cmd *cmds; ty_Vec2i prev_mouse_pos; } Ctx; static tyui_Style default_style = { .fg_normal = ty_color(200, 200, 200), .bg_normal = ty_color(20, 20, 20), .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; int win_width = uictx.active->rect.w - PADDING * 2; float x_perc = 0; for (int i = 0; i < uictx.layout_idx; i++) x_perc += uictx.layout[i]; next.x = win_width * x_perc + PADDING; uictx.layout_idx++; if (uictx.layout_idx >= uictx.layout_size) { uictx.layout_idx = 0; uictx.active->next_pos.y += height; } return next; } // Must be called before next_pos() static int get_element_width() { float start_x_perc = 0; for (int i = 0; i < uictx.layout_idx; i++) start_x_perc += uictx.layout[i]; float end_x_perc = 0; if (uictx.layout_idx + 1 >= uictx.layout_size) { end_x_perc = 1; } else { for (int i = 0; i < uictx.layout_idx + 1; i++) end_x_perc += uictx.layout[i]; } int win_width = uictx.active->rect.w - 2; return win_width * (end_x_perc - start_x_perc); } 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; tyui_layout(NULL); } 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_layout(const float *layout) { if (!layout) layout = (float[]){0}; uictx.layout = layout; uictx.layout_size = 1; for (const float *p = layout; *p > 0; p++) uictx.layout_size++; uictx.layout_idx = 0; } 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(); uictx.root.next_pos = ty_vec2i(2, 1); } 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(10, PADDING); if (win->flags & TYUI_WIN_INVISIBLE) goto done; ty_Vec2i mouse_pos = ty_mouse_pos(); ty_Vec2i md = mouse_delta(); mouse_pos.x -= md.x; mouse_pos.y -= md.y; // Grabbing resizer? ty_Vec2i win_end = ty_recti_end(win->rect); ty_Recti resize_rect = ty_recti( win_end.x - 10, win_end.y - 10 + TITLE_BAR_HEIGHT, 10, 10 ); if (ty_button_down(TY_BTN_DB_LMB) && ty_pointi_in_recti(mouse_pos, resize_rect) ) { win->rect.w += md.x; win->rect.h += md.y; ty_free_image(win->content); win->content = ty_create_image(win->rect.w, win->rect.h, NULL); } ty_Recti title_rect = win->rect; title_rect.h = TEXT_HEIGHT; // Grabbing title bar? if (ty_button_down(TY_BTN_DB_LMB) && ty_pointi_in_recti(mouse_pos, title_rect) ) { 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); rect_cmd(resize_rect, uictx.style.bg_normal); 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 *fmt, ...) { va_list args; va_start(args, fmt); char *text = ty_format_args(fmt, args); va_end(args); text_cmd(text, next_pos(TEXT_HEIGHT)); } bool tyui_button(const char *text) { int width = ty_font_width(uictx.font, text); ty_Vec2i pos = next_pos(TEXT_HEIGHT + PADDING + 1); ty_Recti rect = ty_recti( pos.x, pos.y, width + PADDING, TEXT_HEIGHT + PADDING ); ty_Color bg_col = uictx.style.bg_normal; ty_Vec2i mouse_pos = ty_mouse_pos(); mouse_pos.x -= uictx.active->rect.x; mouse_pos.y -= uictx.active->rect.y + TEXT_HEIGHT; bool clicked = false; if (ty_pointi_in_recti(mouse_pos, rect)) { bg_col = uictx.style.bg_hover; clicked = ty_button_pressed(TY_BTN_DB_LMB); } rect_cmd(rect, bg_col); text_cmd(text, ty_vec2i(pos.x+1, pos.y+1)); return clicked; } void tyui_slider(float min, float max, float step, float *value) { assert(value); ty_Vec2i mouse_pos = ty_mouse_pos(); mouse_pos.x -= uictx.active->rect.x; mouse_pos.y -= uictx.active->rect.y + TEXT_HEIGHT; int width = get_element_width(); ty_Vec2i pos = next_pos(TEXT_HEIGHT + PADDING + 1); float p = (*value - min) / (max - min); ty_Recti bar_rect = ty_recti( pos.x, pos.y, width, TEXT_HEIGHT ); rect_cmd(bar_rect, uictx.style.bg_normal); bar_rect.x += 1; bar_rect.y += 1; bar_rect.w -= 2; bar_rect.h -= 2; rect_cmd(bar_rect, uictx.style.win_bg); bool hovered = ty_pointi_in_recti(mouse_pos, bar_rect); int grabber_x = width * p + pos.x; ty_Recti grabber_rect = ty_recti( grabber_x, pos.y + 1, 6, TEXT_HEIGHT - 2 ); ty_Color color = hovered ? uictx.style.bg_hover : uictx.style.bg_normal; rect_cmd(grabber_rect, color); if (hovered && ty_button_down(TY_BTN_DB_LMB)) { float new_val = (float)(mouse_pos.x - bar_rect.x) / bar_rect.w * (max - min) + min; new_val = round(new_val / step) * step; *value = new_val; } }