From c276fa7bd54fbb793c73e04afec1c46b337e9e51 Mon Sep 17 00:00:00 2001 From: Emil Kosz Date: Sun, 15 Jun 2025 08:50:09 +0200 Subject: [PATCH] Scrollbar improvements --- game/res/shaders/uiwidget.frag | 26 +++++++++-- game/res/shaders/uiwidget.vert | 2 +- game/res/textures/icons.png | Bin 337 -> 533 bytes game/res/textures/icons.xcf | Bin 3967 -> 6750 bytes game/src/resources.c | 32 +++++++------- game/src/ui/uiman.c | 5 ++- game/src/ui/widget.c | 78 +++++++++++++++++++++++++-------- game/src/ui/widget.h | 32 +++++++++----- 8 files changed, 123 insertions(+), 52 deletions(-) diff --git a/game/res/shaders/uiwidget.frag b/game/res/shaders/uiwidget.frag index b499d5a..a4f1fbd 100644 --- a/game/res/shaders/uiwidget.frag +++ b/game/res/shaders/uiwidget.frag @@ -11,6 +11,10 @@ uniform vec2 widget_size; uniform bool has_icon; +uniform bool has_scrollbar; +uniform vec2 scrollbar_position; +uniform vec2 scrollbar_size; + uniform sampler2D background_image; uniform sampler2D icons; @@ -28,9 +32,23 @@ void main () vec4 bgc = has_background_image ? texture2D (background_image, f_uv) : background_color; vec4 base_color = mix (bgc, border_color, is_border); - if (is_border > 0.5) { - gl_FragColor = base_color; - return; + if (is_border > 0.5) + { + gl_FragColor = base_color; + return; + } + + if (has_scrollbar) { + bool in_scrollbar = + f_uv.x >= scrollbar_position.x && + f_uv.x <= (scrollbar_position.x + scrollbar_size.x) && + f_uv.y >= scrollbar_position.y && + f_uv.y <= (scrollbar_position.y + scrollbar_size.y); + + if (in_scrollbar) { + gl_FragColor = vec4 (1, 1, 1, 1); + return; + } } if (has_icon) @@ -39,6 +57,6 @@ void main () gl_FragColor = mix (base_color, fg, fg.a); base_color = mix (base_color, fg, fg.a); } - + gl_FragColor = base_color; } diff --git a/game/res/shaders/uiwidget.vert b/game/res/shaders/uiwidget.vert index 29f08e1..f836ec4 100644 --- a/game/res/shaders/uiwidget.vert +++ b/game/res/shaders/uiwidget.vert @@ -15,7 +15,7 @@ void main () { float tex_w = 40.0; - float tex_h = 40.0; + float tex_h = 80.0; float icon_w = 20.0; float icon_h = 20.0; diff --git a/game/res/textures/icons.png b/game/res/textures/icons.png index fe22510249ba3a663cd0f08406f1a7d78a236e91..0016319028fbb74d599fc0ac10fa8186ba58933d 100644 GIT binary patch delta 449 zcmcb}G?it7I9C7%8v_GFcE9<-iHhF!jFUZG978hhy`6P?(;)*6*VdQ+{}-J*{nBTR z?`F>F3z++FJ<{@R$X#$Ps#8aA4KB6pP0vnm@Vdn8IjJ&ZW%Np;hg&1KJa1ZO)v0c0G7#(~wD?QL*Q9g~ zFu7dr33;KQnffgMOYQsjyOlCNw)d-S%CvfQ>nQ6F<4S|YdYZmL4NE=M{_(x+kGAXZ z)ZNyy`$){&`+QP?k{PG7aBFTkQQdcC7!CerziP0|SGn LtDnm{r-UW|_odOY delta 251 zcmbQra*=6*IF|+o8v_GF&%YhH6BWJd8P0pUIEF+VemmnJUyA{U%iLrC|EKP`(PBEw zN%zegw!T>to=@RZaB(qvbR+U0!vp3G_A`24d+s@Vz1S>_^*6hDnuV7GFFQkZNa6Yh z?!bU^5sczho;DNw3lzCtDQ|PQckm@g-~}$rChpaa+)G&Xd=3gO5!E}&zJ7w-4wJ?E znA0}?N{v##=pbk8pId4itF~9XTJfM=;Pze{u7aDFj%=G;Q~KolnV!&Q)`*6%*&p?+ z#JnY+R#&b6{C8TDcHNJPDNARrcr7OW;QkMV$8kZ{CvRIYu)Le#)3;4po`HdZ!PC{x JWt~$(695AsYrFse diff --git a/game/res/textures/icons.xcf b/game/res/textures/icons.xcf index 96cd3eea6dc3927945e81a10601c84fac86232f4..27619902b928ce8e36552099f14963ad1effdc94 100644 GIT binary patch literal 6750 zcmYe#%q>u;NKR8IGcYt{U|?X-U|?Vf05KRC7^X2WFbFa*FfcMQFfa%+Ffed9O<-UE zVFm^U5e5bZ76t}}I0gm=F(@0PPMm>(!5wMgd6B1jLfq_GTfq{V+i&7IyQd1Pl zGfOfQ+&z84E*AhR&CE?qPt{E?%1nW&{;g45Qkj#gP@JEWnWD+1k(QpEpOat2rJ$go zpaEtoD0u{jDWv7(CzdEF7#b-k#TXz#BLjmNb91l&gsG%ppsC5Fkwm^Z1~CRuLn?|3 z5|cCY(iMyhG`Tb?QFu^}ZfRa-i9%*xaz<*gCYMG(xO!7{JgZx^q|r_-L%Y{RNaESblud7g8ZTq-JHZ?SO7DsYGme>q!#5Q zCa0z-M3gWR zoRTINC~!r<${=YjH?<@&C9xzCB+kgdz?x`dUy+-mP?lN*3N>4GLp=j^h19&{{FKbR zbX)b%5I0>5b$dH58<<8rE(HaflEid{yu{p8TP4?u%rreuP;%CD$}A}kNG%FZ%q_@C zRkE{Cuu!nD(Jx6%$F0RBu_QGlGdESq&d9*X)XKoj%Fsx`$NzQhN7sPlb`o_W9E`3Jl;lC>U8+JsQEFOdg{@KnC~S)}@(Yv{N{e!A zl`=|73as??^NRHnQ}UBi^^)^*_2Fvt4fPE4mF#Tv;pU*_jELLRG;;Wd z!14!S*Hz@=b5})T0Z}fh$Su%=xC7)OqMU*#A3$CL8Q=>ocPKF;KP5A*k_-bp3rdpp z9199^GLsWQ8PW(dXmt(DbPcg)QB2LCHs2s>HRVW)Ir2=CA|NkH4R%IkM ztYrpjUV+p>G3Q?>1DS#a2h^=xAU`uOFi1Q_QUp`Oz`(%e2oV>)3!xbpQvdTYg#YJc z;QY_Qzy+q+8Myy*GI0H8W#IYG$-w=enSu5He+IVyoD4kw85!81Y*92?@c(}XCa9Re ze?~Z)^Z$Pamj6s(zS{r)3?MB2pP!+g;p~5I29EzM42=I-8Cb!lGeOzx3_nSuTP9|o{6m|_E~WMW|Z53&cO8sugs2G;+~ zV3R@aVgxG(iL-!10b(*6140~RBO}-*Ua;E#47g~P|Jd|_!T}V9F#BQl;BpssU*PgH z$lsuF0htbpUl1GQVo>;j;&2H#Btblo+dwi9_cQ(bb#ekRF!*oM1MnegT#6O#j&#nEo???PdhmHLMIw;M##7tPaFx0@orS zdq8C?2!qO85Fb>NfO0X24XRPt!RCPKC&vFAP&O;We>fYpJO=3kl?N~r$Yn#yb5Om7 zyeSH1Q`UvjNoMfI3q!-7ML)o zxPs(JP}Riz{~rUmZ^gp!50qU%jdxI118D~F(X$geA6-4FY3O#r+yF8ii__43fbK(3 zz6D{JF)$iZLI#5R$Dlqjs2>aJwt{HzL;(W>18d09rhJefp^iUD4YrOyXdD4s#~&m< zP#u5RU{f$h@?~yga(r^H4F?4QdVFQ1}QfP&A?F4g@l?b*miQgV%v?WNG8DbiLG=v6|AaM|8U|`@c28%E-Fx0a-z%c+* CStnWm diff --git a/game/src/resources.c b/game/src/resources.c index 1d60003..677ab34 100644 --- a/game/src/resources.c +++ b/game/src/resources.c @@ -146,23 +146,23 @@ resources_init () #define RAT resources_add_texture #define RLT resources_load_texture - RAP ("default", RLP ("res/shaders/default.vert", "res/shaders/default.frag")); - RAP ("chunk", RLP ("res/shaders/chunk.vert", "res/shaders/chunk.frag")); - 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 ("default", RLP ("res/shaders/default.vert", "res/shaders/default.frag")); + RAP ("chunk", RLP ("res/shaders/chunk.vert", "res/shaders/chunk.frag")); + 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")); - RAT ("terrain0", RLT ("res/textures/dirt.png")); - RAT ("terrain1", RLT ("res/textures/drygrass.png")); - RAT ("terrain2", RLT ("res/textures/grass.png")); - RAT ("terrain3", RLT ("res/textures/rock_cement.png")); - RAT ("terrain4", RLT ("res/textures/asphalt_gravel.png")); - RAT ("tree0", RLT ("res/textures/tree.png")); - RAT ("grass0", RLT ("res/textures/grass0.png")); - RAT ("clouds", RLT ("res/textures/clouds.png")); - - RAT ("font0", RLT ("res/textures/font_9x20.png")); - RAT ("icons", RLT ("res/textures/icons.png")); + RAT ("terrain0", RLT ("res/textures/dirt.png")); + RAT ("terrain1", RLT ("res/textures/drygrass.png")); + RAT ("terrain2", RLT ("res/textures/grass.png")); + RAT ("terrain3", RLT ("res/textures/rock_cement.png")); + RAT ("terrain4", RLT ("res/textures/asphalt_gravel.png")); + RAT ("tree0", RLT ("res/textures/tree.png")); + RAT ("grass0", RLT ("res/textures/grass0.png")); + RAT ("clouds", RLT ("res/textures/clouds.png")); + + RAT ("font0", RLT ("res/textures/font_9x20.png")); + RAT ("icons", RLT ("res/textures/icons.png")); #undef RAP #undef RLP diff --git a/game/src/ui/uiman.c b/game/src/ui/uiman.c index 2eef95d..647907b 100644 --- a/game/src/ui/uiman.c +++ b/game/src/ui/uiman.c @@ -49,8 +49,9 @@ uiman_init () Widget_add_child_weighed (chkboxes, Widget_composite_checkbox_make ("Flight", "flight_enabled"), 1); Widget_add_child_weighed (debug_menu, chkboxes, 1); - Widget *content = Widget_label_new ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", TEXT_ALIGN_LEFT); - VEC2_SETI (content->size, 3000, 500); + Widget *content = Widget_plain_new (); + Widget_set_background_image (content, RESTEX ("terrain1")); + VEC2_SETI (content->size, 400, 600); Widget *scrollable_window = Widget_composite_scrollable_make (content); Widget_set_background_image (scrollable_window, RESTEX ("terrain4")); diff --git a/game/src/ui/widget.c b/game/src/ui/widget.c index 7b96085..63fc19c 100644 --- a/game/src/ui/widget.c +++ b/game/src/ui/widget.c @@ -45,7 +45,16 @@ Widget * Widget_scrollable_new () { Widget *e = ui_el_new_internal (WIDGET_SCROLLABLE); - e->scrollable.scroll_amount = 5.0; + e->scrollable.scroll_amount = 10.0; + return e; +} + +Widget * +Widget_scrollbar_new (Widget *scrollable, gboolean horizontal) +{ + Widget *e = ui_el_new_internal (WIDGET_SCROLLBAR); + e->scrollbar.scrollable = scrollable; + e->scrollbar.horizontal = horizontal; return e; } @@ -252,17 +261,20 @@ Widget_scrollable_layout (Widget *parent) WIDGET_FOREACH_CHILD (parent, i, child) { - child->position[0] = parent->position[0] - parent->scrollable.scroll_position[0]; - child->position[1] = parent->position[1] - parent->scrollable.scroll_position[1]; + float logical_x = 0.0f; + float logical_y = 0.0f; + + child->position[0] = parent->position[0] + logical_x - parent->scrollable.scroll_position[0]; + child->position[1] = parent->position[1] + logical_y - parent->scrollable.scroll_position[1]; if (child->layout_weight > 0.0) { child->size[0] = child->layout_weight * parent->size[0]; child->size[1] = child->layout_weight * parent->size[1]; } - - float right = child->position[0] + child->size[0]; - float bottom = child->position[1] + child->size[1]; + + float right = logical_x + child->size[0]; + float bottom = logical_y + child->size[1]; if (right > content_size[0]) { @@ -415,6 +427,33 @@ Widget_render (Widget *widget, mat4 projection, int window_height) program_set_int (RESPRG ("uiwidget"), "has_icon", widget->has_icon); program_set_float (RESPRG ("uiwidget"), "icon_index", widget->icon); texture_use (RESTEX ("icons"), 1); + + if (widget->kind == WIDGET_SCROLLBAR) + { + program_set_int (RESPRG ("uiwidget"), "has_scrollbar", TRUE); + /* Calculate the scrollbar size. */ + unsigned int idx = widget->scrollbar.horizontal ? 0 : 1; + + Widget *scr = widget->scrollbar.scrollable; + + float content_size = scr->scrollable.content_size[idx]; + float scroll_pos = scr->scrollable.scroll_position[idx]; + float view_size = scr->size[idx]; + float thumb_size_norm = view_size / content_size; + float thumb_pos_norm = scroll_pos / content_size; + + program_set_vec2 (RESPRG ("uiwidget"), "scrollbar_position", widget->scrollbar.horizontal + ? (vec2) { thumb_pos_norm, 0.0f } + : (vec2) { 0.0f, thumb_pos_norm }); + + program_set_vec2 (RESPRG ("uiwidget"), "scrollbar_size", widget->scrollbar.horizontal + ? (vec2) { thumb_size_norm, 1.0f } + : (vec2) { 1.0f, thumb_size_norm }); + } + else + { + program_set_int (RESPRG ("uiwidget"), "has_scrollbar", FALSE); + } mat3 model; glm_translate2d_make (model, widget->position); @@ -445,10 +484,10 @@ Widget_render (Widget *widget, mat4 projection, int window_height) vec2 clipped_aabb[2]; glm_aabb2d_crop (widget_aabb, parent_aabb, clipped_aabb); - int x = floor (clipped_aabb[0][0]); - int y = floor (window_height - clipped_aabb[1][1]); - int w = ceil (clipped_aabb[1][0] - clipped_aabb[0][0]); - int h = ceil (clipped_aabb[1][1] - clipped_aabb[0][1]); + int x = roundf (clipped_aabb[0][0]); + int y = roundf (window_height - clipped_aabb[1][1]); + int w = roundf (clipped_aabb[1][0] - clipped_aabb[0][0]); + int h = roundf (clipped_aabb[1][1] - clipped_aabb[0][1]); /* X, Y is the lower left corner of the box. */ glScissor (x, y, w, h); @@ -461,9 +500,10 @@ Widget_render (Widget *widget, mat4 projection, int window_height) } Widget *child; - WIDGET_FOREACH_CHILD (widget, i, child) { - Widget_render (child, projection, window_height); - } + WIDGET_FOREACH_CHILD (widget, i, child) + { + Widget_render (child, projection, window_height); + } } /* --------- */ @@ -630,11 +670,11 @@ Widget_composite_scrollable_make (Widget *content) Widget *scroll_up = Widget_plain_new (); Widget *scroll_down = Widget_plain_new (); - Widget_set_icon (scroll_up, ICON_SPINNER_LEFT); + Widget_set_icon (scroll_up, ICON_ARROW_UP); Widget_set_on_left_click (scroll_up, uicb_scrollable_scroll_up, scrollable); scroll_up->aspect_ratio = 1.0; - Widget_set_icon (scroll_down, ICON_SPINNER_RIGHT); + Widget_set_icon (scroll_down, ICON_ARROW_DOWN); Widget_set_on_left_click (scroll_down, uicb_scrollable_scroll_down, scrollable); scroll_down->aspect_ratio = 1.0; @@ -642,7 +682,7 @@ Widget_composite_scrollable_make (Widget *content) Widget *scrollbar_vertical = Widget_container_new (FALSE, 0, 0); Widget_add_child_weighed (scrollbar_vertical, scroll_up, 1); - Widget_add_child_weighed (scrollbar_vertical, Widget_plain_new (), 1); + Widget_add_child_weighed (scrollbar_vertical, Widget_scrollbar_new (scrollable, FALSE), 1); Widget_add_child_weighed (scrollbar_vertical, scroll_down, 1); Widget_add_child_weighed (cont_horiz, scrollable, 0.93); @@ -653,11 +693,11 @@ Widget_composite_scrollable_make (Widget *content) Widget *scroll_left = Widget_plain_new (); Widget *scroll_right = Widget_plain_new (); - Widget_set_icon (scroll_left, ICON_SPINNER_LEFT); + Widget_set_icon (scroll_left, ICON_ARROW_LEFT); Widget_set_on_left_click (scroll_left, uicb_scrollable_scroll_left, scrollable); scroll_left->aspect_ratio = 1.0; - Widget_set_icon (scroll_right, ICON_SPINNER_RIGHT); + Widget_set_icon (scroll_right, ICON_ARROW_RIGHT); Widget_set_on_left_click (scroll_right, uicb_scrollable_scroll_right, scrollable); scroll_right->aspect_ratio = 1.0; @@ -667,7 +707,7 @@ Widget_composite_scrollable_make (Widget *content) padding->aspect_ratio = 1.0; Widget_add_child_weighed (scrollbar_horizontal, scroll_left, 1); - Widget_add_child_weighed (scrollbar_horizontal, Widget_plain_new (), 1); + Widget_add_child_weighed (scrollbar_horizontal, Widget_scrollbar_new (scrollable, TRUE), 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 2a02a1d..6363a04 100644 --- a/game/src/ui/widget.h +++ b/game/src/ui/widget.h @@ -13,16 +13,27 @@ for (unsigned int i = 0; i < widget->children->len && \ ((child = g_array_index(widget->children, Widget *, i)), 1); ++i) - - typedef enum WidgetKind { WIDGET_PLAIN, WIDGET_CONTAINER, WIDGET_LABEL, - WIDGET_SCROLLABLE + WIDGET_SCROLLABLE, + WIDGET_SCROLLBAR } WidgetKind; +typedef enum WidgetIcon + { + ICON_CHECKBOX_UNCHECKED = 0, + ICON_CHECKBOX_CHECKED, + ICON_SPINNER_LEFT, + ICON_SPINNER_RIGHT, + ICON_ARROW_LEFT, + ICON_ARROW_RIGHT, + ICON_ARROW_UP, + ICON_ARROW_DOWN + } WidgetIcon; + typedef struct ContainerWidget { gboolean horizontal; @@ -51,13 +62,12 @@ typedef struct ScrollableWidget vec2 content_size; } ScrollableWidget; -typedef enum WidgetIcon - { - ICON_CHECKBOX_UNCHECKED = 0, - ICON_CHECKBOX_CHECKED, - ICON_SPINNER_LEFT, - ICON_SPINNER_RIGHT - } WidgetIcon; +typedef struct ScrollbarWidget +{ + struct Widget *scrollable; + gboolean horizontal; + float bar_size; +} ScrollbarWidget; typedef struct Widget { @@ -69,6 +79,7 @@ typedef struct Widget ContainerWidget container; LabelWidget label; ScrollableWidget scrollable; + ScrollbarWidget scrollbar; }; gboolean visible; @@ -104,6 +115,7 @@ Widget *Widget_plain_new (); Widget *Widget_container_new (gboolean horizontal, float padding, float child_spacing); Widget *Widget_label_new (const char *text, TextAlignment alignment); Widget *Widget_scrollable_new (); +Widget *Widget_scrollbar_new (Widget *scrollable, gboolean horizontal); void Widget_add_child (Widget *parent, Widget *child); void Widget_add_child_weighed (Widget *parent, Widget *child,