commit 00cefe189580f8332951122af1f6d6230d9652c3
parent adc45f7ba5398d573a017db1a1b7c9044d3a3455
Author: lumidify <nobody@lumidify.org>
Date:   Mon, 25 Apr 2022 22:03:01 +0200
Add hilariously bad pixmap cache
This will have to be improved later
Diffstat:
23 files changed, 798 insertions(+), 218 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -4,7 +4,7 @@ for third-party licenses.
 ISC License
 
 The Lumidify ToolKit (LTK)
-Copyright (c) 2016-2021 lumidify <nobody@lumidify.org>
+Copyright (c) 2016-2022 lumidify <nobody@lumidify.org>
 
 Permission to use, copy, modify, and/or distribute this software for any
 purpose with or without fee is hereby granted, provided that the above
diff --git a/Makefile b/Makefile
@@ -6,13 +6,13 @@ VERSION = -999-prealpha0
 
 # FIXME: Using DEBUG here doesn't work because it somehow
 # interferes with a predefined macro, at least on OpenBSD.
-DEV = 0
+DEV = 1
 USE_PANGO = 0
 
 # FIXME: When using _POSIX_C_SOURCE on OpenBSD, strtonum isn't defined anymore -
 # should strtonum just only be used from the local copy?
 
-CFLAGS += -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -std=c99 `pkg-config --cflags x11 fontconfig xext` -D_POSIX_C_SOURCE=200809L
+CFLAGS += -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -Wall -Wextra -std=c99 `pkg-config --cflags x11 fontconfig xext` -D_POSIX_C_SOURCE=200809L
 LDFLAGS += -lm `pkg-config --libs x11 fontconfig xext`
 
 # Note: this macro magic for debugging and pango rendering seems ugly; it should probably be changed
@@ -46,9 +46,10 @@ OBJ = \
 	src/scrollbar.o \
 	src/button.o \
 	src/label.o \
-	src/draw.o \
-	src/graphics.o \
+	src/graphics_xlib.o \
+	src/surface_cache.o \
 	$(EXTRA_OBJ)
+#	src/draw.o \
 
 # Note: This could be improved so a change in a header only causes the .c files
 # which include that header to be recompiled, but the compile times are
@@ -57,7 +58,6 @@ HDR = \
 	src/box.h \
 	src/button.h \
 	src/color.h \
-	src/draw.h \
 	src/grid.h \
 	src/ini.h \
 	src/khash.h \
@@ -70,7 +70,9 @@ HDR = \
 	src/stb_truetype.h \
 	src/text.h \
 	src/util.h \
-	src/graphics.h
+	src/graphics.h \
+	src/surface_cache.h
+#	src/draw.h \
 
 CFLAGS += $(EXTRA_CFLAGS)
 LDFLAGS += $(EXTRA_LDFLAGS)
diff --git a/src/box.c b/src/box.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -57,7 +57,7 @@ static struct ltk_widget_vtable vtable = {
 	.mouse_release = <k_box_mouse_release,
 	.motion_notify = <k_box_motion_notify,
 	.needs_redraw = 0,
-	.needs_pixmap = 0,
+	.needs_surface = 0,
 	.type = LTK_BOX
 };
 
@@ -179,6 +179,7 @@ ltk_recalculate_box(ltk_widget *self) {
 		sc_rect->y = box->widget.rect.y;
 		sc_rect->h = box->widget.rect.h;
 	}
+	ltk_widget_resize((ltk_widget *)box->sc);
 }
 
 /* FIXME: This entire resizing thing is a bit weird. For instance, if a label
diff --git a/src/button.c b/src/button.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016, 2017, 2018, 2020, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -32,6 +32,7 @@
 #include "text.h"
 #include "button.h"
 #include "graphics.h"
+#include "surface_cache.h"
 
 static void ltk_button_draw(ltk_widget *self, ltk_rect clip);
 static int ltk_button_mouse_release(ltk_widget *self, XEvent event);
@@ -39,7 +40,7 @@ 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_redraw_pixmap(ltk_button *button);
+static void ltk_button_redraw_surface(ltk_button *button, ltk_surface *s);
 
 static struct ltk_widget_vtable vtable = {
     .mouse_release = <k_button_mouse_release,
@@ -48,7 +49,7 @@ static struct ltk_widget_vtable vtable = {
     .destroy = <k_button_destroy,
     .type = LTK_BUTTON,
     .needs_redraw = 1,
-    .needs_pixmap = 1
+    .needs_surface = 1
 };
 
 static struct {
@@ -136,24 +137,20 @@ ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) 
 static void
 ltk_button_draw(ltk_widget *self, ltk_rect clip) {
 	ltk_button *button = (ltk_button *)self;
-	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;
-	ltk_copy_clipped(self, clip_final);
+	ltk_surface *s;
+	if (!ltk_surface_cache_get_surface(self->surface_key, &s) || self->dirty)
+		ltk_button_redraw_surface(button, s);
+	ltk_surface_copy_to_window(s, self->window, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y);
 }
 
 static void
-ltk_button_redraw_pixmap(ltk_button *button) {
+ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) {
 	ltk_window *window = button->widget.window;
 	ltk_rect rect = button->widget.rect;
 	int bw = theme.border_width;
-	ltk_color *border;
-	ltk_color *fill;
+	ltk_color *border = NULL, *fill = NULL;
 	switch (button->widget.state) {
 	case LTK_NORMAL:
 		border = &theme.border;
@@ -176,8 +173,9 @@ ltk_button_redraw_pixmap(ltk_button *button) {
 	}
 	rect.x = 0;
 	rect.y = 0;
-	ltk_fill_widget_rect(&button->widget, fill, rect);
-	ltk_draw_widget_rect(&button->widget, border, rect, bw);
+	ltk_surface_fill_rect(s, fill, rect);
+	if (bw > 0)
+		ltk_surface_draw_rect(s, border, (ltk_rect){bw / 2, bw / 2, rect.w - bw, rect.h - bw}, bw);
 
 	int text_w, text_h;
 	ltk_text_line_get_size(button->tl, &text_w, &text_h);
@@ -185,14 +183,14 @@ 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->widget.pixmap, window->gc, text_x, text_y, rect);
+	ltk_text_line_draw(button->tl, s, window->gc, text_x, text_y, rect);
 	button->widget.dirty = 0;
 }
 
 static void
 ltk_button_change_state(ltk_widget *self) {
 	ltk_button *button = (ltk_button *)self;
-	ltk_color *fill;
+	ltk_color *fill = NULL;
 	switch (button->widget.state) {
 	case LTK_NORMAL:
 		fill = &theme.fill;
@@ -209,7 +207,7 @@ ltk_button_change_state(ltk_widget *self) {
 	default:
 		ltk_fatal("No style found for button!\n");
 	}
-	ltk_text_line_render(button->tl, fill, &theme.text_color);
+	ltk_text_line_change_colors(button->tl, &theme.text_color, fill);
 	self->dirty = 1;
 }
 
@@ -228,7 +226,7 @@ ltk_button_create(ltk_window *window, const char *id, const char *text) {
 
 	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);
+	button->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1, &theme.text_color, &theme.fill);
 	int text_w, text_h;
 	ltk_text_line_get_size(button->tl, &text_w, &text_h);
 	button->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2;
@@ -248,6 +246,8 @@ ltk_button_destroy(ltk_widget *self, int shallow) {
 		ltk_warn("Tried to destroy NULL button.\n");
 		return;
 	}
+	/* FIXME: this should be generic part of widget */
+	ltk_surface_cache_release_key(self->surface_key);
 	ltk_text_line_destroy(button->tl);
 	ltk_remove_widget(button->widget.id);
 	ltk_free(button->widget.id);
diff --git a/src/graphics.c b/src/graphics.c
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
- *
- * Permission to use, copy, modify, and/or 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 <stdint.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->drawable, 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -17,22 +17,43 @@
 #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);
+#include <X11/Xft/Xft.h>
+#include "rect.h"
+#include "color.h"
+#include "ltk.h"
+
+typedef struct ltk_surface ltk_surface;
+
+/* FIXME: graphics context */
+ltk_surface *ltk_surface_create(ltk_window *window, int w, int h);
+void ltk_surface_destroy(ltk_surface *s);
+void ltk_surface_resize(ltk_surface *s, int w, int h);
+void ltk_surface_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y);
+void ltk_surface_copy_to_window(ltk_surface *src, ltk_window *dst, ltk_rect src_rect, int dst_x, int dst_y);
+void ltk_surface_get_size(ltk_surface *s, int *w, int *h);
 
-/* 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);
+/* The ltk_surface* and ltk_window* functions do the same things, but a window cannot
+   be wrapped in an ltk_surface since the resize function wouldn't make sense there */
+/* FIXME: avoid this ugliness */
+void ltk_surface_draw_rect(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width);
+void ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect);
+void ltk_window_draw_rect(ltk_window *window, ltk_color *c, ltk_rect rect, int line_width);
+void ltk_window_fill_rect(ltk_window *window, ltk_color *c, ltk_rect rect);
+
+/* TODO */
+/*
+void ltk_surface_draw_arc(ltk_surface *s, ltk_color *c, int x, int y, int w, int h, int angle1, int angle2, int line_width);
+void ltk_surface_fill_arc(ltk_surface *s, ltk_color *c, int x, int y, int w, int h, int angle1, int angle2);
+void ltk_surface_draw_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r, int line_width);
+void ltk_surface_fill_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r);
+*/
 
-/* 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);
+#if USE_PANGO == 1
+XftDraw *ltk_surface_get_xft_draw(ltk_surface *s);
+#endif
+/* FIXME: only expose this when needed */
+Pixmap ltk_surface_get_pixmap(ltk_surface *s);
 
 #endif /* _LTK_GRAPHICS_H_ */
diff --git a/src/graphics_xlib.c b/src/graphics_xlib.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2022 lumidify <nobody@lumidify.org>
+ *
+ * Permission to use, copy, modify, and/or 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 <stdint.h>
+
+#include "color.h"
+#include "rect.h"
+#include "widget.h"
+#include "ltk.h"
+#include "memory.h"
+
+struct ltk_surface {
+        int w, h;
+        ltk_window *window;
+        Pixmap p;
+        #if USE_PANGO == 1
+        XftDraw *xftdraw;
+        #endif
+};
+
+ltk_surface *
+ltk_surface_create(ltk_window *window, int w, int h) {
+	ltk_surface *s = ltk_malloc(sizeof(ltk_surface));
+	s->w = w;
+	s->h = h;
+	s->window = window;
+	s->p = XCreatePixmap(window->dpy, window->xwindow, w, h, window->depth);
+	#if USE_PANGO == 1
+	s->xftdraw = XftDrawCreate(window->dpy, s->p, window->vis, window->cm);
+	#endif
+	return s;
+}
+
+void
+ltk_surface_destroy(ltk_surface *s) {
+	#if USE_PANGO == 1
+	XftDrawDestroy(s->xftdraw);
+	#endif
+	XFreePixmap(s->window->dpy, s->p);
+	ltk_free(s);
+}
+
+void
+ltk_surface_resize(ltk_surface *s, int w, int h) {
+	s->w = w;
+	s->h = h;
+	XFreePixmap(s->window->dpy, s->p);
+	s->p = XCreatePixmap(s->window->dpy, s->window->xwindow, w, h, s->window->depth);
+	#if USE_PANGO == 1
+	XftDrawChange(s->xftdraw, s->p);
+	#endif
+}
+
+void
+ltk_surface_get_size(ltk_surface *s, int *w, int *h) {
+	*w = s->w;
+	*h = s->h;
+}
+
+void
+ltk_surface_draw_rect(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width) {
+	XSetForeground(s->window->dpy, s->window->gc, c->xcolor.pixel);
+	XSetLineAttributes(s->window->dpy, s->window->gc, line_width, LineSolid, CapButt, JoinMiter);
+	XDrawRectangle(s->window->dpy, s->p, s->window->gc, rect.x, rect.y, rect.w, rect.h);
+}
+
+void
+ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect) {
+	XSetForeground(s->window->dpy, s->window->gc, c->xcolor.pixel);
+	XFillRectangle(s->window->dpy, s->p, s->window->gc, rect.x, rect.y, rect.w, rect.h);
+}
+
+void
+ltk_window_draw_rect(ltk_window *window, ltk_color *c, ltk_rect rect, int line_width) {
+	XSetForeground(window->dpy, window->gc, c->xcolor.pixel);
+	XSetLineAttributes(window->dpy, window->gc, line_width, LineSolid, CapButt, JoinMiter);
+	XDrawRectangle(window->dpy, window->drawable, window->gc, rect.x, rect.y, rect.w, rect.h);
+}
+
+void
+ltk_window_fill_rect(ltk_window *window, ltk_color *c, ltk_rect rect) {
+	XSetForeground(window->dpy, window->gc, c->xcolor.pixel);
+	XFillRectangle(window->dpy, window->drawable, window->gc, rect.x, rect.y, rect.w, rect.h);
+}
+
+/* TODO */
+/*
+void
+ltk_surface_draw_arc(ltk_surface *s, ltk_color *c, int x, int y, int w, int h, int angle1, int angle2, int line_width) {
+}
+
+void
+ltk_surface_fill_arc(ltk_surface *s, ltk_color *c, int x, int y, int w, int h, int angle1, int angle2) {
+}
+
+void
+ltk_surface_draw_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r, int line_width) {
+}
+
+void
+ltk_surface_fill_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r) {
+}
+*/
+
+void
+ltk_surface_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y) {
+	XCopyArea(
+	    src->window->dpy, src->p, dst->p, src->window->gc,
+	    src_rect.x, src_rect.y, src_rect.w, src_rect.h, dst_x, dst_y
+	);
+}
+
+void
+ltk_surface_copy_to_window(ltk_surface *src, ltk_window *dst, ltk_rect src_rect, int dst_x, int dst_y) {
+	XCopyArea(
+	    src->window->dpy, src->p, dst->drawable, src->window->gc,
+	    src_rect.x, src_rect.y, src_rect.w, src_rect.h, dst_x, dst_y
+	);
+}
+
+#if USE_PANGO == 1
+XftDraw *
+ltk_surface_get_xft_draw(ltk_surface *s) {
+	return s->xftdraw;
+}
+#endif
+
+Pixmap
+ltk_surface_get_pixmap(ltk_surface *s) {
+	return s->p;
+}
diff --git a/src/grid.c b/src/grid.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, 2018, 2020, 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016, 2017, 2018, 2020, 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -66,7 +66,7 @@ static struct ltk_widget_vtable vtable = {
 	.motion_notify = <k_grid_motion_notify,
 	.type = LTK_GRID,
 	.needs_redraw = 0,
-	.needs_pixmap = 0
+	.needs_surface = 0
 };
 
 static int ltk_grid_cmd_add(
diff --git a/src/label.c b/src/label.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -32,19 +32,20 @@
 #include "text.h"
 #include "label.h"
 #include "graphics.h"
+#include "surface_cache.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 void ltk_label_redraw_surface(ltk_label *label, ltk_surface *s);
 
 static struct ltk_widget_vtable vtable = {
 	.draw = <k_label_draw,
 	.destroy = <k_label_destroy,
 	.type = LTK_LABEL,
 	.needs_redraw = 1,
-	.needs_pixmap = 1
+	.needs_surface = 1
 };
 
 static struct {
@@ -80,29 +81,26 @@ ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value) {
 static void
 ltk_label_draw(ltk_widget *self, ltk_rect clip) {
 	ltk_label *label = (ltk_label *)self;
-	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;
-	ltk_copy_clipped(self, clip_final);
+	ltk_surface *s;
+	if (!ltk_surface_cache_get_surface(self->surface_key, &s) || self->dirty)
+		ltk_label_redraw_surface(label, s);
+	ltk_surface_copy_to_window(s, self->window, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y);
 }
 
 static void
-ltk_label_redraw_pixmap(ltk_label *label) {
+ltk_label_redraw_surface(ltk_label *label, ltk_surface *s) {
 	ltk_rect r = label->widget.rect;
 	r.x = 0;
 	r.y = 0;
-	ltk_fill_widget_rect(&label->widget, &theme.bg_color, r);
+	ltk_surface_fill_rect(s, &theme.bg_color, r);
 
 	int text_w, text_h;
 	ltk_text_line_get_size(label->tl, &text_w, &text_h);
 	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);
+	ltk_text_line_draw(label->tl, s, label->widget.window->gc, text_x, text_y, r);
 }
 
 static ltk_label *
@@ -112,8 +110,7 @@ ltk_label_create(ltk_window *window, const char *id, const char *text) {
 
 	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);
-	ltk_text_line_render(label->tl, &window->theme.bg, &theme.text_color);
+	label->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1, &theme.text_color, &theme.bg_color);
 	int text_w, text_h;
 	ltk_text_line_get_size(label->tl, &text_w, &text_h);
 	label->widget.ideal_w = text_w + theme.pad * 2;
@@ -131,6 +128,7 @@ ltk_label_destroy(ltk_widget *self, int shallow) {
 		ltk_warn("Tried to destroy NULL label.\n");
 		return;
 	}
+	ltk_surface_cache_release_key(self->surface_key);
 	ltk_text_line_destroy(label->tl);
 	ltk_remove_widget(label->widget.id);
 	ltk_free(label->widget.id);
diff --git a/src/ltk.h b/src/ltk.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016, 2017, 2018, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -17,10 +17,13 @@
 #ifndef _LTK_H_
 #define _LTK_H_
 
-/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "widget.h" */
-
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
 #include <stdint.h>
 #include <X11/extensions/Xdbe.h>
+#include "color.h"
+#include "widget.h"
+#include "surface_cache.h"
 
 typedef enum {
 	LTK_EVENT_RESIZE = 1 << 0,
@@ -56,6 +59,7 @@ struct ltk_event_queue {
 typedef struct ltk_window {
 	Display *dpy;
 	Visual *vis;
+	ltk_surface_cache *surface_cache;
 	Colormap cm;
 	GC gc;
 	int screen;
diff --git a/src/ltkd.c b/src/ltkd.c
@@ -5,7 +5,7 @@
 /* FIXME: parsing doesn't work properly with bs? */
 /* FIXME: strip whitespace at end of lines in socket format */
 /*
- * Copyright (c) 2016, 2017, 2018, 2020, 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016, 2017, 2018, 2020, 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -50,7 +50,7 @@
 #include "util.h"
 #include "text.h"
 #include "grid.h"
-#include "draw.h"
+/* #include "draw.h" */
 #include "button.h"
 #include "label.h"
 #include "scrollbar.h"
@@ -614,6 +614,8 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
 	window->active_widget = NULL;
 	window->pressed_widget = NULL;
 
+	window->surface_cache = ltk_surface_cache_create(window);
+
 	ltk_init_text(window->theme.font, window->dpy, window->screen, window->cm);
 
 	window->other_event = <k_window_other_event;
@@ -1014,8 +1016,10 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) {
 			err = ltk_label_cmd(window, tokens, num_tokens, &errstr);
 		} else if (strcmp(tokens[0], "set-root-widget") == 0) {
 			err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errstr);
+/*
 		} else if (strcmp(tokens[0], "draw") == 0) {
 			err = ltk_draw_cmd(window, tokens, num_tokens, &errstr);
+*/
 		} else if (strcmp(tokens[0], "quit") == 0) {
 			ltk_quit(window);
 		} else if (strcmp(tokens[0], "destroy") == 0) {
diff --git a/src/memory.c b/src/memory.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -23,6 +23,7 @@
 #include <stdint.h>
 #include "color.h"
 #include "util.h"
+#include "memory.h"
 
 char *
 ltk_strdup_impl(const char *s) {
@@ -101,3 +102,38 @@ ltk_free_debug(void *ptr, const char *caller, const char *file, int line) {
 	fprintf(stderr, "DEBUG: free %p in %s (%s:%d)\n", ptr, caller, file, line);
 	free(ptr);
 }
+
+/*
+ * This (reallocarray) is from OpenBSD (adapted to exit on error):
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ */
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+ltk_reallocarray(void *optr, size_t nmemb, size_t size)
+{
+	if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    nmemb > 0 && SIZE_MAX / nmemb < size) {
+		ltk_fatal("Integer overflow in allocation.\n");
+	}
+	return ltk_realloc(optr, size * nmemb);
+}
+
+/* FIXME: maybe don't double when already very large? */
+/* FIXME: better start size when old == 0? */
+size_t
+ideal_array_size(size_t old, size_t needed) {
+	size_t ret = old;
+	if (old < needed)
+		ret = old * 2 > needed ? old * 2 : needed;
+	else if (needed * 4 < old)
+		ret = old / 2;
+	if (ret == 0)
+		ret = 1; /* not sure if this is necessary */
+	return ret;
+}
diff --git a/src/memory.h b/src/memory.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -45,5 +45,8 @@ void *ltk_malloc_debug(size_t size, const char *caller, const char *file, int li
 void *ltk_calloc_debug(size_t nmemb, size_t size, const char *caller, const char *file, int line);
 void *ltk_realloc_debug(void *ptr, size_t size, const char *caller, const char *file, int line);
 void ltk_free_debug(void *ptr, const char *caller, const char *file, int line);
+void *ltk_reallocarray(void *optr, size_t nmemb, size_t size);
+
+size_t ideal_array_size(size_t old, size_t needed);
 
 #endif /* _LTK_MEMORY_H_ */
diff --git a/src/rect.c b/src/rect.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -33,6 +33,11 @@ ltk_rect_intersect(ltk_rect r1, ltk_rect r2) {
 }
 
 ltk_rect
+ltk_rect_relative(ltk_rect parent, ltk_rect child) {
+	return (ltk_rect){child.x - parent.x, child.y - parent.y, child.w, child.h};
+}
+
+ltk_rect
 ltk_rect_union(ltk_rect r1, ltk_rect r2) {
 	ltk_rect u;
 	u.x = MIN(r1.x, r2.x);
diff --git a/src/rect.h b/src/rect.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -25,6 +25,7 @@ typedef struct {
 } ltk_rect;
 
 ltk_rect ltk_rect_intersect(ltk_rect r1, ltk_rect r2);
+ltk_rect ltk_rect_relative(ltk_rect parent, ltk_rect child);
 ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
 int ltk_collide_rect(ltk_rect rect, int x, int y);
 
diff --git a/src/scrollbar.c b/src/scrollbar.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -43,7 +43,7 @@ static struct ltk_widget_vtable vtable = {
 	.destroy = <k_scrollbar_destroy,
 	.type = LTK_UNKNOWN, /* FIXME */
 	.needs_redraw = 1,
-	.needs_pixmap = 1
+	.needs_surface = 1
 };
 
 static struct {
@@ -111,11 +111,10 @@ ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size) {
 
 static void
 ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) {
-	(void)clip; /* FIXME: actually use this */
+	/* FIXME: dirty attribute */
 	ltk_scrollbar *scrollbar = (ltk_scrollbar *)self;
-	ltk_color *bg, *fg;
+	ltk_color *bg = NULL, *fg = NULL;
 	int handle_x, handle_y, handle_w, handle_h;
-	ltk_window *window = scrollbar->widget.window;
 	ltk_rect rect = scrollbar->widget.rect;
 	switch (scrollbar->widget.state) {
 	case LTK_NORMAL:
@@ -137,33 +136,31 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) {
 	default:
 		ltk_fatal("No style found for current scrollbar state.\n");
 	}
-	XSetForeground(window->dpy, window->gc, bg->xcolor.pixel);
-	XFillRectangle(window->dpy, window->drawable, window->gc,
-	    rect.x, rect.y, rect.w, rect.h);
-	XSetForeground(window->dpy, window->gc, fg->xcolor.pixel);
+	ltk_surface *s;
+	ltk_surface_cache_get_surface(self->surface_key, &s);
+	ltk_surface_fill_rect(s, bg, (ltk_rect){0, 0, rect.w, rect.h});
 	/* FIXME: maybe too much calculation in draw function - move to
 	   resizing function? */
 	if (scrollbar->orient == LTK_HORIZONTAL) {
-		handle_y = rect.y;
+		handle_y = 0;
 		handle_h = rect.h;
-		handle_x = (int)(rect.x + (scrollbar->cur_pos / (double)scrollbar->virtual_size) * rect.w);
+		handle_x = (int)((scrollbar->cur_pos / (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_x = 0;
 		handle_w = rect.w;
-		handle_y = (int)(rect.y + (scrollbar->cur_pos / (double)scrollbar->virtual_size) * rect.h);
+		handle_y = (int)((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;
-	XFillRectangle(window->dpy, window->drawable, window->gc,
-	    handle_x, handle_y, handle_w, handle_h);
+	ltk_surface_fill_rect(s, fg, (ltk_rect){handle_x, handle_y, handle_w, handle_h});
+	ltk_rect clip_final = ltk_rect_intersect(clip, rect);
+	ltk_surface_copy_to_window(s, self->window, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y);
 }
 
 static int
@@ -252,6 +249,7 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback
 static void
 ltk_scrollbar_destroy(ltk_widget *self, int shallow) {
 	(void)shallow;
+	ltk_surface_cache_release_key(self->surface_key);
 	ltk_scrollbar *scrollbar = (ltk_scrollbar *)self;
 	ltk_free(scrollbar);
 }
diff --git a/src/surface_cache.c b/src/surface_cache.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2022 lumidify <nobody@lumidify.org>
+ *
+ * Permission to use, copy, modify, and/or 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 "surface_cache.h"
+#include "memory.h"
+
+/* FIXME: Implement a proper cache that isn't as stupid as this one. */
+
+#define MAX_CACHE_PIXELS (long)3145728; /* 3*1024*1024 */
+
+struct ltk_surface_cache_key {
+	ltk_surface_cache *parent_cache;
+	struct cache_surface *s; /* NULL if invalid */
+	int min_w;
+	int min_h;
+	int is_named;
+	ltk_widget_type widget_type;
+	int id;
+	unsigned int refcount;
+};
+
+struct named_cache_widget_entry {
+	ltk_surface_cache_key **entries;
+	size_t entries_num;
+	size_t entries_alloc;
+};
+
+/* FIXME: maybe optimization using pixmap sizes so pixmaps aren't constantly resized
+   -> somehow make sure already large pixmaps are reused by widgets needing large
+      - possibly add some sort of penalty for reassignment to a widget needing a
+	pixmap of drastically different size */
+
+struct cache_surface {
+	ltk_surface *s;
+	ltk_surface_cache_key *key; /* NULL if not assigned */
+};
+
+struct ltk_surface_cache {
+	/* FIXME: many widgets won't use named keys anyways, so this is a bit wasteful */
+	ltk_window *window;
+	struct named_cache_widget_entry named_keys[LTK_NUM_WIDGETS];
+	struct cache_surface **surfaces;
+	size_t surfaces_num; /* total number of stored surfaces */
+	size_t surfaces_realnum; /* number of currently assigned surfaces */
+	size_t surfaces_alloc;
+	size_t clock_pos;
+	long free_pixels;
+};
+
+/* Cache structure (cs == cache surface):
+ * |    assigned cs     | unassigned cs (but with valid ltk_surface) | NULL or cs with NULL ltk_surface |
+ * |--surfaces_realnum--|
+ * |--surfaces_num---------------------------------------------------|
+ * |--surfaces_alloc------------------------------------------------------------------------------------|
+ */
+
+ltk_surface_cache *
+ltk_surface_cache_create(ltk_window *window) {
+	ltk_surface_cache *sc = ltk_malloc(sizeof(ltk_surface_cache));
+	sc->window = window;
+	for (int i = 0; i < LTK_NUM_WIDGETS; i++) {
+		sc->named_keys[i].entries = NULL;
+		sc->named_keys[i].entries_num = 0;
+		sc->named_keys[i].entries_alloc = 0;
+	}
+	sc->surfaces = NULL;
+	sc->surfaces_num = sc->surfaces_alloc = 0;
+	sc->clock_pos = 0;
+	sc->free_pixels = MAX_CACHE_PIXELS;
+	return sc;
+}
+
+void
+ltk_surface_cache_destroy(ltk_surface_cache *cache) {
+	/* FIXME: maybe destroy keys as well in case they haven't been released?
+	   That would require to keep track of unnamed keys as well */
+	for (int i = 0; i < LTK_NUM_WIDGETS; i++) {
+		if (cache->named_keys[i].entries)
+			ltk_free(cache->named_keys[i].entries);
+	}
+	for (size_t i = 0; i < cache->surfaces_realnum; i++) {
+		ltk_surface_destroy(cache->surfaces[i]->s);
+		ltk_free(cache->surfaces[i]);
+	}
+	for (size_t i = cache->surfaces_realnum; i < cache->surfaces_alloc; i++) {
+		if (cache->surfaces[i])
+			ltk_free(cache->surfaces[i]);
+	}
+	ltk_free(cache->surfaces);
+	ltk_free(cache);
+}
+
+ltk_surface_cache_key *
+ltk_surface_cache_get_named_key(ltk_surface_cache *cache, ltk_widget_type type, int id, int min_w, int min_h) {
+	//TODO: ltk_assert(type < LTK_NUM_WIDGETS);
+	//TODO: ltk_assert(min_w > 0 && min_h > 0);
+	struct named_cache_widget_entry *e = &cache->named_keys[type];
+	/* FIXME: binary search */
+	for (size_t i = 0; i < e->entries_num; i++) {
+		if (e->entries[i]->id == id) {
+			/* FIXME: how to protect against overflow? */
+			e->entries[i]->refcount++;
+			return e->entries[i];
+		}
+	}
+	if (e->entries_num >= e->entries_alloc) {
+		e->entries_alloc = ideal_array_size(e->entries_alloc, e->entries_num + 1);
+		e->entries = ltk_reallocarray(e->entries, e->entries_alloc, sizeof(ltk_surface_cache_key *));
+	}
+	ltk_surface_cache_key *key = ltk_malloc(sizeof(ltk_surface_cache_key));
+	key->parent_cache = cache;
+	key->s = NULL;
+	key->min_w = min_w;
+	key->min_h = min_h;
+	key->is_named = 1;
+	key->widget_type = type;
+	key->id = id;
+	key->refcount = 1;
+	e->entries[e->entries_num] = key;
+	e->entries_num++;
+	return key;
+}
+
+ltk_surface_cache_key *
+ltk_surface_cache_get_unnamed_key(ltk_surface_cache *cache, int min_w, int min_h) {
+	ltk_surface_cache_key *key = ltk_malloc(sizeof(ltk_surface_cache_key));
+	key->parent_cache = cache;
+	key->s = NULL;
+	key->min_w = min_w;
+	key->min_h = min_h;
+	key->is_named = 0;
+	key->widget_type = LTK_UNKNOWN;
+	key->id = -1;
+	key->refcount = 1;
+	return key;
+}
+
+/* -> just sets to invalid and changes min_* so appropriate size is taken next time */
+/* -> cannot assume anything about the contents afterwards! (unless still valid) */
+void
+ltk_surface_cache_request_surface_size(ltk_surface_cache_key *key, int min_w, int min_h) {
+	key->min_w = min_w;
+	key->min_h = min_h;
+	if (key->s) {
+		int w, h;
+		ltk_surface_get_size(key->s->s, &w, &h);
+		if (w >= min_w && h >= min_h)
+			return;
+		ltk_surface_cache *c = key->parent_cache;
+		/* move to place for unused surfaces */
+		/* FIXME: any way to avoid searching through the cache? */
+		for (size_t i = 0; i < c->surfaces_num; i++) {
+			if (c->surfaces[i] == key->s) {
+				c->surfaces[i] = c->surfaces[c->surfaces_num - 1];
+				c->surfaces[c->surfaces_num - 1] = key->s;
+				c->surfaces_num--;
+				break;
+			}
+		}
+		key->s->key = NULL;
+		key->s = NULL;
+	}
+}
+
+
+/* returns 1 if key was valid, i.e. surface was already assigned, 0 otherwise */
+int
+ltk_surface_cache_get_surface(ltk_surface_cache_key *key, ltk_surface **s_ret) {
+	if (key->s) {
+		*s_ret = key->s->s;
+		return 1;
+	}
+
+	ltk_surface_cache *c = key->parent_cache;
+	/* FIXME: use generic array everywhere */
+	/* FIXME: make surface bigger than needed to avoid too much resizing */
+	if (c->surfaces_alloc == 0) {
+		c->surfaces_alloc = 4;
+		c->surfaces = ltk_malloc(c->surfaces_alloc * sizeof(struct cache_surface *));
+		for (size_t i = 1; i < c->surfaces_alloc; i++) {
+			c->surfaces[i] = NULL;
+		}
+		struct cache_surface *cs = ltk_malloc(sizeof(struct cache_surface));
+		cs->s = ltk_surface_create(c->window, key->min_w, key->min_h);
+		cs->key = key;
+		key->s = cs;
+		c->surfaces[0] = cs;
+		c->surfaces_num = 1;
+		c->surfaces_realnum = 1;
+		c->free_pixels -= (long)key->min_w * key->min_h;
+	} else if ((long)key->min_w * key->min_h <= c->free_pixels) {
+		if (c->surfaces_num == c->surfaces_realnum) {
+			if (c->surfaces_realnum == c->surfaces_alloc) {
+				size_t old = c->surfaces_alloc;
+				c->surfaces_alloc = ideal_array_size(c->surfaces_alloc, c->surfaces_realnum + 1);
+				c->surfaces = ltk_reallocarray(c->surfaces, c->surfaces_alloc, sizeof(struct cache_surface *));
+				for (size_t i = old; i < c->surfaces_alloc; i++) {
+					c->surfaces[i] = NULL;
+				}
+			}
+			if (c->surfaces[c->surfaces_num] == NULL)
+				c->surfaces[c->surfaces_num] = ltk_malloc(sizeof(struct cache_surface));
+			struct cache_surface *cs = c->surfaces[c->surfaces_num];
+			c->surfaces_num++;
+			c->surfaces_realnum++;
+			cs->s = ltk_surface_create(c->window, key->min_w, key->min_h);
+			cs->key = key;
+			key->s = cs;
+			c->free_pixels -= (long)key->min_w * key->min_h;
+		} else if (c->surfaces_num < c->surfaces_realnum) {
+			struct cache_surface *cs = c->surfaces[c->surfaces_num];
+			cs->key = key;
+			key->s = cs;
+			int w, h;
+			ltk_surface_get_size(cs->s, &w, &h);
+			if (w < key->min_w  || h < key->min_h) {
+				ltk_surface_resize(cs->s, key->min_w, key->min_h);
+				c->free_pixels -= (long)key->min_w * key->min_h - (long)w * h;
+			}
+			c->surfaces_num++;
+		} else {
+			/* FIXME: ERROR!!! */
+		}
+	} else {
+		int w, h;
+		/* First try to delete all currently unused surfaces. */
+		/* c->surfaces_num could be 0! */
+		for (size_t i = c->surfaces_realnum; i > c->surfaces_num; i--) {
+			ltk_surface_get_size(c->surfaces[i-1]->s, &w, &h);
+			if ((long)key->min_w * key->min_h <= c->free_pixels + w * h) {
+				struct cache_surface *cs = c->surfaces[c->surfaces_num];
+				c->surfaces[c->surfaces_num] = c->surfaces[i-1];
+				c->surfaces[i-1] = cs;
+				cs = c->surfaces[c->surfaces_num];
+				cs->key = key;
+				key->s = cs;
+				if (w < key->min_w  || h < key->min_h) {
+					ltk_surface_resize(cs->s, key->min_w, key->min_h);
+					c->free_pixels -= (long)key->min_w * key->min_h - (long)w * h;
+				}
+				c->surfaces_num++;
+				break;
+			} else {
+				ltk_surface_destroy(c->surfaces[i-1]->s);
+				/* setting this to NULL isn't actually necessary, but it
+				   might help with debugging in certain cases */
+				c->surfaces[i-1]->s = NULL;
+				c->surfaces_realnum--;
+				c->free_pixels += (long)w * h;
+			}
+		}
+
+		/* That didn't work, so start deleting or taking over assigned surfaces. */
+		if (!key->s) {
+			while (c->surfaces_num > 0) {
+				c->clock_pos %= c->surfaces_num;
+				struct cache_surface *cs = c->surfaces[c->clock_pos];
+				ltk_surface_get_size(cs->s, &w, &h);
+				if ((long)key->min_w * key->min_h <= c->free_pixels + w * h) {
+					cs->key->s = NULL;
+					cs->key = key;
+					key->s = cs;
+					if (w < key->min_w  || h < key->min_h) {
+						ltk_surface_resize(cs->s, key->min_w, key->min_h);
+						c->free_pixels -= (long)key->min_w * key->min_h - (long)w * h;
+					}
+					c->clock_pos++;
+					break;
+				} else {
+					/* FIXME: This cache architecture really needs to be changed. The whole
+					   purpose of the clock_pos is defeated by switching entries around here.
+					   It would be possible to change that with memmove, but that would be
+					   more inefficient (although it probably wouldn't be too bad since the
+					   cache shouldn't be too big anyways). It's probably stupid to separate
+					   the different parts of the cache as is currently done. */
+					c->surfaces[c->clock_pos] = c->surfaces[c->surfaces_num - 1];
+					c->surfaces[c->surfaces_num - 1] = cs;
+					cs->key->s = NULL;
+					ltk_surface_destroy(cs->s);
+					cs->s = NULL;
+					cs->key = NULL;
+					c->surfaces_realnum--;
+					c->surfaces_num--;
+					c->free_pixels += (long)w * h;
+				}
+			}
+		}
+
+		/* The needed surface contains more pixels than the maximum allowed amount.
+		   In this case, just create a surface of that size, but it will be the only
+		   surface in the cache. */
+		/* c->free_pixels should be the maximum amount again here, otherwise there is a bug! */
+		/* TODO: ltk_assert(c->free_pixels == MAX_CACHE_PIXELS); */
+		if (!key->s) {
+			/* this should be impossible */
+			if (!c->surfaces[0])
+				c->surfaces[0] = ltk_malloc(sizeof(struct cache_surface));
+			struct cache_surface *cs = c->surfaces[0];
+			cs->s = ltk_surface_create(c->window, key->min_w, key->min_h);
+			cs->key = key;
+			key->s = cs;
+			c->surfaces_num = 1;
+			c->surfaces_realnum = 1;
+			c->free_pixels -= (long)key->min_w * key->min_h;
+		}
+	}
+	*s_ret = key->s->s;
+	return 0;
+}
+
+void
+ltk_surface_cache_release_key(ltk_surface_cache_key *key) {
+	int destroy = 0;
+	if (key->is_named) {
+		if (key->refcount > 0)
+			key->refcount--;
+		if (key->refcount == 0) {
+			struct named_cache_widget_entry *e = &key->parent_cache->named_keys[key->widget_type];
+			for (size_t i = 0; i < e->entries_num; i++) {
+				if (e->entries[i]->id == key->id) {
+					e->entries[i] = e->entries[e->entries_num - 1];
+					e->entries_num--;
+					break;
+				}
+			}
+			destroy = 1;
+		}
+	}
+	if (!key->is_named || destroy) {
+		if (key->s) {
+			key->s->key = NULL;
+			ltk_surface_cache *c = key->parent_cache;
+			/* move to place for unused surfaces */
+			/* FIXME: any way to avoid searching through the cache? */
+			for (size_t i = 0; i < c->surfaces_num; i++) {
+				if (c->surfaces[i] == key->s) {
+					c->surfaces[i] = c->surfaces[c->surfaces_num - 1];
+					c->surfaces[c->surfaces_num - 1] = key->s;
+					c->surfaces_num--;
+					break;
+				}
+			}
+		}
+
+		ltk_free(key);
+	}
+}
diff --git a/src/surface_cache.h b/src/surface_cache.h
@@ -0,0 +1,31 @@
+#ifndef _LTK_SURFACE_CACHE_H_
+#define _LTK_SURFACE_CACHE_H_
+
+/* FIXME: It would probably be much better to just have a named cache
+   and then pass a single surface around while drawing other widgets */
+/* FIXME: some sort of "locking" for surfaces so they temporarily can't
+   be reassigned? (e.g. when drawing widget hierarchy) */
+
+typedef struct ltk_surface_cache ltk_surface_cache;
+typedef struct ltk_surface_cache_key ltk_surface_cache_key;
+
+#include "widget.h"
+#include "ltk.h"
+#include "graphics.h"
+
+ltk_surface_cache *ltk_surface_cache_create(ltk_window *window);
+void ltk_surface_cache_destroy(ltk_surface_cache *cache);
+ltk_surface_cache_key *ltk_surface_cache_get_named_key(ltk_surface_cache *cache, ltk_widget_type type, int id, int min_w, int min_h);
+ltk_surface_cache_key *ltk_surface_cache_get_unnamed_key(ltk_surface_cache *cache, int min_w, int min_h);
+
+/* WARNING: DO NOT RESIZE SURFACES MANUALLY, ALWAYS USE ltk_surface_cache_request_surface_size! */
+void ltk_surface_cache_request_surface_size(ltk_surface_cache_key *key, int min_w, int min_h);
+/* -> just sets to invalid and changes min_* so appropriate size is taken next time */
+/* -> cannot assume anything about the contents afterwards! */
+
+/* returns 1 if key was valid, i.e. surface was already assigned, 0 otherwise */
+int ltk_surface_cache_get_surface(ltk_surface_cache_key *key, ltk_surface **s_ret);
+
+void ltk_surface_cache_release_key(ltk_surface_cache_key *key);
+
+#endif /* _LTK_SURFACE_CACHE_H_ */
diff --git a/src/text.h b/src/text.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -18,17 +18,20 @@
 #define _LTK_TEXT_H_
 
 #include "color.h"
+#include "graphics.h"
 
 typedef struct ltk_text_line ltk_text_line;
 
+/* FIXME: remove x-specific stuff from interface */
 void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm);
 void ltk_cleanup_text(void);
-ltk_text_line *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width);
-void ltk_text_line_render(ltk_text_line *tl, ltk_color *bg, ltk_color *fg);
-void ltk_text_line_draw(ltk_text_line *tl, Drawable d, GC gc, int x, int y, ltk_rect clip);
+ltk_text_line *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width, ltk_color *fg, ltk_color *bg);
+/* FIXME: either implement clip rect or remove it from arguments */
+void ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, GC gc, int x, int y, ltk_rect clip);
 void ltk_text_line_set_width(ltk_text_line *tl, int width);
 void ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h);
 void ltk_text_line_destroy(ltk_text_line *tl);
+void ltk_text_line_change_colors(ltk_text_line *tl, ltk_color *fg, ltk_color *bg);
 
 #if USE_PANGO == 1
   #include <pango/pangoxft.h>
diff --git a/src/text_pango.c b/src/text_pango.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -41,8 +41,8 @@ struct ltk_text_line {
 	int h;
 	Window window;
 	PangoLayout *layout;
-	XftDraw *draw;
-	Pixmap pixmap;
+	ltk_color *fg;
+	ltk_color *bg;
 };
 
 struct {
@@ -73,25 +73,24 @@ ltk_cleanup_text(void) {
 void
 ltk_text_line_set_width(ltk_text_line *tl, int width) {
 	pango_layout_set_width(tl->layout, width * PANGO_SCALE);
+	/* FIXME: this is very inefficient because it immediately
+	   accesses the size, forcing pango to recalculate it even
+	   if it may never be needed before e.g. changing text */
 	pango_layout_get_size(tl->layout, &tl->w, &tl->h);
 	tl->w /= PANGO_SCALE;
 	tl->h /= PANGO_SCALE;
 	tl->w = tl->w == 0 ? 1 : tl->w;
 	tl->h = tl->h == 0 ? 1 : tl->h;
-	/* FIXME: make this nicer */
-	if (tl->draw) {
-		XftDrawDestroy(tl->draw);
-		XFreePixmap(tm.dpy, tl->pixmap);
-	}
-	XWindowAttributes attrs;
-	XGetWindowAttributes(tm.dpy, tl->window, &attrs);
-	/* FIXME: use visual from ltk_window */
-	tl->pixmap = XCreatePixmap(tm.dpy, tl->window, tl->w, tl->h, attrs.depth);
-	tl->draw = XftDrawCreate(tm.dpy, tl->pixmap, DefaultVisual(tm.dpy, tm.screen), tm.cm);
+}
+
+void
+ltk_text_line_change_colors(ltk_text_line *tl, ltk_color *fg, ltk_color *bg) {
+	tl->fg = fg;
+	tl->bg = bg;
 }
 
 ltk_text_line *
-ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) {
+ltk_text_line_create(Window window, uint16_t font_size, char *text, int width, ltk_color *fg, ltk_color *bg) {
 	if (!tm.context)
 		ltk_fatal("ltk_text_line_create (pango): text not initialized yet");
 	ltk_text_line *line = ltk_malloc(sizeof(ltk_text_line));
@@ -104,24 +103,22 @@ ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) {
 	pango_layout_set_font_description(line->layout, desc);
 	pango_font_description_free(desc);
 	line->window = window;
-	line->draw = NULL;
 	pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR);
 	pango_layout_set_text(line->layout, text, -1);
 	ltk_text_line_set_width(line, width);
+	line->fg = fg;
+	line->bg = bg;
 
 	return line;
 }
 
+/* FIXME: bg isn't used right now */
 void
-ltk_text_line_render(ltk_text_line *tl, ltk_color *bg, ltk_color *fg) {
-	XftDrawRect(tl->draw, &bg->xftcolor, 0, 0, tl->w, tl->h);
-	pango_xft_render_layout(tl->draw, &fg->xftcolor, tl->layout, 0, 0);
-}
-
-void
-ltk_text_line_draw(ltk_text_line *tl, Drawable d, GC gc, int x, int y, ltk_rect clip) {
+ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, GC gc, int x, int y, ltk_rect clip) {
 	(void)clip; /* FIXME: use this */
-	XCopyArea(tm.dpy, tl->pixmap, d, gc, 0, 0, tl->w, tl->h, x, y);
+	(void)gc;
+	XftDraw *d = ltk_surface_get_xft_draw(s);
+	pango_xft_render_layout(d, &tl->fg->xftcolor, tl->layout, x * PANGO_SCALE, y * PANGO_SCALE);
 }
 
 void
@@ -133,8 +130,6 @@ ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h) {
 void
 ltk_text_line_destroy(ltk_text_line *tl) {
 	g_object_unref(tl->layout);
-	XftDrawDestroy(tl->draw);
-	XFreePixmap(tm.dpy, tl->pixmap);
 	ltk_free(tl->text);
 	ltk_free(tl);
 }
diff --git a/src/text_stb.c b/src/text_stb.c
@@ -1,5 +1,7 @@
+/* FIXME: more dirty flags; cache ximages so not too much ram is used
+   when a lot of text is displayed */
 /*
- * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2017, 2018, 2020, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -86,6 +88,9 @@ struct ltk_text_line {
 	int x_min;
 	int y_min;
 	uint16_t font_size;
+	int dirty;
+	ltk_color *fg;
+	ltk_color *bg;
 };
 
 /* Hash definitions */
@@ -474,12 +479,8 @@ ltk_unref_glyphs(ltk_glyph *glyphs, int num_glyphs) {
 
 /* FIXME: Error checking that tm has been initialized */
 
-static XImage *
-ltk_create_ximage(int w, int h, int depth, XColor bg) {
-	XImage *img = XCreateImage(tm.dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0);
-	img->data = ltk_calloc(img->bytes_per_line, img->height);
-	XInitImage(img);
-
+static void
+ltk_fill_ximage(XImage *img, int w, int h, XColor bg) {
 	int b;
 	for (int i = 0; i < h; i++) {
 		b = img->bytes_per_line * i;
@@ -490,11 +491,20 @@ ltk_create_ximage(int w, int h, int depth, XColor bg) {
 			b++;
 		}
 	}
+}
+
+static XImage *
+ltk_create_ximage(int w, int h, int depth, XColor bg) {
+	XImage *img = XCreateImage(tm.dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0);
+	img->data = ltk_calloc(img->bytes_per_line, img->height);
+	XInitImage(img);
+	ltk_fill_ximage(img, w, h, bg);
 
 	return img;
 }
 
 /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
+/* FIXME: make this work with other pixel representations */
 static void
 ltk_text_line_draw_glyph(ltk_glyph *glyph, int x, int y, XImage *img, XColor fg) {
 	double a;
@@ -554,19 +564,24 @@ ltk_text_line_break_lines(ltk_text_line *tl) {
 	tl->h = tl->line_h * tl->lines;
 }
 
-void
+static void
 ltk_text_line_render(
 	ltk_text_line *tl,
 	ltk_color *bg,
 	ltk_color *fg)
 {
+	/* FIXME: just keep reference to ltk_window so this isn't necessary */
 	XWindowAttributes attrs;
 	XGetWindowAttributes(tm.dpy, tl->window, &attrs);
 	int depth = attrs.depth;
-	/* FIXME: if old image has same or smaller dimensions, just clear it */
-	if (tl->img)
-		XDestroyImage(tl->img);
-	tl->img = ltk_create_ximage(tl->w, tl->h, depth, bg->xcolor);
+	/* FIXME: maybe don't destroy if old image is big enough? */
+	if (!tl->img || tl->img->width != tl->w || tl->img->height != tl->h) {
+		if (tl->img)
+			XDestroyImage(tl->img);
+		tl->img = ltk_create_ximage(tl->w, tl->h, depth, bg->xcolor);
+	} else {
+		ltk_fill_ximage(tl->img, tl->w, tl->h, bg->xcolor);
+	}
 
 	int last_break = 0;
 	for (int i = 0; i < tl->lines; i++) {
@@ -582,12 +597,27 @@ ltk_text_line_render(
 		}
 		last_break = next_break;
 	}
+	tl->dirty = 0;
+}
+
+/* FIXME: improve color handling - where passed as pointer, where as value?
+   In certain cases, it's important to deallocate the color in the end
+   (if the x server doesn't support truecolor - I'm not sure right now if
+   this is the right terminology, but it's something like that) */
+void
+ltk_text_line_change_colors(ltk_text_line *tl, ltk_color *fg, ltk_color *bg) {
+	tl->fg = fg;
+	tl->bg = bg;
+	tl->dirty = 1;
+
 }
 
 /* FIXME: error checking if img is rendered yet, tm initialized, etc. */
 void
-ltk_text_line_draw(ltk_text_line *tl, Drawable d, GC gc, int x, int y, ltk_rect clip) {
+ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, GC gc, int x, int y, ltk_rect clip) {
 	(void)clip;
+	if (tl->dirty)
+		ltk_text_line_render(tl, tl->bg, tl->fg);
 	/*
 	int xoff = clip.x - x;
 	int yoff = clip.y - y;
@@ -597,7 +627,8 @@ ltk_text_line_draw(ltk_text_line *tl, Drawable d, GC gc, int x, int y, ltk_rect 
 	int h = clip.h > tl->h - yoff ? tl->h - yoff : clip.h;
 	XPutImage(tm.dpy, tl->window, gc, tl->img, xoff, yoff, x + xoff, y + yoff, w, h);
 	*/
-	XPutImage(tm.dpy, d, gc, tl->img, 0, 0, x, y, tl->w, tl->h);
+	Pixmap p = ltk_surface_get_pixmap(s);
+	XPutImage(tm.dpy, p, gc, tl->img, 0, 0, x, y, tl->w, tl->h);
 }
 
 void
@@ -606,6 +637,7 @@ ltk_text_line_set_width(ltk_text_line *tl, int width) {
 	tl->w_max = width;
 	tl->w = width;
 	ltk_text_line_break_lines(tl);
+	tl->dirty = 1;
 }
 
 void
@@ -627,7 +659,7 @@ ltk_text_line_create_glyphs(ltk_text_line *tl) {
 }
 
 ltk_text_line *
-ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) {
+ltk_text_line_create(Window window, uint16_t font_size, char *text, int width, ltk_color *fg, ltk_color *bg) {
 	ltk_text_line *line = ltk_malloc(sizeof(ltk_text_line));
 	line->window = window;
 	line->img = NULL;
@@ -640,11 +672,15 @@ ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) {
 	line->lines_alloc = line->lines = 0;
 	line->line_indeces = NULL;
 	ltk_text_line_break_lines(line);
+	line->dirty = 1;
+	line->fg = fg;
+	line->bg = bg;
 	return line;
 }
 
 void
 ltk_text_line_destroy(ltk_text_line *tl) {
+	XDestroyImage(tl->img);
 	ltk_free(tl->text);
 	/* FIXME: Reference count glyph infos */
 	ltk_free(tl->glyphs);
diff --git a/src/widget.c b/src/widget.c
@@ -1,8 +1,7 @@
-/* FIXME: pixmap cache */
 /* FIXME: store coordinates relative to parent widget */
 /* FIXME: Destroy function for widget to destroy pixmap! */
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -30,6 +29,7 @@
 #include "memory.h"
 #include "util.h"
 #include "khash.h"
+#include "surface_cache.h"
 
 static void ltk_destroy_widget_hash(void);
 
@@ -71,10 +71,8 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
 	widget->window = window;
 	widget->parent = NULL;
 
-	widget->pix_w = w;
-	widget->pix_h = h;
-	if (vtable->needs_pixmap)
-		widget->pixmap = XCreatePixmap(window->dpy, window->drawable, w, h, window->depth);
+	if (vtable->needs_surface)
+		widget->surface_key = ltk_surface_cache_get_unnamed_key(window->surface_cache, w, h);
 
 	/* FIXME: possibly check that draw and destroy aren't NULL */
 	widget->vtable = vtable;
@@ -94,27 +92,16 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
 	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) {
+	/* FIXME: should surface maybe be resized first? */
 	if (widget->vtable->resize)
 		widget->vtable->resize(widget);
-	if (!widget->vtable->needs_pixmap)
+	if (!widget->vtable->needs_surface)
 		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->drawable, new_w, new_h, w->depth);
+	ltk_surface_cache_request_surface_size(widget->surface_key, widget->rect.w, widget->rect.h);
 	widget->dirty = 1;
 }
 
diff --git a/src/widget.h b/src/widget.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,11 +14,17 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "rect.h" */
-
 #ifndef _LTK_WIDGET_H_
 #define _LTK_WIDGET_H_
 
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include "rect.h"
+
+/* FIXME: SORT OUT INCLUDES PROPERLY! */
+
+typedef struct ltk_widget ltk_widget;
+
 typedef enum {
 	LTK_STICKY_LEFT = 1 << 0,
 	LTK_STICKY_RIGHT = 1 << 1,
@@ -40,28 +46,29 @@ typedef enum {
 
 typedef enum {
 	/* for e.g. scrollbar, which can't be directly accessed by users */
-	LTK_UNKNOWN,
+	LTK_UNKNOWN = 0,
 	LTK_GRID,
 	LTK_BUTTON,
 	LTK_DRAW,
 	LTK_LABEL,
 	LTK_WIDGET,
-	LTK_BOX
+	LTK_BOX,
+	LTK_NUM_WIDGETS
 } ltk_widget_type;
 
+#include "surface_cache.h"
+
 struct ltk_window;
 
 struct ltk_widget_vtable;
 
-typedef struct ltk_widget {
+struct ltk_widget {
 	struct ltk_window *window;
 	struct ltk_widget *parent;
 	char *id;
 
+	ltk_surface_cache_key *surface_key;
 	struct ltk_widget_vtable *vtable;
-	Pixmap pixmap;
-	int pix_w;
-	int pix_h;
 
 	ltk_rect rect;
 	unsigned int ideal_w;
@@ -74,7 +81,7 @@ typedef struct ltk_widget {
 	unsigned short row_span;
 	unsigned short column_span;
 	char dirty;
-} ltk_widget;
+};
 
 struct ltk_widget_vtable {
 	void (*key_press) (struct ltk_widget *, XEvent);
@@ -96,7 +103,7 @@ struct ltk_widget_vtable {
 
 	ltk_widget_type type;
 	char needs_redraw;
-	char needs_pixmap;
+	char needs_surface;
 };
 
 int ltk_widget_destroy(struct ltk_window *window, char **tokens, size_t num_tokens, char **errstr);