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);