scrollbar.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 }