diff --git a/game/res/shaders/uicursor.frag b/game/res/shaders/uicursor.frag new file mode 100644 index 0000000..cce44d3 --- /dev/null +++ b/game/res/shaders/uicursor.frag @@ -0,0 +1,12 @@ +#version 100 + +precision mediump float; + +uniform vec4 cursor_color; + +varying vec2 f_uv; + +void main () +{ + gl_FragColor = cursor_color; +} diff --git a/game/res/shaders/uicursor.vert b/game/res/shaders/uicursor.vert new file mode 100644 index 0000000..68109e9 --- /dev/null +++ b/game/res/shaders/uicursor.vert @@ -0,0 +1,16 @@ +#version 100 + +attribute vec3 position; +attribute vec2 uv; + +uniform mat3 model; +uniform mat4 projection; + +varying vec2 f_uv; + +void +main () +{ + f_uv = uv; + gl_Position = projection * vec4 (model * vec3 (position.xy, 1.0), 1.0); +} diff --git a/game/res/textures/font_9x20.png b/game/res/textures/font_9x20.png index a005c67..3860f5f 100644 Binary files a/game/res/textures/font_9x20.png and b/game/res/textures/font_9x20.png differ diff --git a/game/res/textures/font_9x20.xcf b/game/res/textures/font_9x20.xcf index ba88e62..3c00cd1 100644 Binary files a/game/res/textures/font_9x20.xcf and b/game/res/textures/font_9x20.xcf differ diff --git a/game/src/event/evtman.c b/game/src/event/evtman.c index 5d39bc0..aab9780 100644 --- a/game/src/event/evtman.c +++ b/game/src/event/evtman.c @@ -73,6 +73,7 @@ evtman_init () evtman_add_handler (2, SDL_EVENT_WINDOW_RESIZED, NULL, NULL, uiman_cb_window_resize, NULL); /* The UI effectively eats most SDL events, as it needs it for it's own calculations. */ + evtman_add_handler (2, SDL_EVENT_KEY_DOWN, evt_pred_key, KEY (SDLK_TAB), uiman_cb_switch_focus, NULL); evtman_add_handler (1, SDL_EVENT_KEY_DOWN, evt_pred_flag, "ui_has_focus", uiman_cb_key_down, NULL); evtman_add_handler (1, SDL_EVENT_KEY_UP, evt_pred_flag, "ui_has_focus", uiman_cb_key_up, NULL); evtman_add_handler (1, SDL_EVENT_MOUSE_MOTION, evt_pred_flag, "ui_has_focus", uiman_cb_mouse_motion, NULL); @@ -89,7 +90,6 @@ evtman_init () evtman_add_handler (0, SDL_EVENT_KEY_UP, evt_pred_key, KEY (SDLK_A), evt_cb_flag_off, "moving_left"); evtman_add_handler (0, SDL_EVENT_KEY_DOWN, evt_pred_key, KEY (SDLK_D), evt_cb_flag_on, "moving_right"); evtman_add_handler (0, SDL_EVENT_KEY_UP, evt_pred_key, KEY (SDLK_D), evt_cb_flag_off, "moving_right"); - evtman_add_handler (0, SDL_EVENT_KEY_DOWN, evt_pred_key, KEY (SDLK_TAB), uiman_cb_switch_focus, NULL); evtman_add_handler (0, SDL_EVENT_MOUSE_MOTION, NULL, NULL, evt_cb_rotate_camera, NULL); evtman_add_handler (0, SDL_EVENT_KEY_DOWN, evt_pred_key, KEY (SDLK_SPACE), evt_cb_flag_on, "flying_up"); diff --git a/game/src/resources.c b/game/src/resources.c index 677ab34..e410743 100644 --- a/game/src/resources.c +++ b/game/src/resources.c @@ -151,6 +151,7 @@ resources_init () RAP ("clouds", RLP ("res/shaders/clouds.vert", "res/shaders/clouds.frag")); RAP ("uiwidget", RLP ("res/shaders/uiwidget.vert", "res/shaders/uiwidget.frag")); RAP ("uitext", RLP ("res/shaders/uitext.vert", "res/shaders/uitext.frag")); + RAP ("uicursor", RLP ("res/shaders/uicursor.vert", "res/shaders/uicursor.frag")); RAT ("terrain0", RLT ("res/textures/dirt.png")); RAT ("terrain1", RLT ("res/textures/drygrass.png")); diff --git a/game/src/ui/uiman.c b/game/src/ui/uiman.c index 1d0c7e5..7ac477d 100644 --- a/game/src/ui/uiman.c +++ b/game/src/ui/uiman.c @@ -15,6 +15,7 @@ static Widget *ui_root; static mat4 projection; static Widget *hovered_widget = NULL; +static Widget *focused_widget = NULL; const char *lorem_ipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut " @@ -70,6 +71,13 @@ uiman_init () Widget_add_child_weighed (window2, Widget_test_new (100), 1); Widget_add_child_weighed (window2, Widget_test_new (100), 1); + Widget *entry = Widget_entry_new ("Input here"); + entry->style->background_image = RESTEX ("terrain4"); + entry->size[0] = 150; + entry->size[1] = 20; + Widget_add_child_weighed (window2, entry, 1); + Widget_add_child_weighed (window2, Widget_test_new (100), 1); + Widget_add_child (ui_root, window2); } @@ -92,11 +100,11 @@ uiman_free () } static inline gboolean -_uiman_call_on_event (Widget *widget, WidgetEvent event) +_uiman_call_on_event (Widget *widget, SDL_Event *sdl_event, WidgetEvent event) { if (widget && widget->on_event[event]) { - widget->on_event[event] (widget, widget->event_data[event]); + widget->on_event[event] (widget, sdl_event, widget->event_data[event]); return TRUE; } @@ -121,12 +129,29 @@ _uiman_set_hovered (Widget *widget, gboolean state) } } +static inline void +_uiman_set_focused (Widget *widget, gboolean state) +{ + if (widget) + { + widget->focused = state; + } +} + gboolean uiman_cb_switch_focus (SDL_Event *event, void *data) { UNUSED (event, data); flags_switch ("ui_has_focus"); SDL_SetWindowRelativeMouseMode (sdl_window, !flags_get ("ui_has_focus")); + + /* We reset the global states of the UI. */ + SDL_StopTextInput (sdl_window); + _uiman_set_hovered (hovered_widget, FALSE); + hovered_widget = NULL; + _uiman_set_focused (focused_widget, FALSE); + focused_widget = NULL; + return TRUE; } @@ -143,6 +168,19 @@ gboolean uiman_cb_key_down (SDL_Event *event, void *data) { UNUSED (event, data); + + SDL_Scancode sc = event->key.scancode; + if (focused_widget && focused_widget->on_key[sc]) + { + focused_widget->on_key[sc] (focused_widget, event, focused_widget->key_data[sc]); + return TRUE; + } + + if (focused_widget && SDL_TextInputActive (sdl_window)) + { + return TRUE; + } + return FALSE; } @@ -162,15 +200,15 @@ uiman_cb_mouse_motion (SDL_Event *event, void *data) if (new_hover != hovered_widget) { - _uiman_call_on_event (hovered_widget, EVENT_MOUSE_LEAVE); + _uiman_call_on_event (hovered_widget, event, EVENT_MOUSE_LEAVE); _uiman_set_hovered (hovered_widget, FALSE); _uiman_set_clicked (hovered_widget, FALSE); - _uiman_call_on_event (new_hover, EVENT_MOUSE_ENTER); + _uiman_call_on_event (new_hover, event, EVENT_MOUSE_ENTER); _uiman_set_hovered (new_hover, TRUE); hovered_widget = new_hover; } - _uiman_call_on_event (hovered_widget, EVENT_MOUSE_MOVE); + _uiman_call_on_event (hovered_widget, event, EVENT_MOUSE_MOVE); /* This callback always eats the event, because why would the game need mouse motion if the UI has stolen the focus? */ @@ -185,7 +223,17 @@ uiman_cb_button_down (SDL_Event *event, void *data) WidgetEvent e = event->button.button == SDL_BUTTON_LEFT ? EVENT_LEFT_DOWN : EVENT_RIGHT_DOWN; Widget *widget = Widget_find_xy (ui_root, (vec2) { event->button.x, event->button.y }); _uiman_set_clicked (widget, TRUE); - return _uiman_call_on_event (widget, e); + + if (widget != focused_widget) + { + _uiman_set_focused (focused_widget, FALSE); + _uiman_call_on_event (focused_widget, event, EVENT_FOCUS_LOST); + _uiman_set_focused (widget, TRUE); + _uiman_call_on_event (widget, event, EVENT_FOCUS_GAINED); + focused_widget = widget; + } + + return _uiman_call_on_event (widget, event, e); } gboolean @@ -196,7 +244,7 @@ uiman_cb_button_up (SDL_Event *event, void *data) WidgetEvent e = event->button.button == SDL_BUTTON_LEFT ? EVENT_LEFT_UP : EVENT_RIGHT_UP; Widget *widget = Widget_find_xy (ui_root, (vec2) { event->button.x, event->button.y }); _uiman_set_clicked (widget, FALSE); - return _uiman_call_on_event (widget, e); + return _uiman_call_on_event (widget, event, e); } gboolean @@ -206,12 +254,19 @@ uiman_cb_mouse_wheel (SDL_Event *event, void *data) WidgetEvent e = event->wheel.y > 0 ? EVENT_SCROLL_UP : EVENT_SCROLL_DOWN; Widget *widget = Widget_find_xy_event (ui_root, (vec2) { event->wheel.mouse_x, event->wheel.mouse_y }, e); - return _uiman_call_on_event (widget, e); + return _uiman_call_on_event (widget, event, e); } gboolean uiman_cb_text_input (SDL_Event *event, void *data) { UNUSED (event, data); + + if (focused_widget) + { + _uiman_call_on_event (focused_widget, event, EVENT_TEXT_INPUT); + return TRUE; + } + return FALSE; } diff --git a/game/src/ui/widget_composites.c b/game/src/ui/widget_composites.c index 378d15e..29f2801 100644 --- a/game/src/ui/widget_composites.c +++ b/game/src/ui/widget_composites.c @@ -29,7 +29,7 @@ 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_widget_op, WidgetOpData_new (OP_LABEL_SHOW_INT, ptr, fmt)); + Widget_set_on_event (w, EVENT_TICK, uicb_widget_op, WidgetOpData_new (OP_LABEL_SHOW_INT, ptr, fmt)); return w; } @@ -37,7 +37,7 @@ 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_widget_op, WidgetOpData_new (OP_LABEL_SHOW_FLOAT, ptr, fmt)); + Widget_set_on_event (w, EVENT_TICK, uicb_widget_op, WidgetOpData_new (OP_LABEL_SHOW_FLOAT, ptr, fmt)); return w; } @@ -78,7 +78,7 @@ Widget_composite_checkbox_make (const char *label, char *flag) box->icon = ICON_CHECKBOX_UNCHECKED; box->aspect_ratio = 1.0; - Widget_set_on_tick (box, uicb_widget_op, WidgetOpData_new (OP_CHECKBOX_SHOW_FLAG, flag)); + Widget_set_on_event (box, EVENT_TICK, uicb_widget_op, WidgetOpData_new (OP_CHECKBOX_SHOW_FLAG, flag)); Widget_set_on_event (box, EVENT_LEFT_UP, uicb_widget_op, WidgetOpData_new (OP_SWITCH_FLAG, flag)); Widget_add_child_weighed (cont, box, 1); diff --git a/game/src/ui/widget_composites.h b/game/src/ui/widget_composites.h index eea0d12..99c1bb1 100644 --- a/game/src/ui/widget_composites.h +++ b/game/src/ui/widget_composites.h @@ -1,6 +1,6 @@ #pragma once -/* #include "widget_fw.h" */ +#include "widget_fw.h" #include "widget_core.h" #include "widget_ops.h" diff --git a/game/src/ui/widget_core.c b/game/src/ui/widget_core.c index 6a60cd7..5c2c10c 100644 --- a/game/src/ui/widget_core.c +++ b/game/src/ui/widget_core.c @@ -20,6 +20,7 @@ WidgetStyle widget_style_default = .text_color = { 1.0, 1.0, 1.0, 1.0 }, .text_color_hovered = { 1.0, 1.0, 1.0, 1.0 }, .text_color_clicked = { 1.0, 1.0, 1.0, 1.0 }, + .ghost_text_color = { 1.0, 1.0, 1.0, 0.5 }, .icon_color = { 1.0, 1.0, 1.0, 1.0 }, .icon_color_hovered = { 1.0, 1.0, 1.0, 1.0 }, @@ -123,6 +124,22 @@ Widget_multiline_text_new (const char *text, float line_height, TextAlignment al return e; } +Widget * +Widget_entry_new (const char *ghost_text) +{ + Widget *e = ui_el_new_internal (WIDGET_ENTRY); + e->do_render = Widget_render_entry; + e->entry.ghost_text = g_string_new (ghost_text); + e->entry.content = g_string_new (NULL); + Widget_set_on_event (e, EVENT_FOCUS_GAINED, uicb_widget_op, WidgetOpData_new (OP_START_TEXT_INPUT, NULL)); + Widget_set_on_event (e, EVENT_FOCUS_LOST, uicb_widget_op, WidgetOpData_new (OP_STOP_TEXT_INPUT, NULL)); + Widget_set_on_event (e, EVENT_TEXT_INPUT, uicb_widget_op, WidgetOpData_new (OP_ENTRY_INPUT_TEXT, NULL)); + Widget_set_on_key (e, SDL_SCANCODE_BACKSPACE, uicb_widget_op, WidgetOpData_new (OP_ENTRY_DELETE_CHAR, NULL)); + Widget_set_on_key (e, SDL_SCANCODE_LEFT, uicb_widget_op, WidgetOpData_new (OP_ENTRY_MOVE_LEFT, NULL)); + Widget_set_on_key (e, SDL_SCANCODE_RIGHT, uicb_widget_op, WidgetOpData_new (OP_ENTRY_MOVE_RIGHT, NULL)); + return e; +} + Widget * Widget_container_new (gboolean horizontal) { @@ -174,22 +191,23 @@ Widget_add_child_weighed (Widget *parent, Widget *child, float layout_weight) Widget_add_child (parent, child); } -void -Widget_set_on_tick (Widget *widget, - void (*on_tick) (Widget *, void *), void *userdata) -{ - widget->on_tick = on_tick; - widget->tick_data = userdata; -} - void Widget_set_on_event (Widget *widget, WidgetEvent event, - void (*on_event) (Widget *, void *), void *userdata) + void (*on_event) (Widget *, SDL_Event *, void *), + void *userdata) { widget->on_event[event] = on_event; widget->event_data[event] = userdata; } +void +Widget_set_on_key (Widget *widget, SDL_Scancode key, + void (*on_key) (Widget *, SDL_Event *, void *), void *userdata) +{ + widget->on_key[key] = on_key; + widget->key_data[key] = userdata; +} + void Widget_set_style (Widget *widget, WidgetStyle style) { @@ -211,6 +229,13 @@ Widget_label_printf (Widget *widget, const char *format, ...) va_end (args); } +void +Widget_entry_input_text (Widget *widget, const char *text) +{ + g_string_insert (widget->entry.content, widget->entry.cursor_position, text); + widget->entry.cursor_position += strlen (text); +} + void Widget_scrollable_scroll (Widget *widget, vec2 direction) { @@ -288,9 +313,9 @@ Widget_find_xy (Widget *widget, vec2 pos) void Widget_tick (Widget *widget) { - if (widget->on_tick) + if (widget->on_event[EVENT_TICK]) { - widget->on_tick (widget, widget->tick_data); + widget->on_event[EVENT_TICK] (widget, NULL, widget->event_data[EVENT_TICK]); } for (unsigned int i = 0; i < widget->children->len; ++i) @@ -341,11 +366,6 @@ Widget_free (Widget *widget) g_string_free (widget->label.text, TRUE); } - if (widget->on_tick && widget->tick_data) - { - g_free (widget->tick_data); - } - for (int ev = 0; ev < EVENT_COUNT; ++ev) { if (widget->on_event[ev] && widget->event_data[ev]) diff --git a/game/src/ui/widget_core.h b/game/src/ui/widget_core.h index a50a2e7..0dfe346 100644 --- a/game/src/ui/widget_core.h +++ b/game/src/ui/widget_core.h @@ -3,6 +3,7 @@ #include #include +#include "widget_fw.h" #include "widget_render.h" #include "widget_layout.h" #include "widget_style.h" @@ -24,6 +25,7 @@ typedef enum WidgetKind WIDGET_LABEL, WIDGET_SCROLLBAR, WIDGET_PROGRESS, + WIDGET_ENTRY, WIDGET_MULTILINE_TEXT, @@ -48,7 +50,8 @@ typedef enum WidgetIcon typedef enum WidgetEvent { EVENT_NONE = -1, /* Don't use. */ - EVENT_LEFT_DOWN = 0, + EVENT_TICK = 0, + EVENT_LEFT_DOWN, EVENT_LEFT_UP, EVENT_RIGHT_DOWN, EVENT_RIGHT_UP, @@ -57,6 +60,9 @@ typedef enum WidgetEvent EVENT_MOUSE_MOVE, EVENT_SCROLL_UP, EVENT_SCROLL_DOWN, + EVENT_FOCUS_GAINED, + EVENT_FOCUS_LOST, + EVENT_TEXT_INPUT, EVENT_COUNT } WidgetEvent; @@ -97,6 +103,13 @@ typedef struct MultilineTextWidget TextAlignment text_alignment; } MultilineTextWidget; +typedef struct EntryWidget +{ + GString *ghost_text; + GString *content; + float cursor_position; +} EntryWidget; + typedef struct ContainerWidget { gboolean horizontal; @@ -128,6 +141,7 @@ typedef struct Widget ScrollbarWidget scrollbar; ProgressWidget progress; MultilineTextWidget multiline_text; + EntryWidget entry; ContainerWidget container; ScrollableWidget scrollable; @@ -136,7 +150,7 @@ typedef struct Widget gboolean visible; gboolean layout_dirty; - gboolean hovered, clicked; + gboolean hovered, clicked, focused; vec2 position; vec2 size; @@ -149,11 +163,11 @@ typedef struct Widget void (*do_layout) (struct Widget *); void (*do_render) (struct Widget *, mat4, int); - void (*on_tick) (struct Widget *, void *); - void *tick_data; - - void (*on_event[EVENT_COUNT]) (struct Widget *, void *); + void (*on_event[EVENT_COUNT]) (struct Widget *, SDL_Event *sdl_event, void *); void *event_data[EVENT_COUNT]; + + void (*on_key[SDL_SCANCODE_COUNT]) (struct Widget *, SDL_Event *null, void *); + void *key_data[SDL_SCANCODE_COUNT]; } Widget; Widget *Widget_plain_new (); @@ -162,6 +176,7 @@ Widget *Widget_label_new (const char *text, TextAlignment alignment); Widget *Widget_scrollbar_new (Widget *target, gboolean horizontal); Widget *Widget_progress_new (gboolean horizontal, float min, float max, float *ptr); Widget *Widget_multiline_text_new (const char *text, float line_height, TextAlignment alignment); +Widget *Widget_entry_new (const char *ghost_text); Widget *Widget_container_new (gboolean horizontal); Widget *Widget_scrollable_new (); Widget *Widget_flowbox_new (gboolean horizontal); @@ -170,13 +185,15 @@ 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) (Widget *, void *), void *userdata); -void Widget_set_on_event (Widget *widget, WidgetEvent event, void (*on_event) (Widget *, void *), void *userdata); +void Widget_set_on_event (Widget *widget, WidgetEvent event, void (*on_event) (Widget *, SDL_Event *, void *), void *userdata); +void Widget_set_on_key (Widget *widget, SDL_Scancode key, void (*on_key) (Widget *, SDL_Event *, void *), void *userdata); void Widget_set_style (Widget *widget, WidgetStyle style); void Widget_label_set_text (Widget *widget, const char *text); void Widget_label_printf (Widget *widget, const char *format, ...); +void Widget_entry_input_text (Widget *widget, const char *text); + void Widget_scrollable_scroll (Widget *widget, vec2 direction); Widget *Widget_find_xy_event (Widget *widget, vec2 pos, WidgetEvent event); diff --git a/game/src/ui/widget_ops.c b/game/src/ui/widget_ops.c index 621f36f..03d2bb9 100644 --- a/game/src/ui/widget_ops.c +++ b/game/src/ui/widget_ops.c @@ -1,5 +1,7 @@ #include "widget_ops.h" +extern SDL_Window *sdl_window; + WidgetOpData * WidgetOpData_new (WidgetOp op, void *ptr, ...) { @@ -31,8 +33,9 @@ WidgetOpData_new (WidgetOp op, void *ptr, ...) } void -uicb_widget_op (Widget *self, void *userdata) +uicb_widget_op (Widget *self, SDL_Event *sdl_event, void *userdata) { + UNUSED (sdl_event); WidgetOpData *data = userdata; switch (data->op) @@ -55,6 +58,34 @@ uicb_widget_op (Widget *self, void *userdata) case OP_SCROLLABLE_SCROLL: Widget_scrollable_scroll ((Widget *) data->ptr, data->scroll_dir); break; + case OP_START_TEXT_INPUT: + SDL_StartTextInput (sdl_window); + break; + case OP_STOP_TEXT_INPUT: + SDL_StopTextInput (sdl_window); + break; + case OP_ENTRY_INPUT_TEXT: + Widget_entry_input_text (self, sdl_event->text.text); + break; + case OP_ENTRY_DELETE_CHAR: + if (self->entry.cursor_position > 0 + && self->entry.content->len > 0) { + g_string_erase (self->entry.content, self->entry.cursor_position - 1, 1); + self->entry.cursor_position--; + } + break; + case OP_ENTRY_MOVE_LEFT: + if (self->entry.cursor_position > 0) + { + self->entry.cursor_position--; + } + break; + case OP_ENTRY_MOVE_RIGHT: + if (self->entry.cursor_position < self->entry.content->len) + { + self->entry.cursor_position++; + } + break; default: break; } @@ -63,14 +94,12 @@ uicb_widget_op (Widget *self, void *userdata) /* Scrollbar boilerplate. */ void -uicb_scrollbar_start_scroll (Widget *self, void *userdata) +uicb_scrollbar_start_scroll (Widget *self, SDL_Event *sdl_event, void *userdata) { UNUSED (userdata); Widget *scr = self->scrollbar.target; - vec2 mouse_pos; - SDL_GetMouseState ((float*) &mouse_pos[0], (float*) &mouse_pos[1]); - + vec2 mouse_pos = { sdl_event->button.x, sdl_event->button.y }; unsigned int idx = self->scrollbar.horizontal ? 0 : 1; float content_size = scr->scrollable.content_size[idx]; @@ -97,7 +126,7 @@ uicb_scrollbar_start_scroll (Widget *self, void *userdata) } void -uicb_scrollbar_scroll (Widget *self, void *userdata) +uicb_scrollbar_scroll (Widget *self, SDL_Event *sdl_event, void *userdata) { UNUSED (userdata); @@ -107,8 +136,7 @@ uicb_scrollbar_scroll (Widget *self, void *userdata) } Widget *scr = self->scrollbar.target; - vec2 mouse_pos; - SDL_GetMouseState((float*) &mouse_pos[0], (float*) &mouse_pos[1]); + vec2 mouse_pos = { sdl_event->motion.x, sdl_event->motion.y }; unsigned int idx = self->scrollbar.horizontal ? 0 : 1; float content_size = scr->scrollable.content_size[idx]; @@ -129,8 +157,8 @@ uicb_scrollbar_scroll (Widget *self, void *userdata) } void -uicb_scrollbar_stop_scroll (Widget *self, void *userdata) +uicb_scrollbar_stop_scroll (Widget *self, SDL_Event *sdl_event, void *userdata) { - UNUSED (userdata); + UNUSED (sdl_event, userdata); self->scrollbar.dragging = FALSE; } diff --git a/game/src/ui/widget_ops.h b/game/src/ui/widget_ops.h index 2519be5..7e041ff 100644 --- a/game/src/ui/widget_ops.h +++ b/game/src/ui/widget_ops.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "widget_fw.h" #include "widget_core.h" @@ -7,10 +9,20 @@ typedef enum WidgetOp { OP_SWITCH_FLAG, OP_MODIFY_INT, + OP_LABEL_SHOW_INT, OP_LABEL_SHOW_FLOAT, + OP_CHECKBOX_SHOW_FLAG, - OP_SCROLLABLE_SCROLL + + OP_SCROLLABLE_SCROLL, + + OP_START_TEXT_INPUT, + OP_STOP_TEXT_INPUT, + OP_ENTRY_INPUT_TEXT, + OP_ENTRY_DELETE_CHAR, + OP_ENTRY_MOVE_LEFT, + OP_ENTRY_MOVE_RIGHT } WidgetOp; typedef struct WidgetOpData @@ -25,8 +37,8 @@ typedef struct WidgetOpData } WidgetOpData; WidgetOpData *WidgetOpData_new (WidgetOp op, void *ptr, ...); -void uicb_widget_op (Widget *self, void *userdata); +void uicb_widget_op (Widget *self, SDL_Event *sdl_event, void *userdata); -void uicb_scrollbar_start_scroll (Widget *self, void *userdata); -void uicb_scrollbar_scroll (Widget *self, void *userdata); -void uicb_scrollbar_stop_scroll (Widget *self, void *userdata); +void uicb_scrollbar_start_scroll (Widget *self, SDL_Event *sdl_event, void *userdata); +void uicb_scrollbar_scroll (Widget *self, SDL_Event *sdl_event, void *userdata); +void uicb_scrollbar_stop_scroll (Widget *self, SDL_Event *sdl_event, void *userdata); diff --git a/game/src/ui/widget_render.c b/game/src/ui/widget_render.c index 2dba062..d9e56c6 100644 --- a/game/src/ui/widget_render.c +++ b/game/src/ui/widget_render.c @@ -15,6 +15,50 @@ _Widget_get_aabb (Widget *self, vec2 aabb[2]) glm_vec2_copy ((vec2) { self->position[0] + self->size[0], self->position[1] + self->size[1] }, aabb[1]); } +static inline void +_Widget_render_text_line(const char *text, TextAlignment alignment, vec2 position, + vec2 size, float max_width, vec4 color, mat4 projection) +{ + program_use (RESPRG ("uitext")); + program_set_mat4(RESPRG ("uitext"), "projection", projection); + program_set_vec4(RESPRG ("uitext"), "color", color); + + int nchars = g_utf8_strlen (text, -1); + float total_w = nchars * size[0]; + + float xoff = 0.0; + + switch (alignment) + { + case TEXT_ALIGN_CENTER: + xoff = (max_width - total_w) * 0.5; + break; + case TEXT_ALIGN_RIGHT: + xoff = max_width - total_w; + break; + case TEXT_ALIGN_LEFT: + default: + xoff = 0.0; + } + + const char *p = text; + while (*p) + { + gunichar ch = g_utf8_get_char (p); + program_set_float (RESPRG ("uitext"), "character", ch); + + mat3 model; + glm_translate2d_make (model, (vec2) { position[0] + xoff, position[1] }); + glm_scale2d (model, size); + program_set_mat3 (RESPRG ("uitext"), "model", model); + + SimpleMesh_render (&quad, FALSE); + + xoff += size[0]; + p = g_utf8_next_char (p); + } +} + void Widget_render_base (Widget *self, mat4 projection, int window_height) { @@ -94,58 +138,15 @@ Widget_render_label (Widget *self, mat4 projection, int window_height) { Widget_render_base (self, projection, window_height); + float char_w = self->size[1] * (9.0f / 20.0f); + vec2 pos = { self->position[0], self->position[1] }; + WidgetStyle *style = self->style; - - program_use (RESPRG ("uitext")); - program_set_mat4 (RESPRG ("uitext"), "projection", projection); texture_use (style->font_bitmap, 0); - - program_set_vec4 (RESPRG ("uitext"), "color", style->text_color); - - if (self->clicked) - { - program_set_vec4 (RESPRG ("uitext"), "color", style->text_color_clicked); - } - else if (self->hovered) - { - program_set_vec4 (RESPRG ("uitext"), "color", style->text_color_hovered); - } - - float char_width = self->size[1] * (9.0 / 20.0); - float total_width = g_utf8_strlen (self->label.text->str, -1) * char_width; - float offset = 0.0; - - switch (self->label.text_alignment) - { - case TEXT_ALIGN_LEFT: - offset = -char_width; - break; - case TEXT_ALIGN_CENTER: - offset = (self->size[0] / 2) - (total_width / 2.0) -char_width; - break; - case TEXT_ALIGN_RIGHT: - offset = self->size[0] - total_width - char_width; - break; - } - - const char *p = self->label.text->str; - while (*p) - { - gunichar ch = g_utf8_get_char (p); - - program_set_float (RESPRG ("uitext"), "character", ch); - - mat3 model; - glm_translate2d_make (model, (vec2) { self->position[0] + offset + char_width, self->position[1] }); - glm_scale2d (model, (vec2) { char_width, self->size[1] }); - offset += char_width; - - program_set_mat3 (RESPRG ("uitext"), "model", model); - - SimpleMesh_render (&quad, FALSE); - - p = g_utf8_next_char (p); - } + _Widget_render_text_line (self->label.text->str, self->label.text_alignment, + pos, (vec2) { char_w, self->size[1] }, self->size[0], + self->clicked ? style->text_color_clicked : self->hovered + ? style->text_color_hovered : style->text_color, projection); } void @@ -212,67 +213,70 @@ Widget_render_progress (Widget *self, mat4 projection, int window_height) void Widget_render_multiline_text (Widget *self, mat4 projection, int window_height) { - Widget_render_base(self, projection, window_height); + Widget_render_base (self, projection, window_height); WidgetStyle *style = self->style; - - program_use (RESPRG ("uitext")); - program_set_mat4 (RESPRG ("uitext"), "projection", projection); texture_use (style->font_bitmap, 0); - program_set_vec4 (RESPRG ("uitext"), "color", style->text_color); float pad = style->layout_padding; - guint lines = self->multiline_text.lines->len; - - if (lines == 0) - { - return; - } - float row_h = self->multiline_text.line_height; float char_w = row_h * (9.0 / 20.0); - float x0 = self->position[0] + pad; - float y0 = self->position[1] + pad; - float usable = self->size[0] - 2 * pad; + vec2 pos0 = { self->position[0] + pad, self->position[1] + pad }; - for (guint i = 0; i < lines; ++i) + for (unsigned int i = 0; i < self->multiline_text.lines->len; ++i) { GString *line = g_array_index (self->multiline_text.lines, GString *, i); - guint nchars = g_utf8_strlen (line->str, -1); - float line_w = nchars * char_w; + vec2 pos = { pos0[0], pos0[1] + i * row_h }; + _Widget_render_text_line(line->str, self->multiline_text.text_alignment, pos, + (vec2) { char_w, row_h }, self->size[0] - 2 * pad, + style->text_color, projection); + } +} + +void +Widget_render_entry (Widget *self, mat4 projection, int window_height) +{ + Widget_render_base (self, projection, window_height); + + WidgetStyle *style = self->style; + texture_use (style->font_bitmap, 0); + + float char_w = self->size[1] * (9.0f / 20.0f); + float cursor_in_px = self->entry.cursor_position * char_w; + /* -char_w because we leave space for the cursor at the end of the + text. */ + float offset = glm_max (0, cursor_in_px - (self->size[0] - char_w)); + + if (!self->focused && self->entry.content->len == 0) + { + _Widget_render_text_line (self->entry.ghost_text->str, TEXT_ALIGN_LEFT, + self->position, (vec2) { char_w, self->size[1] }, self->size[0], + style->ghost_text_color, projection); + } + else + { + vec2 pos = { self->position[0] - offset, self->position[1] }; + _Widget_render_text_line (self->entry.content->str, TEXT_ALIGN_LEFT, + pos, (vec2) { char_w, self->size[1] }, self->size[0], + style->text_color, projection); + } + + if (self->focused) + { + float x = self->position[0] + cursor_in_px - offset; + float y = self->position[1]; + float w = 2.0; + float h = self->size[1]; - float y = y0 + i * row_h; - float x; + mat3 model; + glm_translate2d_make(model, (vec2) { x, y }); + glm_scale2d(model, (vec2) { w, h }); - switch (self->multiline_text.text_alignment) - { - case TEXT_ALIGN_CENTER: - x = x0 + (usable - line_w) * 0.5; - break; - case TEXT_ALIGN_RIGHT: - x = x0 + (usable - line_w); - break; - case TEXT_ALIGN_LEFT: - default: - x = x0; - break; - } + program_use (RESPRG ("uicursor")); + program_set_mat3 (RESPRG ("uicursor"), "model", model); + program_set_mat4 (RESPRG ("uicursor"), "projection", projection); + program_set_vec4 (RESPRG ("uicursor"), "cursor_color", style->text_color); - const char *p = line->str; - while (*p) - { - gunichar ch = g_utf8_get_char (p); - program_set_float (RESPRG ("uitext"), "character", ch); - - mat3 model; - glm_translate2d_make (model, (vec2) { x, y }); - glm_scale2d (model, (vec2) { char_w, row_h }); - program_set_mat3 (RESPRG ("uitext"), "model", model); - - SimpleMesh_render (&quad, FALSE); - - x += char_w; - p = g_utf8_next_char (p); - } + SimpleMesh_render(&quad, FALSE); } } diff --git a/game/src/ui/widget_render.h b/game/src/ui/widget_render.h index a4f2dac..687f8bc 100644 --- a/game/src/ui/widget_render.h +++ b/game/src/ui/widget_render.h @@ -12,3 +12,4 @@ void Widget_render_label (Widget *self, mat4 projection, int window_height); void Widget_render_scrollbar (Widget *self, mat4 projection, int window_height); void Widget_render_progress (Widget *self, mat4 projection, int window_height); void Widget_render_multiline_text (Widget *self, mat4 projection, int window_height); +void Widget_render_entry (Widget *self, mat4 projection, int window_height); diff --git a/game/src/ui/widget_style.h b/game/src/ui/widget_style.h index f329916..f94cbb6 100644 --- a/game/src/ui/widget_style.h +++ b/game/src/ui/widget_style.h @@ -12,6 +12,7 @@ typedef struct WidgetStyle vec4 text_color; vec4 text_color_hovered; vec4 text_color_clicked; + vec4 ghost_text_color; vec4 icon_color; vec4 icon_color_hovered;