commit b0338320849b8d570511491a73dee2d75aff5fd9
parent d0099e84bdff7d265d14fc3ae226b0881ba98ed6
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 17 Jan 2021 21:25:32 +0100
Add very broken scrollbar support to box
Diffstat:
13 files changed, 156 insertions(+), 87 deletions(-)
diff --git a/.ltk/theme.ini b/.ltk/theme.ini
@@ -23,7 +23,7 @@ text_color = #FFFFFF
 pad = 5
 
 [scrollbar]
-size = 5
+size = 15
 bg = #000000
 bg_disabled = #555555
 fg = #113355
diff --git a/box.c b/box.c
@@ -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);
@@ -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 *
@@ -95,7 +99,7 @@ ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
 	box->widget.child_size_change = <k_box_child_size_change;
 	box->widget.remove_child = <k_box_remove;
 
-	box->sc = NULL;
+	box->sc = ltk_scrollbar_create(window, orient);
 	box->widgets = NULL;
 	box->num_alloc = 0;
 	box->num_widgets = 0;
@@ -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)
@@ -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
@@ -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;
 	}
 
@@ -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);
@@ -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;
@@ -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);
@@ -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++) {
@@ -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, <k_widget_motion_notify_event);
 }
 
diff --git a/button.c b/button.c
@@ -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);
@@ -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;
@@ -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
diff --git a/grid.c b/grid.c
@@ -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);
@@ -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);
 	}
 }
 
diff --git a/label.c b/label.c
@@ -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);
@@ -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;
 
@@ -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 *
diff --git a/ltk.h b/ltk.h
@@ -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,
@@ -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);
 
@@ -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, ...);
diff --git a/ltkd.c b/ltkd.c
@@ -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 */
@@ -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);
@@ -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);
@@ -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;
 }
 
@@ -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
@@ -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;
 	}
@@ -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");
 	}
@@ -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);
@@ -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)
diff --git a/scrollbar.c b/scrollbar.c
@@ -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 {
@@ -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;
@@ -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;
@@ -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, <k_scrollbar_draw,
-	    NULL, <k_scrollbar_destroy, 1);
+	ltk_fill_widget_defaults(sc, NULL, window, <k_scrollbar_draw,
+	    NULL, <k_scrollbar_destroy, 1, LTK_UNKNOWN);
 	sc->last_mouse_x = sc->last_mouse_y = 0;
-	sc->virtual_size = 0;
+	sc->widget.motion_notify = <k_scrollbar_motion_notify;
+	sc->widget.mouse_press = <k_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)
diff --git a/scrollbar.h b/scrollbar.h
@@ -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_ */
diff --git a/text.h b/text.h
@@ -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);
diff --git a/text_line.c b/text_line.c
@@ -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);
 
@@ -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;
@@ -95,7 +95,8 @@ ltk_text_line_render(
 	GC gc,
 	Colormap colormap,
 	XColor fg,
-	XColor bg)
+	XColor bg,
+	ltk_rect clip)
 {
 	ltk_glyph *glyph;
 
@@ -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;
 }
diff --git a/text_pango.c b/text_pango.c
@@ -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);
 }
 
diff --git a/text_stb.c b/text_stb.c
@@ -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);
 }