Working scrollbars

This commit is contained in:
2025-06-15 11:35:51 +02:00
parent c276fa7bd5
commit 2bd3b09f32
3 changed files with 236 additions and 119 deletions
+71 -48
View File
@@ -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;
}
+132 -54
View File
@@ -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);
+33 -17
View File
@@ -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);