scrollbar.c - ltk - GUI toolkit for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
scrollbar.c (8584B)
---
1 /*
2 * Copyright (c) 2021-2026 lumidify <nobody@lumidify.org>
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stddef.h>
18
19 #include "event.h"
20 #include "config.h"
21 #include "memory.h"
22 #include "color.h"
23 #include "rect.h"
24 #include "widget.h"
25 #include "util.h"
26 #include "graphics.h"
27 #include "scrollbar.h"
28 #include "eventdefs.h"
29 #include "ltk.h"
30
31 #define MAX_SCROLLBAR_WIDTH 10000 /* completely arbitrary */
32
33 static void ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
34 static int ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event);
35 static int ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event);
36 static void ltk_scrollbar_destroy(ltk_widget *self, int shallow);
37 static void ltk_scrollbar_recalc_ideal_size(ltk_widget *self);
38
39 static struct ltk_widget_vtable vtable = {
40 .draw = <k_scrollbar_draw,
41 .destroy = <k_scrollbar_destroy,
42 .change_state = NULL,
43 .hide = NULL,
44 .resize = NULL,
45 .mouse_press = <k_scrollbar_mouse_press,
46 .mouse_release = NULL,
47 .motion_notify = <k_scrollbar_motion_notify,
48 .get_child_at_pos = NULL,
49 .mouse_leave = NULL,
50 .mouse_enter = NULL,
51 .child_size_change = NULL,
52 .remove_child = NULL,
53 .recalc_ideal_size = <k_scrollbar_recalc_ideal_size,
54 .type = LTK_WIDGET_SCROLLBAR,
55 /* FIXME: need different activatable state so arrow keys don't move onto scrollbar */
56 .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
57 .invalid_signal = LTK_SCROLLBAR_SIGNAL_INVALID,
58 };
59
60 static struct {
61 ltk_color *bg_normal;
62 ltk_color *bg_disabled;
63 ltk_color *fg_normal;
64 ltk_color *fg_active;
65 ltk_color *fg_pressed;
66 ltk_color *fg_disabled;
67 ltk_size size; /* width or height, depending on orientation */
68 } theme;
69
70 static ltk_theme_parseinfo parseinfo[] = {
71 {"size", THEME_SIZE, {.size = &theme.size}, {.size = {.val = 350, .unit = LTK_UNIT_MM}}, 0, MAX_SCROLLBAR_WIDTH, 0},
72 {"bg", THEME_COLOR, {.color = &theme.bg_normal}, {.color = "#000000"}, 0, 0, 0},
73 {"bg-disabled", THEME_COLOR, {.color = &theme.bg_disabled}, {.color = "#555555"}, 0, 0, 0},
74 {"fg", THEME_COLOR, {.color = &theme.fg_normal}, {.color = "#113355"}, 0, 0, 0},
75 {"fg-active", THEME_COLOR, {.color = &theme.fg_active}, {.color = "#738194"}, 0, 0, 0},
76 {"fg-pressed", THEME_COLOR, {.color = &theme.fg_pressed}, {.color = "#113355"}, 0, 0, 0},
77 {"fg-disabled", THEME_COLOR, {.color = &theme.fg_disabled}, {.color = "#292929"}, 0, 0, 0},
78 };
79
80 void
81 ltk_scrollbar_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
82 *p = parseinfo;
83 *len = LENGTH(parseinfo);
84 }
85
86 void
87 ltk_scrollbar_set_virtual_size(ltk_widget_id scrollbarid, int virtual_size) {
88 ltk_widget *self = ltk_get_widget_from_id(scrollbarid);
89 ltk_scrollbar *scrollbar = LTK_CAST_SCROLLBAR(self);
90 /* FIXME: some sort of error? */
91 if (virtual_size <= 0)
92 return;
93 scrollbar->cur_pos = ((double)scrollbar->cur_pos / scrollbar->virtual_size) * virtual_size;
94 scrollbar->virtual_size = virtual_size;
95 }
96
97 /* get rekt */
98 static ltk_rect
99 handle_get_rect(ltk_scrollbar *sc) {
100 ltk_rect r;
101 ltk_rect sc_rect = LTK_CAST_WIDGET(sc)->lrect;
102 if (sc->orient == LTK_HORIZONTAL) {
103 r.y = 0;
104 r.h = sc_rect.h;
105 r.x = (int)((sc->cur_pos / (double)sc->virtual_size) * sc_rect.w);
106 if (sc->virtual_size > sc_rect.w)
107 r.w = (int)((sc_rect.w / (double)sc->virtual_size) * sc_rect.w);
108 else
109 r.w = sc_rect.w;
110 } else {
111 r.x = 0;
112 r.w = sc_rect.w;
113 r.y = (int)((sc->cur_pos / (double)sc->virtual_size) * sc_rect.h);
114 if (sc->virtual_size > sc_rect.h)
115 r.h = (int)((sc_rect.h / (double)sc->virtual_size) * sc_rect.h);
116 else
117 r.h = sc_rect.h;
118 }
119 return r;
120 }
121
122 static void
123 ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
124 ltk_scrollbar *scrollbar = LTK_CAST_SCROLLBAR(self);
125 ltk_color *bg = NULL, *fg = NULL;
126 ltk_rect lrect = self->lrect;
127 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
128 if (clip_final.w <= 0 || clip_final.h <= 0)
129 return;
130 /* FIXME: proper theme for hover */
131 if (self->state & LTK_DISABLED) {
132 bg = theme.bg_disabled;
133 fg = theme.fg_disabled;
134 } else if (self->state & LTK_PRESSED) {
135 bg = theme.bg_normal;
136 fg = theme.fg_pressed;
137 } else if (self->state & LTK_HOVERACTIVE) {
138 bg = theme.bg_normal;
139 fg = theme.fg_active;
140 } else {
141 bg = theme.bg_normal;
142 fg = theme.fg_normal;
143 }
144 ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
145 ltk_surface_fill_rect(draw_surf, bg, draw_clip);
146 draw_clip = handle_get_rect(scrollbar);
147 draw_clip.x += x;
148 draw_clip.y += y;
149 ltk_surface_fill_rect(draw_surf, fg, draw_clip);
150 }
151
152 static int
153 ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) {
154 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self);
155 int max_pos;
156 if (event->button != LTK_BUTTONL || event->type != LTK_BUTTONPRESS_EVENT)
157 return 0;
158 int ex = event->x, ey = event->y;
159 ltk_rect handle_rect = handle_get_rect(sc);
160 if (sc->orient == LTK_HORIZONTAL) {
161 if (ex < handle_rect.x || ex > handle_rect.x + handle_rect.w) {
162 sc->cur_pos = (sc->virtual_size / (double)self->lrect.w) * (ex - handle_rect.w / 2 - self->lrect.x);
163 }
164 max_pos = sc->virtual_size > self->lrect.w ? sc->virtual_size - self->lrect.w : 0;
165 } else {
166 if (ey < handle_rect.y || ey > handle_rect.y + handle_rect.h) {
167 sc->cur_pos = (sc->virtual_size / (double)self->lrect.h) * (ey - handle_rect.h / 2 - self->lrect.y);
168 }
169 max_pos = sc->virtual_size > self->lrect.h ? sc->virtual_size - self->lrect.h : 0;
170 }
171 if (sc->cur_pos < 0)
172 sc->cur_pos = 0;
173 else if (sc->cur_pos > max_pos)
174 sc->cur_pos = max_pos;
175 ltk_widget_emit_signal(self->id, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST);
176 sc->last_mouse_x = event->x;
177 sc->last_mouse_y = event->y;
178 return 1;
179 }
180
181 /* FIXME: also queue redraw */
182 /* FIXME: improve interface (scaled is weird) */
183 void
184 ltk_scrollbar_scroll(ltk_widget_id scrollbarid, int delta, int scaled) {
185 ltk_widget *self = ltk_get_widget_from_id(scrollbarid);
186 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self);
187 int max_pos;
188 double scale;
189 if (sc->orient == LTK_HORIZONTAL) {
190 max_pos = sc->virtual_size > self->lrect.w ? sc->virtual_size - self->lrect.w : 0;
191 scale = sc->virtual_size / (double)self->lrect.w;
192 } else {
193 max_pos = sc->virtual_size > self->lrect.h ? sc->virtual_size - self->lrect.h : 0;
194 scale = sc->virtual_size / (double)self->lrect.h;
195 }
196 if (scaled)
197 sc->cur_pos += scale * delta;
198 else
199 sc->cur_pos += delta;
200 if (sc->cur_pos < 0)
201 sc->cur_pos = 0;
202 else if (sc->cur_pos > max_pos)
203 sc->cur_pos = max_pos;
204 ltk_widget_emit_signal(self->id, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST);
205 }
206
207 static int
208 ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) {
209 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self);
210 int delta;
211 if (!(self->state & LTK_PRESSED)) {
212 return 1;
213 }
214 if (sc->orient == LTK_HORIZONTAL)
215 delta = event->x - sc->last_mouse_x;
216 else
217 delta = event->y - sc->last_mouse_y;
218 ltk_scrollbar_scroll(self->id, delta, 1);
219 sc->last_mouse_x = event->x;
220 sc->last_mouse_y = event->y;
221 return 1;
222 }
223
224 static void
225 ltk_scrollbar_recalc_ideal_size(ltk_widget *self) {
226 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self);
227 int size = ltk_size_to_pixel(theme.size, self->last_dpi);
228 if (sc->orient == LTK_HORIZONTAL)
229 self->ideal_h = size;
230 else
231 self->ideal_w = size;
232 }
233
234 ltk_widget_id
235 ltk_scrollbar_create(ltk_widget_id windowid, ltk_orientation orient) {
236 ltk_scrollbar *sc = ltk_malloc(sizeof(ltk_scrollbar));
237 ltk_widget_id id = ltk_initialize_widget(LTK_CAST_WIDGET(sc), windowid, &vtable, 1, 1); /* FIXME: proper size */
238 sc->last_mouse_x = sc->last_mouse_y = 0;
239 /* This cannot be 0 because that leads to divide-by-zero */
240 sc->virtual_size = 1;
241 sc->cur_pos = 0;
242 sc->orient = orient;
243 ltk_scrollbar_recalc_ideal_size(LTK_CAST_WIDGET(sc));
244
245 return id;
246 }
247
248 static void
249 ltk_scrollbar_destroy(ltk_widget *self, int shallow) {
250 (void)shallow;
251 ltk_free(self);
252 }