commit 657c2bb9354fc1f5d1e224c5db412ce0c4da447f
parent 997414b6693f3a4bc08c43347baa4e829d8e6cca
Author: lumidify <nobody@lumidify.org>
Date:   Sat, 27 Feb 2021 21:26:15 +0100
Add graphics helper functions
These aren't used everywhere yet, and everything is still a bit buggy,
but that will hopefully change eventually.
Diffstat:
15 files changed, 219 insertions(+), 99 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,5 +1,2 @@
-ltkd
-ltkc
-ltk.sock
 *.o
 *.core
diff --git a/LICENSE b/LICENSE
@@ -1,4 +1,5 @@
-See khash.h, ini.*, stb_truetype.*, and strtonum.c for third-party licenses.
+See src/khash.h, src/ini.*, src/stb_truetype.*, and src/strtonum.c
+for third-party licenses.
 
 ISC License
 
diff --git a/Makefile b/Makefile
@@ -47,6 +47,7 @@ OBJ = \
 	src/button.o \
 	src/label.o \
 	src/draw.o \
+	src/graphics.o \
 	$(EXTRA_OBJ)
 
 # Note: This could be improved so a change in a header only causes the .c files
@@ -68,7 +69,8 @@ HDR = \
 	src/scrollbar.h \
 	src/stb_truetype.h \
 	src/text.h \
-	src/util.h
+	src/util.h \
+	src/graphics.h
 
 CFLAGS += $(EXTRA_CFLAGS)
 LDFLAGS += $(EXTRA_LDFLAGS)
diff --git a/src/.gitignore b/src/.gitignore
@@ -0,0 +1,4 @@
+ltkd
+ltkc
+*.o
+*.core
diff --git a/src/box.c b/src/box.c
@@ -55,7 +55,10 @@ static struct ltk_widget_vtable vtable = {
 	.remove_child = <k_box_remove,
 	.mouse_press = <k_box_mouse_press,
 	.mouse_release = <k_box_mouse_release,
-	.motion_notify = <k_box_motion_notify
+	.motion_notify = <k_box_motion_notify,
+	.needs_redraw = 0,
+	.needs_pixmap = 0,
+	.type = LTK_BOX
 };
 
 static int ltk_box_cmd_add(
@@ -99,7 +102,7 @@ static ltk_box *
 ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
 	ltk_box *box = ltk_malloc(sizeof(ltk_box));
 
-	ltk_fill_widget_defaults(&box->widget, id, window, &vtable, 0, LTK_BOX);
+	ltk_fill_widget_defaults(&box->widget, id, window, &vtable, 0, 0);
 
 	box->sc = ltk_scrollbar_create(window, orient, <k_box_scroll, box);
 	box->widgets = NULL;
@@ -150,8 +153,7 @@ ltk_recalculate_box(ltk_widget *self) {
 				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->vtable->resize)
-				ptr->vtable->resize(ptr);
+			ltk_widget_resize(ptr);
 			cur_pos += ptr->rect.w;
 		} else {
 			ptr->rect.y = cur_pos - box->sc->cur_pos;
@@ -163,8 +165,7 @@ ltk_recalculate_box(ltk_widget *self) {
 				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->vtable->resize)
-				ptr->vtable->resize(ptr);
+			ltk_widget_resize(ptr);
 			cur_pos += ptr->rect.h;
 		}
 	}
@@ -278,16 +279,16 @@ ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_widget *self, char **
 					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->vtable->resize)
-					box->widget.parent->vtable->resize(box->widget.parent);
+				if (box->widget.parent)
+					ltk_widget_resize(box->widget.parent);
 			} 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 + sc_w > box->widget.ideal_w)
 						box->widget.ideal_w = box->widgets[j]->ideal_w + sc_w;
 				}
-				if (box->widget.parent && box->widget.parent->vtable->resize)
-					box->widget.parent->vtable->resize(box->widget.parent);
+				if (box->widget.parent)
+					ltk_widget_resize(box->widget.parent);
 			}
 			return 0;
 		}
diff --git a/src/button.c b/src/button.c
@@ -31,6 +31,7 @@
 #include "util.h"
 #include "text.h"
 #include "button.h"
+#include "graphics.h"
 
 static void ltk_button_draw(ltk_widget *self, ltk_rect clip);
 static int ltk_button_mouse_release(ltk_widget *self, XEvent event);
@@ -38,14 +39,16 @@ static ltk_button *ltk_button_create(ltk_window *window,
     const char *id, const char *text);
 static void ltk_button_destroy(ltk_widget *self, int shallow);
 static void ltk_button_change_state(ltk_widget *self);
-static void ltk_button_resize(ltk_widget *self);
+static void ltk_button_redraw_pixmap(ltk_button *button);
 
 static struct ltk_widget_vtable vtable = {
     .mouse_release = <k_button_mouse_release,
     .change_state = <k_button_change_state,
     .draw = <k_button_draw,
     .destroy = <k_button_destroy,
-    .resize = <k_button_resize
+    .type = LTK_BUTTON,
+    .needs_redraw = 1,
+    .needs_pixmap = 1
 };
 
 static struct {
@@ -136,12 +139,12 @@ ltk_button_draw(ltk_widget *self, ltk_rect clip) {
 	ltk_window *window = button->widget.window;
 	ltk_rect rect = button->widget.rect;
 	ltk_rect clip_final = ltk_rect_intersect(clip, rect);
+	if (self->dirty)
+		ltk_button_redraw_pixmap(button);
 	/* no idea why it would be less than 0, but whatever */
 	if (clip_final.w <= 0 || clip_final.h <= 0)
 		return;
-	XCopyArea(window->dpy, button->pixmap, window->xwindow, window->gc,
-	    clip_final.x - rect.x, clip_final.y - rect.y,
-	    clip_final.w, clip_final.h, clip_final.x, clip_final.y);
+	ltk_copy_clipped(self, clip_final);
 }
 
 static void
@@ -171,15 +174,10 @@ ltk_button_redraw_pixmap(ltk_button *button) {
 	default:
 		ltk_fatal("No style found for button!\n");
 	}
-	XSetForeground(window->dpy, window->gc, fill->xcolor.pixel);
-	XFillRectangle(window->dpy, button->pixmap, window->gc, 0, 0, rect.w, rect.h);
-	/* FIXME: Why did I do this? */
-	if (bw < 1) return;
-	XSetForeground(window->dpy, window->gc, border->xcolor.pixel);
-	XSetLineAttributes(window->dpy, window->gc, bw, LineSolid,
-	    CapButt, JoinMiter);
-	XDrawRectangle(window->dpy, button->pixmap, window->gc,
-	    bw / 2, bw / 2, rect.w - bw, rect.h - bw);
+	rect.x = 0;
+	rect.y = 0;
+	ltk_fill_widget_rect(&button->widget, fill, rect);
+	ltk_draw_widget_rect(&button->widget, border, rect, bw);
 
 	int text_w, text_h;
 	ltk_text_line_get_size(button->tl, &text_w, &text_h);
@@ -187,29 +185,8 @@ ltk_button_redraw_pixmap(ltk_button *button) {
 	int text_y = (rect.h - text_h) / 2;
 	/* FIXME: Remove clipping rect from text line - this is just used here as a dummy
 	   because it is completely ignored */
-	ltk_text_line_draw(button->tl, button->pixmap, window->gc, text_x, text_y, rect);
-}
-
-/* FIXME: Make this amortised constant; make it generic for all widgets */
-static void
-ltk_button_resize(ltk_widget *self) {
-	ltk_button *button = (ltk_button *)self;
-	Window win;
-	int x, y;
-	unsigned int w, h, bw, d;
-	unsigned int new_w, new_h;
-	ltk_window *window = button->widget.window;
-	ltk_rect rect = button->widget.rect;
-	XGetGeometry(window->dpy, button->pixmap, &win, &x, &y, &w, &h, &bw, &d);
-
-	new_w = (int)w < rect.w ? rect.w : (int)w;
-	new_h = (int)h < rect.h ? rect.h : (int)h;
-	if (new_w < w && new_h < h)
-		return;
-	XFreePixmap(window->dpy, button->pixmap);
-	button->pixmap = XCreatePixmap(window->dpy, window->xwindow,
-	    new_w, new_h, window->depth);
-	ltk_button_redraw_pixmap(button);
+	ltk_text_line_draw(button->tl, button->widget.pixmap, window->gc, text_x, text_y, rect);
+	button->widget.dirty = 0;
 }
 
 static void
@@ -233,7 +210,7 @@ ltk_button_change_state(ltk_widget *self) {
 		ltk_fatal("No style found for button!\n");
 	}
 	ltk_text_line_render(button->tl, fill, &theme.text_color);
-	ltk_button_redraw_pixmap(button);
+	self->dirty = 1;
 }
 
 static int
@@ -249,7 +226,6 @@ ltk_button_create(ltk_window *window, const char *id, const char *text) {
 	char *text_copy;
 	ltk_button *button = ltk_malloc(sizeof(ltk_button));
 
-	ltk_fill_widget_defaults(&button->widget, id, window, &vtable, 1, LTK_BUTTON);
 	uint16_t font_size = window->theme.font_size;
 	text_copy = ltk_strdup(text);
 	button->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1);
@@ -257,8 +233,7 @@ ltk_button_create(ltk_window *window, const char *id, const char *text) {
 	ltk_text_line_get_size(button->tl, &text_w, &text_h);
 	button->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2;
 	button->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2;
-	button->pixmap = XCreatePixmap(window->dpy, window->xwindow,
-	    button->widget.ideal_w, button->widget.ideal_h, window->depth);
+	ltk_fill_widget_defaults(&button->widget, id, window, &vtable, button->widget.ideal_w, button->widget.ideal_h);
 	/* render text */
 	ltk_button_change_state((ltk_widget *)button);
 
diff --git a/src/draw.c b/src/draw.c
@@ -45,6 +45,11 @@ static struct ltk_widget_vtable vtable = {
 	.draw = <k_draw_draw,
 	.resize = <k_draw_resize,
 	.destroy = <k_draw_destroy,
+	.type = LTK_DRAW,
+	.needs_redraw = 1,
+	/* FIXME: use the widget pixmap here and store the drawn stuff
+	   logically as paths, not just on the pixmap */
+	.needs_pixmap = 0
 };
 
 static int ltk_draw_cmd_clear(
@@ -87,7 +92,7 @@ static ltk_draw *
 ltk_draw_create(ltk_window *window, const char *id, int w, int h, const char *color) {
 	ltk_draw *draw = ltk_malloc(sizeof(ltk_draw));
 
-	ltk_fill_widget_defaults(&draw->widget, id, window, &vtable, 1, LTK_DRAW);
+	ltk_fill_widget_defaults(&draw->widget, id, window, &vtable, w, h);
 	draw->widget.rect.w = w;
 	draw->widget.rect.h = h;
 	draw->pix = XCreatePixmap(window->dpy, window->xwindow, w, h, window->depth);
diff --git a/src/graphics.c b/src/graphics.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "color.h"
+#include "rect.h"
+#include "widget.h"
+#include "ltk.h"
+
+void
+ltk_fill_widget_rect(ltk_widget *widget, ltk_color *color, ltk_rect rect) {
+	ltk_window *win = widget->window;
+	XSetForeground(win->dpy, win->gc, color->xcolor.pixel);
+	XFillRectangle(win->dpy, widget->pixmap, win->gc, rect.x, rect.y, rect.w, rect.h);
+}
+
+void
+ltk_draw_widget_rect(ltk_widget *widget, ltk_color *color, ltk_rect rect, int border_width) {
+	ltk_window *win = widget->window;
+	if (border_width <= 0)
+		return;
+	XSetForeground(win->dpy, win->gc, color->xcolor.pixel);
+	XSetLineAttributes(win->dpy, win->gc, border_width, LineSolid, CapButt, JoinMiter);
+	XDrawRectangle(
+	    win->dpy, widget->pixmap, win->gc,
+	    border_width / 2, border_width / 2,
+	    rect.w - border_width, rect.h - border_width
+	);
+}
+
+void
+ltk_copy_clipped(ltk_widget *widget, ltk_rect clip) {
+	ltk_window *win = widget->window;
+	ltk_rect clip_final = ltk_rect_intersect(clip, widget->rect);
+	if (clip_final.w <= 0 || clip_final.h <= 0)
+		return;
+	XCopyArea(
+	    win->dpy, widget->pixmap, win->xwindow, win->gc,
+	    clip_final.x - widget->rect.x, clip_final.y - widget->rect.y,
+	    clip_final.w, clip_final.h, clip_final.x, clip_final.y
+	);
+}
diff --git a/src/graphics.h b/src/graphics.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LTK_GRAPHICS_H_
+#define _LTK_GRAPHICS_H_
+
+/* Requires: "color.h", "rect.h", "widget.h" */
+
+/* FIXME: Is it faster to take ltk_color* or ltk_color? */
+
+/* Fill `rect` with `color` on `widget`'s pixmap.
+ * `rect` is relative to `widget`'s rect. */
+void ltk_fill_widget_rect(ltk_widget *widget, ltk_color *color, ltk_rect rect);
+
+/* Draw `rect` with `color` and `border_width` on `widget`'s pixmap.
+ * `rect` is relative to `widget`'s rect. */
+void ltk_draw_widget_rect(ltk_widget *widget, ltk_color *color, ltk_rect rect, int border_width);
+
+/* Copy the part of `widget`'s pixmap covered by the intersection of `clip`
+ * and `widget`'s rect to the window `widget` is contained within.
+ * `clip` is absolute, i.e. relative to the coordinates of the window,
+ * not of `widget`. */
+void ltk_copy_clipped(ltk_widget *widget, ltk_rect clip);
+
+#endif /* _LTK_GRAPHICS_H_ */
diff --git a/src/grid.c b/src/grid.c
@@ -63,7 +63,10 @@ static struct ltk_widget_vtable vtable = {
 	.remove_child = <k_grid_ungrid,
 	.mouse_press = <k_grid_mouse_press,
 	.mouse_release = <k_grid_mouse_release,
-	.motion_notify = <k_grid_motion_notify
+	.motion_notify = <k_grid_motion_notify,
+	.type = LTK_GRID,
+	.needs_redraw = 0,
+	.needs_pixmap = 0
 };
 
 static int ltk_grid_cmd_add(
@@ -120,7 +123,7 @@ static ltk_grid *
 ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
 	ltk_grid *grid = ltk_malloc(sizeof(ltk_grid));
 
-	ltk_fill_widget_defaults(&grid->widget, id, window, &vtable, 0, LTK_GRID);
+	ltk_fill_widget_defaults(&grid->widget, id, window, &vtable, 0, 0);
 
 	grid->rows = rows;
 	grid->columns = columns;
@@ -246,11 +249,8 @@ ltk_recalculate_grid(ltk_widget *self) {
 			if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) {
 				ptr->rect.h = grid->row_pos[end_row] - grid->row_pos[i];
 			}
-			if (orig_width != ptr->rect.w || orig_height != ptr->rect.h) {
-				if (ptr->vtable->resize) {
-					ptr->vtable->resize(ptr);
-				}
-			}
+			if (orig_width != ptr->rect.w || orig_height != ptr->rect.h)
+				ltk_widget_resize(ptr);
 
 			if (ptr->sticky & LTK_STICKY_RIGHT) {
 				ptr->rect.x = grid->column_pos[end_column] - ptr->rect.w;
diff --git a/src/label.c b/src/label.c
@@ -31,15 +31,20 @@
 #include "util.h"
 #include "text.h"
 #include "label.h"
+#include "graphics.h"
 
 static void ltk_label_draw(ltk_widget *self, ltk_rect clip);
 static ltk_label *ltk_label_create(ltk_window *window,
     const char *id, const char *text);
 static void ltk_label_destroy(ltk_widget *self, int shallow);
+static void ltk_label_redraw_pixmap(ltk_label *label);
 
 static struct ltk_widget_vtable vtable = {
 	.draw = <k_label_draw,
-	.destroy = <k_label_destroy
+	.destroy = <k_label_destroy,
+	.type = LTK_LABEL,
+	.needs_redraw = 1,
+	.needs_pixmap = 1
 };
 
 static struct {
@@ -78,19 +83,26 @@ ltk_label_draw(ltk_widget *self, ltk_rect clip) {
 	ltk_window *window = label->widget.window;
 	ltk_rect rect = label->widget.rect;
 	ltk_rect clip_final = ltk_rect_intersect(clip, rect);
+	if (self->dirty)
+		ltk_label_redraw_pixmap(label);
 	/* no idea why it would be less than 0, but whatever */
 	if (clip_final.w <= 0 || clip_final.h <= 0)
 		return;
-	XCopyArea(window->dpy, label->text_pixmap, window->xwindow, window->gc,
-	    clip_final.x - rect.x, clip_final.y - rect.y,
-	    clip_final.w, clip_final.h, clip_final.x, clip_final.y);
-	/*
+	ltk_copy_clipped(self, clip_final);
+}
+
+static void
+ltk_label_redraw_pixmap(ltk_label *label) {
+	ltk_rect r = label->widget.rect;
+	r.x = 0;
+	r.y = 0;
+	ltk_fill_widget_rect(&label->widget, &theme.bg_color, r);
+
 	int text_w, text_h;
 	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, clip);
-	*/
+	int text_x = (r.w - text_w) / 2;
+	int text_y = (r.h - text_h) / 2;
+	ltk_text_line_draw(label->tl, label->widget.pixmap, label->widget.window->gc, text_x, text_y, r);
 }
 
 static ltk_label *
@@ -98,7 +110,6 @@ ltk_label_create(ltk_window *window, const char *id, const char *text) {
 	char *text_copy;
 	ltk_label *label = ltk_malloc(sizeof(ltk_label));
 
-	ltk_fill_widget_defaults(&label->widget, id, window, &vtable, 1, LTK_LABEL);
 	uint16_t font_size = window->theme.font_size;
 	text_copy = ltk_strdup(text);
 	label->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1);
@@ -107,11 +118,7 @@ ltk_label_create(ltk_window *window, const char *id, const char *text) {
 	label->widget.ideal_w = text_w + theme.pad * 2;
 	label->widget.ideal_h = text_h + theme.pad * 2;
 	ltk_text_line_render(label->tl, &window->theme.bg, &theme.text_color);
-	label->text_pixmap = XCreatePixmap(window->dpy, window->xwindow,
-	    label->widget.ideal_w, label->widget.ideal_h, window->depth);
-	XSetForeground(window->dpy, window->gc, theme.bg_color.xcolor.pixel);
-	XFillRectangle(window->dpy, label->text_pixmap, window->gc, 0, 0, label->widget.ideal_w, label->widget.ideal_h);
-	ltk_text_line_draw(label->tl, label->text_pixmap, window->gc, theme.pad, theme.pad, label->widget.rect);
+	ltk_fill_widget_defaults(&label->widget, id, window, &vtable, label->widget.ideal_w, label->widget.ideal_h);
 
 	return label;
 }
diff --git a/src/ltkd.c b/src/ltkd.c
@@ -420,8 +420,7 @@ ltk_set_root_widget_cmd(
 	ltk_window_invalidate_rect(window, widget->rect);
 	widget->rect.w = window->rect.w;
 	widget->rect.h = window->rect.h;
-	if (widget->vtable->resize)
-		widget->vtable->resize(widget);
+	ltk_widget_resize(widget);
 
 	return 0;
 }
@@ -494,10 +493,10 @@ ltk_window_other_event(ltk_window *window, XEvent event) {
 			window->rect.w = w;
 			window->rect.h = h;
 			ltk_window_invalidate_rect(window, window->rect);
-			if (ptr && ptr->vtable->resize) {
+			if (ptr) {
 				ptr->rect.w = w;
 				ptr->rect.h = h;
-				ptr->vtable->resize(ptr);
+				ltk_widget_resize(ptr);
 			}
 		}
 	} else if (event.type == Expose && event.xexpose.count == 0) {
diff --git a/src/scrollbar.c b/src/scrollbar.c
@@ -40,7 +40,10 @@ static struct ltk_widget_vtable vtable = {
 	.draw = <k_scrollbar_draw,
 	.mouse_press = <k_scrollbar_mouse_press,
 	.motion_notify = <k_scrollbar_motion_notify,
-	.destroy = <k_scrollbar_destroy
+	.destroy = <k_scrollbar_destroy,
+	.type = LTK_UNKNOWN, /* FIXME */
+	.needs_redraw = 1,
+	.needs_pixmap = 1
 };
 
 static struct {
@@ -230,7 +233,7 @@ ltk_scrollbar_motion_notify(ltk_widget *self, XEvent event) {
 ltk_scrollbar *
 ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback)(ltk_widget *), void *data) {
 	ltk_scrollbar *sc = ltk_malloc(sizeof(ltk_scrollbar));
-	ltk_fill_widget_defaults((ltk_widget *)sc, NULL, window, &vtable, 1, LTK_UNKNOWN);
+	ltk_fill_widget_defaults((ltk_widget *)sc, NULL, window, &vtable, 1, 1); /* FIXME: proper size */
 	sc->last_mouse_x = sc->last_mouse_y = 0;
 	/* This cannot be 0 because that leads to divide-by-zero */
 	sc->virtual_size = 1;
diff --git a/src/widget.c b/src/widget.c
@@ -59,41 +59,66 @@ ltk_widgets_cleanup() {
 
 void
 ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
-    struct ltk_widget_vtable *vtable, unsigned int needs_redraw, ltk_widget_type type) {
+    struct ltk_widget_vtable *vtable, int w, int h) {
 	if (id)
 		widget->id = ltk_strdup(id);
 	else
 		widget->id = NULL;
 	widget->window = window;
-	widget->active_widget = NULL;
 	widget->parent = NULL;
-	widget->type = type;
+
+	widget->pix_w = w;
+	widget->pix_h = h;
+	if (vtable->needs_pixmap)
+		widget->pixmap = XCreatePixmap(window->dpy, window->xwindow, w, h, window->depth);
 
 	/* FIXME: possibly check that draw and destroy aren't NULL */
 	widget->vtable = vtable;
 
-	widget->needs_redraw = needs_redraw;
 	widget->state = LTK_NORMAL;
 	widget->row = 0;
 	widget->rect.x = 0;
 	widget->rect.y = 0;
-	widget->rect.w = 0;
-	widget->rect.h = 0;
-	widget->ideal_w = 0;
-	widget->ideal_h = 0;
+	widget->rect.w = w;
+	widget->rect.h = h;
 
 	widget->row = 0;
 	widget->column = 0;
 	widget->row_span = 0;
 	widget->column_span = 0;
 	widget->sticky = 0;
+	widget->dirty = 1;
+}
+
+/* FIXME: Make this properly amortised constant */
+/* FIXME: Maybe pass the new width as arg here?
+   That would make a bit more sense */
+void
+ltk_widget_resize(ltk_widget *widget) {
+	if (widget->vtable->resize)
+		widget->vtable->resize(widget);
+	if (!widget->vtable->needs_pixmap)
+		return;
+	int new_w, new_h;
+	ltk_window *w = widget->window;
+	ltk_rect r = widget->rect;
+	int pw = widget->pix_w;
+	int ph = widget->pix_h;
+
+	new_w = pw < r.w ? r.w : pw;
+	new_h = ph < r.h ? r.h : ph;
+	if (new_w == pw && new_h == ph)
+		return;
+	XFreePixmap(w->dpy, widget->pixmap);
+	widget->pixmap = XCreatePixmap(w->dpy, w->xwindow, new_w, new_h, w->depth);
+	widget->dirty = 1;
 }
 
 void
 ltk_widget_change_state(ltk_widget *widget) {
 	if (widget->vtable->change_state)
 		widget->vtable->change_state(widget);
-	if (widget->needs_redraw)
+	if (widget->vtable->needs_redraw)
 		ltk_window_invalidate_rect(widget->window, widget->rect);
 }
 
@@ -170,7 +195,7 @@ ltk_get_widget(const char *id, ltk_widget_type type, char **errstr) {
 		return NULL;
 	}
 	widget = kh_value(widget_hash, k);
-	if (type != LTK_WIDGET && widget->type != type) {
+	if (type != LTK_WIDGET && widget->vtable->type != type) {
 		*errstr = "Widget with given ID has wrong type.\n";
 		return NULL;
 	}
diff --git a/src/widget.h b/src/widget.h
@@ -55,24 +55,25 @@ struct ltk_widget_vtable;
 
 typedef struct ltk_widget {
 	struct ltk_window *window;
-	struct ltk_widget *active_widget;
 	struct ltk_widget *parent;
 	char *id;
 
 	struct ltk_widget_vtable *vtable;
+	Pixmap pixmap;
+	int pix_w;
+	int pix_h;
 
 	ltk_rect rect;
 	unsigned int ideal_w;
 	unsigned int ideal_h;
 
-	ltk_widget_type type;
 	ltk_widget_state state;
 	unsigned int sticky;
 	unsigned short row;
 	unsigned short column;
 	unsigned short row_span;
 	unsigned short column_span;
-	unsigned char needs_redraw;
+	char dirty;
 } ltk_widget;
 
 struct ltk_widget_vtable {
@@ -92,11 +93,15 @@ struct ltk_widget_vtable {
 
 	void (*child_size_change) (struct ltk_widget *, struct ltk_widget *);
 	int (*remove_child) (struct ltk_window *, struct ltk_widget *, struct ltk_widget *, char **);
+
+	ltk_widget_type type;
+	char needs_redraw;
+	char needs_pixmap;
 };
 
 int ltk_widget_destroy(struct ltk_window *window, char **tokens, size_t num_tokens, char **errstr);
 void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, struct ltk_window *window,
-    struct ltk_widget_vtable *vtable, unsigned int needs_redraw, ltk_widget_type type);
+    struct ltk_widget_vtable *vtable, int w, int h);
 void ltk_widget_change_state(ltk_widget *widget);
 void ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event);
 void ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event);
@@ -107,5 +112,6 @@ void ltk_set_widget(ltk_widget *widget, const char *id);
 void ltk_remove_widget(const char *id);
 void ltk_widgets_cleanup();
 void ltk_widgets_init();
+void ltk_widget_resize(ltk_widget *widget);
 
 #endif /* _LTK_WIDGET_H_ */