tscrollbar.c - ltk - Socket-based GUI 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)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tscrollbar.c (8976B)
---
1 /* FIXME: make scrollbar a "real" widget that is also in widget hash */
2 /*
3 * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <stdint.h>
21 #include <string.h>
22 #include <stdarg.h>
23
24 #include "event.h"
25 #include "memory.h"
26 #include "color.h"
27 #include "rect.h"
28 #include "widget.h"
29 #include "ltk.h"
30 #include "util.h"
31 #include "scrollbar.h"
32 #include "theme.h"
33
34 #define MAX_SCROLLBAR_WIDTH 100 /* completely arbitrary */
35
36 static void ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
37 static int ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event);
38 static int ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event);
39 static void ltk_scrollbar_destroy(ltk_widget *self, int shallow);
40
41 static struct ltk_widget_vtable vtable = {
42 .draw = <k_scrollbar_draw,
43 .destroy = <k_scrollbar_destroy,
44 .change_state = NULL,
45 .hide = NULL,
46 .resize = NULL,
47 .mouse_press = <k_scrollbar_mouse_press,
48 .mouse_release = NULL,
49 .motion_notify = <k_scrollbar_motion_notify,
50 .get_child_at_pos = NULL,
51 .mouse_leave = NULL,
52 .mouse_enter = NULL,
53 .child_size_change = NULL,
54 .remove_child = NULL,
55 .type = LTK_WIDGET_UNKNOWN, /* FIXME */
56 /* FIXME: need different activatable state so arrow keys don't move onto scrollbar */
57 .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
58 };
59
60 static struct {
61 int size; /* width or height, depending on orientation */
62 ltk_color bg_normal;
63 ltk_color bg_disabled;
64 ltk_color fg_normal;
65 ltk_color fg_active;
66 ltk_color fg_pressed;
67 ltk_color fg_disabled;
68 } theme;
69
70 static ltk_theme_parseinfo parseinfo[] = {
71 {"size", THEME_INT, {.i = &theme.size}, {.i = 15}, 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 static int parseinfo_sorted = 0;
80
81 int
82 ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value) {
83 return ltk_theme_handle_value(window, "scrollbar", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
84 }
85
86 int
87 ltk_scrollbar_fill_theme_defaults(ltk_window *window) {
88 return ltk_theme_fill_defaults(window, "scrollbar", parseinfo, LENGTH(parseinfo));
89 }
90
91 void
92 ltk_scrollbar_uninitialize_theme(ltk_window *window) {
93 ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
94 }
95
96 void
97 ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size) {
98 /* FIXME: some sort of error? */
99 if (virtual_size <= 0)
100 return;
101 scrollbar->cur_pos = ((double)scrollbar->cur_pos / scrollbar->virtual_size) * virtual_size;
102 scrollbar->virtual_size = virtual_size;
103 }
104
105 /* get rekt */
106 static ltk_rect
107 handle_get_rect(ltk_scrollbar *sc) {
108 ltk_rect r;
109 ltk_rect sc_rect = sc->widget.lrect;
110 if (sc->orient == LTK_HORIZONTAL) {
111 r.y = 0;
112 r.h = sc_rect.h;
113 r.x = (int)((sc->cur_pos / (double)sc->virtual_size) * sc_rect.w);
114 if (sc->virtual_size > sc_rect.w)
115 r.w = (int)((sc_rect.w / (double)sc->virtual_size) * sc_rect.w);
116 else
117 r.w = sc_rect.w;
118 } else {
119 r.x = 0;
120 r.w = sc_rect.w;
121 r.y = (int)((sc->cur_pos / (double)sc->virtual_size) * sc_rect.h);
122 if (sc->virtual_size > sc_rect.h)
123 r.h = (int)((sc_rect.h / (double)sc->virtual_size) * sc_rect.h);
124 else
125 r.h = sc_rect.h;
126 }
127 return r;
128 }
129
130 /* FIXME: implement clipping directly without extra surface */
131 static void
132 ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
133 /* FIXME: dirty attribute */
134 ltk_scrollbar *scrollbar = (ltk_scrollbar *)self;
135 ltk_color *bg = NULL, *fg = NULL;
136 ltk_rect lrect = self->lrect;
137 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
138 if (clip_final.w <= 0 || clip_final.h <= 0)
139 return;
140 /* FIXME: proper theme for hover */
141 if (self->state & LTK_DISABLED) {
142 bg = &theme.bg_disabled;
143 fg = &theme.fg_disabled;
144 } else if (self->state & LTK_PRESSED) {
145 bg = &theme.bg_normal;
146 fg = &theme.fg_pressed;
147 } else if (self->state & LTK_HOVERACTIVE) {
148 bg = &theme.bg_normal;
149 fg = &theme.fg_active;
150 } else {
151 bg = &theme.bg_normal;
152 fg = &theme.fg_normal;
153 }
154 ltk_surface *s;
155 ltk_surface_cache_request_surface_size(scrollbar->key, lrect.w, lrect.h);
156 ltk_surface_cache_get_surface(scrollbar->key, &s);
157 ltk_surface_fill_rect(s, bg, (ltk_rect){0, 0, lrect.w, lrect.h});
158 /* FIXME: maybe too much calculation in draw function - move to
159 resizing function? */
160 ltk_surface_fill_rect(s, fg, handle_get_rect(scrollbar));
161 ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y);
162 }
163
164 static int
165 ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) {
166 ltk_scrollbar *sc = (ltk_scrollbar *)self;
167 int max_pos;
168 if (event->button != LTK_BUTTONL || event->type != LTK_BUTTONPRESS_EVENT)
169 return 0;
170 int ex = event->x, ey = event->y;
171 ltk_rect handle_rect = handle_get_rect(sc);
172 if (sc->orient == LTK_HORIZONTAL) {
173 if (ex < handle_rect.x || ex > handle_rect.x + handle_rect.w) {
174 sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.w) * (ex - handle_rect.w / 2 - sc->widget.lrect.x);
175 }
176 max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0;
177 } else {
178 if (ey < handle_rect.y || ey > handle_rect.y + handle_rect.h) {
179 sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.h) * (ey - handle_rect.h / 2 - sc->widget.lrect.y);
180 }
181 max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0;
182 }
183 if (sc->cur_pos < 0)
184 sc->cur_pos = 0;
185 else if (sc->cur_pos > max_pos)
186 sc->cur_pos = max_pos;
187 sc->callback(sc->callback_data);
188 sc->last_mouse_x = event->x;
189 sc->last_mouse_y = event->y;
190 return 1;
191 }
192
193 /* FIXME: also queue redraw */
194 /* FIXME: improve interface (scaled is weird) */
195 void
196 ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) {
197 ltk_scrollbar *sc = (ltk_scrollbar *)self;
198 int max_pos;
199 double scale;
200 if (sc->orient == LTK_HORIZONTAL) {
201 max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0;
202 scale = sc->virtual_size / (double)sc->widget.lrect.w;
203 } else {
204 max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0;
205 scale = sc->virtual_size / (double)sc->widget.lrect.h;
206 }
207 if (scaled)
208 sc->cur_pos += scale * delta;
209 else
210 sc->cur_pos += delta;
211 if (sc->cur_pos < 0)
212 sc->cur_pos = 0;
213 else if (sc->cur_pos > max_pos)
214 sc->cur_pos = max_pos;
215 sc->callback(sc->callback_data);
216 }
217
218 static int
219 ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) {
220 ltk_scrollbar *sc = (ltk_scrollbar *)self;
221 int delta;
222 if (!(self->state & LTK_PRESSED)) {
223 return 1;
224 }
225 if (sc->orient == LTK_HORIZONTAL)
226 delta = event->x - sc->last_mouse_x;
227 else
228 delta = event->y - sc->last_mouse_y;
229 ltk_scrollbar_scroll(self, delta, 1);
230 sc->last_mouse_x = event->x;
231 sc->last_mouse_y = event->y;
232 return 1;
233 }
234
235 ltk_scrollbar *
236 ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback)(ltk_widget *), void *data) {
237 ltk_scrollbar *sc = ltk_malloc(sizeof(ltk_scrollbar));
238 ltk_fill_widget_defaults((ltk_widget *)sc, NULL, window, &vtable, 1, 1); /* FIXME: proper size */
239 sc->last_mouse_x = sc->last_mouse_y = 0;
240 /* This cannot be 0 because that leads to divide-by-zero */
241 sc->virtual_size = 1;
242 sc->cur_pos = 0;
243 sc->orient = orient;
244 if (orient == LTK_HORIZONTAL)
245 sc->widget.ideal_h = theme.size;
246 else
247 sc->widget.ideal_w = theme.size;
248 sc->callback = callback;
249 sc->callback_data = data;
250 sc->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, sc->widget.ideal_w, sc->widget.ideal_h);
251
252 return sc;
253 }
254
255 static void
256 ltk_scrollbar_destroy(ltk_widget *self, int shallow) {
257 (void)shallow;
258 ltk_scrollbar *sc = (ltk_scrollbar *)self;
259 ltk_surface_cache_release_key(sc->key);
260 ltk_free(self);
261 }