From 438083503d9f9d45fae1927224c689b69c060d89 Mon Sep 17 00:00:00 2001 From: Emil Kosz Date: Tue, 10 Jun 2025 20:33:51 +0200 Subject: [PATCH] More UI additions --- game/res/shaders/uitext.vert | 3 +- game/res/shaders/uiwidget.frag | 2 +- game/src/chunk/chunkman.c | 15 +- game/src/entity/entman.c | 9 +- game/src/gl/texture.c | 3 +- game/src/gl/texture.h | 2 +- game/src/ui/uiman.c | 107 +++++--------- game/src/ui/widget.c | 261 +++++++++++++++++++++++++++++++-- game/src/ui/widget.h | 67 ++++++++- 9 files changed, 365 insertions(+), 104 deletions(-) diff --git a/game/res/shaders/uitext.vert b/game/res/shaders/uitext.vert index b64e5fb..9564d27 100644 --- a/game/res/shaders/uitext.vert +++ b/game/res/shaders/uitext.vert @@ -4,6 +4,7 @@ attribute vec3 position; attribute vec2 uv; uniform mat3 model; +uniform mat4 projection; uniform float character; varying vec2 f_uv; @@ -26,5 +27,5 @@ void main () f_uv = char_offset + uv * char_size; - gl_Position = vec4 (model * vec3 (position.xy, 1.0), 1.0); + gl_Position = projection * vec4 (model * vec3 (position.xy, 1.0), 1.0); } diff --git a/game/res/shaders/uiwidget.frag b/game/res/shaders/uiwidget.frag index 1ef47bb..9c255cf 100644 --- a/game/res/shaders/uiwidget.frag +++ b/game/res/shaders/uiwidget.frag @@ -16,6 +16,6 @@ void main () } else { - gl_FragColor = vec4 (1.0, 1.0, 1.0, 1.0); + gl_FragColor = vec4 (0.0, 0.0, 0.0, 0.0); } } diff --git a/game/src/chunk/chunkman.c b/game/src/chunk/chunkman.c index e589529..3d6896d 100644 --- a/game/src/chunk/chunkman.c +++ b/game/src/chunk/chunkman.c @@ -164,16 +164,11 @@ chunkman_render (vec3 position, vec4 planes[6]) program_use (RESPRG ("chunk")); - glActiveTexture (GL_TEXTURE0); - glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain0")); - glActiveTexture (GL_TEXTURE1); - glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain1")); - glActiveTexture (GL_TEXTURE2); - glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain2")); - glActiveTexture (GL_TEXTURE3); - glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain3")); - glActiveTexture (GL_TEXTURE4); - glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain4")); + texture_use (RESTEX ("terrain0"), 0); + texture_use (RESTEX ("terrain1"), 1); + texture_use (RESTEX ("terrain2"), 2); + texture_use (RESTEX ("terrain3"), 3); + texture_use (RESTEX ("terrain4"), 4); n_frame_chunks = 0; diff --git a/game/src/entity/entman.c b/game/src/entity/entman.c index 84ab608..e18acdf 100644 --- a/game/src/entity/entman.c +++ b/game/src/entity/entman.c @@ -18,9 +18,9 @@ static GArray *entities; static GQueue *spawn_requests; static SimpleMesh mesh_quad1, mesh_quad2, mesh_quad_x; -static unsigned int last_program = -1; -static unsigned int last_type = -1; -static unsigned int last_texture = -1; +static unsigned int last_program = 0; +static unsigned int last_type = 0; +static unsigned int last_texture = 0; void character_on_tick (Entity *entity) @@ -224,8 +224,7 @@ entman_render_entity (Entity *entity, void *userdata) if (entity->texture != last_texture) { - glActiveTexture (GL_TEXTURE0); - last_texture = texture_use (entity->texture); + last_texture = texture_use (entity->texture, 0); } if (entity->mesh_type != last_type) diff --git a/game/src/gl/texture.c b/game/src/gl/texture.c index c5baf45..776d156 100644 --- a/game/src/gl/texture.c +++ b/game/src/gl/texture.c @@ -1,8 +1,9 @@ #include "texture.h" unsigned int -texture_use (unsigned int texture) +texture_use (unsigned int texture, unsigned int slot) { + glActiveTexture (GL_TEXTURE0 + slot); glBindTexture (GL_TEXTURE_2D, texture); return texture; } diff --git a/game/src/gl/texture.h b/game/src/gl/texture.h index c22f1d2..91f0773 100644 --- a/game/src/gl/texture.h +++ b/game/src/gl/texture.h @@ -2,5 +2,5 @@ #include -unsigned int texture_use (unsigned int texture); +unsigned int texture_use (unsigned int texture, unsigned int slot); unsigned int texture_create (int width, int height, unsigned char *data); diff --git a/game/src/ui/uiman.c b/game/src/ui/uiman.c index ed5e79a..8dd0030 100644 --- a/game/src/ui/uiman.c +++ b/game/src/ui/uiman.c @@ -5,6 +5,12 @@ extern SDL_Window *sdl_window; extern int sdl_window_w, sdl_window_h; +extern float delta_time; +extern int n_frame_ents; +extern int n_frame_chunks; +extern int render_distance; +extern vec3 camera_position; + static Widget *ui_root; static mat4 projection; @@ -15,11 +21,25 @@ uiman_init () ui_widget_mesh_init (); glm_ortho (0.0, (float) sdl_window_w, (float) sdl_window_h, 0.0, -1.0, 1.0, projection); - Widget *debug_menu = Widget_plain_new (); + Widget *debug_menu = Widget_container_new (FALSE); Widget_set_texture (debug_menu, RESTEX ("terrain4")); Widget_set_position (debug_menu, 32, 32); - Widget_set_size (debug_menu, 300, 300); + Widget_set_size (debug_menu, 300, 150); Widget_add_child (ui_root, debug_menu); + + Widget_add_child (debug_menu, Widget_label_make_float ("Frame time: %.3f", &delta_time, TEXT_ALIGN_LEFT)); + Widget_add_child (debug_menu, Widget_label_make_int ("Render distance: %d", &render_distance, TEXT_ALIGN_LEFT)); + Widget_add_child (debug_menu, Widget_label_make_int ("Chunks rendered: %d", &n_frame_chunks, TEXT_ALIGN_LEFT)); + Widget_add_child (debug_menu, Widget_label_make_int ("Entities rendered: %d", &n_frame_ents, TEXT_ALIGN_LEFT)); + + Widget *poscont = Widget_container_new (TRUE); + Widget_add_child (poscont, Widget_label_make_float ("X: %.0f", &camera_position[0], TEXT_ALIGN_LEFT)); + Widget_add_child (poscont, Widget_label_make_float ("Y: %.0f", &camera_position[1], TEXT_ALIGN_CENTER)); + Widget_add_child (poscont, Widget_label_make_float ("Z: %.0f", &camera_position[2], TEXT_ALIGN_RIGHT)); + Widget_add_child (debug_menu, poscont); + + Widget_add_child (debug_menu, Widget_label_make_flag_switch_button ("Toggle wireframe", "wireframe_enabled")); + Widget_add_child (debug_menu, Widget_label_make_flag_switch_button ("Toggle flight", "flight_enabled")); } void @@ -68,30 +88,11 @@ uiman_cb_key_up (SDL_Event *event, void *data) gboolean uiman_cb_mouse_motion (SDL_Event *event, void *data) { - UNUSED (event, data); - /* int win_w, win_h; */ - /* SDL_GetWindowSize (sdl_window, &win_w, &win_h); */ + UNUSED (data); - /* vec2 ui_pos; */ - /* screen_to_ui_coords (event->motion.x, event->motion.y, win_w, win_h, ui_pos); */ - - /* Widget *widget = Widget_find_xy (ui_root, GLM_MAT3_IDENTITY, ui_pos); */ - - /* if (widget != hovered_widget) */ - /* { */ - /* if (hovered_widget) */ - /* { */ - /* hovered_widget->hovered = false; */ - /* } */ - - /* if (widget) */ - /* { */ - /* widget->hovered = true; */ - /* } */ - - /* hovered_widget = widget; */ - /* } */ - + Widget *widget = Widget_find_xy (ui_root, (vec2) { event->motion.x, event->motion.y }); + UNUSED (widget); + /* This callback always eats the event, because why would the game need mouse motion if the UI has stolen the focus? */ return TRUE; @@ -102,43 +103,22 @@ uiman_cb_button_down (SDL_Event *event, void *data) { UNUSED (event, data); - /* int win_w, win_h; */ - /* SDL_GetWindowSize (sdl_window, &win_w, &win_h); */ - - /* vec2 ui_pos; */ - /* screen_to_ui_coords (event->motion.x, event->motion.y, win_w, win_h, ui_pos); */ - - /* Widget *widget = Widget_find_xy (ui_root, GLM_MAT3_IDENTITY, ui_pos); */ - - /* if (widget != clicked_widget) */ - /* { */ - /* if (clicked_widget) */ - /* { */ - /* clicked_widget->clicked = FALSE; */ - /* } */ - - /* if (widget) */ - /* { */ - /* widget->clicked = TRUE; */ - /* } */ - - /* clicked_widget = widget; */ - /* } */ + Widget *widget = Widget_find_xy (ui_root, (vec2) { event->motion.x, event->motion.y }); - /* if (widget) */ - /* { */ - /* if (event->button.button == SDL_BUTTON_LEFT && widget->on_left_click) */ - /* { */ - /* widget->on_left_click (widget, widget->left_click_data); */ - /* return true; */ - /* } */ + if (widget) + { + if (event->button.button == SDL_BUTTON_LEFT && widget->on_left_click) + { + widget->on_left_click (widget, widget->left_click_data); + return TRUE; + } - /* if (event->button.button == SDL_BUTTON_RIGHT && widget->on_right_click) */ - /* { */ - /* widget->on_right_click (widget, widget->right_click_data); */ - /* return true; */ - /* } */ - /* } */ + if (event->button.button == SDL_BUTTON_RIGHT && widget->on_right_click) + { + widget->on_right_click (widget, widget->right_click_data); + return TRUE; + } + } return FALSE; } @@ -147,13 +127,6 @@ gboolean uiman_cb_button_up (SDL_Event *event, void *data) { UNUSED (event, data); - - /* if (clicked_widget) */ - /* { */ - /* clicked_widget->clicked = FALSE; */ - /* clicked_widget = NULL; */ - /* } */ - return FALSE; } diff --git a/game/src/ui/widget.c b/game/src/ui/widget.c index f1d5aea..74a4936 100644 --- a/game/src/ui/widget.c +++ b/game/src/ui/widget.c @@ -1,5 +1,10 @@ #include "widget.h" +/* TODO: padding */ +/* TODO: movable and resizable windows */ +/* TODO: "spinner" UI element */ +/* TODO: "checkbox" UI element */ + static SimpleMesh quad; static unsigned int last_program; @@ -12,6 +17,7 @@ ui_el_new_internal (WidgetKind kind) e->kind = kind; e->visible = true; e->children = g_array_new (false, false, sizeof (Widget *)); + e->layout_weight = 1; return e; } @@ -22,6 +28,24 @@ Widget_plain_new () return e; } +Widget * +Widget_container_new (gboolean horizontal) +{ + Widget *e = ui_el_new_internal (WIDGET_CONTAINER); + e->container.horizontal = horizontal; + return e; +} + +Widget * +Widget_label_new (const char *text, TextAlignment alignment) +{ + Widget *e = ui_el_new_internal (WIDGET_LABEL); + e->label.text = g_string_new (text); + e->label.text_alignment = alignment; + glm_vec4_one (e->label.text_color); + return e; +} + void Widget_add_child (Widget *parent, Widget *child) { @@ -29,9 +53,16 @@ Widget_add_child (Widget *parent, Widget *child) g_array_append_val (parent->children, child); } +void +Widget_add_child_weighed (Widget *parent, Widget *child, float layout_weight) +{ + child->layout_weight = layout_weight; + Widget_add_child (parent, child); +} + void Widget_set_on_tick (Widget *widget, - void (*on_tick) (struct Widget *, void *), + void (*on_tick) (Widget *, void *), void *userdata) { widget->on_tick = on_tick; @@ -40,7 +71,7 @@ Widget_set_on_tick (Widget *widget, void Widget_set_on_left_click (Widget *widget, - void (*on_left_click) (struct Widget *, void *), + void (*on_left_click) (Widget *, void *), void *userdata) { widget->on_left_click = on_left_click; @@ -66,19 +97,60 @@ Widget_set_texture (Widget *widget, unsigned int texture) widget->has_texture = TRUE; } +void +Widget_label_set_text (Widget *widget, const char *text) +{ + g_string_overwrite (widget->label.text, 0, text); +} + +void +Widget_label_printf (Widget *widget, const char *format, ...) +{ + va_list args; + va_start (args, format); + g_string_vprintf (widget->label.text, format, args); + va_end (args); +} + gboolean Widget_hit_test (Widget *widget, vec2 point) { - UNUSED (widget, point); - /* TODO: figure out. */ - return FALSE; + float x = point[0]; + float y = point[1]; + + float left = widget->position[0]; + float top = widget->position[1]; + float right = left + widget->size[0]; + float bottom = top + widget->size[1]; + + return (x >= left && x <= right + && y >= top && y <= bottom); } Widget * Widget_find_xy (Widget *widget, vec2 pos) { - UNUSED (widget, pos); - /* TODO: figure out. */ + if (!widget->visible) + { + return NULL; + } + + for (gint i = widget->children->len - 1; i >= 0; --i) + { + Widget *child = g_array_index (widget->children, Widget *, i); + Widget *found = Widget_find_xy (child, pos); + + if (found != NULL) + { + return found; + } + } + + if (Widget_hit_test (widget, pos)) + { + return widget; + } + return NULL; } @@ -96,13 +168,102 @@ Widget_tick (Widget *widget) widget->on_tick (widget, widget->tick_data); } - for (guint i = 0; i < widget->children->len; ++i) + for (unsigned int i = 0; i < widget->children->len; ++i) { Widget *child = g_array_index (widget->children, Widget *, i); Widget_tick (child); } } +void +Widget_label_render_text (Widget *widget, mat4 projection) +{ + if (last_program != RESPRG ("uitext")) + { + last_program = program_use (RESPRG ("uitext")); + } + + program_set_mat4 (RESPRG ("uitext"), "projection", projection); + texture_use (RESTEX ("font0"), 0); + + float char_width = widget->size[1] * 0.5; + float total_width = g_utf8_strlen (widget->label.text->str, -1) * char_width; + float offset = 0.0; + + switch (widget->label.text_alignment) + { + case TEXT_ALIGN_LEFT: + offset = -char_width; + break; + case TEXT_ALIGN_CENTER: + offset = (widget->size[0] / 2) - (total_width / 2.0) -char_width; + break; + case TEXT_ALIGN_RIGHT: + offset = widget->size[0] - total_width - char_width; + break; + } + + const char *p = widget->label.text->str; + while (*p) + { + gunichar ch = g_utf8_get_char (p); + + program_set_float (RESPRG ("uitext"), "character", ch); + program_set_vec4 (RESPRG ("uitext"), "text_color", widget->label.text_color); + + mat3 model; + glm_translate2d_make (model, (vec2) { widget->position[0] + offset + char_width, widget->position[1] }); + glm_scale2d (model, (vec2) { char_width, widget->size[1] }); + offset += char_width; + + program_set_mat3 (RESPRG ("uitext"), "model", model); + + SimpleMesh_render (&quad, FALSE); + + p = g_utf8_next_char (p); + } +} + +void +Widget_container_layout (Widget *parent, gboolean horizontal) +{ + float total_weights = 0.0; + for (unsigned int i = 0; i < parent->children->len; ++i) + { + Widget *child = g_array_index (parent->children, Widget *, i); + total_weights += child->layout_weight; + } + + float offset = 0.0; + for (unsigned int i = 0; i < parent->children->len; ++i) + { + Widget *child = g_array_index (parent->children, Widget *, i); + + if (horizontal) + { + float weights_size = parent->size[0] / total_weights; + child->size[0] = child->layout_weight * weights_size; + child->size[1] = parent->size[1]; + + child->position[0] = parent->position[0] + offset; + child->position[1] = parent->position[1]; + + offset += child->size[0]; + } + else + { + float weights_size = parent->size[1] / total_weights; + child->size[1] = child->layout_weight * weights_size; + child->size[0] = parent->size[0]; + + child->position[1] = parent->position[1] + offset; + child->position[0] = parent->position[0]; + + offset += child->size[1]; + } + } +} + void Widget_render (Widget *widget, mat4 projection) { @@ -124,20 +285,94 @@ Widget_render (Widget *widget, mat4 projection) } program_set_int (RESPRG ("uiwidget"), "has_texture", widget->has_texture); - glActiveTexture (GL_TEXTURE0); - texture_use (widget->texture); /* If it doesn't have a texture, it'll be 0 anyway. */ + texture_use (widget->texture, 0); /* If it doesn't have a texture, it'll be 0 anyway. */ - mat3 model; + mat3 model; glm_translate2d_make (model, widget->position); glm_scale2d (model, widget->size); program_set_mat3 (RESPRG ("uiwidget"), "model", model); program_set_mat4 (RESPRG ("uiwidget"), "projection", projection); - SimpleMesh_render (&quad, false); + SimpleMesh_render (&quad, FALSE); + + if (widget->kind == WIDGET_CONTAINER) + { + Widget_container_layout (widget, widget->container.horizontal); + } + + if (widget->kind == WIDGET_LABEL) + { + Widget_label_render_text (widget, projection); + } - for (guint i = 0; i < widget->children->len; ++i) + for (unsigned int i = 0; i < widget->children->len; ++i) { Widget *child = g_array_index (widget->children, Widget *, i); Widget_render (child, projection); } } + +/* --------- */ + +LabelFormatData * +LabelFormatData_new_int (const char *fmt, int *ptr) +{ + LabelFormatData *data = g_new (LabelFormatData, 1); + data->fmt = fmt; + data->ptr_int = ptr; + return data; +} + +LabelFormatData * +LabelFormatData_new_float (const char *fmt, float *ptr) +{ + LabelFormatData *data = g_new (LabelFormatData, 1); + data->fmt = fmt; + data->ptr_float = ptr; + return data; +} + +void +uicb_label_show_int (Widget *self, void *userdata) +{ + LabelFormatData *format_data = userdata; + Widget_label_printf (self, format_data->fmt, *format_data->ptr_int); +} + +void uicb_label_show_float (Widget *self, void *userdata) +{ + LabelFormatData *format_data = userdata; + Widget_label_printf (self, format_data->fmt, *format_data->ptr_float); +} + +void +uicb_switch_flag (Widget *self, void *userdata) +{ + UNUSED (self); + char *flag = userdata; + flags_switch (flag); +} + +Widget * +Widget_label_make_int (const char *fmt, int *ptr, TextAlignment alignment) +{ + Widget *w = Widget_label_new (NULL, alignment); + Widget_set_on_tick (w, uicb_label_show_int, LabelFormatData_new_int (fmt, ptr)); + return w; +} + +Widget * +Widget_label_make_float (const char *fmt, float *ptr, TextAlignment alignment) +{ + Widget *w = Widget_label_new (NULL, alignment); + Widget_set_on_tick (w, uicb_label_show_float, LabelFormatData_new_float (fmt, ptr)); + return w; +} + +Widget * +Widget_label_make_flag_switch_button (const char *text, char *flag) +{ + Widget *w = Widget_label_new (text, TEXT_ALIGN_CENTER); + Widget_set_on_left_click (w, uicb_switch_flag, flag); + return w; +} diff --git a/game/src/ui/widget.h b/game/src/ui/widget.h index d85c31e..87315e7 100644 --- a/game/src/ui/widget.h +++ b/game/src/ui/widget.h @@ -11,21 +11,46 @@ typedef enum WidgetKind { - WIDGET_PLAIN + WIDGET_PLAIN, + WIDGET_CONTAINER, + WIDGET_LABEL } WidgetKind; +typedef struct ContainerWidget +{ + gboolean horizontal; +} ContainerWidget; + +typedef enum TextAlignment + { + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT + } TextAlignment; + +typedef struct LabelWidget +{ + GString *text; + vec4 text_color; + TextAlignment text_alignment; +} LabelWidget; + typedef struct Widget { struct Widget *parent; GArray *children; WidgetKind kind; + union { + ContainerWidget container; + LabelWidget label; + }; gboolean visible; - + vec2 position; vec2 size; - float padding; + float layout_weight; gboolean has_texture; unsigned int texture; @@ -39,18 +64,50 @@ typedef struct Widget } Widget; Widget *Widget_plain_new (); +Widget *Widget_container_new (gboolean horizontal); +Widget *Widget_label_new (const char *text, TextAlignment alignment); void Widget_add_child (Widget *parent, Widget *child); +void Widget_add_child_weighed (Widget *parent, Widget *child, + float layout_weight); -void Widget_set_on_tick (Widget *widget, void (*on_tick) (struct Widget *, void *), void *userdata); -void Widget_set_on_left_click (Widget *widget, void (*on_left_click) (struct Widget *, void *), void *userdata); +void Widget_set_on_tick (Widget *widget, + void (*on_tick) (Widget *, void *), + void *userdata); +void Widget_set_on_left_click (Widget *widget, + void (*on_left_click) (Widget *, void *), + void *userdata); void Widget_set_position (Widget *widget, float x, float y); void Widget_set_size (Widget *widget, float x, float y); void Widget_set_texture (Widget *widget, unsigned int texture); +void Widget_label_set_text (Widget *widget, const char *text); +void Widget_label_printf (Widget *widget, const char *format, ...); + Widget *Widget_find_xy (Widget *widget, vec2 point); void ui_widget_mesh_init (); void Widget_tick (Widget *widget); void Widget_render (Widget *widget, mat4 projection); + +/* ---------------------------------------------- */ +typedef struct LabelFormatData +{ + const char *fmt; + union { + int *ptr_int; + float *ptr_float; + }; +} LabelFormatData; + +LabelFormatData *LabelFormatData_new_int (const char *fmt, int *ptr); +LabelFormatData *LabelFormatData_new_float (const char *fmt, float *ptr); + +void uicb_label_show_int (Widget *self, void *userdata); +void uicb_label_show_float (Widget *self, void *userdata); +void uicb_switch_flag (Widget *self, void *userdata); + +Widget *Widget_label_make_int (const char *fmt, int *ptr, TextAlignment alignment); +Widget *Widget_label_make_float (const char *fmt, float *ptr, TextAlignment alignment); +Widget *Widget_label_make_flag_switch_button (const char *text, char *flag);