URI: 
       tscrollbar.c - ltk - Socket-based GUI 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)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tscrollbar.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 }