ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

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:
MLICENSE | 2+-
MMakefile | 14++++++++------
Msrc/box.c | 5+++--
Msrc/button.c | 38+++++++++++++++++++-------------------
Dsrc/graphics.c | 58----------------------------------------------------------
Msrc/graphics.h | 49+++++++++++++++++++++++++++++++++++--------------
Asrc/graphics_xlib.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/grid.c | 4++--
Msrc/label.c | 28+++++++++++++---------------
Msrc/ltk.h | 10+++++++---
Msrc/ltkd.c | 8++++++--
Msrc/memory.c | 38+++++++++++++++++++++++++++++++++++++-
Msrc/memory.h | 5++++-
Msrc/rect.c | 7++++++-
Msrc/rect.h | 3++-
Msrc/scrollbar.c | 32+++++++++++++++-----------------
Asrc/surface_cache.c | 360+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/surface_cache.h | 31+++++++++++++++++++++++++++++++
Msrc/text.h | 11+++++++----
Msrc/text_pango.c | 45++++++++++++++++++++-------------------------
Msrc/text_stb.c | 66+++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/widget.c | 27+++++++--------------------
Msrc/widget.h | 29++++++++++++++++++-----------
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 = &ltk_box_mouse_release, .motion_notify = &ltk_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 = &ltk_button_mouse_release, @@ -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 { @@ -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 = &ltk_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 = &ltk_label_draw, .destroy = &ltk_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 = &ltk_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 = &ltk_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);