ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

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 = &ltk_scrollbar_draw,
     40 	.destroy = &ltk_scrollbar_destroy,
     41 	.change_state = NULL,
     42 	.hide = NULL,
     43 	.resize = NULL,
     44 	.mouse_press = &ltk_scrollbar_mouse_press,
     45 	.mouse_release = NULL,
     46 	.motion_notify = &ltk_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 = &ltk_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 }