From 07cd736f75ca4dffb731cc964e66beed60859976 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 25 Sep 2024 00:39:47 +0200 Subject: [PATCH 1/7] ui: import console ui --- include/libtrx/game/clock.h | 3 + include/libtrx/game/console/common.h | 15 ++ include/libtrx/game/input.h | 4 + include/libtrx/game/ui/common.h | 22 ++ include/libtrx/game/ui/events.h | 22 ++ include/libtrx/game/ui/widgets/base.h | 25 ++ include/libtrx/game/ui/widgets/console.h | 12 + include/libtrx/game/ui/widgets/label.h | 18 ++ include/libtrx/game/ui/widgets/prompt.h | 11 + include/libtrx/game/ui/widgets/stack.h | 18 ++ include/libtrx/game/ui/widgets/window.h | 7 + meson.build | 7 +- src/game/console/common.c | 71 +++++- src/game/console/extern.h | 1 - src/game/ui/common.c | 43 ++++ src/game/ui/events.c | 65 +++++ src/game/ui/widgets/console.c | 216 ++++++++++++++++ src/game/ui/widgets/prompt.c | 300 +++++++++++++++++++++++ src/game/ui/widgets/stack.c | 188 ++++++++++++++ 19 files changed, 1045 insertions(+), 3 deletions(-) create mode 100644 include/libtrx/game/clock.h create mode 100644 include/libtrx/game/input.h create mode 100644 include/libtrx/game/ui/common.h create mode 100644 include/libtrx/game/ui/events.h create mode 100644 include/libtrx/game/ui/widgets/base.h create mode 100644 include/libtrx/game/ui/widgets/console.h create mode 100644 include/libtrx/game/ui/widgets/label.h create mode 100644 include/libtrx/game/ui/widgets/prompt.h create mode 100644 include/libtrx/game/ui/widgets/stack.h create mode 100644 include/libtrx/game/ui/widgets/window.h create mode 100644 src/game/ui/common.c create mode 100644 src/game/ui/events.c create mode 100644 src/game/ui/widgets/console.c create mode 100644 src/game/ui/widgets/prompt.c create mode 100644 src/game/ui/widgets/stack.c diff --git a/include/libtrx/game/clock.h b/include/libtrx/game/clock.h new file mode 100644 index 0000000..13e8d78 --- /dev/null +++ b/include/libtrx/game/clock.h @@ -0,0 +1,3 @@ +#pragma once + +extern double Clock_GetHighPrecisionCounter(void); diff --git a/include/libtrx/game/console/common.h b/include/libtrx/game/console/common.h index 80312bf..cb64659 100644 --- a/include/libtrx/game/console/common.h +++ b/include/libtrx/game/console/common.h @@ -3,6 +3,7 @@ #include "../types.h" #include +#include typedef enum { CR_SUCCESS, @@ -22,5 +23,19 @@ typedef struct __PACKING CONSOLE_COMMAND { COMMAND_RESULT (*proc)(const COMMAND_CONTEXT *ctx); } CONSOLE_COMMAND; +void Console_Init(void); +void Console_Shutdown(void); + +void Console_Open(void); +void Console_Close(void); +bool Console_IsOpened(void); + +void Console_ScrollLogs(void); +int32_t Console_GetVisibleLogCount(void); +int32_t Console_GetMaxLogCount(void); + void Console_Log(const char *fmt, ...); COMMAND_RESULT Console_Eval(const char *cmdline); + +void Console_Draw(void); +extern void Console_DrawBackdrop(void); diff --git a/include/libtrx/game/input.h b/include/libtrx/game/input.h new file mode 100644 index 0000000..4f5d68e --- /dev/null +++ b/include/libtrx/game/input.h @@ -0,0 +1,4 @@ +#pragma once + +extern void Input_EnterListenMode(void); +extern void Input_ExitListenMode(void); diff --git a/include/libtrx/game/ui/common.h b/include/libtrx/game/ui/common.h new file mode 100644 index 0000000..a47690b --- /dev/null +++ b/include/libtrx/game/ui/common.h @@ -0,0 +1,22 @@ +#pragma once + +#include "./events.h" + +typedef enum { + UI_KEY_LEFT, + UI_KEY_RIGHT, + UI_KEY_HOME, + UI_KEY_END, + UI_KEY_BACK, + UI_KEY_RETURN, + UI_KEY_ESCAPE, +} UI_INPUT; + +void UI_Init(void); +void UI_Shutdown(void); + +void UI_HandleKeyDown(uint32_t key); +void UI_HandleKeyUp(uint32_t key); +void UI_HandleTextEdit(const char *text); + +extern UI_INPUT UI_TranslateInput(uint32_t system_keycode); diff --git a/include/libtrx/game/ui/events.h b/include/libtrx/game/ui/events.h new file mode 100644 index 0000000..6cfef98 --- /dev/null +++ b/include/libtrx/game/ui/events.h @@ -0,0 +1,22 @@ +#pragma once + +#include "./widgets/base.h" + +typedef struct { + const char *name; + const UI_WIDGET *sender; + void *data; +} UI_EVENT; + +typedef void (*UI_EVENT_LISTENER)(const UI_EVENT *, void *user_data); + +void UI_Events_Init(void); +void UI_Events_Shutdown(void); + +int32_t UI_Events_Subscribe( + const char *event_name, const UI_WIDGET *sender, UI_EVENT_LISTENER listener, + void *user_data); + +void UI_Events_Unsubscribe(int32_t listener_id); + +void UI_Events_Fire(const UI_EVENT *event); diff --git a/include/libtrx/game/ui/widgets/base.h b/include/libtrx/game/ui/widgets/base.h new file mode 100644 index 0000000..eb6388f --- /dev/null +++ b/include/libtrx/game/ui/widgets/base.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +struct UI_WIDGET; + +typedef void (*UI_WIDGET_CONTROL)(struct UI_WIDGET *self); +typedef void (*UI_WIDGET_DRAW)(struct UI_WIDGET *self); +typedef int32_t (*UI_WIDGET_GET_WIDTH)(const struct UI_WIDGET *self); +typedef int32_t (*UI_WIDGET_GET_HEIGHT)(const struct UI_WIDGET *self); +typedef void (*UI_WIDGET_SET_POSITION)( + struct UI_WIDGET *self, int32_t x, int32_t y); +typedef void (*UI_WIDGET_FREE)(struct UI_WIDGET *self); + +typedef struct UI_WIDGET { + UI_WIDGET_CONTROL control; + UI_WIDGET_DRAW draw; + UI_WIDGET_GET_WIDTH get_width; + UI_WIDGET_GET_HEIGHT get_height; + UI_WIDGET_SET_POSITION set_position; + UI_WIDGET_FREE free; +} UI_WIDGET; + +typedef UI_WIDGET UI_WIDGET_VTABLE; diff --git a/include/libtrx/game/ui/widgets/console.h b/include/libtrx/game/ui/widgets/console.h new file mode 100644 index 0000000..26ddacd --- /dev/null +++ b/include/libtrx/game/ui/widgets/console.h @@ -0,0 +1,12 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Console_Create(void); + +void UI_Console_HandleOpen(UI_WIDGET *widget); +void UI_Console_HandleClose(UI_WIDGET *widget); +void UI_Console_HandleLog(UI_WIDGET *widget, const char *text); +void UI_Console_ScrollLogs(UI_WIDGET *widget); +int32_t UI_Console_GetVisibleLogCount(UI_WIDGET *widget); +int32_t UI_Console_GetMaxLogCount(UI_WIDGET *widget); diff --git a/include/libtrx/game/ui/widgets/label.h b/include/libtrx/game/ui/widgets/label.h new file mode 100644 index 0000000..d6ed8b1 --- /dev/null +++ b/include/libtrx/game/ui/widgets/label.h @@ -0,0 +1,18 @@ +#pragma once + +#include "./base.h" + +#define UI_LABEL_AUTO_SIZE (-1) + +extern UI_WIDGET *UI_Label_Create( + const char *text, int32_t width, int32_t height); + +extern void UI_Label_ChangeText(UI_WIDGET *widget, const char *text); +extern const char *UI_Label_GetText(UI_WIDGET *widget); + +extern void UI_Label_AddFrame(UI_WIDGET *widget); +extern void UI_Label_RemoveFrame(UI_WIDGET *widget); +extern void UI_Label_Flash(UI_WIDGET *widget, bool enable, int32_t rate); +extern void UI_Label_SetScale(UI_WIDGET *widget, float scale); +extern void UI_Label_SetZIndex(UI_WIDGET *widget, int32_t z_index); +extern int32_t UI_Label_MeasureTextWidth(UI_WIDGET *widget); diff --git a/include/libtrx/game/ui/widgets/prompt.h b/include/libtrx/game/ui/widgets/prompt.h new file mode 100644 index 0000000..4b783a1 --- /dev/null +++ b/include/libtrx/game/ui/widgets/prompt.h @@ -0,0 +1,11 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Prompt_Create(int32_t width, int32_t height); + +void UI_Prompt_SetFocus(UI_WIDGET *widget, bool is_focused); +void UI_Prompt_Clear(UI_WIDGET *widget); + +extern const char *UI_Prompt_GetPromptChar(void); +extern int32_t UI_Prompt_GetCaretFlashRate(void); diff --git a/include/libtrx/game/ui/widgets/stack.h b/include/libtrx/game/ui/widgets/stack.h new file mode 100644 index 0000000..4a4eaa5 --- /dev/null +++ b/include/libtrx/game/ui/widgets/stack.h @@ -0,0 +1,18 @@ +#pragma once + +#include "./base.h" + +#define UI_STACK_AUTO_SIZE (-1) + +typedef enum { + UI_STACK_LAYOUT_HORIZONTAL, + UI_STACK_LAYOUT_VERTICAL, + UI_STACK_LAYOUT_HORIZONTAL_INVERSE, + UI_STACK_LAYOUT_VERTICAL_INVERSE, +} UI_STACK_LAYOUT; + +UI_WIDGET *UI_Stack_Create( + UI_STACK_LAYOUT layout, int32_t width, int32_t height); +void UI_Stack_AddChild(UI_WIDGET *self, UI_WIDGET *child); +void UI_Stack_DoLayout(UI_WIDGET *self); +void UI_Stack_SetInverse(UI_WIDGET *self, bool inverse); diff --git a/include/libtrx/game/ui/widgets/window.h b/include/libtrx/game/ui/widgets/window.h new file mode 100644 index 0000000..7d7089e --- /dev/null +++ b/include/libtrx/game/ui/widgets/window.h @@ -0,0 +1,7 @@ +#pragma once + +#include "./base.h" + +extern UI_WIDGET *UI_Window_Create( + UI_WIDGET *root, int32_t border_top, int32_t border_right, + int32_t border_bottom, int32_t border_left); diff --git a/meson.build b/meson.build index 51bae37..6610ef2 100644 --- a/meson.build +++ b/meson.build @@ -81,9 +81,14 @@ sources = [ 'src/game/console/cmd/set_health.c', 'src/game/console/cmd/sfx.c', 'src/game/console/cmd/teleport.c', - 'src/game/game_string.c', 'src/game/console/common.c', + 'src/game/game_string.c', 'src/game/items.c', + 'src/game/ui/common.c', + 'src/game/ui/events.c', + 'src/game/ui/widgets/console.c', + 'src/game/ui/widgets/prompt.c', + 'src/game/ui/widgets/stack.c', 'src/gfx/2d/2d_renderer.c', 'src/gfx/2d/2d_surface.c', 'src/gfx/3d/3d_renderer.c', diff --git a/src/game/console/common.c b/src/game/console/common.c index 57de5c6..e1a942b 100644 --- a/src/game/console/common.c +++ b/src/game/console/common.c @@ -2,6 +2,7 @@ #include "./extern.h" #include "game/game_string.h" +#include "game/ui/widgets/console.h" #include "log.h" #include "memory.h" #include "strings.h" @@ -11,6 +12,9 @@ #include #include +static bool m_IsOpened = false; +static UI_WIDGET *m_Console; + static void M_LogMultiline(const char *text); static void M_Log(const char *text); @@ -40,7 +44,57 @@ static void M_LogMultiline(const char *const text) static void M_Log(const char *text) { assert(text != NULL); - Console_LogImpl(text); + UI_Console_HandleLog(m_Console, text); +} + +void Console_Init(void) +{ + m_Console = UI_Console_Create(); +} + +void Console_Shutdown(void) +{ + if (m_Console != NULL) { + m_Console->free(m_Console); + m_Console = NULL; + } + + m_IsOpened = false; +} + +void Console_Open(void) +{ + if (m_IsOpened) { + UI_Console_HandleClose(m_Console); + } + m_IsOpened = true; + UI_Console_HandleOpen(m_Console); +} + +void Console_Close(void) +{ + UI_Console_HandleClose(m_Console); + m_IsOpened = false; +} + +bool Console_IsOpened(void) +{ + return m_IsOpened; +} + +void Console_ScrollLogs(void) +{ + UI_Console_ScrollLogs(m_Console); +} + +int32_t Console_GetVisibleLogCount(void) +{ + return UI_Console_GetVisibleLogCount(m_Console); +} + +int32_t Console_GetMaxLogCount(void) +{ + return UI_Console_GetMaxLogCount(m_Console); } void Console_Log(const char *fmt, ...) @@ -118,3 +172,18 @@ COMMAND_RESULT Console_Eval(const char *const cmdline) } return result; } + +void Console_Draw(void) +{ + if (m_Console == NULL) { + return; + } + + Console_ScrollLogs(); + + if (Console_IsOpened() || Console_GetVisibleLogCount() > 0) { + Console_DrawBackdrop(); + } + + m_Console->draw(m_Console); +} diff --git a/src/game/console/extern.h b/src/game/console/extern.h index 8e46987..c20bc32 100644 --- a/src/game/console/extern.h +++ b/src/game/console/extern.h @@ -5,5 +5,4 @@ #include extern int32_t Console_GetMaxLineLength(void); -extern void Console_LogImpl(const char *text); extern CONSOLE_COMMAND **Console_GetCommands(void); diff --git a/src/game/ui/common.c b/src/game/ui/common.c new file mode 100644 index 0000000..cf39e19 --- /dev/null +++ b/src/game/ui/common.c @@ -0,0 +1,43 @@ +#include "game/ui/common.h" + +#include "vector.h" + +void UI_Init(void) +{ + UI_Events_Init(); +} + +void UI_Shutdown(void) +{ + UI_Events_Shutdown(); +} + +void UI_HandleKeyDown(const uint32_t key) +{ + const UI_EVENT event = { + .name = "key_down", + .sender = NULL, + .data = (void *)UI_TranslateInput(key), + }; + UI_Events_Fire(&event); +} + +void UI_HandleKeyUp(const uint32_t key) +{ + const UI_EVENT event = { + .name = "key_up", + .sender = NULL, + .data = (void *)UI_TranslateInput(key), + }; + UI_Events_Fire(&event); +} + +void UI_HandleTextEdit(const char *const text) +{ + const UI_EVENT event = { + .name = "text_edit", + .sender = NULL, + .data = (void *)text, + }; + UI_Events_Fire(&event); +} diff --git a/src/game/ui/events.c b/src/game/ui/events.c new file mode 100644 index 0000000..670ee4f --- /dev/null +++ b/src/game/ui/events.c @@ -0,0 +1,65 @@ +#include "game/ui/events.h" + +#include "memory.h" +#include "vector.h" + +#include +#include + +typedef struct { + int32_t listener_id; + const char *event_name; + const UI_WIDGET *sender; + UI_EVENT_LISTENER listener; + void *user_data; +} M_LISTENER; + +static VECTOR *m_Listeners = NULL; +static int32_t m_ListenerID = 0; + +void UI_Events_Init(void) +{ + m_Listeners = Vector_Create(sizeof(M_LISTENER)); +} + +void UI_Events_Shutdown(void) +{ + Vector_Free(m_Listeners); +} + +int32_t UI_Events_Subscribe( + const char *const event_name, const UI_WIDGET *const sender, + const UI_EVENT_LISTENER listener, void *const user_data) +{ + M_LISTENER entry = { + .listener_id = m_ListenerID++, + .event_name = event_name, + .sender = sender, + .listener = listener, + .user_data = user_data, + }; + Vector_Add(m_Listeners, &entry); + return entry.listener_id; +} + +void UI_Events_Unsubscribe(const int32_t listener_id) +{ + for (int32_t i = 0; i < m_Listeners->count; i++) { + M_LISTENER entry = *(M_LISTENER *)Vector_Get(m_Listeners, i); + if (entry.listener_id == listener_id) { + Vector_RemoveAt(m_Listeners, i); + return; + } + } +} + +void UI_Events_Fire(const UI_EVENT *const event) +{ + for (int32_t i = 0; i < m_Listeners->count; i++) { + M_LISTENER entry = *(M_LISTENER *)Vector_Get(m_Listeners, i); + if (strcmp(entry.event_name, event->name) == 0 + && entry.sender == event->sender) { + entry.listener(event, entry.user_data); + } + } +} diff --git a/src/game/ui/widgets/console.c b/src/game/ui/widgets/console.c new file mode 100644 index 0000000..f2e17a8 --- /dev/null +++ b/src/game/ui/widgets/console.c @@ -0,0 +1,216 @@ +#include "game/ui/widgets/console.h" + +#include "game/clock.h" +#include "game/console/common.h" +#include "game/ui/events.h" +#include "game/ui/widgets/label.h" +#include "game/ui/widgets/prompt.h" +#include "game/ui/widgets/stack.h" +#include "memory.h" +#include "utils.h" + +#include + +#define WINDOW_MARGIN 5 +#define LOG_MARGIN 3 +#define TEXT_HEIGHT 15 +#define MAX_LOG_LINES 20 +#define LOG_SCALE 0.8 +#define DELAY_PER_CHAR 0.2 + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *container; + UI_WIDGET *prompt; + char *log_lines; + int32_t logs_on_screen; + + int32_t listener1; + int32_t listener2; + + struct { + double expire_at; + UI_WIDGET *label; + } logs[MAX_LOG_LINES]; +} UI_CONSOLE; + +static void M_HandlePromptCancel(const UI_EVENT *event, void *data); +static void M_HandlePromptConfirm(const UI_EVENT *event, void *data); +static void M_UpdateLogCount(UI_CONSOLE *self); + +static int32_t M_GetWidth(const UI_CONSOLE *self); +static int32_t M_GetHeight(const UI_CONSOLE *self); +static void M_SetPosition(UI_CONSOLE *self, int32_t x, int32_t y); +static void M_Control(UI_CONSOLE *self); +static void M_Draw(UI_CONSOLE *self); +static void M_Free(UI_CONSOLE *self); + +static void M_HandlePromptCancel(const UI_EVENT *const event, void *const data) +{ + Console_Close(); +} + +static void M_HandlePromptConfirm(const UI_EVENT *const event, void *const data) +{ + const char *text = event->data; + Console_Eval(text); + Console_Close(); +} + +static void M_UpdateLogCount(UI_CONSOLE *const self) +{ + self->logs_on_screen = 0; + for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) { + if (self->logs[i].expire_at) { + self->logs_on_screen = i + 1; + break; + } + } +} + +static int32_t M_GetWidth(const UI_CONSOLE *const self) +{ + return 640 - 2 * WINDOW_MARGIN; +} + +static int32_t M_GetHeight(const UI_CONSOLE *const self) +{ + return 480 - 2 * WINDOW_MARGIN; +} + +static void M_SetPosition(UI_CONSOLE *const self, int32_t x, int32_t y) +{ + return self->container->set_position(self->container, x, y); +} + +static void M_Control(UI_CONSOLE *const self) +{ + if (self->container->control != NULL) { + self->container->control(self->container); + } +} + +static void M_Draw(UI_CONSOLE *const self) +{ + if (self->container->draw != NULL) { + self->container->draw(self->container); + } +} + +static void M_Free(UI_CONSOLE *const self) +{ + self->prompt->free(self->prompt); + self->container->free(self->container); + UI_Events_Unsubscribe(self->listener1); + UI_Events_Unsubscribe(self->listener2); + Memory_Free(self); +} + +UI_WIDGET *UI_Console_Create(void) +{ + UI_CONSOLE *const self = Memory_Alloc(sizeof(UI_CONSOLE)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_CONTROL)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->container = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL_INVERSE, M_GetWidth(self), M_GetHeight(self)); + + self->prompt = UI_Prompt_Create(M_GetWidth(self), TEXT_HEIGHT + LOG_MARGIN); + UI_Stack_AddChild(self->container, self->prompt); + + for (int32_t i = 0; i < MAX_LOG_LINES; i++) { + self->logs[i].label = + UI_Label_Create("", M_GetWidth(self), UI_LABEL_AUTO_SIZE); + UI_Label_SetScale(self->logs[i].label, LOG_SCALE); + UI_Stack_AddChild(self->container, self->logs[i].label); + } + + M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN); + + self->listener1 = UI_Events_Subscribe( + "confirm", self->prompt, M_HandlePromptConfirm, NULL); + self->listener2 = + UI_Events_Subscribe("cancel", self->prompt, M_HandlePromptCancel, NULL); + + return (UI_WIDGET *)self; +} + +void UI_Console_HandleOpen(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + UI_Prompt_SetFocus(self->prompt, true); +} + +void UI_Console_HandleClose(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + UI_Prompt_SetFocus(self->prompt, false); + UI_Prompt_Clear(self->prompt); +} + +void UI_Console_HandleLog(UI_WIDGET *const widget, const char *const text) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + + int32_t dst_idx = -1; + for (int32_t i = MAX_LOG_LINES - 1; i > 0; i--) { + if (self->logs[i].label == NULL) { + continue; + } + UI_Label_ChangeText( + self->logs[i].label, UI_Label_GetText(self->logs[i - 1].label)); + self->logs[i].expire_at = self->logs[i - 1].expire_at; + } + + if (self->logs[0].label == NULL) { + return; + } + + self->logs[0].expire_at = + Clock_GetHighPrecisionCounter() + 1000 * strlen(text) * DELAY_PER_CHAR; + UI_Label_ChangeText(self->logs[0].label, text); + + UI_Stack_DoLayout(self->container); + M_UpdateLogCount(self); +} + +void UI_Console_ScrollLogs(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + + int32_t i = MAX_LOG_LINES - 1; + while (i >= 0 && !self->logs[i].expire_at) { + i--; + } + + bool need_layout = false; + while (i >= 0 && self->logs[i].expire_at + && Clock_GetHighPrecisionCounter() >= self->logs[i].expire_at) { + self->logs[i].expire_at = 0; + UI_Label_ChangeText(self->logs[i].label, ""); + need_layout = true; + i--; + } + + if (need_layout) { + M_UpdateLogCount(self); + UI_Stack_DoLayout(self->container); + } +} + +int32_t UI_Console_GetVisibleLogCount(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + return self->logs_on_screen; +} + +int32_t UI_Console_GetMaxLogCount(UI_WIDGET *const widget) +{ + return MAX_LOG_LINES; +} diff --git a/src/game/ui/widgets/prompt.c b/src/game/ui/widgets/prompt.c new file mode 100644 index 0000000..8487893 --- /dev/null +++ b/src/game/ui/widgets/prompt.c @@ -0,0 +1,300 @@ +#include "game/ui/widgets/prompt.h" + +#include "game/input.h" +#include "game/ui/common.h" +#include "game/ui/events.h" +#include "game/ui/widgets/label.h" +#include "memory.h" +#include "strings.h" + +#include + +static const char m_ValidPromptChars[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- "; + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *label; + UI_WIDGET *caret; + + int32_t listener1; + int32_t listener2; + + struct { + int32_t x; + int32_t y; + } pos; + bool is_focused; + int32_t current_text_capacity; + char *current_text; + int32_t caret_pos; +} UI_PROMPT; + +static void M_UpdatePromptLabel(UI_PROMPT *self); +static void M_UpdateCaretLabel(UI_PROMPT *self); +static void M_MoveCaretLeft(UI_PROMPT *self); +static void M_MoveCaretRight(UI_PROMPT *self); +static void M_MoveCaretStart(UI_PROMPT *self); +static void M_MoveCaretEnd(UI_PROMPT *self); +static void M_DeleteCharBack(UI_PROMPT *self); +static void M_Confirm(UI_PROMPT *self); +static void M_Cancel(UI_PROMPT *self); +static void M_Clear(UI_PROMPT *self); + +static int32_t M_GetWidth(const UI_PROMPT *self); +static int32_t M_GetHeight(const UI_PROMPT *self); +static void M_SetPosition(UI_PROMPT *self, int32_t x, int32_t y); +static void M_Control(UI_PROMPT *self); +static void M_Draw(UI_PROMPT *self); +static void M_Free(UI_PROMPT *self); +static void M_HandleKeyDown(const UI_EVENT *event, void *user_data); +static void M_HandleTextEdit(const UI_EVENT *event, void *user_data); + +static void M_UpdatePromptLabel(UI_PROMPT *const self) +{ + UI_Label_ChangeText(self->label, self->current_text); +} + +static void M_UpdateCaretLabel(UI_PROMPT *const self) +{ + const char old = self->current_text[self->caret_pos]; + self->current_text[self->caret_pos] = '\0'; + UI_Label_ChangeText(self->label, self->current_text); + const int32_t width = UI_Label_MeasureTextWidth(self->label); + self->current_text[self->caret_pos] = old; + UI_Label_ChangeText(self->label, self->current_text); + + self->caret->set_position(self->caret, self->pos.x + width, self->pos.y); +} + +static int32_t M_GetWidth(const UI_PROMPT *const self) +{ + return self->label->get_width(self->label); +} + +static int32_t M_GetHeight(const UI_PROMPT *const self) +{ + return self->label->get_height(self->label); +} + +static void M_SetPosition( + UI_PROMPT *const self, const int32_t x, const int32_t y) +{ + self->pos.x = x; + self->pos.y = y; + self->label->set_position(self->label, x, y); + M_UpdateCaretLabel(self); +} + +static void M_Control(UI_PROMPT *const self) +{ + if (self->label->control != NULL) { + self->label->control(self->label); + } + if (self->caret->control != NULL) { + self->caret->control(self->caret); + } +} + +static void M_Draw(UI_PROMPT *const self) +{ + if (self->label->draw != NULL) { + self->label->draw(self->label); + } + if (self->caret->draw != NULL) { + self->caret->draw(self->caret); + } +} + +static void M_Free(UI_PROMPT *const self) +{ + self->label->free(self->label); + self->caret->free(self->caret); + UI_Events_Unsubscribe(self->listener1); + UI_Events_Unsubscribe(self->listener2); + Memory_FreePointer(&self->current_text); + Memory_Free(self); +} + +static void M_MoveCaretLeft(UI_PROMPT *const self) +{ + if (self->caret_pos > 0) { + self->caret_pos--; + M_UpdateCaretLabel(self); + } +} + +static void M_MoveCaretRight(UI_PROMPT *const self) +{ + if (self->caret_pos < (int32_t)strlen(self->current_text)) { + self->caret_pos++; + M_UpdateCaretLabel(self); + } +} + +static void M_MoveCaretStart(UI_PROMPT *const self) +{ + self->caret_pos = 0; + M_UpdateCaretLabel(self); +} + +static void M_MoveCaretEnd(UI_PROMPT *const self) +{ + self->caret_pos = strlen(self->current_text); + M_UpdateCaretLabel(self); +} + +static void M_DeleteCharBack(UI_PROMPT *const self) +{ + if (self->caret_pos <= 0) { + return; + } + + memmove( + self->current_text + self->caret_pos - 1, + self->current_text + self->caret_pos, + strlen(self->current_text) + 1 - self->caret_pos); + + self->caret_pos--; + M_UpdatePromptLabel(self); + M_UpdateCaretLabel(self); +} + +static void M_Confirm(UI_PROMPT *const self) +{ + if (String_IsEmpty(self->current_text)) { + M_Cancel(self); + return; + } + const UI_EVENT event = { + .name = "confirm", + .sender = (const UI_WIDGET *)self, + .data = self->current_text, + }; + UI_Events_Fire(&event); + M_Clear(self); + M_UpdateCaretLabel(self); +} + +static void M_Cancel(UI_PROMPT *const self) +{ + const UI_EVENT event = { + .name = "cancel", + .sender = (const UI_WIDGET *)self, + .data = self->current_text, + }; + UI_Events_Fire(&event); + M_Clear(self); +} + +static void M_Clear(UI_PROMPT *const self) +{ + strcpy(self->current_text, ""); + self->caret_pos = 0; + M_UpdatePromptLabel(self); + M_UpdateCaretLabel(self); +} + +static void M_HandleKeyDown(const UI_EVENT *const event, void *const user_data) +{ + const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data; + UI_PROMPT *const self = user_data; + + if (!self->is_focused) { + return; + } + + // clang-format off + switch (key) { + case UI_KEY_LEFT: M_MoveCaretLeft(self); break; + case UI_KEY_RIGHT: M_MoveCaretRight(self); break; + case UI_KEY_HOME: M_MoveCaretStart(self); break; + case UI_KEY_END: M_MoveCaretEnd(self); break; + case UI_KEY_BACK: M_DeleteCharBack(self); break; + case UI_KEY_RETURN: M_Confirm(self); break; + case UI_KEY_ESCAPE: M_Cancel(self); break; + } + // clang-format on +} + +static void M_HandleTextEdit(const UI_EVENT *const event, void *const user_data) +{ + const char *insert_string = event->data; + const size_t insert_length = strlen(insert_string); + UI_PROMPT *const self = user_data; + + if (!self->is_focused) { + return; + } + + if (strlen(insert_string) != 1 + || !strstr(m_ValidPromptChars, insert_string)) { + return; + } + + const size_t available_space = + self->current_text_capacity - strlen(self->current_text); + if (insert_length >= available_space) { + self->current_text_capacity *= 2; + self->current_text = + Memory_Realloc(self->current_text, self->current_text_capacity); + } + + memmove( + self->current_text + self->caret_pos + insert_length, + self->current_text + self->caret_pos, + strlen(self->current_text) + 1 - self->caret_pos); + memcpy(self->current_text + self->caret_pos, insert_string, insert_length); + + self->caret_pos += insert_length; + M_UpdatePromptLabel(self); + M_UpdateCaretLabel(self); +} + +UI_WIDGET *UI_Prompt_Create(const int32_t width, const int32_t height) +{ + UI_PROMPT *const self = Memory_Alloc(sizeof(UI_PROMPT)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_CONTROL)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->current_text_capacity = 1; + self->current_text = Memory_Alloc(self->current_text_capacity); + self->label = UI_Label_Create(self->current_text, width, height); + self->caret = UI_Label_Create("", width, height); + UI_Label_SetZIndex(self->label, 16); + UI_Label_SetZIndex(self->caret, 8); + self->is_focused = false; + + self->listener1 = + UI_Events_Subscribe("key_down", NULL, M_HandleKeyDown, self); + self->listener2 = + UI_Events_Subscribe("text_edit", NULL, M_HandleTextEdit, self); + + return (UI_WIDGET *)self; +} + +void UI_Prompt_SetFocus(UI_WIDGET *const widget, const bool is_focused) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + self->is_focused = is_focused; + if (is_focused) { + Input_EnterListenMode(); + UI_Label_ChangeText(self->caret, UI_Prompt_GetPromptChar()); + UI_Label_Flash(self->caret, 1, UI_Prompt_GetCaretFlashRate()); + } else { + Input_ExitListenMode(); + UI_Label_ChangeText(self->caret, ""); + } +} + +void UI_Prompt_Clear(UI_WIDGET *const widget) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + M_Clear(self); +} diff --git a/src/game/ui/widgets/stack.c b/src/game/ui/widgets/stack.c new file mode 100644 index 0000000..2423e75 --- /dev/null +++ b/src/game/ui/widgets/stack.c @@ -0,0 +1,188 @@ +#include "game/ui/widgets/stack.h" + +#include "memory.h" +#include "utils.h" +#include "vector.h" + +typedef struct { + UI_WIDGET_VTABLE vtable; + + int32_t width; + int32_t height; + int32_t x; + int32_t y; + UI_STACK_LAYOUT layout; + VECTOR *children; +} UI_STACK; + +static int32_t M_GetHeight(const UI_STACK *self); +static int32_t M_GetWidth(const UI_STACK *self); +static void M_SetPosition(UI_STACK *self, int32_t x, int32_t y); +static void M_Control(UI_STACK *self); +static void M_Draw(UI_STACK *self); +static void M_Free(UI_STACK *self); + +static int32_t M_GetWidth(const UI_STACK *const self) +{ + if (self->width != UI_STACK_AUTO_SIZE) { + return self->width; + } + int32_t result = 0; + for (int32_t i = 0; i < self->children->count; i++) { + const UI_WIDGET *const child = + *(UI_WIDGET **)Vector_Get(self->children, i); + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: + result += child->get_width(child); + break; + case UI_STACK_LAYOUT_VERTICAL: + case UI_STACK_LAYOUT_VERTICAL_INVERSE: + result = MAX(result, child->get_width(child)); + break; + } + } + return result; +} + +static int32_t M_GetHeight(const UI_STACK *const self) +{ + if (self->height != UI_STACK_AUTO_SIZE) { + return self->height; + } + int32_t result = 0; + for (int32_t i = 0; i < self->children->count; i++) { + const UI_WIDGET *const child = + *(UI_WIDGET **)Vector_Get(self->children, i); + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: + result = MAX(result, child->get_height(child)); + break; + case UI_STACK_LAYOUT_VERTICAL: + case UI_STACK_LAYOUT_VERTICAL_INVERSE: + result += child->get_height(child); + break; + } + } + return result; +} + +static void M_SetPosition( + UI_STACK *const self, const int32_t x, const int32_t y) +{ + self->x = x; + self->y = y; + UI_Stack_DoLayout((UI_WIDGET *)self); +} + +static void M_Control(UI_STACK *const self) +{ + for (int32_t i = 0; i < self->children->count; i++) { + UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); + if (child->control != NULL) { + child->control(child); + } + } +} + +static void M_Draw(UI_STACK *const self) +{ + for (int32_t i = 0; i < self->children->count; i++) { + UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); + if (child->draw != NULL) { + child->draw(child); + } + } +} + +static void M_Free(UI_STACK *const self) +{ + Vector_Free(self->children); + Memory_Free(self); +} + +void UI_Stack_AddChild(UI_WIDGET *const widget, UI_WIDGET *const child) +{ + UI_STACK *const self = (UI_STACK *)widget; + Vector_Add(self->children, (void *)&child); +} + +UI_WIDGET *UI_Stack_Create( + const UI_STACK_LAYOUT layout, const int32_t width, const int32_t height) +{ + UI_STACK *const self = Memory_Alloc(sizeof(UI_STACK)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->width = width; + self->height = height; + self->layout = layout; + self->children = Vector_Create(sizeof(UI_WIDGET *)); + return (UI_WIDGET *)self; +} + +void UI_Stack_DoLayout(UI_WIDGET *const widget) +{ + UI_STACK *const self = (UI_STACK *)widget; + const int32_t self_width = M_GetWidth(self); + const int32_t self_height = M_GetHeight(self); + const int32_t start_x = self->x; + const int32_t start_y = self->y; + int32_t x; + int32_t y; + + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + case UI_STACK_LAYOUT_VERTICAL: + x = start_x; + y = start_y; + break; + case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: + case UI_STACK_LAYOUT_VERTICAL_INVERSE: + x = start_x + self_width; + y = start_y + self_height; + break; + } + + for (int32_t i = 0; i < self->children->count; i++) { + UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); + const int32_t child_width = child->get_width(child); + const int32_t child_height = child->get_height(child); + + // centre in the other axis + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: + y = start_y + (self_height - child_height) / 2; + break; + case UI_STACK_LAYOUT_VERTICAL: + case UI_STACK_LAYOUT_VERTICAL_INVERSE: + x = start_x + (self_width - child_width) / 2; + break; + } + + child->set_position(child, x, y); + + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: + x -= child_width; + break; + case UI_STACK_LAYOUT_VERTICAL_INVERSE: + y -= child_height; + break; + case UI_STACK_LAYOUT_HORIZONTAL: + x += child_width; + break; + case UI_STACK_LAYOUT_VERTICAL: + y += child_height; + break; + } + } +} From 4969b57cae4979bf91bf1c8a212f15d04289ba22 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Thu, 26 Sep 2024 00:05:31 +0200 Subject: [PATCH 2/7] ui/console: support TR1X text scaling --- include/libtrx/game/ui/common.h | 2 ++ include/libtrx/game/ui/widgets/label.h | 1 + include/libtrx/game/ui/widgets/prompt.h | 1 + include/libtrx/game/ui/widgets/stack.h | 2 +- src/game/ui/widgets/console.c | 21 +++++++++++++++++++-- src/game/ui/widgets/prompt.c | 6 ++++++ src/game/ui/widgets/stack.c | 9 +++++++++ 7 files changed, 39 insertions(+), 3 deletions(-) diff --git a/include/libtrx/game/ui/common.h b/include/libtrx/game/ui/common.h index a47690b..1d53742 100644 --- a/include/libtrx/game/ui/common.h +++ b/include/libtrx/game/ui/common.h @@ -19,4 +19,6 @@ void UI_HandleKeyDown(uint32_t key); void UI_HandleKeyUp(uint32_t key); void UI_HandleTextEdit(const char *text); +extern int32_t UI_GetCanvasWidth(void); +extern int32_t UI_GetCanvasHeight(void); extern UI_INPUT UI_TranslateInput(uint32_t system_keycode); diff --git a/include/libtrx/game/ui/widgets/label.h b/include/libtrx/game/ui/widgets/label.h index d6ed8b1..6c80119 100644 --- a/include/libtrx/game/ui/widgets/label.h +++ b/include/libtrx/game/ui/widgets/label.h @@ -9,6 +9,7 @@ extern UI_WIDGET *UI_Label_Create( extern void UI_Label_ChangeText(UI_WIDGET *widget, const char *text); extern const char *UI_Label_GetText(UI_WIDGET *widget); +extern void UI_Label_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); extern void UI_Label_AddFrame(UI_WIDGET *widget); extern void UI_Label_RemoveFrame(UI_WIDGET *widget); diff --git a/include/libtrx/game/ui/widgets/prompt.h b/include/libtrx/game/ui/widgets/prompt.h index 4b783a1..fbf387d 100644 --- a/include/libtrx/game/ui/widgets/prompt.h +++ b/include/libtrx/game/ui/widgets/prompt.h @@ -3,6 +3,7 @@ #include "./base.h" UI_WIDGET *UI_Prompt_Create(int32_t width, int32_t height); +void UI_Prompt_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); void UI_Prompt_SetFocus(UI_WIDGET *widget, bool is_focused); void UI_Prompt_Clear(UI_WIDGET *widget); diff --git a/include/libtrx/game/ui/widgets/stack.h b/include/libtrx/game/ui/widgets/stack.h index 4a4eaa5..b5ec70b 100644 --- a/include/libtrx/game/ui/widgets/stack.h +++ b/include/libtrx/game/ui/widgets/stack.h @@ -14,5 +14,5 @@ typedef enum { UI_WIDGET *UI_Stack_Create( UI_STACK_LAYOUT layout, int32_t width, int32_t height); void UI_Stack_AddChild(UI_WIDGET *self, UI_WIDGET *child); +void UI_Stack_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); void UI_Stack_DoLayout(UI_WIDGET *self); -void UI_Stack_SetInverse(UI_WIDGET *self, bool inverse); diff --git a/src/game/ui/widgets/console.c b/src/game/ui/widgets/console.c index f2e17a8..02a8744 100644 --- a/src/game/ui/widgets/console.c +++ b/src/game/ui/widgets/console.c @@ -2,6 +2,7 @@ #include "game/clock.h" #include "game/console/common.h" +#include "game/ui/common.h" #include "game/ui/events.h" #include "game/ui/widgets/label.h" #include "game/ui/widgets/prompt.h" @@ -27,6 +28,7 @@ typedef struct { int32_t listener1; int32_t listener2; + int32_t listener3; struct { double expire_at; @@ -36,6 +38,7 @@ typedef struct { static void M_HandlePromptCancel(const UI_EVENT *event, void *data); static void M_HandlePromptConfirm(const UI_EVENT *event, void *data); +static void M_HandleConfigChange(const UI_EVENT *event, void *data); static void M_UpdateLogCount(UI_CONSOLE *self); static int32_t M_GetWidth(const UI_CONSOLE *self); @@ -57,6 +60,17 @@ static void M_HandlePromptConfirm(const UI_EVENT *const event, void *const data) Console_Close(); } +static void M_HandleConfigChange(const UI_EVENT *event, void *data) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)data; + UI_Prompt_SetSize(self->prompt, M_GetWidth(self), TEXT_HEIGHT + LOG_MARGIN); + for (int32_t i = 0; i < MAX_LOG_LINES; i++) { + UI_Label_SetSize( + self->logs[i].label, M_GetWidth(self), UI_LABEL_AUTO_SIZE); + } + UI_Stack_SetSize(self->container, M_GetWidth(self), M_GetHeight(self)); +} + static void M_UpdateLogCount(UI_CONSOLE *const self) { self->logs_on_screen = 0; @@ -70,12 +84,12 @@ static void M_UpdateLogCount(UI_CONSOLE *const self) static int32_t M_GetWidth(const UI_CONSOLE *const self) { - return 640 - 2 * WINDOW_MARGIN; + return UI_GetCanvasWidth() - 2 * WINDOW_MARGIN; } static int32_t M_GetHeight(const UI_CONSOLE *const self) { - return 480 - 2 * WINDOW_MARGIN; + return UI_GetCanvasHeight() - 2 * WINDOW_MARGIN; } static void M_SetPosition(UI_CONSOLE *const self, int32_t x, int32_t y) @@ -103,6 +117,7 @@ static void M_Free(UI_CONSOLE *const self) self->container->free(self->container); UI_Events_Unsubscribe(self->listener1); UI_Events_Unsubscribe(self->listener2); + UI_Events_Unsubscribe(self->listener3); Memory_Free(self); } @@ -137,6 +152,8 @@ UI_WIDGET *UI_Console_Create(void) "confirm", self->prompt, M_HandlePromptConfirm, NULL); self->listener2 = UI_Events_Subscribe("cancel", self->prompt, M_HandlePromptCancel, NULL); + self->listener3 = + UI_Events_Subscribe("config_change", NULL, M_HandleConfigChange, self); return (UI_WIDGET *)self; } diff --git a/src/game/ui/widgets/prompt.c b/src/game/ui/widgets/prompt.c index 8487893..8a31c27 100644 --- a/src/game/ui/widgets/prompt.c +++ b/src/game/ui/widgets/prompt.c @@ -279,6 +279,12 @@ UI_WIDGET *UI_Prompt_Create(const int32_t width, const int32_t height) return (UI_WIDGET *)self; } +void UI_Prompt_SetSize(UI_WIDGET *widget, int32_t width, int32_t height) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + UI_Label_SetSize(self->label, width, height); +} + void UI_Prompt_SetFocus(UI_WIDGET *const widget, const bool is_focused) { UI_PROMPT *const self = (UI_PROMPT *)widget; diff --git a/src/game/ui/widgets/stack.c b/src/game/ui/widgets/stack.c index 2423e75..37c8a49 100644 --- a/src/game/ui/widgets/stack.c +++ b/src/game/ui/widgets/stack.c @@ -128,6 +128,15 @@ UI_WIDGET *UI_Stack_Create( return (UI_WIDGET *)self; } +void UI_Stack_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) +{ + UI_STACK *const self = (UI_STACK *)widget; + self->width = width; + self->height = height; + UI_Stack_DoLayout(widget); +} + void UI_Stack_DoLayout(UI_WIDGET *const widget) { UI_STACK *const self = (UI_STACK *)widget; From fd620ee643324695c0dde7b03176e5da95f91861 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Thu, 26 Sep 2024 11:05:37 +0200 Subject: [PATCH 3/7] ui/stack: remove inverse layouts; add alignment --- include/libtrx/game/ui/widgets/stack.h | 16 +++- src/game/ui/widgets/console.c | 19 ++-- src/game/ui/widgets/stack.c | 126 ++++++++++++++++++------- 3 files changed, 114 insertions(+), 47 deletions(-) diff --git a/include/libtrx/game/ui/widgets/stack.h b/include/libtrx/game/ui/widgets/stack.h index b5ec70b..46b2699 100644 --- a/include/libtrx/game/ui/widgets/stack.h +++ b/include/libtrx/game/ui/widgets/stack.h @@ -7,12 +7,24 @@ typedef enum { UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_LAYOUT_VERTICAL, - UI_STACK_LAYOUT_HORIZONTAL_INVERSE, - UI_STACK_LAYOUT_VERTICAL_INVERSE, } UI_STACK_LAYOUT; +typedef enum { + UI_STACK_H_ALIGN_LEFT, + UI_STACK_H_ALIGN_CENTER, + UI_STACK_H_ALIGN_RIGHT, +} UI_STACK_H_ALIGN; + +typedef enum { + UI_STACK_V_ALIGN_TOP, + UI_STACK_V_ALIGN_CENTER, + UI_STACK_V_ALIGN_BOTTOM, +} UI_STACK_V_ALIGN; + UI_WIDGET *UI_Stack_Create( UI_STACK_LAYOUT layout, int32_t width, int32_t height); +void UI_Stack_SetHAlign(UI_WIDGET *self, UI_STACK_H_ALIGN align); +void UI_Stack_SetVAlign(UI_WIDGET *self, UI_STACK_V_ALIGN align); void UI_Stack_AddChild(UI_WIDGET *self, UI_WIDGET *child); void UI_Stack_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); void UI_Stack_DoLayout(UI_WIDGET *self); diff --git a/src/game/ui/widgets/console.c b/src/game/ui/widgets/console.c index 02a8744..61f7f00 100644 --- a/src/game/ui/widgets/console.c +++ b/src/game/ui/widgets/console.c @@ -63,11 +63,6 @@ static void M_HandlePromptConfirm(const UI_EVENT *const event, void *const data) static void M_HandleConfigChange(const UI_EVENT *event, void *data) { UI_CONSOLE *const self = (UI_CONSOLE *)data; - UI_Prompt_SetSize(self->prompt, M_GetWidth(self), TEXT_HEIGHT + LOG_MARGIN); - for (int32_t i = 0; i < MAX_LOG_LINES; i++) { - UI_Label_SetSize( - self->logs[i].label, M_GetWidth(self), UI_LABEL_AUTO_SIZE); - } UI_Stack_SetSize(self->container, M_GetWidth(self), M_GetHeight(self)); } @@ -134,18 +129,20 @@ UI_WIDGET *UI_Console_Create(void) }; self->container = UI_Stack_Create( - UI_STACK_LAYOUT_VERTICAL_INVERSE, M_GetWidth(self), M_GetHeight(self)); - - self->prompt = UI_Prompt_Create(M_GetWidth(self), TEXT_HEIGHT + LOG_MARGIN); - UI_Stack_AddChild(self->container, self->prompt); + UI_STACK_LAYOUT_VERTICAL, M_GetWidth(self), M_GetHeight(self)); + UI_Stack_SetVAlign(self->container, UI_STACK_V_ALIGN_BOTTOM); - for (int32_t i = 0; i < MAX_LOG_LINES; i++) { + for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) { self->logs[i].label = - UI_Label_Create("", M_GetWidth(self), UI_LABEL_AUTO_SIZE); + UI_Label_Create("", UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE); UI_Label_SetScale(self->logs[i].label, LOG_SCALE); UI_Stack_AddChild(self->container, self->logs[i].label); } + self->prompt = + UI_Prompt_Create(UI_LABEL_AUTO_SIZE, TEXT_HEIGHT + LOG_MARGIN); + UI_Stack_AddChild(self->container, self->prompt); + M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN); self->listener1 = UI_Events_Subscribe( diff --git a/src/game/ui/widgets/stack.c b/src/game/ui/widgets/stack.c index 37c8a49..72794b1 100644 --- a/src/game/ui/widgets/stack.c +++ b/src/game/ui/widgets/stack.c @@ -7,6 +7,10 @@ typedef struct { UI_WIDGET_VTABLE vtable; + struct { + UI_STACK_H_ALIGN h; + UI_STACK_V_ALIGN v; + } align; int32_t width; int32_t height; int32_t x; @@ -15,6 +19,8 @@ typedef struct { VECTOR *children; } UI_STACK; +static int32_t M_GetChildrenWidth(const UI_STACK *self); +static int32_t M_GetChildrenHeight(const UI_STACK *self); static int32_t M_GetHeight(const UI_STACK *self); static int32_t M_GetWidth(const UI_STACK *self); static void M_SetPosition(UI_STACK *self, int32_t x, int32_t y); @@ -22,22 +28,17 @@ static void M_Control(UI_STACK *self); static void M_Draw(UI_STACK *self); static void M_Free(UI_STACK *self); -static int32_t M_GetWidth(const UI_STACK *const self) +static int32_t M_GetChildrenWidth(const UI_STACK *const self) { - if (self->width != UI_STACK_AUTO_SIZE) { - return self->width; - } int32_t result = 0; for (int32_t i = 0; i < self->children->count; i++) { const UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); switch (self->layout) { case UI_STACK_LAYOUT_HORIZONTAL: - case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: result += child->get_width(child); break; case UI_STACK_LAYOUT_VERTICAL: - case UI_STACK_LAYOUT_VERTICAL_INVERSE: result = MAX(result, child->get_width(child)); break; } @@ -45,22 +46,17 @@ static int32_t M_GetWidth(const UI_STACK *const self) return result; } -static int32_t M_GetHeight(const UI_STACK *const self) +static int32_t M_GetChildrenHeight(const UI_STACK *const self) { - if (self->height != UI_STACK_AUTO_SIZE) { - return self->height; - } int32_t result = 0; for (int32_t i = 0; i < self->children->count; i++) { const UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); switch (self->layout) { case UI_STACK_LAYOUT_HORIZONTAL: - case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: result = MAX(result, child->get_height(child)); break; case UI_STACK_LAYOUT_VERTICAL: - case UI_STACK_LAYOUT_VERTICAL_INVERSE: result += child->get_height(child); break; } @@ -68,6 +64,22 @@ static int32_t M_GetHeight(const UI_STACK *const self) return result; } +static int32_t M_GetWidth(const UI_STACK *const self) +{ + if (self->width != UI_STACK_AUTO_SIZE) { + return self->width; + } + return M_GetChildrenWidth(self); +} + +static int32_t M_GetHeight(const UI_STACK *const self) +{ + if (self->height != UI_STACK_AUTO_SIZE) { + return self->height; + } + return M_GetChildrenHeight(self); +} + static void M_SetPosition( UI_STACK *const self, const int32_t x, const int32_t y) { @@ -121,6 +133,8 @@ UI_WIDGET *UI_Stack_Create( .free = (UI_WIDGET_FREE)M_Free, }; + self->align.h = UI_STACK_H_ALIGN_LEFT; + self->align.v = UI_STACK_V_ALIGN_TOP; self->width = width; self->height = height; self->layout = layout; @@ -128,6 +142,18 @@ UI_WIDGET *UI_Stack_Create( return (UI_WIDGET *)self; } +void UI_Stack_SetHAlign(UI_WIDGET *const widget, const UI_STACK_H_ALIGN align) +{ + UI_STACK *const self = (UI_STACK *)widget; + self->align.h = align; +} + +void UI_Stack_SetVAlign(UI_WIDGET *const widget, const UI_STACK_V_ALIGN align) +{ + UI_STACK *const self = (UI_STACK *)widget; + self->align.v = align; +} + void UI_Stack_SetSize( UI_WIDGET *const widget, const int32_t width, const int32_t height) { @@ -142,21 +168,39 @@ void UI_Stack_DoLayout(UI_WIDGET *const widget) UI_STACK *const self = (UI_STACK *)widget; const int32_t self_width = M_GetWidth(self); const int32_t self_height = M_GetHeight(self); - const int32_t start_x = self->x; - const int32_t start_y = self->y; - int32_t x; - int32_t y; + const int32_t children_width = M_GetChildrenWidth(self); + const int32_t children_height = M_GetChildrenHeight(self); + // calculate main axis placement + int32_t x = -999; + int32_t y = -999; switch (self->layout) { case UI_STACK_LAYOUT_HORIZONTAL: - case UI_STACK_LAYOUT_VERTICAL: - x = start_x; - y = start_y; + switch (self->align.h) { + case UI_STACK_H_ALIGN_LEFT: + x = self->x; + break; + case UI_STACK_H_ALIGN_CENTER: + x = self->x + (self_width - children_width) / 2; + break; + case UI_STACK_H_ALIGN_RIGHT: + x = self->x + self_width - children_width; + break; + } break; - case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: - case UI_STACK_LAYOUT_VERTICAL_INVERSE: - x = start_x + self_width; - y = start_y + self_height; + + case UI_STACK_LAYOUT_VERTICAL: + switch (self->align.v) { + case UI_STACK_V_ALIGN_TOP: + y = self->y; + break; + case UI_STACK_V_ALIGN_CENTER: + y = self->y + (self_height - children_height) / 2; + break; + case UI_STACK_V_ALIGN_BOTTOM: + y = self->y + self_height - children_height; + break; + } break; } @@ -165,27 +209,41 @@ void UI_Stack_DoLayout(UI_WIDGET *const widget) const int32_t child_width = child->get_width(child); const int32_t child_height = child->get_height(child); - // centre in the other axis + // calculate other axis placement switch (self->layout) { case UI_STACK_LAYOUT_HORIZONTAL: - case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: - y = start_y + (self_height - child_height) / 2; + switch (self->align.v) { + case UI_STACK_V_ALIGN_TOP: + y = self->y; + break; + case UI_STACK_V_ALIGN_CENTER: + y = self->y + (self_height - child_height) / 2; + break; + case UI_STACK_V_ALIGN_BOTTOM: + y = self->y + self_height - child_height; + break; + } break; + case UI_STACK_LAYOUT_VERTICAL: - case UI_STACK_LAYOUT_VERTICAL_INVERSE: - x = start_x + (self_width - child_width) / 2; + switch (self->align.h) { + case UI_STACK_H_ALIGN_LEFT: + x = self->x; + break; + case UI_STACK_H_ALIGN_CENTER: + x = self->x + (self_width - child_width) / 2; + break; + case UI_STACK_H_ALIGN_RIGHT: + x = self->x + self_width - child_width; + break; + } break; } child->set_position(child, x, y); + // calculate main axis offset switch (self->layout) { - case UI_STACK_LAYOUT_HORIZONTAL_INVERSE: - x -= child_width; - break; - case UI_STACK_LAYOUT_VERTICAL_INVERSE: - y -= child_height; - break; case UI_STACK_LAYOUT_HORIZONTAL: x += child_width; break; From 4e694eaa960e03f99cf2205df0ae7fd89761e975 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Thu, 26 Sep 2024 11:33:21 +0200 Subject: [PATCH 4/7] ui: extract EventManager --- include/libtrx/event_manager.h | 24 +++++++++++ include/libtrx/game/ui/events.h | 13 ++---- meson.build | 1 + src/event_manager.c | 73 +++++++++++++++++++++++++++++++++ src/game/ui/common.c | 6 +-- src/game/ui/events.c | 53 +++++------------------- src/game/ui/widgets/console.c | 14 +++---- src/game/ui/widgets/prompt.c | 16 ++++---- 8 files changed, 131 insertions(+), 69 deletions(-) create mode 100644 include/libtrx/event_manager.h create mode 100644 src/event_manager.c diff --git a/include/libtrx/event_manager.h b/include/libtrx/event_manager.h new file mode 100644 index 0000000..3516b50 --- /dev/null +++ b/include/libtrx/event_manager.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef struct { + const char *name; + const void *sender; + void *data; +} EVENT; + +typedef void (*EVENT_LISTENER)(const EVENT *, void *user_data); + +typedef struct EVENT_MANAGER EVENT_MANAGER; + +EVENT_MANAGER *EventManager_Create(void); +void EventManager_Free(EVENT_MANAGER *manager); + +int32_t EventManager_Subscribe( + EVENT_MANAGER *manager, const char *event_name, const void *sender, + EVENT_LISTENER listener, void *user_data); + +void EventManager_Unsubscribe(EVENT_MANAGER *manager, int32_t listener_id); + +void EventManager_Fire(EVENT_MANAGER *manager, const EVENT *event); diff --git a/include/libtrx/game/ui/events.h b/include/libtrx/game/ui/events.h index 6cfef98..28c699e 100644 --- a/include/libtrx/game/ui/events.h +++ b/include/libtrx/game/ui/events.h @@ -1,22 +1,17 @@ #pragma once +#include "../../event_manager.h" #include "./widgets/base.h" -typedef struct { - const char *name; - const UI_WIDGET *sender; - void *data; -} UI_EVENT; - -typedef void (*UI_EVENT_LISTENER)(const UI_EVENT *, void *user_data); +typedef void (*EVENT_LISTENER)(const EVENT *, void *user_data); void UI_Events_Init(void); void UI_Events_Shutdown(void); int32_t UI_Events_Subscribe( - const char *event_name, const UI_WIDGET *sender, UI_EVENT_LISTENER listener, + const char *event_name, const UI_WIDGET *sender, EVENT_LISTENER listener, void *user_data); void UI_Events_Unsubscribe(int32_t listener_id); -void UI_Events_Fire(const UI_EVENT *event); +void UI_Events_Fire(const EVENT *event); diff --git a/meson.build b/meson.build index 6610ef2..b55cdac 100644 --- a/meson.build +++ b/meson.build @@ -65,6 +65,7 @@ sources = [ 'src/engine/audio_stream.c', 'src/engine/image.c', 'src/enum_str.c', + 'src/event_manager.c', 'src/filesystem.c', 'src/game/backpack.c', 'src/game/console/cmd/config.c', diff --git a/src/event_manager.c b/src/event_manager.c new file mode 100644 index 0000000..0a74ae0 --- /dev/null +++ b/src/event_manager.c @@ -0,0 +1,73 @@ +#include "event_manager.h" + +#include "memory.h" +#include "vector.h" + +#include +#include + +typedef struct { + int32_t listener_id; + const char *event_name; + const void *sender; + EVENT_LISTENER listener; + void *user_data; +} M_LISTENER; + +typedef struct EVENT_MANAGER { + VECTOR *listeners; + int32_t listener_id; +} EVENT_MANAGER; + +EVENT_MANAGER *EventManager_Create(void) +{ + EVENT_MANAGER *manager = Memory_Alloc(sizeof(EVENT_MANAGER)); + manager->listeners = Vector_Create(sizeof(M_LISTENER)); + manager->listener_id = 0; + return manager; +} + +void EventManager_Free(EVENT_MANAGER *const manager) +{ + Vector_Free(manager->listeners); + Memory_Free(manager); +} + +int32_t EventManager_Subscribe( + EVENT_MANAGER *const manager, const char *const event_name, + const void *const sender, const EVENT_LISTENER listener, + void *const user_data) +{ + M_LISTENER entry = { + .listener_id = manager->listener_id++, + .event_name = event_name, + .sender = sender, + .listener = listener, + .user_data = user_data, + }; + Vector_Add(manager->listeners, &entry); + return entry.listener_id; +} + +void EventManager_Unsubscribe( + EVENT_MANAGER *const manager, const int32_t listener_id) +{ + for (int32_t i = 0; i < manager->listeners->count; i++) { + M_LISTENER entry = *(M_LISTENER *)Vector_Get(manager->listeners, i); + if (entry.listener_id == listener_id) { + Vector_RemoveAt(manager->listeners, i); + return; + } + } +} + +void EventManager_Fire(EVENT_MANAGER *const manager, const EVENT *const event) +{ + for (int32_t i = 0; i < manager->listeners->count; i++) { + M_LISTENER entry = *(M_LISTENER *)Vector_Get(manager->listeners, i); + if (strcmp(entry.event_name, event->name) == 0 + && entry.sender == event->sender) { + entry.listener(event, entry.user_data); + } + } +} diff --git a/src/game/ui/common.c b/src/game/ui/common.c index cf39e19..eefa861 100644 --- a/src/game/ui/common.c +++ b/src/game/ui/common.c @@ -14,7 +14,7 @@ void UI_Shutdown(void) void UI_HandleKeyDown(const uint32_t key) { - const UI_EVENT event = { + const EVENT event = { .name = "key_down", .sender = NULL, .data = (void *)UI_TranslateInput(key), @@ -24,7 +24,7 @@ void UI_HandleKeyDown(const uint32_t key) void UI_HandleKeyUp(const uint32_t key) { - const UI_EVENT event = { + const EVENT event = { .name = "key_up", .sender = NULL, .data = (void *)UI_TranslateInput(key), @@ -34,7 +34,7 @@ void UI_HandleKeyUp(const uint32_t key) void UI_HandleTextEdit(const char *const text) { - const UI_EVENT event = { + const EVENT event = { .name = "text_edit", .sender = NULL, .data = (void *)text, diff --git a/src/game/ui/events.c b/src/game/ui/events.c index 670ee4f..6eb72b6 100644 --- a/src/game/ui/events.c +++ b/src/game/ui/events.c @@ -1,65 +1,34 @@ #include "game/ui/events.h" -#include "memory.h" -#include "vector.h" +#include -#include -#include - -typedef struct { - int32_t listener_id; - const char *event_name; - const UI_WIDGET *sender; - UI_EVENT_LISTENER listener; - void *user_data; -} M_LISTENER; - -static VECTOR *m_Listeners = NULL; -static int32_t m_ListenerID = 0; +static EVENT_MANAGER *m_EventManager = NULL; void UI_Events_Init(void) { - m_Listeners = Vector_Create(sizeof(M_LISTENER)); + m_EventManager = EventManager_Create(); } void UI_Events_Shutdown(void) { - Vector_Free(m_Listeners); + EventManager_Free(m_EventManager); + m_EventManager = NULL; } int32_t UI_Events_Subscribe( const char *const event_name, const UI_WIDGET *const sender, - const UI_EVENT_LISTENER listener, void *const user_data) + const EVENT_LISTENER listener, void *const user_data) { - M_LISTENER entry = { - .listener_id = m_ListenerID++, - .event_name = event_name, - .sender = sender, - .listener = listener, - .user_data = user_data, - }; - Vector_Add(m_Listeners, &entry); - return entry.listener_id; + return EventManager_Subscribe( + m_EventManager, event_name, sender, listener, user_data); } void UI_Events_Unsubscribe(const int32_t listener_id) { - for (int32_t i = 0; i < m_Listeners->count; i++) { - M_LISTENER entry = *(M_LISTENER *)Vector_Get(m_Listeners, i); - if (entry.listener_id == listener_id) { - Vector_RemoveAt(m_Listeners, i); - return; - } - } + EventManager_Unsubscribe(m_EventManager, listener_id); } -void UI_Events_Fire(const UI_EVENT *const event) +void UI_Events_Fire(const EVENT *const event) { - for (int32_t i = 0; i < m_Listeners->count; i++) { - M_LISTENER entry = *(M_LISTENER *)Vector_Get(m_Listeners, i); - if (strcmp(entry.event_name, event->name) == 0 - && entry.sender == event->sender) { - entry.listener(event, entry.user_data); - } - } + EventManager_Fire(m_EventManager, event); } diff --git a/src/game/ui/widgets/console.c b/src/game/ui/widgets/console.c index 61f7f00..ba163c6 100644 --- a/src/game/ui/widgets/console.c +++ b/src/game/ui/widgets/console.c @@ -36,9 +36,9 @@ typedef struct { } logs[MAX_LOG_LINES]; } UI_CONSOLE; -static void M_HandlePromptCancel(const UI_EVENT *event, void *data); -static void M_HandlePromptConfirm(const UI_EVENT *event, void *data); -static void M_HandleConfigChange(const UI_EVENT *event, void *data); +static void M_HandlePromptCancel(const EVENT *event, void *data); +static void M_HandlePromptConfirm(const EVENT *event, void *data); +static void M_HandleCanvasResize(const EVENT *event, void *data); static void M_UpdateLogCount(UI_CONSOLE *self); static int32_t M_GetWidth(const UI_CONSOLE *self); @@ -48,19 +48,19 @@ static void M_Control(UI_CONSOLE *self); static void M_Draw(UI_CONSOLE *self); static void M_Free(UI_CONSOLE *self); -static void M_HandlePromptCancel(const UI_EVENT *const event, void *const data) +static void M_HandlePromptCancel(const EVENT *const event, void *const data) { Console_Close(); } -static void M_HandlePromptConfirm(const UI_EVENT *const event, void *const data) +static void M_HandlePromptConfirm(const EVENT *const event, void *const data) { const char *text = event->data; Console_Eval(text); Console_Close(); } -static void M_HandleConfigChange(const UI_EVENT *event, void *data) +static void M_HandleCanvasResize(const EVENT *event, void *data) { UI_CONSOLE *const self = (UI_CONSOLE *)data; UI_Stack_SetSize(self->container, M_GetWidth(self), M_GetHeight(self)); @@ -150,7 +150,7 @@ UI_WIDGET *UI_Console_Create(void) self->listener2 = UI_Events_Subscribe("cancel", self->prompt, M_HandlePromptCancel, NULL); self->listener3 = - UI_Events_Subscribe("config_change", NULL, M_HandleConfigChange, self); + UI_Events_Subscribe("canvas_resize", NULL, M_HandleCanvasResize, self); return (UI_WIDGET *)self; } diff --git a/src/game/ui/widgets/prompt.c b/src/game/ui/widgets/prompt.c index 8a31c27..a519688 100644 --- a/src/game/ui/widgets/prompt.c +++ b/src/game/ui/widgets/prompt.c @@ -47,8 +47,8 @@ static void M_SetPosition(UI_PROMPT *self, int32_t x, int32_t y); static void M_Control(UI_PROMPT *self); static void M_Draw(UI_PROMPT *self); static void M_Free(UI_PROMPT *self); -static void M_HandleKeyDown(const UI_EVENT *event, void *user_data); -static void M_HandleTextEdit(const UI_EVENT *event, void *user_data); +static void M_HandleKeyDown(const EVENT *event, void *user_data); +static void M_HandleTextEdit(const EVENT *event, void *user_data); static void M_UpdatePromptLabel(UI_PROMPT *const self) { @@ -166,9 +166,9 @@ static void M_Confirm(UI_PROMPT *const self) M_Cancel(self); return; } - const UI_EVENT event = { + const EVENT event = { .name = "confirm", - .sender = (const UI_WIDGET *)self, + .sender = self, .data = self->current_text, }; UI_Events_Fire(&event); @@ -178,9 +178,9 @@ static void M_Confirm(UI_PROMPT *const self) static void M_Cancel(UI_PROMPT *const self) { - const UI_EVENT event = { + const EVENT event = { .name = "cancel", - .sender = (const UI_WIDGET *)self, + .sender = self, .data = self->current_text, }; UI_Events_Fire(&event); @@ -195,7 +195,7 @@ static void M_Clear(UI_PROMPT *const self) M_UpdateCaretLabel(self); } -static void M_HandleKeyDown(const UI_EVENT *const event, void *const user_data) +static void M_HandleKeyDown(const EVENT *const event, void *const user_data) { const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data; UI_PROMPT *const self = user_data; @@ -217,7 +217,7 @@ static void M_HandleKeyDown(const UI_EVENT *const event, void *const user_data) // clang-format on } -static void M_HandleTextEdit(const UI_EVENT *const event, void *const user_data) +static void M_HandleTextEdit(const EVENT *const event, void *const user_data) { const char *insert_string = event->data; const size_t insert_length = strlen(insert_string); From 401454ac9e5bc8e7085d32385db33ac565029568 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Thu, 26 Sep 2024 11:49:45 +0200 Subject: [PATCH 5/7] ui: introduce spacer --- include/libtrx/game/ui/widgets/spacer.h | 6 +++ meson.build | 1 + src/game/ui/widgets/console.c | 17 ++++--- src/game/ui/widgets/prompt.c | 5 ++- src/game/ui/widgets/spacer.c | 60 +++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 include/libtrx/game/ui/widgets/spacer.h create mode 100644 src/game/ui/widgets/spacer.c diff --git a/include/libtrx/game/ui/widgets/spacer.h b/include/libtrx/game/ui/widgets/spacer.h new file mode 100644 index 0000000..9c20e21 --- /dev/null +++ b/include/libtrx/game/ui/widgets/spacer.h @@ -0,0 +1,6 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Spacer_Create(int32_t width, int32_t height); +void UI_Spacer_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); diff --git a/meson.build b/meson.build index b55cdac..8563b96 100644 --- a/meson.build +++ b/meson.build @@ -89,6 +89,7 @@ sources = [ 'src/game/ui/events.c', 'src/game/ui/widgets/console.c', 'src/game/ui/widgets/prompt.c', + 'src/game/ui/widgets/spacer.c', 'src/game/ui/widgets/stack.c', 'src/gfx/2d/2d_renderer.c', 'src/gfx/2d/2d_surface.c', diff --git a/src/game/ui/widgets/console.c b/src/game/ui/widgets/console.c index ba163c6..c859a25 100644 --- a/src/game/ui/widgets/console.c +++ b/src/game/ui/widgets/console.c @@ -6,6 +6,7 @@ #include "game/ui/events.h" #include "game/ui/widgets/label.h" #include "game/ui/widgets/prompt.h" +#include "game/ui/widgets/spacer.h" #include "game/ui/widgets/stack.h" #include "memory.h" #include "utils.h" @@ -13,8 +14,8 @@ #include #define WINDOW_MARGIN 5 -#define LOG_MARGIN 3 -#define TEXT_HEIGHT 15 +#define LOG_HEIGHT 16 +#define LOG_MARGIN 10 #define MAX_LOG_LINES 20 #define LOG_SCALE 0.8 #define DELAY_PER_CHAR 0.2 @@ -23,6 +24,7 @@ typedef struct { UI_WIDGET_VTABLE vtable; UI_WIDGET *container; UI_WIDGET *prompt; + UI_WIDGET *spacer; char *log_lines; int32_t logs_on_screen; @@ -108,6 +110,7 @@ static void M_Draw(UI_CONSOLE *const self) static void M_Free(UI_CONSOLE *const self) { + self->spacer->free(self->spacer); self->prompt->free(self->prompt); self->container->free(self->container); UI_Events_Unsubscribe(self->listener1); @@ -121,7 +124,7 @@ UI_WIDGET *UI_Console_Create(void) UI_CONSOLE *const self = Memory_Alloc(sizeof(UI_CONSOLE)); self->vtable = (UI_WIDGET_VTABLE) { .control = (UI_WIDGET_CONTROL)M_Control, - .draw = (UI_WIDGET_CONTROL)M_Draw, + .draw = (UI_WIDGET_DRAW)M_Draw, .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, @@ -134,13 +137,15 @@ UI_WIDGET *UI_Console_Create(void) for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) { self->logs[i].label = - UI_Label_Create("", UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE); + UI_Label_Create("", UI_LABEL_AUTO_SIZE, LOG_HEIGHT * LOG_SCALE); UI_Label_SetScale(self->logs[i].label, LOG_SCALE); UI_Stack_AddChild(self->container, self->logs[i].label); } - self->prompt = - UI_Prompt_Create(UI_LABEL_AUTO_SIZE, TEXT_HEIGHT + LOG_MARGIN); + self->spacer = UI_Spacer_Create(LOG_MARGIN, LOG_MARGIN); + UI_Stack_AddChild(self->container, self->spacer); + + self->prompt = UI_Prompt_Create(UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE); UI_Stack_AddChild(self->container, self->prompt); M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN); diff --git a/src/game/ui/widgets/prompt.c b/src/game/ui/widgets/prompt.c index a519688..2259247 100644 --- a/src/game/ui/widgets/prompt.c +++ b/src/game/ui/widgets/prompt.c @@ -256,7 +256,7 @@ UI_WIDGET *UI_Prompt_Create(const int32_t width, const int32_t height) UI_PROMPT *const self = Memory_Alloc(sizeof(UI_PROMPT)); self->vtable = (UI_WIDGET_VTABLE) { .control = (UI_WIDGET_CONTROL)M_Control, - .draw = (UI_WIDGET_CONTROL)M_Draw, + .draw = (UI_WIDGET_DRAW)M_Draw, .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, @@ -279,7 +279,8 @@ UI_WIDGET *UI_Prompt_Create(const int32_t width, const int32_t height) return (UI_WIDGET *)self; } -void UI_Prompt_SetSize(UI_WIDGET *widget, int32_t width, int32_t height) +void UI_Prompt_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) { UI_PROMPT *const self = (UI_PROMPT *)widget; UI_Label_SetSize(self->label, width, height); diff --git a/src/game/ui/widgets/spacer.c b/src/game/ui/widgets/spacer.c new file mode 100644 index 0000000..ac58b38 --- /dev/null +++ b/src/game/ui/widgets/spacer.c @@ -0,0 +1,60 @@ +#include "game/ui/widgets/spacer.h" + +#include "memory.h" + +typedef struct { + UI_WIDGET_VTABLE vtable; + int32_t width; + int32_t height; +} UI_SPACER; + +static int32_t M_GetWidth(const UI_SPACER *self); +static int32_t M_GetHeight(const UI_SPACER *self); +static void M_SetPosition(UI_SPACER *self, int32_t x, int32_t y); +static void M_Control(UI_SPACER *self); +static void M_Draw(UI_SPACER *self); +static void M_Free(UI_SPACER *self); + +static int32_t M_GetWidth(const UI_SPACER *const self) +{ + return self->width; +} + +static int32_t M_GetHeight(const UI_SPACER *const self) +{ + return self->height; +} + +static void M_SetPosition( + UI_SPACER *const self, const int32_t x, const int32_t y) +{ +} + +static void M_Free(UI_SPACER *const self) +{ + Memory_Free(self); +} + +UI_WIDGET *UI_Spacer_Create(const int32_t width, const int32_t height) +{ + UI_SPACER *const self = Memory_Alloc(sizeof(UI_SPACER)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = NULL, + .draw = NULL, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + self->width = width; + self->height = height; + return (UI_WIDGET *)self; +} + +void UI_Spacer_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) +{ + UI_SPACER *const self = (UI_SPACER *)widget; + self->width = width; + self->height = height; +} From 463a76b81e7477cf62021a3af44208c41d2242bb Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Thu, 26 Sep 2024 12:07:58 +0200 Subject: [PATCH 6/7] config: move Config_Load and _Read to libtrx --- include/libtrx/config/common.h | 16 ++++++++++++ include/libtrx/config/config.h | 9 ------- .../libtrx/config/{config_file.h => file.h} | 2 +- include/libtrx/config/{config_map.h => map.h} | 2 +- .../config/{config_option.h => option.h} | 0 include/libtrx/filesystem.h | 2 ++ include/libtrx/game/console/cmd/config.h | 2 +- meson.build | 3 ++- src/config/common.c | 26 +++++++++++++++++++ src/config/{config_file.c => file.c} | 2 +- src/filesystem.c | 12 +++++++++ src/game/console/cmd/config.c | 4 +-- 12 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 include/libtrx/config/common.h delete mode 100644 include/libtrx/config/config.h rename include/libtrx/config/{config_file.h => file.h} (96%) rename include/libtrx/config/{config_map.h => map.h} (98%) rename include/libtrx/config/{config_option.h => option.h} (100%) create mode 100644 src/config/common.c rename src/config/{config_file.c => file.c} (99%) diff --git a/include/libtrx/config/common.h b/include/libtrx/config/common.h new file mode 100644 index 0000000..3c4bbdd --- /dev/null +++ b/include/libtrx/config/common.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../json.h" +#include "./option.h" + +#include + +bool Config_Read(void); +bool Config_Write(void); + +extern const char *Config_GetPath(void); +extern void Config_Sanitize(void); +extern void Config_ApplyChanges(void); +extern const CONFIG_OPTION *Config_GetOptionMap(void); +extern void Config_LoadFromJSON(JSON_OBJECT *root_obj); +extern void Config_DumpToJSON(JSON_OBJECT *root_obj); diff --git a/include/libtrx/config/config.h b/include/libtrx/config/config.h deleted file mode 100644 index 5999263..0000000 --- a/include/libtrx/config/config.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "config_option.h" - -bool Config_Read(void); -bool Config_Write(void); -void Config_Sanitize(void); -void Config_ApplyChanges(void); -const CONFIG_OPTION *Config_GetOptionMap(void); diff --git a/include/libtrx/config/config_file.h b/include/libtrx/config/file.h similarity index 96% rename from include/libtrx/config/config_file.h rename to include/libtrx/config/file.h index 7760b28..2ee98ed 100644 --- a/include/libtrx/config/config_file.h +++ b/include/libtrx/config/file.h @@ -2,7 +2,7 @@ #include "../enum_str.h" #include "../json.h" -#include "config_option.h" +#include "./option.h" #include #include diff --git a/include/libtrx/config/config_map.h b/include/libtrx/config/map.h similarity index 98% rename from include/libtrx/config/config_map.h rename to include/libtrx/config/map.h index 91cdce5..d48b55d 100644 --- a/include/libtrx/config/config_map.h +++ b/include/libtrx/config/map.h @@ -1,6 +1,6 @@ #include "../enum_str.h" #include "../utils.h" -#include "config_option.h" +#include "./option.h" #include #include diff --git a/include/libtrx/config/config_option.h b/include/libtrx/config/option.h similarity index 100% rename from include/libtrx/config/config_option.h rename to include/libtrx/config/option.h diff --git a/include/libtrx/filesystem.h b/include/libtrx/filesystem.h index 3347ec4..ad7bed7 100644 --- a/include/libtrx/filesystem.h +++ b/include/libtrx/filesystem.h @@ -34,6 +34,8 @@ const char *File_GetGameDirectory(void); // only be necessary when interacting with external libraries. char *File_GetFullPath(const char *path); +char *File_GetParentDirectory(const char *path); + char *File_GuessExtension(const char *path, const char **extensions); MYFILE *File_Open(const char *path, FILE_OPEN_MODE mode); diff --git a/include/libtrx/game/console/cmd/config.h b/include/libtrx/game/console/cmd/config.h index 3a552b9..cdf477c 100644 --- a/include/libtrx/game/console/cmd/config.h +++ b/include/libtrx/game/console/cmd/config.h @@ -1,6 +1,6 @@ #pragma once -#include "../../../config/config_option.h" +#include "../../../config/option.h" #include "../common.h" extern CONSOLE_COMMAND g_Console_Cmd_Config; diff --git a/meson.build b/meson.build index 8563b96..b740a2d 100644 --- a/meson.build +++ b/meson.build @@ -59,7 +59,8 @@ endif sources = [ 'src/benchmark.c', - 'src/config/config_file.c', + 'src/config/common.c', + 'src/config/file.c', 'src/engine/audio.c', 'src/engine/audio_sample.c', 'src/engine/audio_stream.c', diff --git a/src/config/common.c b/src/config/common.c new file mode 100644 index 0000000..45a3c14 --- /dev/null +++ b/src/config/common.c @@ -0,0 +1,26 @@ +#include "config/common.h" + +#include "config/file.h" +#include "game/ui/events.h" + +bool Config_Read(void) +{ + const bool result = ConfigFile_Read(Config_GetPath(), &Config_LoadFromJSON); + if (result) { + Config_Sanitize(); + Config_ApplyChanges(); + } + return result; +} + +bool Config_Write(void) +{ + const EVENT event = { + .name = "canvas_resize", + .sender = NULL, + .data = NULL, + }; + UI_Events_Fire(&event); + + return ConfigFile_Write(Config_GetPath(), &Config_DumpToJSON); +} diff --git a/src/config/config_file.c b/src/config/file.c similarity index 99% rename from src/config/config_file.c rename to src/config/file.c index 187d3b5..d370f80 100644 --- a/src/config/config_file.c +++ b/src/config/file.c @@ -1,4 +1,4 @@ -#include "config/config_file.h" +#include "config/file.h" #include "filesystem.h" #include "log.h" diff --git a/src/filesystem.c b/src/filesystem.c index 47e27df..4bb0587 100644 --- a/src/filesystem.c +++ b/src/filesystem.c @@ -3,6 +3,7 @@ #include "log.h" #include "memory.h" #include "strings.h" +#include "utils.h" #include #include @@ -190,6 +191,17 @@ char *File_GetFullPath(const char *path) return full_path; } +char *File_GetParentDirectory(const char *path) +{ + char *full_path = File_GetFullPath(path); + char *const last_delim = + MAX(strrchr(full_path, '/'), strrchr(full_path, '\\')); + if (last_delim != NULL) { + *last_delim = '\0'; + } + return full_path; +} + char *File_GuessExtension(const char *path, const char **extensions) { if (!File_Exists(path)) { diff --git a/src/game/console/cmd/config.c b/src/game/console/cmd/config.c index 2c11add..f66e986 100644 --- a/src/game/console/cmd/config.c +++ b/src/game/console/cmd/config.c @@ -1,7 +1,7 @@ #include "game/console/cmd/config.h" -#include "config/config.h" -#include "config/config_map.h" +#include "config/common.h" +#include "config/map.h" #include "game/game_string.h" #include "memory.h" #include "strings.h" From e1d12599cac2d06331d13f3758e763b8588904e1 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Thu, 26 Sep 2024 16:40:36 +0200 Subject: [PATCH 7/7] config: add own event manager --- include/libtrx/config.h | 6 ++++ include/libtrx/config/common.h | 11 +++++++ src/config/common.c | 52 ++++++++++++++++++++++++++++------ src/config/file.c | 23 +++++++++------ src/filesystem.c | 6 +++- src/game/console/cmd/config.c | 2 -- src/game/ui/events.c | 15 ++++++++++ 7 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 include/libtrx/config.h diff --git a/include/libtrx/config.h b/include/libtrx/config.h new file mode 100644 index 0000000..7daa7d0 --- /dev/null +++ b/include/libtrx/config.h @@ -0,0 +1,6 @@ +#pragma once + +#include "config/common.h" +#include "config/file.h" +#include "config/map.h" +#include "config/option.h" diff --git a/include/libtrx/config/common.h b/include/libtrx/config/common.h index 3c4bbdd..a42c33e 100644 --- a/include/libtrx/config/common.h +++ b/include/libtrx/config/common.h @@ -1,16 +1,27 @@ #pragma once +#include "../event_manager.h" #include "../json.h" #include "./option.h" #include +#include + +void Config_Init(void); +void Config_Shutdown(void); bool Config_Read(void); bool Config_Write(void); +int32_t Config_SubscribeChanges(EVENT_LISTENER listener, void *user_data); +void Config_UnsubscribeChanges(int32_t listener_id); + extern const char *Config_GetPath(void); + extern void Config_Sanitize(void); extern void Config_ApplyChanges(void); + extern const CONFIG_OPTION *Config_GetOptionMap(void); + extern void Config_LoadFromJSON(JSON_OBJECT *root_obj); extern void Config_DumpToJSON(JSON_OBJECT *root_obj); diff --git a/src/config/common.c b/src/config/common.c index 45a3c14..55e8801 100644 --- a/src/config/common.c +++ b/src/config/common.c @@ -1,7 +1,21 @@ #include "config/common.h" #include "config/file.h" -#include "game/ui/events.h" + +#include + +EVENT_MANAGER *m_EventManager = NULL; + +void Config_Init(void) +{ + m_EventManager = EventManager_Create(); +} + +void Config_Shutdown(void) +{ + EventManager_Free(m_EventManager); + m_EventManager = NULL; +} bool Config_Read(void) { @@ -15,12 +29,32 @@ bool Config_Read(void) bool Config_Write(void) { - const EVENT event = { - .name = "canvas_resize", - .sender = NULL, - .data = NULL, - }; - UI_Events_Fire(&event); - - return ConfigFile_Write(Config_GetPath(), &Config_DumpToJSON); + Config_Sanitize(); + const bool updated = ConfigFile_Write(Config_GetPath(), &Config_DumpToJSON); + if (updated) { + Config_ApplyChanges(); + if (m_EventManager != NULL) { + const EVENT event = { + .name = "write", + .sender = NULL, + .data = NULL, + }; + EventManager_Fire(m_EventManager, &event); + } + } + return updated; +} + +int32_t Config_SubscribeChanges( + const EVENT_LISTENER listener, void *const user_data) +{ + assert(m_EventManager != NULL); + return EventManager_Subscribe( + m_EventManager, "write", NULL, listener, user_data); +} + +void Config_UnsubscribeChanges(const int32_t listener_id) +{ + assert(m_EventManager != NULL); + return EventManager_Unsubscribe(m_EventManager, listener_id); } diff --git a/src/config/file.c b/src/config/file.c index d370f80..84acb54 100644 --- a/src/config/file.c +++ b/src/config/file.c @@ -83,17 +83,24 @@ bool ConfigFile_Write(const char *path, void (*dump)(JSON_OBJECT *root_obj)) { LOG_INFO("Saving user settings"); - MYFILE *fp = File_Open(path, FILE_OPEN_WRITE); - if (!fp) { - return false; - } + char *old_data; + File_Load(path, &old_data, NULL); + bool updated = false; char *data = M_WriteToJSON(dump); - File_WriteData(fp, data, strlen(data)); - File_Close(fp); - Memory_FreePointer(&data); + if (old_data == NULL || strcmp(data, old_data) != 0) { + MYFILE *const fp = File_Open(path, FILE_OPEN_WRITE); + if (fp == NULL) { + LOG_ERROR("Failed to write settings!"); + } else { + File_WriteData(fp, data, strlen(data)); + File_Close(fp); + updated = true; + } + } - return true; + Memory_FreePointer(&data); + return updated; } void ConfigFile_LoadOptions(JSON_OBJECT *root_obj, const CONFIG_OPTION *options) diff --git a/src/filesystem.c b/src/filesystem.c index 4bb0587..a8f03bf 100644 --- a/src/filesystem.c +++ b/src/filesystem.c @@ -394,9 +394,12 @@ void File_Close(MYFILE *file) bool File_Load(const char *path, char **output_data, size_t *output_size) { + assert(output_data != NULL); + MYFILE *fp = File_Open(path, FILE_OPEN_READ); if (!fp) { LOG_ERROR("Can't open file %s", path); + *output_data = NULL; return false; } @@ -404,6 +407,7 @@ bool File_Load(const char *path, char **output_data, size_t *output_size) char *data = Memory_Alloc(data_size + 1); File_ReadData(fp, data, data_size); if (File_Pos(fp) != data_size) { + *output_data = NULL; LOG_ERROR("Can't read file %s", path); Memory_FreePointer(&data); File_Close(fp); @@ -413,7 +417,7 @@ bool File_Load(const char *path, char **output_data, size_t *output_size) data[data_size] = '\0'; *output_data = data; - if (output_size) { + if (output_size != NULL) { *output_size = data_size; } return true; diff --git a/src/game/console/cmd/config.c b/src/game/console/cmd/config.c index f66e986..ef184fc 100644 --- a/src/game/console/cmd/config.c +++ b/src/game/console/cmd/config.c @@ -234,9 +234,7 @@ COMMAND_RESULT Console_Cmd_Config_Helper( } if (M_SetCurrentValue(option, new_value)) { - Config_Sanitize(); Config_Write(); - Config_ApplyChanges(); char final_value[128]; assert(M_GetCurrentValue(option, final_value, 128)); diff --git a/src/game/ui/events.c b/src/game/ui/events.c index 6eb72b6..dba8442 100644 --- a/src/game/ui/events.c +++ b/src/game/ui/events.c @@ -1,12 +1,27 @@ #include "game/ui/events.h" +#include "config/common.h" + #include static EVENT_MANAGER *m_EventManager = NULL; +static void M_HandleConfigChange(const EVENT *event, void *data); + +static void M_HandleConfigChange(const EVENT *const event, void *const data) +{ + const EVENT new_event = { + .name = "canvas_resize", + .sender = NULL, + .data = NULL, + }; + EventManager_Fire(m_EventManager, &new_event); +} + void UI_Events_Init(void) { m_EventManager = EventManager_Create(); + Config_SubscribeChanges(M_HandleConfigChange, NULL); } void UI_Events_Shutdown(void)