ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

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