scrollbar.c (8427B)
1 /* 2 * Copyright (c) 2021-2024 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 30 #define MAX_SCROLLBAR_WIDTH 10000 /* completely arbitrary */ 31 32 static void ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip); 33 static int ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event); 34 static int ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event); 35 static void ltk_scrollbar_destroy(ltk_widget *self, int shallow); 36 static void ltk_scrollbar_recalc_ideal_size(ltk_widget *self); 37 38 static struct ltk_widget_vtable vtable = { 39 .draw = <k_scrollbar_draw, 40 .destroy = <k_scrollbar_destroy, 41 .change_state = NULL, 42 .hide = NULL, 43 .resize = NULL, 44 .mouse_press = <k_scrollbar_mouse_press, 45 .mouse_release = NULL, 46 .motion_notify = <k_scrollbar_motion_notify, 47 .get_child_at_pos = NULL, 48 .mouse_leave = NULL, 49 .mouse_enter = NULL, 50 .child_size_change = NULL, 51 .remove_child = NULL, 52 .recalc_ideal_size = <k_scrollbar_recalc_ideal_size, 53 .type = LTK_WIDGET_SCROLLBAR, 54 /* FIXME: need different activatable state so arrow keys don't move onto scrollbar */ 55 .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS, 56 .invalid_signal = LTK_SCROLLBAR_SIGNAL_INVALID, 57 }; 58 59 static struct { 60 ltk_color *bg_normal; 61 ltk_color *bg_disabled; 62 ltk_color *fg_normal; 63 ltk_color *fg_active; 64 ltk_color *fg_pressed; 65 ltk_color *fg_disabled; 66 ltk_size size; /* width or height, depending on orientation */ 67 } theme; 68 69 static ltk_theme_parseinfo parseinfo[] = { 70 {"size", THEME_SIZE, {.size = &theme.size}, {.size = {.val = 350, .unit = LTK_UNIT_MM}}, 0, MAX_SCROLLBAR_WIDTH, 0}, 71 {"bg", THEME_COLOR, {.color = &theme.bg_normal}, {.color = "#000000"}, 0, 0, 0}, 72 {"bg-disabled", THEME_COLOR, {.color = &theme.bg_disabled}, {.color = "#555555"}, 0, 0, 0}, 73 {"fg", THEME_COLOR, {.color = &theme.fg_normal}, {.color = "#113355"}, 0, 0, 0}, 74 {"fg-active", THEME_COLOR, {.color = &theme.fg_active}, {.color = "#738194"}, 0, 0, 0}, 75 {"fg-pressed", THEME_COLOR, {.color = &theme.fg_pressed}, {.color = "#113355"}, 0, 0, 0}, 76 {"fg-disabled", THEME_COLOR, {.color = &theme.fg_disabled}, {.color = "#292929"}, 0, 0, 0}, 77 }; 78 79 void 80 ltk_scrollbar_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) { 81 *p = parseinfo; 82 *len = LENGTH(parseinfo); 83 } 84 85 void 86 ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size) { 87 /* FIXME: some sort of error? */ 88 if (virtual_size <= 0) 89 return; 90 scrollbar->cur_pos = ((double)scrollbar->cur_pos / scrollbar->virtual_size) * virtual_size; 91 scrollbar->virtual_size = virtual_size; 92 } 93 94 /* get rekt */ 95 static ltk_rect 96 handle_get_rect(ltk_scrollbar *sc) { 97 ltk_rect r; 98 ltk_rect sc_rect = sc->widget.lrect; 99 if (sc->orient == LTK_HORIZONTAL) { 100 r.y = 0; 101 r.h = sc_rect.h; 102 r.x = (int)((sc->cur_pos / (double)sc->virtual_size) * sc_rect.w); 103 if (sc->virtual_size > sc_rect.w) 104 r.w = (int)((sc_rect.w / (double)sc->virtual_size) * sc_rect.w); 105 else 106 r.w = sc_rect.w; 107 } else { 108 r.x = 0; 109 r.w = sc_rect.w; 110 r.y = (int)((sc->cur_pos / (double)sc->virtual_size) * sc_rect.h); 111 if (sc->virtual_size > sc_rect.h) 112 r.h = (int)((sc_rect.h / (double)sc->virtual_size) * sc_rect.h); 113 else 114 r.h = sc_rect.h; 115 } 116 return r; 117 } 118 119 static void 120 ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { 121 ltk_scrollbar *scrollbar = LTK_CAST_SCROLLBAR(self); 122 ltk_color *bg = NULL, *fg = NULL; 123 ltk_rect lrect = self->lrect; 124 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); 125 if (clip_final.w <= 0 || clip_final.h <= 0) 126 return; 127 /* FIXME: proper theme for hover */ 128 if (self->state & LTK_DISABLED) { 129 bg = theme.bg_disabled; 130 fg = theme.fg_disabled; 131 } else if (self->state & LTK_PRESSED) { 132 bg = theme.bg_normal; 133 fg = theme.fg_pressed; 134 } else if (self->state & LTK_HOVERACTIVE) { 135 bg = theme.bg_normal; 136 fg = theme.fg_active; 137 } else { 138 bg = theme.bg_normal; 139 fg = theme.fg_normal; 140 } 141 ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h}; 142 ltk_surface_fill_rect(draw_surf, bg, draw_clip); 143 draw_clip = handle_get_rect(scrollbar); 144 draw_clip.x += x; 145 draw_clip.y += y; 146 ltk_surface_fill_rect(draw_surf, fg, draw_clip); 147 } 148 149 static int 150 ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) { 151 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self); 152 int max_pos; 153 if (event->button != LTK_BUTTONL || event->type != LTK_BUTTONPRESS_EVENT) 154 return 0; 155 int ex = event->x, ey = event->y; 156 ltk_rect handle_rect = handle_get_rect(sc); 157 if (sc->orient == LTK_HORIZONTAL) { 158 if (ex < handle_rect.x || ex > handle_rect.x + handle_rect.w) { 159 sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.w) * (ex - handle_rect.w / 2 - sc->widget.lrect.x); 160 } 161 max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0; 162 } else { 163 if (ey < handle_rect.y || ey > handle_rect.y + handle_rect.h) { 164 sc->cur_pos = (sc->virtual_size / (double)sc->widget.lrect.h) * (ey - handle_rect.h / 2 - sc->widget.lrect.y); 165 } 166 max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0; 167 } 168 if (sc->cur_pos < 0) 169 sc->cur_pos = 0; 170 else if (sc->cur_pos > max_pos) 171 sc->cur_pos = max_pos; 172 ltk_widget_emit_signal(self, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST); 173 sc->last_mouse_x = event->x; 174 sc->last_mouse_y = event->y; 175 return 1; 176 } 177 178 /* FIXME: also queue redraw */ 179 /* FIXME: improve interface (scaled is weird) */ 180 void 181 ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) { 182 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self); 183 int max_pos; 184 double scale; 185 if (sc->orient == LTK_HORIZONTAL) { 186 max_pos = sc->virtual_size > sc->widget.lrect.w ? sc->virtual_size - sc->widget.lrect.w : 0; 187 scale = sc->virtual_size / (double)sc->widget.lrect.w; 188 } else { 189 max_pos = sc->virtual_size > sc->widget.lrect.h ? sc->virtual_size - sc->widget.lrect.h : 0; 190 scale = sc->virtual_size / (double)sc->widget.lrect.h; 191 } 192 if (scaled) 193 sc->cur_pos += scale * delta; 194 else 195 sc->cur_pos += delta; 196 if (sc->cur_pos < 0) 197 sc->cur_pos = 0; 198 else if (sc->cur_pos > max_pos) 199 sc->cur_pos = max_pos; 200 ltk_widget_emit_signal(self, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST); 201 } 202 203 static int 204 ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) { 205 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self); 206 int delta; 207 if (!(self->state & LTK_PRESSED)) { 208 return 1; 209 } 210 if (sc->orient == LTK_HORIZONTAL) 211 delta = event->x - sc->last_mouse_x; 212 else 213 delta = event->y - sc->last_mouse_y; 214 ltk_scrollbar_scroll(self, delta, 1); 215 sc->last_mouse_x = event->x; 216 sc->last_mouse_y = event->y; 217 return 1; 218 } 219 220 static void 221 ltk_scrollbar_recalc_ideal_size(ltk_widget *self) { 222 ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self); 223 int size = ltk_size_to_pixel(theme.size, self->last_dpi); 224 if (sc->orient == LTK_HORIZONTAL) 225 sc->widget.ideal_h = size; 226 else 227 sc->widget.ideal_w = size; 228 } 229 230 ltk_scrollbar * 231 ltk_scrollbar_create(ltk_window *window, ltk_orientation orient) { 232 ltk_scrollbar *sc = ltk_malloc(sizeof(ltk_scrollbar)); 233 ltk_fill_widget_defaults(LTK_CAST_WIDGET(sc), window, &vtable, 1, 1); /* FIXME: proper size */ 234 sc->last_mouse_x = sc->last_mouse_y = 0; 235 /* This cannot be 0 because that leads to divide-by-zero */ 236 sc->virtual_size = 1; 237 sc->cur_pos = 0; 238 sc->orient = orient; 239 ltk_scrollbar_recalc_ideal_size(LTK_CAST_WIDGET(sc)); 240 241 return sc; 242 } 243 244 static void 245 ltk_scrollbar_destroy(ltk_widget *self, int shallow) { 246 (void)shallow; 247 ltk_free(self); 248 }