URI: 
       tAdd very broken scrollbar support to box - 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
       ---
   DIR commit b0338320849b8d570511491a73dee2d75aff5fd9
   DIR parent d0099e84bdff7d265d14fc3ae226b0881ba98ed6
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun, 17 Jan 2021 21:25:32 +0100
       
       Add very broken scrollbar support to box
       
       Diffstat:
         M .ltk/theme.ini                      |       2 +-
         M box.c                               |      88 +++++++++++++++++++++----------
         M button.c                            |       6 +++---
         M grid.c                              |       6 +++---
         M label.c                             |       6 +++---
         M ltk.h                               |       6 +++++-
         M ltkd.c                              |      40 +++++++++++++++++++++----------
         M scrollbar.c                         |      71 ++++++++++++++++++++-----------
         M scrollbar.h                         |       1 +
         M text.h                              |       2 +-
         M text_line.c                         |      11 ++++++-----
         M text_pango.c                        |       2 +-
         M text_stb.c                          |       2 +-
       
       13 files changed, 156 insertions(+), 87 deletions(-)
       ---
   DIR diff --git a/.ltk/theme.ini b/.ltk/theme.ini
       t@@ -23,7 +23,7 @@ text_color = #FFFFFF
        pad = 5
        
        [scrollbar]
       -size = 5
       +size = 15
        bg = #000000
        bg_disabled = #555555
        fg = #113355
   DIR diff --git a/box.c b/box.c
       t@@ -34,7 +34,7 @@
        #include "scrollbar.h"
        #include "box.h"
        
       -static void ltk_box_draw(ltk_box *box);
       +static void ltk_box_draw(ltk_box *box, ltk_rect clip);
        static ltk_box *ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient);
        static void ltk_box_destroy(ltk_box *box, int shallow);
        static void ltk_recalculate_box(ltk_box *box);
       t@@ -72,12 +72,16 @@ static int ltk_box_cmd_create(
            char **errstr);
        
        static void
       -ltk_box_draw(ltk_box *box) {
       +ltk_box_draw(ltk_box *box, ltk_rect clip) {
                ltk_widget *ptr;
       +        ltk_rect real_clip = ltk_rect_intersect(box->widget.rect, clip);
                for (size_t i = 0; i < box->num_widgets; i++) {
                        ptr = box->widgets[i];
       -                ptr->draw(ptr);
       +                /* FIXME: Maybe continue immediately if widget is
       +                   obviously outside of clipping rect */
       +                ptr->draw(ptr, real_clip);
                }
       +        box->sc->widget.draw(box->sc, real_clip);
        }
        
        static ltk_box *
       t@@ -95,7 +99,7 @@ ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
                box->widget.child_size_change = &ltk_box_child_size_change;
                box->widget.remove_child = &ltk_box_remove;
        
       -        box->sc = NULL;
       +        box->sc = ltk_scrollbar_create(window, orient);
                box->widgets = NULL;
                box->num_alloc = 0;
                box->num_widgets = 0;
       t@@ -116,38 +120,42 @@ ltk_box_destroy(ltk_box *box, int shallow) {
                free(box->widgets);
                ltk_remove_widget(box->widget.window, box->widget.id);
                free(box->widget.id);
       +        box->sc->widget.destroy(box->sc, 0);
                free(box);
        }
        
       -/* FIXME: Need some sort of "visible rect" so widgets don't draw outside */
        /* FIXME: Make this function name more consistent */
       +/* FIXME: The widget positions are set with the old scrollbar->cur_pos, before the
       +   virtual_size is set - this can cause problems when a widget changes its size
       +   (in the scrolled direction) when resized. */
        static void
        ltk_recalculate_box(ltk_box *box) {
                ltk_widget *ptr;
       +        ltk_rect *sc_rect = &box->sc->widget.rect;
                int cur_pos = 0;
                for (size_t i = 0; i < box->num_widgets; i++) {
                        ptr = box->widgets[i];
                        if (box->orient == LTK_HORIZONTAL) {
       -                        ptr->rect.x = cur_pos;
       +                        ptr->rect.x = cur_pos - box->sc->cur_pos;
                                if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM)
       -                                ptr->rect.h = box->widget.rect.h;
       +                                ptr->rect.h = box->widget.rect.h - sc_rect->h;
                                if (ptr->sticky & LTK_STICKY_TOP)
                                        ptr->rect.y = box->widget.rect.y;
                                else if (ptr->sticky & LTK_STICKY_BOTTOM)
       -                                ptr->rect.y = box->widget.rect.y + box->widget.rect.h - ptr->rect.h;
       +                                ptr->rect.y = box->widget.rect.y + box->widget.rect.h - ptr->rect.h - sc_rect->h;
                                else
                                        ptr->rect.y = box->widget.rect.y + (box->widget.rect.h - ptr->rect.h) / 2;
                                if (ptr->resize)
                                        ptr->resize(ptr);
                                cur_pos += ptr->rect.w;
                        } else {
       -                        ptr->rect.y = cur_pos;
       +                        ptr->rect.y = cur_pos - box->sc->cur_pos;
                                if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT)
       -                                ptr->rect.w = box->widget.rect.w;
       +                                ptr->rect.w = box->widget.rect.w - sc_rect->w;
                                if (ptr->sticky & LTK_STICKY_LEFT)
                                        ptr->rect.x = box->widget.rect.x;
                                else if (ptr->sticky & LTK_STICKY_RIGHT)
       -                                ptr->rect.x = box->widget.rect.x + box->widget.rect.w - ptr->rect.w;
       +                                ptr->rect.x = box->widget.rect.x + box->widget.rect.w - ptr->rect.w - sc_rect->w;
                                else
                                        ptr->rect.x = box->widget.rect.x + (box->widget.rect.w - ptr->rect.w) / 2;
                                if (ptr->resize)
       t@@ -155,6 +163,14 @@ ltk_recalculate_box(ltk_box *box) {
                                cur_pos += ptr->rect.h;
                        }
                }
       +        ltk_scrollbar_set_virtual_size(box->sc, cur_pos);
       +        if (box->orient == LTK_HORIZONTAL) {
       +                sc_rect->y = box->widget.rect.y + box->widget.rect.h - sc_rect->h;
       +                sc_rect->w = box->widget.rect.w;
       +        } else {
       +                sc_rect->x = box->widget.rect.x + box->widget.rect.w - sc_rect->w;
       +                sc_rect->h = box->widget.rect.h;
       +        }
        }
        
        /* FIXME: This entire resizing thing is a bit weird. For instance, if a label
       t@@ -179,11 +195,13 @@ ltk_box_child_size_change(ltk_box *box, ltk_widget *widget) {
                   settings, but there'd probably be some catch as well. */
                widget->rect.w = widget->ideal_w;
                widget->rect.h = widget->ideal_h;
       -        if (box->orient == LTK_HORIZONTAL && widget->ideal_h > box->widget.ideal_h) {
       -                box->widget.ideal_h = widget->ideal_h;
       +        int sc_w = box->sc->widget.rect.w;
       +        int sc_h = box->sc->widget.rect.h;
       +        if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h > box->widget.ideal_h) {
       +                box->widget.ideal_h = widget->ideal_h + sc_h;
                        size_changed = 1;
       -        } else if (box->orient == LTK_VERTICAL && widget->ideal_w > box->widget.ideal_h) {
       -                box->widget.ideal_w = widget->ideal_w;
       +        } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w > box->widget.ideal_h) {
       +                box->widget.ideal_w = widget->ideal_w + sc_w;
                        size_changed = 1;
                }
        
       t@@ -208,11 +226,19 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short
                        box->widgets = new;
                }
        
       +        int sc_w = box->sc->widget.rect.w;
       +        int sc_h = box->sc->widget.rect.h;
       +
                box->widgets[box->num_widgets++] = widget;
       -        if (box->orient == LTK_HORIZONTAL)
       +        if (box->orient == LTK_HORIZONTAL) {
                        box->widget.ideal_w += widget->ideal_w;
       -        else
       +                if (widget->ideal_h + sc_h > box->widget.ideal_h)
       +                        box->widget.ideal_h = widget->ideal_h + sc_h;
       +        } else {
                        box->widget.ideal_h += widget->ideal_h;
       +                if (widget->ideal_w + sc_w > box->widget.ideal_w)
       +                        box->widget.ideal_w = widget->ideal_w + sc_w;
       +        }
                widget->parent = box;
                widget->sticky = sticky;
                ltk_box_child_size_change(box, widget);
       t@@ -223,6 +249,8 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short
        
        static int
        ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_box *box, char **errstr) {
       +        int sc_w = box->sc->widget.rect.w;
       +        int sc_h = box->sc->widget.rect.h;
                if (widget->parent != box) {
                        *errstr = "Widget isn't contained in given box.\n";
                        return 1;
       t@@ -237,19 +265,19 @@ ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_box *box, char **errs
                                ltk_window_invalidate_rect(window, box->widget.rect);
                                /* search for new ideal width/height */
                                /* FIXME: make this all a bit nicer and break the lines better */
       -                        if (box->orient == LTK_HORIZONTAL && widget->ideal_h == box->widget.ideal_h) {
       +                        if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h == box->widget.ideal_h) {
                                        box->widget.ideal_h = 0;
                                        for (size_t j = 0; j < box->num_widgets; j++) {
       -                                        if (box->widgets[j]->ideal_h > box->widget.ideal_h)
       -                                                box->widget.ideal_h = box->widgets[j]->ideal_h;
       +                                        if (box->widgets[j]->ideal_h + sc_h > box->widget.ideal_h)
       +                                                box->widget.ideal_h = box->widgets[j]->ideal_h + sc_h;
                                        }
                                        if (box->widget.parent && box->widget.parent->resize)
                                                box->widget.parent->resize(box->widget.parent);
       -                        } else if (box->orient == LTK_VERTICAL && widget->ideal_w == box->widget.ideal_w) {
       +                        } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w == box->widget.ideal_w) {
                                        box->widget.ideal_w = 0;
                                        for (size_t j = 0; j < box->num_widgets; j++) {
       -                                        if (box->widgets[j]->ideal_w > box->widget.ideal_w)
       -                                                box->widget.ideal_w = box->widgets[j]->ideal_w;
       +                                        if (box->widgets[j]->ideal_w + sc_w > box->widget.ideal_w)
       +                                                box->widget.ideal_w = box->widgets[j]->ideal_w + sc_w;
                                        }
                                        if (box->widget.parent && box->widget.parent->resize)
                                                box->widget.parent->resize(box->widget.parent);
       t@@ -266,11 +294,16 @@ static void
        ltk_box_mouse_event(ltk_box *box, XEvent event, void (*handler)(ltk_widget *, XEvent)) {
                int pos, start, size;
                ltk_widget *widget;
       +        int old_sc_pos = box->sc->cur_pos;
        
       -        /*
       -        if (ltk_collide_rect(box->sc.widget.rect, event.xbutton.x, event.xbutton.y))
       +        if (ltk_collide_rect(box->sc->widget.rect, event.xbutton.x, event.xbutton.y)) {
                        handler(box->sc, event);
       -        */
       +                if (old_sc_pos != box->sc->cur_pos) {
       +                        ltk_recalculate_box(box);
       +                        ltk_window_invalidate_rect(box->widget.window, box->widget.rect);
       +                }
       +                return;
       +        }
        
                /* FIXME: When scrolling is implemented, check only the currently visible items */
                for (size_t i = 0; i < box->num_widgets; i++) {
       t@@ -297,9 +330,6 @@ ltk_box_mouse_release(ltk_box *box, XEvent event) {
           even if not actually hovered over! */
        static void
        ltk_box_motion_notify(ltk_box *box, XEvent event) {
       -        short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
       -        if (pressed)
       -                return;
                ltk_box_mouse_event(box, event, &ltk_widget_motion_notify_event);
        }
        
   DIR diff --git a/button.c b/button.c
       t@@ -36,7 +36,7 @@
        #include "text.h"
        #include "button.h"
        
       -static void ltk_button_draw(ltk_button *button);
       +static void ltk_button_draw(ltk_button *button, ltk_rect clip);
        static void ltk_button_mouse_release(ltk_button *button, XEvent event);
        static ltk_button *ltk_button_create(ltk_window *window,
            const char *id, const char *text);
       t@@ -125,7 +125,7 @@ ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) 
        }
        
        static void
       -ltk_button_draw(ltk_button *button) {
       +ltk_button_draw(ltk_button *button, ltk_rect clip) {
                ltk_window *window = button->widget.window;
                ltk_rect rect = button->widget.rect;
                int bw = theme.border_width;
       t@@ -166,7 +166,7 @@ ltk_button_draw(ltk_button *button) {
                ltk_text_line_get_size(button->tl, &text_w, &text_h);
                int text_x = rect.x + (rect.w - text_w) / 2;
                int text_y = rect.y + (rect.h - text_h) / 2;
       -        ltk_text_line_draw(button->tl, window->gc, text_x, text_y);
       +        ltk_text_line_draw(button->tl, window->gc, text_x, text_y, clip);
        }
        
        static void
   DIR diff --git a/grid.c b/grid.c
       t@@ -42,7 +42,7 @@
        
        static void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight);
        static void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight);
       -static void ltk_grid_draw(ltk_grid *grid);
       +static void ltk_grid_draw(ltk_grid *grid, ltk_rect clip);
        static ltk_grid *ltk_grid_create(ltk_window *window, const char *id,
            int rows, int columns);
        static void ltk_grid_destroy(ltk_grid *grid, int shallow);
       t@@ -96,13 +96,13 @@ ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight) {
        }
        
        static void
       -ltk_grid_draw(ltk_grid *grid) {
       +ltk_grid_draw(ltk_grid *grid, ltk_rect clip) {
                int i;
                for (i = 0; i < grid->rows * grid->columns; i++) {
                        if (!grid->widget_grid[i])
                                continue;
                        ltk_widget *ptr = grid->widget_grid[i];
       -                ptr->draw(ptr);
       +                ptr->draw(ptr, clip);
                }
        }
        
   DIR diff --git a/label.c b/label.c
       t@@ -36,7 +36,7 @@
        #include "text.h"
        #include "label.h"
        
       -static void ltk_label_draw(ltk_label *label);
       +static void ltk_label_draw(ltk_label *label, ltk_rect clip);
        static ltk_label *ltk_label_create(ltk_window *window,
            const char *id, const char *text);
        static void ltk_label_destroy(ltk_label *label, int shallow);
       t@@ -66,7 +66,7 @@ ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value) {
        }
        
        static void
       -ltk_label_draw(ltk_label *label) {
       +ltk_label_draw(ltk_label *label, ltk_rect clip) {
                ltk_window *window = label->widget.window;
                ltk_rect rect = label->widget.rect;
        
       t@@ -74,7 +74,7 @@ ltk_label_draw(ltk_label *label) {
                ltk_text_line_get_size(label->tl, &text_w, &text_h);
                int text_x = rect.x + (rect.w - text_w) / 2;
                int text_y = rect.y + (rect.h - text_h) / 2;
       -        ltk_text_line_draw(label->tl, window->gc, text_x, text_y);
       +        ltk_text_line_draw(label->tl, window->gc, text_x, text_y, clip);
        }
        
        static ltk_label *
   DIR diff --git a/ltk.h b/ltk.h
       t@@ -59,6 +59,8 @@ typedef enum {
        } ltk_widget_state;
        
        typedef enum {
       +        /* for e.g. scrollbar, which can't be directly accessed by users */
       +        LTK_UNKNOWN,
                LTK_GRID,
                LTK_BUTTON,
                LTK_DRAW,
       t@@ -88,7 +90,7 @@ typedef struct ltk_widget {
                void (*mouse_enter) (void *, XEvent);
        
                void (*resize) (void *);
       -        void (*draw) (void *);
       +        void (*draw) (void *, ltk_rect);
                void (*change_state) (void *);
                void (*destroy) (void *, int);
        
       t@@ -147,6 +149,8 @@ typedef struct ltk_window {
                struct ltk_event_queue *last_event;
        } ltk_window;
        
       +ltk_rect ltk_rect_intersect(ltk_rect r1, ltk_rect r2);
       +ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
        void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
        void ltk_warn(const char *format, ...);
        void ltk_fatal(const char *format, ...);
   DIR diff --git a/ltkd.c b/ltkd.c
       t@@ -1,3 +1,4 @@
       +/* FIXME: PROPERLY CLEANUP ALL THEMES */
        /* FIXME: error checking in tokenizer (is this necessary?) */
        /* FIXME: parsing doesn't work properly with bs? */
        /* FIXME: strip whitespace at end of lines in socket format */
       t@@ -101,7 +102,6 @@ static void ltk_redraw_window(ltk_window *window);
        static void ltk_window_other_event(ltk_window *window, XEvent event);
        static void ltk_handle_event(ltk_window *window, XEvent event);
        static void ltk_load_theme(ltk_window *window, const char *path);
       -static ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
        static int read_sock(struct ltk_sock_info *sock);
        static int push_token(struct token_list *tl, char *token);
        static int read_sock(struct ltk_sock_info *sock);
       t@@ -110,7 +110,6 @@ static int queue_sock_write(struct ltk_sock_info *sock, const char *str, int len
        static int tokenize_command(struct ltk_sock_info *sock);
        static int ltk_set_root_widget_cmd(ltk_window *window, char **tokens, int num_tokens, char **errstr);
        static void process_commands(ltk_window *window, struct ltk_sock_info *sock);
       -static ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
        static int add_client(int fd);
        static int listen_sock(const char *sock_path);
        static int accept_sock(int listenfd);
       t@@ -406,15 +405,26 @@ ltk_set_root_widget_cmd(
                return 0;
        }
        
       -static ltk_rect
       +#define MAX(a, b) ((a) > (b) ? (a) : (b))
       +#define MIN(a, b) ((a) < (b) ? (a) : (b))
       +
       +ltk_rect
       +ltk_rect_intersect(ltk_rect r1, ltk_rect r2) {
       +        ltk_rect i;
       +        i.x = MAX(r1.x, r2.x);
       +        i.y = MAX(r1.y, r2.y);
       +        i.w = MIN(r1.x + r1.w, r2.x + r2.w) - i.x;
       +        i.h = MIN(r1.y + r1.h, r2.y + r2.h) - i.y;
       +        return i;
       +}
       +
       +ltk_rect
        ltk_rect_union(ltk_rect r1, ltk_rect r2) {
                ltk_rect u;
       -        u.x = r1.x < r2.x ? r1.x : r2.x;
       -        u.y = r1.y < r2.y ? r1.y : r2.y;
       -        int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w;
       -        int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h;
       -        u.w = x2 - u.x;
       -        u.h = y2 - u.y;
       +        u.x = MIN(r1.x, r2.x);
       +        u.y = MIN(r1.y, r2.y);
       +        u.w = MAX(r1.x + r1.w, r2.x + r2.w) - u.x;
       +        u.h = MAX(r1.y + r1.h, r2.y + r2.h) - u.y;
                return u;
        }
        
       t@@ -530,9 +540,9 @@ ltk_redraw_window(ltk_window *window) {
                        window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
                XClearArea(window->dpy, window->xwindow, window->dirty_rect.x, window->dirty_rect.y, window->dirty_rect.w, window->dirty_rect.h, False);
                if (!window->root_widget) return;
       -        /* FIXME: actually respect the dirty rect... */
                ptr = window->root_widget;
       -        ptr->draw(ptr);
       +        if (ptr)
       +                ptr->draw(ptr, window->dirty_rect);
        }
        
        static void
       t@@ -675,6 +685,8 @@ ltk_ini_handler(void *window, const char *widget, const char *prop, const char *
                        ltk_button_ini_handler(window, prop, value);
                } else if (strcmp(widget, "label") == 0) {
                        ltk_label_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "scrollbar") == 0) {
       +                ltk_scrollbar_ini_handler(window, prop, value);
                } else {
                        return 0;
                }
       t@@ -698,6 +710,7 @@ ltk_load_theme(ltk_window *window, const char *path) {
                ltk_window_setup_theme_defaults(window);
                ltk_button_setup_theme_defaults(window);
                ltk_label_setup_theme_defaults(window);
       +        ltk_scrollbar_setup_theme_defaults(window);
                if (ini_parse(path, ltk_ini_handler, window) < 0) {
                        ltk_warn("Can't load theme.\n");
                }
       t@@ -792,7 +805,7 @@ ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
                if (!widget || widget->state == LTK_DISABLED)
                        return;
                if (event.xbutton.button == 1) {
       -                /* ltk_widget *parent = widget->parent; FIXME */
       +                /* ltk_widget *parent = widget->parent; FIXME: set pressed widget hierarchy */
                        widget->state = LTK_PRESSED;
                        if (widget->change_state)
                                widget->change_state(widget);
       t@@ -825,7 +838,8 @@ ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
        void
        ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
                if (!widget) return;
       -        short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
       +        /* FIXME: THIS WHOLE STATE HANDLING IS BROKEN */
       +        int pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
                if ((widget->state == LTK_NORMAL) && !pressed) {
                        widget->state = LTK_ACTIVE;
                        if (widget->change_state)
   DIR diff --git a/scrollbar.c b/scrollbar.c
       t@@ -35,10 +35,9 @@
        #include "util.h"
        #include "scrollbar.h"
        
       -static void ltk_scrollbar_draw(ltk_scrollbar *scrollbar);
       +static void ltk_scrollbar_draw(ltk_scrollbar *scrollbar, ltk_rect clip);
        static void ltk_scrollbar_mouse_press(ltk_scrollbar *scrollbar, XEvent event);
        static void ltk_scrollbar_motion_notify(ltk_scrollbar *scrollbar, XEvent event);
       -static ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient);
        static void ltk_scrollbar_destroy(ltk_scrollbar *scrollbar, int shallow);
        
        static struct {
       t@@ -95,8 +94,17 @@ ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *valu
                }
        }
        
       +void
       +ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size) {
       +        /* FIXME: some sort of error? */
       +        if (virtual_size <= 0)
       +                return;
       +        scrollbar->cur_pos = (int)(((double)scrollbar->cur_pos / scrollbar->virtual_size) * virtual_size);
       +        scrollbar->virtual_size = virtual_size;
       +}
       +
        static void
       -ltk_scrollbar_draw(ltk_scrollbar *scrollbar) {
       +ltk_scrollbar_draw(ltk_scrollbar *scrollbar, ltk_rect clip) {
                LtkColor *bg, *fg;
                int handle_x, handle_y, handle_w, handle_h;
                ltk_window *window = scrollbar->widget.window;
       t@@ -131,12 +139,18 @@ ltk_scrollbar_draw(ltk_scrollbar *scrollbar) {
                        handle_y = rect.y;
                        handle_h = rect.h;
                        handle_x = (int)(rect.x + (scrollbar->cur_pos / (double)scrollbar->virtual_size) * rect.w);
       -                handle_w = (int)((rect.w / (double)scrollbar->virtual_size) * rect.w);
       +                if (scrollbar->virtual_size > rect.w)
       +                        handle_w = (int)((rect.w / (double)scrollbar->virtual_size) * rect.w);
       +                else
       +                        handle_w = rect.w;
                } else {
                        handle_x = rect.x;
                        handle_w = rect.w;
       -                handle_y = (int)(rect.x + (scrollbar->cur_pos / (double)scrollbar->virtual_size) * rect.h);
       -                handle_h = (int)((rect.h / (double)scrollbar->virtual_size) * rect.h);
       +                handle_y = (int)(rect.y + (scrollbar->cur_pos / (double)scrollbar->virtual_size) * rect.h);
       +                if (scrollbar->virtual_size > rect.h)
       +                        handle_h = (int)((rect.h / (double)scrollbar->virtual_size) * rect.h);
       +                else
       +                        handle_h = rect.h;
                }
                if (handle_w <= 0 || handle_h <= 0)
                        return;
       t@@ -151,38 +165,43 @@ ltk_scrollbar_mouse_press(ltk_scrollbar *scrollbar, XEvent event) {
        }
        
        static void
       -ltk_scrollbar_motion_notify(ltk_scrollbar *scrollbar, XEvent event) {
       +ltk_scrollbar_motion_notify(ltk_scrollbar *sc, XEvent event) {
                double scale;
                int delta, max_pos;
       -        if (scrollbar->widget.state != LTK_PRESSED)
       +        /* FIXME: Make this work properly with LTK_PRESSED */
       +        if ((event.xmotion.state & Button1Mask) != Button1Mask)
                        return;
       -        if (scrollbar->orient == LTK_HORIZONTAL) {
       -                delta = event.xbutton.x - scrollbar->last_mouse_x;
       -                max_pos = scrollbar->virtual_size - scrollbar->widget.rect.w;
       -                scale = scrollbar->virtual_size / (double)scrollbar->widget.rect.w;
       +        ltk_warn("adasd\n");
       +        if (sc->orient == LTK_HORIZONTAL) {
       +                delta = event.xbutton.x - sc->last_mouse_x;
       +                max_pos = sc->virtual_size > sc->widget.rect.w ? sc->virtual_size - sc->widget.rect.w : 0;
       +                scale = sc->virtual_size / (double)sc->widget.rect.w;
                } else {
       -                delta = event.xbutton.y - scrollbar->last_mouse_y;
       -                max_pos = scrollbar->virtual_size - scrollbar->widget.rect.h;
       -                scale = scrollbar->virtual_size / (double)scrollbar->widget.rect.h;
       +                delta = event.xbutton.y - sc->last_mouse_y;
       +                max_pos = sc->virtual_size > sc->widget.rect.h ? sc->virtual_size - sc->widget.rect.h : 0;
       +                scale = sc->virtual_size / (double)sc->widget.rect.h;
                }
       -        scrollbar->cur_pos += (int)(scale * delta);
       -        if (scrollbar->cur_pos < 0)
       -                scrollbar->cur_pos = 0;
       -        else if (scrollbar->cur_pos > max_pos)
       -                scrollbar->cur_pos = max_pos;
       -        scrollbar->last_mouse_x = event.xbutton.x;
       -        scrollbar->last_mouse_y = event.xbutton.y;
       +        sc->cur_pos += (int)(scale * delta);
       +        if (sc->cur_pos < 0)
       +                sc->cur_pos = 0;
       +        else if (sc->cur_pos > max_pos)
       +                sc->cur_pos = max_pos;
       +        sc->last_mouse_x = event.xbutton.x;
       +        sc->last_mouse_y = event.xbutton.y;
        }
        
       -static ltk_scrollbar *
       +ltk_scrollbar *
        ltk_scrollbar_create(ltk_window *window, ltk_orientation orient) {
                ltk_scrollbar *sc = malloc(sizeof(ltk_scrollbar));
                if (!sc)
                        ltk_fatal_errno("Unable to allocate memory for scrollbar.\n");
       -        ltk_fill_widget_default(sc, NULL, window, &ltk_scrollbar_draw,
       -            NULL, &ltk_scrollbar_destroy, 1);
       +        ltk_fill_widget_defaults(sc, NULL, window, &ltk_scrollbar_draw,
       +            NULL, &ltk_scrollbar_destroy, 1, LTK_UNKNOWN);
                sc->last_mouse_x = sc->last_mouse_y = 0;
       -        sc->virtual_size = 0;
       +        sc->widget.motion_notify = &ltk_scrollbar_motion_notify;
       +        sc->widget.mouse_press = &ltk_scrollbar_mouse_press;
       +        /* This cannot be 0 because that leads to divide-by-zero */
       +        sc->virtual_size = 1;
                sc->cur_pos = 0;
                sc->orient = orient;
                if (orient == LTK_HORIZONTAL)
   DIR diff --git a/scrollbar.h b/scrollbar.h
       t@@ -38,5 +38,6 @@ typedef struct {
        void ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size);
        void ltk_scrollbar_setup_theme_defaults(ltk_window *window);
        void ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value);
       +ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient);
        
        #endif /* _LTK_SCROLLBAR_H_ */
   DIR diff --git a/text.h b/text.h
       t@@ -10,7 +10,7 @@ void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap 
        void ltk_cleanup_text(void);
        LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width);
        void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg);
       -void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y);
       +void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y, ltk_rect clip);
        void ltk_text_line_set_width(LtkTextLine *tl, int width);
        void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h);
        void ltk_text_line_destroy(LtkTextLine *tl);
   DIR diff --git a/text_line.c b/text_line.c
       t@@ -35,14 +35,14 @@ void ltk_cleanup_text(void);
        
        LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width);
        void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg);
       -void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y);
       +void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y, ltk_rect clip);
        void ltk_text_line_set_width(LtkTextLine *tl, int width);
        void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h);
        void ltk_text_line_destroy(LtkTextLine *tl);
        
        static void ltk_text_line_create_glyphs(struct ltk_text_line *tl);
        static void ltk_text_line_draw_glyph(ltk_glyph *glyph, int xoff, int yoff,
       -    XImage *img, XColor fg);
       +    XImage *img, XColor fg, ltk_rect clip);
        static XImage *ltk_create_ximage(Display *dpy, int w, int h, int depth,
            XColor bg);
        
       t@@ -68,7 +68,7 @@ ltk_create_ximage(Display *dpy, int w, int h, int depth, XColor bg) {
        
        /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
        static void
       -ltk_text_line_draw_glyph(ltk_glyph *glyph, int xoff, int yoff, XImage *img, XColor fg) {
       +ltk_text_line_draw_glyph(ltk_glyph *glyph, int xoff, int yoff, XImage *img, XColor fg, ltk_rect clip) {
                int x = glyph->x + xoff;
                int y = glyph->y + yoff;
                double a;
       t@@ -95,7 +95,8 @@ ltk_text_line_render(
                GC gc,
                Colormap colormap,
                XColor fg,
       -        XColor bg)
       +        XColor bg,
       +        ltk_rect clip)
        {
                ltk_glyph *glyph;
        
       t@@ -105,7 +106,7 @@ ltk_text_line_render(
                /* FIXME: pass old image; if it has same dimensions, just clear it */
                XImage *img = ltk_create_ximage(dpy, tl->w, tl->h, depth, bg);
                for (int i = 0; i < tl->glyph_len; i++) {
       -                ltk_text_line_draw_glyph(&tl->glyphs[i], -tl->x_min, -tl->y_min, img, fg);
       +                ltk_text_line_draw_glyph(&tl->glyphs[i], -tl->x_min, -tl->y_min, img, fg, clip);
                }
                return img;
        }
   DIR diff --git a/text_pango.c b/text_pango.c
       t@@ -98,7 +98,7 @@ ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg) {
        }
        
        void
       -ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y) {
       +ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y, ltk_rect clip) {
                XCopyArea(tm.dpy, tl->pixmap, tl->window, gc, 0, 0, tl->w, tl->h, x, y);
        }
        
   DIR diff --git a/text_stb.c b/text_stb.c
       t@@ -534,7 +534,7 @@ ltk_text_line_render(
        
        /* FIXME: error checking if img is rendered yet, tm initialized, etc. */
        void
       -ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y) {
       +ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y, ltk_rect clip) {
                XPutImage(tm.dpy, tl->window, gc, tl->img, 0, 0, x, y, tl->w, tl->h);
        }