URI: 
       tAdd hilariously bad pixmap cache - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 00cefe189580f8332951122af1f6d6230d9652c3
   DIR parent adc45f7ba5398d573a017db1a1b7c9044d3a3455
  HTML 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:
         M LICENSE                             |       2 +-
         M Makefile                            |      14 ++++++++------
         M src/box.c                           |       5 +++--
         M src/button.c                        |      38 ++++++++++++++++----------------
         D src/graphics.c                      |      58 ------------------------------
         M src/graphics.h                      |      49 ++++++++++++++++++++++---------
         A src/graphics_xlib.c                 |     146 +++++++++++++++++++++++++++++++
         M src/grid.c                          |       4 ++--
         M src/label.c                         |      28 +++++++++++++---------------
         M src/ltk.h                           |      10 +++++++---
         M src/ltkd.c                          |       8 ++++++--
         M src/memory.c                        |      38 ++++++++++++++++++++++++++++++-
         M src/memory.h                        |       5 ++++-
         M src/rect.c                          |       7 ++++++-
         M src/rect.h                          |       3 ++-
         M src/scrollbar.c                     |      32 +++++++++++++++----------------
         A src/surface_cache.c                 |     360 +++++++++++++++++++++++++++++++
         A src/surface_cache.h                 |      31 +++++++++++++++++++++++++++++++
         M src/text.h                          |      11 +++++++----
         M src/text_pango.c                    |      45 ++++++++++++++-----------------
         M src/text_stb.c                      |      66 ++++++++++++++++++++++++-------
         M src/widget.c                        |      27 +++++++--------------------
         M src/widget.h                        |      29 ++++++++++++++++++-----------
       
       23 files changed, 798 insertions(+), 218 deletions(-)
       ---
   DIR diff --git a/LICENSE b/LICENSE
       t@@ -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
   DIR diff --git a/Makefile b/Makefile
       t@@ -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
       t@@ -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
       t@@ -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 \
       t@@ -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)
   DIR diff --git a/src/box.c b/src/box.c
       t@@ -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
       t@@ -57,7 +57,7 @@ static struct ltk_widget_vtable vtable = {
                .mouse_release = &ltk_box_mouse_release,
                .motion_notify = &ltk_box_motion_notify,
                .needs_redraw = 0,
       -        .needs_pixmap = 0,
       +        .needs_surface = 0,
                .type = LTK_BOX
        };
        
       t@@ -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
   DIR diff --git a/src/button.c b/src/button.c
       t@@ -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
       t@@ -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);
       t@@ -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 = &ltk_button_mouse_release,
       t@@ -48,7 +49,7 @@ static struct ltk_widget_vtable vtable = {
            .destroy = &ltk_button_destroy,
            .type = LTK_BUTTON,
            .needs_redraw = 1,
       -    .needs_pixmap = 1
       +    .needs_surface = 1
        };
        
        static struct {
       t@@ -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;
       t@@ -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);
       t@@ -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;
       t@@ -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;
        }
        
       t@@ -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;
       t@@ -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);
   DIR diff --git a/src/graphics.c b/src/graphics.c
       t@@ -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
       -        );
       -}
   DIR diff --git a/src/graphics.h b/src/graphics.h
       t@@ -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
       t@@ -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_ */
   DIR diff --git a/src/graphics_xlib.c b/src/graphics_xlib.c
       t@@ -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;
       +}
   DIR diff --git a/src/grid.c b/src/grid.c
       t@@ -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
       t@@ -66,7 +66,7 @@ static struct ltk_widget_vtable vtable = {
                .motion_notify = &ltk_grid_motion_notify,
                .type = LTK_GRID,
                .needs_redraw = 0,
       -        .needs_pixmap = 0
       +        .needs_surface = 0
        };
        
        static int ltk_grid_cmd_add(
   DIR diff --git a/src/label.c b/src/label.c
       t@@ -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
       t@@ -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 = &ltk_label_draw,
                .destroy = &ltk_label_destroy,
                .type = LTK_LABEL,
                .needs_redraw = 1,
       -        .needs_pixmap = 1
       +        .needs_surface = 1
        };
        
        static struct {
       t@@ -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 *
       t@@ -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;
       t@@ -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);
   DIR diff --git a/src/ltk.h b/src/ltk.h
       t@@ -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
       t@@ -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,
       t@@ -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;
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -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
       t@@ -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"
       t@@ -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 = &ltk_window_other_event;
       t@@ -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) {
   DIR diff --git a/src/memory.c b/src/memory.c
       t@@ -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
       t@@ -23,6 +23,7 @@
        #include <stdint.h>
        #include "color.h"
        #include "util.h"
       +#include "memory.h"
        
        char *
        ltk_strdup_impl(const char *s) {
       t@@ -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;
       +}
   DIR diff --git a/src/memory.h b/src/memory.h
       t@@ -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
       t@@ -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_ */
   DIR diff --git a/src/rect.c b/src/rect.c
       t@@ -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
       t@@ -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);
   DIR diff --git a/src/rect.h b/src/rect.h
       t@@ -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
       t@@ -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);
        
   DIR diff --git a/src/scrollbar.c b/src/scrollbar.c
       t@@ -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
       t@@ -43,7 +43,7 @@ static struct ltk_widget_vtable vtable = {
                .destroy = &ltk_scrollbar_destroy,
                .type = LTK_UNKNOWN, /* FIXME */
                .needs_redraw = 1,
       -        .needs_pixmap = 1
       +        .needs_surface = 1
        };
        
        static struct {
       t@@ -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:
       t@@ -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
       t@@ -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);
        }
   DIR diff --git a/src/surface_cache.c b/src/surface_cache.c
       t@@ -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);
       +        }
       +}
   DIR diff --git a/src/surface_cache.h b/src/surface_cache.h
       t@@ -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_ */
   DIR diff --git a/src/text.h b/src/text.h
       t@@ -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
       t@@ -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>
   DIR diff --git a/src/text_pango.c b/src/text_pango.c
       t@@ -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
       t@@ -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 {
       t@@ -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));
       t@@ -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
       t@@ -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);
        }
   DIR diff --git a/src/text_stb.c b/src/text_stb.c
       t@@ -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
       t@@ -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 */
       t@@ -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;
       t@@ -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;
       t@@ -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++) {
       t@@ -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;
       t@@ -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
       t@@ -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
       t@@ -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;
       t@@ -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);
   DIR diff --git a/src/widget.c b/src/widget.c
       t@@ -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
       t@@ -30,6 +29,7 @@
        #include "memory.h"
        #include "util.h"
        #include "khash.h"
       +#include "surface_cache.h"
        
        static void ltk_destroy_widget_hash(void);
        
       t@@ -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;
       t@@ -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;
        }
        
   DIR diff --git a/src/widget.h b/src/widget.h
       t@@ -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
       t@@ -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,
       t@@ -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;
       t@@ -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);
       t@@ -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);