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