diff --git a/game/src/ui/uiman.c b/game/src/ui/uiman.c index 647907b..cd088b3 100644 --- a/game/src/ui/uiman.c +++ b/game/src/ui/uiman.c @@ -14,7 +14,7 @@ extern vec3 camera_position; static Widget *ui_root; static mat4 projection; -static Widget *hovered_widget, *clicked_widget; +static Widget *hovered_widget = NULL; void uiman_init () @@ -25,7 +25,7 @@ uiman_init () glm_ortho (0.0, (float) sdl_window_w, (float) sdl_window_h, 0.0, -1.0, 1.0, projection); Widget *debug_menu = Widget_container_new (FALSE, 8, 4); - Widget_set_background_image (debug_menu, RESTEX ("terrain4")); + debug_menu->background_image = RESTEX ("terrain4"); VEC2_SETI (debug_menu->position, 32, 32); VEC2_SETI (debug_menu->size, 350, 170); debug_menu->border_width = 4; @@ -50,11 +50,11 @@ uiman_init () Widget_add_child_weighed (debug_menu, chkboxes, 1); Widget *content = Widget_plain_new (); - Widget_set_background_image (content, RESTEX ("terrain1")); + content->background_image = RESTEX ("terrain1"); VEC2_SETI (content->size, 400, 600); Widget *scrollable_window = Widget_composite_scrollable_make (content); - Widget_set_background_image (scrollable_window, RESTEX ("terrain4")); + scrollable_window->background_image = RESTEX ("terrain4"); VEC2_SETI (scrollable_window->position, 390, 32); VEC2_SETI (scrollable_window->size, 350, 350); scrollable_window->border_width = 4; @@ -109,23 +109,28 @@ uiman_cb_key_up (SDL_Event *event, void *data) gboolean uiman_cb_mouse_motion (SDL_Event *event, void *data) { - UNUSED (data); + UNUSED (event, data); - Widget *new_hover = Widget_find_xy (ui_root, (vec2) { event->motion.x, event->motion.y }); + Widget *widget = Widget_find_xy (ui_root, (vec2) { event->motion.x, event->motion.y }); - if (new_hover != hovered_widget) + if (widget != hovered_widget) { - if (hovered_widget) + if (hovered_widget && hovered_widget->on_mouse_leave) { - hovered_widget->hovered = FALSE; + hovered_widget->on_mouse_leave (hovered_widget, hovered_widget->mouse_leave_data); } - if (new_hover) + if (widget && widget->on_mouse_enter) { - new_hover->hovered = TRUE; + widget->on_mouse_enter (widget, widget->mouse_enter_data); } - hovered_widget = new_hover; + hovered_widget = widget; + } + + if (hovered_widget && hovered_widget->on_mouse_move) + { + hovered_widget->on_mouse_move (hovered_widget, hovered_widget->mouse_move_data); } /* This callback always eats the event, because why would the game @@ -133,55 +138,25 @@ uiman_cb_mouse_motion (SDL_Event *event, void *data) return TRUE; } +gboolean widget_match_bdown (Widget *self) { return self->on_left_down || self->on_right_down; }; gboolean uiman_cb_button_down (SDL_Event *event, void *data) { UNUSED (data); - - Widget *new_clicked = Widget_find_xy (ui_root, (vec2) { event->motion.x, event->motion.y }); - if (new_clicked != clicked_widget) - { - if (clicked_widget && (clicked_widget->on_left_click || clicked_widget->on_right_click)) - { - clicked_widget->clicked = FALSE; - } + Widget *widget = Widget_find_xy_matching (ui_root, (vec2) { event->button.x, event->button.y }, widget_match_bdown); - if (new_clicked && (new_clicked->on_left_click || new_clicked->on_right_click)) - { - new_clicked->clicked = TRUE; - } - - clicked_widget = new_clicked; - } - - return TRUE; -} - -gboolean -uiman_cb_button_up (SDL_Event *event, void *data) -{ - UNUSED (data); - - Widget *widget = Widget_find_xy (ui_root, (vec2) { event->motion.x, event->motion.y }); - - if (clicked_widget) - { - clicked_widget->clicked = FALSE; - clicked_widget = NULL; - } - if (widget) { - if (event->button.button == SDL_BUTTON_LEFT && widget->on_left_click) + if (event->button.button == SDL_BUTTON_LEFT && widget->on_left_down) { - widget->on_left_click (widget, widget->left_click_data); + widget->on_left_down (widget, widget->left_down_data); return TRUE; } - if (event->button.button == SDL_BUTTON_RIGHT && widget->on_right_click) + if (event->button.button == SDL_BUTTON_RIGHT && widget->on_right_down) { - widget->on_right_click (widget, widget->right_click_data); + widget->on_right_down (widget, widget->right_down_data); return TRUE; } } @@ -189,10 +164,58 @@ uiman_cb_button_up (SDL_Event *event, void *data) return FALSE; } +gboolean widget_match_bup (Widget *self) { return self->on_left_up || self->on_right_up; }; +gboolean +uiman_cb_button_up (SDL_Event *event, void *data) +{ + UNUSED (data); + + Widget *widget = Widget_find_xy_matching (ui_root, (vec2) { event->button.x, event->button.y }, widget_match_bup); + + if (widget) + { + if (event->button.button == SDL_BUTTON_LEFT && widget->on_left_up) + { + widget->on_left_up (widget, widget->left_up_data); + return TRUE; + } + + if (event->button.button == SDL_BUTTON_RIGHT && widget->on_right_up) + { + widget->on_right_up (widget, widget->right_up_data); + return TRUE; + } + } + + return FALSE; +} + +gboolean widget_match_scroll (Widget *self) { return self->on_scroll_down || self->on_scroll_up; }; gboolean uiman_cb_mouse_wheel (SDL_Event *event, void *data) { UNUSED (event, data); + + Widget *widget = Widget_find_xy_matching (ui_root, (vec2) { event->wheel.mouse_x, event->wheel.mouse_y }, widget_match_scroll); + + if (widget) + { + if (event->wheel.y > 0) + { + if (widget->on_scroll_up) + { + widget->on_scroll_up (widget, widget->scroll_up_data); + } + } + else + { + if (widget->on_scroll_down) + { + widget->on_scroll_down (widget, widget->scroll_down_data); + } + } + } + return FALSE; } diff --git a/game/src/ui/widget.c b/game/src/ui/widget.c index 63fc19c..5cdf04c 100644 --- a/game/src/ui/widget.c +++ b/game/src/ui/widget.c @@ -11,6 +11,7 @@ ui_el_new_internal (WidgetKind kind) e->kind = kind; e->visible = true; e->children = g_array_new (false, false, sizeof (Widget *)); + e->icon = ICON_NONE; glm_vec4_zero (e->background_color); return e; } @@ -78,20 +79,6 @@ Widget_add_child_weighed (Widget *parent, Widget *child, float layout_weight) Widget_add_child (parent, child); } -void -Widget_set_icon (Widget *widget, WidgetIcon icon) -{ - widget->has_icon = TRUE; - widget->icon = icon; -} - -void -Widget_set_background_image (Widget *widget, unsigned int image) -{ - widget->has_background_image = TRUE; - widget->background_image = image; -} - void Widget_set_on_tick (Widget *widget, void (*on_tick) (Widget *, void *), @@ -102,12 +89,12 @@ Widget_set_on_tick (Widget *widget, } void -Widget_set_on_left_click (Widget *widget, - void (*on_left_click) (Widget *, void *), - void *userdata) +Widget_set_on_left_up (Widget *widget, + void (*on_left_up) (Widget *, void *), + void *userdata) { - widget->on_left_click = on_left_click; - widget->left_click_data = userdata; + widget->on_left_up = on_left_up; + widget->left_up_data = userdata; } void @@ -153,13 +140,18 @@ Widget_hit_test (Widget *widget, vec2 point) } Widget * -Widget_find_xy (Widget *widget, vec2 pos) +Widget_find_xy_matching (Widget *widget, vec2 pos, gboolean (*match_fn) (Widget *widget)) { if (!widget->visible) { return NULL; } + if (!Widget_hit_test (widget, pos)) + { + return NULL; + } + vec2 pos_scrolled; glm_vec2_copy (pos, pos_scrolled); @@ -168,15 +160,10 @@ Widget_find_xy (Widget *widget, vec2 pos) glm_vec2_add (pos_scrolled, widget->scrollable.scroll_position, pos_scrolled); } - if (!Widget_hit_test (widget, pos_scrolled)) - { - return NULL; - } - for (int i = widget->children->len - 1; i >= 0; --i) { Widget *child = g_array_index (widget->children, Widget *, i); - Widget *found = Widget_find_xy (child, pos_scrolled); + Widget *found = Widget_find_xy_matching (child, pos_scrolled, match_fn); if (found != NULL) { @@ -184,7 +171,18 @@ Widget_find_xy (Widget *widget, vec2 pos) } } - return widget; + if (!match_fn || match_fn (widget)) + { + return widget; + } + + return NULL; +} + +Widget * +Widget_find_xy (Widget *widget, vec2 pos) +{ + return Widget_find_xy_matching (widget, pos, NULL); } void @@ -410,21 +408,12 @@ Widget_render (Widget *widget, mat4 projection, int window_height) program_set_vec4 (RESPRG ("uiwidget"), "border_color", widget->border_color); program_set_vec2 (RESPRG ("uiwidget"), "widget_size", widget->size); - program_set_int (RESPRG ("uiwidget"), "has_background_image", widget->has_background_image); + program_set_int (RESPRG ("uiwidget"), "has_background_image", widget->background_image != 0); texture_use (widget->background_image, 0); /* If it doesn't have a texture, it'll be 0 anyway. */ program_set_vec4 (RESPRG ("uiwidget"), "background_color", widget->background_color); - - if (widget->clicked) - { - program_set_vec4 (RESPRG ("uiwidget"), "background_color", widget->clicked_bg_color); - } - else if (widget->hovered) - { - program_set_vec4 (RESPRG ("uiwidget"), "background_color", widget->hovered_bg_color); - } - program_set_int (RESPRG ("uiwidget"), "has_icon", widget->has_icon); + program_set_int (RESPRG ("uiwidget"), "has_icon", widget->icon != -1); program_set_float (RESPRG ("uiwidget"), "icon_index", widget->icon); texture_use (RESTEX ("icons"), 1); @@ -603,6 +592,78 @@ uicb_scrollable_scroll_right (Widget *self, void *userdata) Widget_scrollable_scroll (scrollable, (vec2) { 1.0, 0.0 }); } +void +uicb_scrollbar_start_scroll (Widget *self, void *userdata) +{ + UNUSED (userdata); + + Widget *scr = self->scrollbar.scrollable; + vec2 mouse_pos; + SDL_GetMouseState ((float*)&mouse_pos[0], (float*)&mouse_pos[1]); + + unsigned int idx = self->scrollbar.horizontal ? 0 : 1; + + float content_size = scr->scrollable.content_size[idx]; + float view_size = scr->size[idx]; + float track_pixels = self->size[idx]; + + float thumb_size_norm = view_size / content_size; + float thumb_pixels = thumb_size_norm * track_pixels; + + float max_scroll = content_size - view_size; + float thumb_pos_norm = max_scroll > 0.0f + ? scr->scrollable.scroll_position[idx] / max_scroll + : 0.0f; + + float thumb_start_px = thumb_pos_norm * (track_pixels - thumb_pixels); + + float click_axis = self->scrollbar.horizontal ? mouse_pos[0] : mouse_pos[1]; + float track_start = self->scrollbar.horizontal + ? self->position[0] + : self->position[1]; + + self->scrollbar.drag_offset = click_axis - track_start - thumb_start_px; + self->scrollbar.dragging = TRUE; +} + +void +uicb_scrollbar_scroll (Widget *self, void *userdata) +{ + UNUSED (userdata); + + if (self->scrollbar.dragging == FALSE) + { + return; + } + + Widget *scr = self->scrollbar.scrollable; + vec2 mouse_pos; + SDL_GetMouseState((float*) &mouse_pos[0], (float*) &mouse_pos[1]); + + unsigned int idx = self->scrollbar.horizontal ? 0 : 1; + float content_size = scr->scrollable.content_size[idx]; + float view_size = scr->size[idx]; + float track_pixels = self->size[idx]; + + float click_axis = self->scrollbar.horizontal ? mouse_pos[0] : mouse_pos[1]; + float track_start = self->scrollbar.horizontal ? self->position[0] : self->position[1]; + + float t = (click_axis - track_start - self->scrollbar.drag_offset) + / track_pixels; + t = glm_clamp (t, 0.0f, 1.0f); + + float max_scroll = content_size - view_size; + scr->scrollable.scroll_position[idx] = + glm_clamp (t * content_size, 0.0f, max_scroll > 0.0f ? max_scroll : 0.0f); +} + +void +uicb_scrollbar_stop_scroll (Widget *self, void *userdata) +{ + UNUSED (userdata); + self->scrollbar.dragging = FALSE; +} + Widget * Widget_label_make_int (const char *fmt, int *ptr, TextAlignment alignment) { @@ -627,11 +688,11 @@ Widget_composite_spinner_make_int (const char *fmt, int *ptr) Widget *label = Widget_label_make_int (fmt, ptr, TEXT_ALIGN_CENTER); Widget *right = Widget_plain_new (); - Widget_set_on_left_click (left, uicb_decrement_int, ptr); - Widget_set_icon (left, ICON_SPINNER_LEFT); + Widget_set_on_left_up (left, uicb_decrement_int, ptr); + left->icon = ICON_SPINNER_LEFT; left->aspect_ratio = 1.0; - Widget_set_on_left_click (right, uicb_increment_int, ptr); - Widget_set_icon (right, ICON_SPINNER_RIGHT); + Widget_set_on_left_up (right, uicb_increment_int, ptr); + right->icon = ICON_SPINNER_RIGHT; right->aspect_ratio = 1.0; Widget_add_child_weighed (cont, left, 1); @@ -649,10 +710,10 @@ Widget_composite_checkbox_make (const char *label, char *flag) Widget *cont = Widget_container_new (TRUE, 0, 8); Widget *box = Widget_plain_new (); - Widget_set_icon (box, ICON_CHECKBOX_UNCHECKED); + box->icon = ICON_CHECKBOX_UNCHECKED; box->aspect_ratio = 1.0; Widget_set_on_tick (box, uicb_checkbox_show_flag, flag); - Widget_set_on_left_click (box, uicb_switch_flag, flag); + Widget_set_on_left_up (box, uicb_switch_flag, flag); Widget_add_child_weighed (cont, box, 1); Widget_add_child_weighed (cont, Widget_label_new (label, TEXT_ALIGN_LEFT), 1); @@ -666,23 +727,34 @@ Widget_composite_scrollable_make (Widget *content) Widget *cont_main = Widget_container_new (FALSE, 8, 4); Widget *scrollable = Widget_scrollable_new (); Widget_add_child (scrollable, content); + + scrollable->on_scroll_down = uicb_scrollable_scroll_down; + scrollable->scroll_down_data = scrollable; + scrollable->on_scroll_up = uicb_scrollable_scroll_up; + scrollable->scroll_up_data = scrollable; Widget *scroll_up = Widget_plain_new (); Widget *scroll_down = Widget_plain_new (); - Widget_set_icon (scroll_up, ICON_ARROW_UP); - Widget_set_on_left_click (scroll_up, uicb_scrollable_scroll_up, scrollable); + scroll_up->icon = ICON_ARROW_UP; + Widget_set_on_left_up (scroll_up, uicb_scrollable_scroll_up, scrollable); scroll_up->aspect_ratio = 1.0; - Widget_set_icon (scroll_down, ICON_ARROW_DOWN); - Widget_set_on_left_click (scroll_down, uicb_scrollable_scroll_down, scrollable); + scroll_down->icon = ICON_ARROW_DOWN; + Widget_set_on_left_up (scroll_down, uicb_scrollable_scroll_down, scrollable); scroll_down->aspect_ratio = 1.0; Widget *cont_horiz = Widget_container_new (TRUE, 0, 4); Widget *scrollbar_vertical = Widget_container_new (FALSE, 0, 0); + Widget *actual_scrollbar_vertical = Widget_scrollbar_new (scrollable, FALSE); + + actual_scrollbar_vertical->on_left_down = uicb_scrollbar_start_scroll; + actual_scrollbar_vertical->on_left_up = uicb_scrollbar_stop_scroll; + actual_scrollbar_vertical->on_mouse_leave = uicb_scrollbar_stop_scroll; + actual_scrollbar_vertical->on_mouse_move = uicb_scrollbar_scroll; Widget_add_child_weighed (scrollbar_vertical, scroll_up, 1); - Widget_add_child_weighed (scrollbar_vertical, Widget_scrollbar_new (scrollable, FALSE), 1); + Widget_add_child_weighed (scrollbar_vertical, actual_scrollbar_vertical, 1); Widget_add_child_weighed (scrollbar_vertical, scroll_down, 1); Widget_add_child_weighed (cont_horiz, scrollable, 0.93); @@ -693,21 +765,27 @@ Widget_composite_scrollable_make (Widget *content) Widget *scroll_left = Widget_plain_new (); Widget *scroll_right = Widget_plain_new (); - Widget_set_icon (scroll_left, ICON_ARROW_LEFT); - Widget_set_on_left_click (scroll_left, uicb_scrollable_scroll_left, scrollable); + scroll_left->icon = ICON_ARROW_LEFT; + Widget_set_on_left_up (scroll_left, uicb_scrollable_scroll_left, scrollable); scroll_left->aspect_ratio = 1.0; - Widget_set_icon (scroll_right, ICON_ARROW_RIGHT); - Widget_set_on_left_click (scroll_right, uicb_scrollable_scroll_right, scrollable); + scroll_right->icon = ICON_ARROW_RIGHT; + Widget_set_on_left_up (scroll_right, uicb_scrollable_scroll_right, scrollable); scroll_right->aspect_ratio = 1.0; Widget *scrollbar_horizontal = Widget_container_new (TRUE, 0, 0); + Widget *actual_scrollbar_horizontal = Widget_scrollbar_new (scrollable, TRUE); + actual_scrollbar_horizontal->on_left_down = uicb_scrollbar_start_scroll; + actual_scrollbar_horizontal->on_left_up = uicb_scrollbar_stop_scroll; + actual_scrollbar_horizontal->on_mouse_leave = uicb_scrollbar_stop_scroll; + actual_scrollbar_horizontal->on_mouse_move = uicb_scrollbar_scroll; + Widget *padding = Widget_plain_new (); padding->aspect_ratio = 1.0; Widget_add_child_weighed (scrollbar_horizontal, scroll_left, 1); - Widget_add_child_weighed (scrollbar_horizontal, Widget_scrollbar_new (scrollable, TRUE), 1); + Widget_add_child_weighed (scrollbar_horizontal, actual_scrollbar_horizontal, 1); Widget_add_child_weighed (scrollbar_horizontal, scroll_right, 1); Widget_add_child_weighed (scrollbar_horizontal, padding, 1); diff --git a/game/src/ui/widget.h b/game/src/ui/widget.h index 6363a04..cf35930 100644 --- a/game/src/ui/widget.h +++ b/game/src/ui/widget.h @@ -24,6 +24,7 @@ typedef enum WidgetKind typedef enum WidgetIcon { + ICON_NONE = -1, ICON_CHECKBOX_UNCHECKED = 0, ICON_CHECKBOX_CHECKED, ICON_SPINNER_LEFT, @@ -64,9 +65,11 @@ typedef struct ScrollableWidget typedef struct ScrollbarWidget { - struct Widget *scrollable; + struct Widget *scrollable; /* Rename to target. */ gboolean horizontal; - float bar_size; + /* Used for input. */ + gboolean dragging; + float drag_offset; } ScrollbarWidget; typedef struct Widget @@ -83,30 +86,40 @@ typedef struct Widget }; gboolean visible; - gboolean hovered, clicked; - + vec2 position; vec2 size; float layout_weight; float aspect_ratio; vec4 background_color; - vec4 hovered_bg_color; - vec4 clicked_bg_color; - - gboolean has_background_image; unsigned int background_image; - - gboolean has_icon; WidgetIcon icon; float border_width; vec4 border_color; - void (*on_left_click) (struct Widget *, void *); - void *left_click_data; - void (*on_right_click) (struct Widget *, void *); - void *right_click_data; + void (*on_left_down) (struct Widget *, void *); + void *left_down_data; + void (*on_left_up) (struct Widget *, void *); + void *left_up_data; + void (*on_right_down) (struct Widget *, void *); + void *right_down_data; + void (*on_right_up) (struct Widget *, void *); + void *right_up_data; + + void (*on_mouse_enter) (struct Widget *, void *); + void *mouse_enter_data; + void (*on_mouse_leave) (struct Widget *, void *); + void *mouse_leave_data; + void (*on_mouse_move) (struct Widget *, void *); + void *mouse_move_data; + + void (*on_scroll_down) (struct Widget *, void *); + void *scroll_down_data; + void (*on_scroll_up) (struct Widget *, void *); + void *scroll_up_data; + void (*on_tick) (struct Widget *, void *); void *tick_data; } Widget; @@ -121,16 +134,15 @@ void Widget_add_child (Widget *parent, Widget *child); void Widget_add_child_weighed (Widget *parent, Widget *child, float layout_weight); -void Widget_set_background_image (Widget *widget, unsigned int image); -void Widget_set_icon (Widget *widget, WidgetIcon icon); 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_on_left_up (Widget *widget, void (*on_left_up) (Widget *, void *), void *userdata); void Widget_label_set_text (Widget *widget, const char *text); void Widget_label_printf (Widget *widget, const char *format, ...); void Widget_scrollable_scroll (Widget *widget, vec2 direction); +Widget *Widget_find_xy_matching (Widget *widget, vec2 pos, gboolean (*match_fn) (Widget *widget)); Widget *Widget_find_xy (Widget *widget, vec2 point); void ui_widget_mesh_init (); @@ -162,6 +174,10 @@ void uicb_scrollable_scroll_up (Widget *self, void *userdata); void uicb_scrollable_scroll_left (Widget *self, void *userdata); void uicb_scrollable_scroll_right (Widget *self, 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); + Widget *Widget_label_make_int (const char *fmt, int *ptr, TextAlignment alignment); Widget *Widget_label_make_float (const char *fmt, float *ptr, TextAlignment alignment);