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 d1f0903f23c0887590329483d8993cbefb1b6189
parent 00cefe189580f8332951122af1f6d6230d9652c3
Author: lumidify <nobody@lumidify.org>
Date:   Thu,  5 May 2022 13:14:22 +0200

Slightly improve graphics and text interface

Diffstat:
MMakefile | 3++-
Msrc/box.h | 4+++-
Msrc/button.c | 37++++++-------------------------------
Msrc/color.h | 8+++++---
Asrc/compat.h | 9+++++++++
Msrc/graphics.h | 29++++++++++++++++-------------
Msrc/graphics_xlib.c | 95++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/label.c | 12+++++-------
Msrc/ltk.h | 6++++++
Msrc/ltkd.c | 9+++++----
Msrc/scrollbar.c | 8++++----
Msrc/text.h | 37+++++++++++++++++++++++++++----------
Msrc/text_pango.c | 111++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/text_stb.c | 254++++++++++++++++++++++++++++++++++++-------------------------------------------
14 files changed, 311 insertions(+), 311 deletions(-)

diff --git a/Makefile b/Makefile @@ -18,7 +18,8 @@ LDFLAGS += -lm `pkg-config --libs x11 fontconfig xext` # Note: this macro magic for debugging and pango rendering seems ugly; it should probably be changed # debug -DEV_1 = -g -Wall -Werror -Wextra -pedantic +DEV_1 = -g -Wall -Wextra -pedantic +#-Werror # stb rendering EXTRA_OBJ_0 = src/stb_truetype.o src/text_stb.o diff --git a/src/box.h b/src/box.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 @@ -22,6 +22,8 @@ typedef struct { ltk_widget widget; ltk_scrollbar *sc; + ltk_widget *pressed_widget; + ltk_widget *active_widget; ltk_widget **widgets; size_t num_alloc; size_t num_widgets; diff --git a/src/button.c b/src/button.c @@ -37,7 +37,7 @@ static void ltk_button_draw(ltk_widget *self, ltk_rect clip); static int ltk_button_mouse_release(ltk_widget *self, XEvent event); static ltk_button *ltk_button_create(ltk_window *window, - const char *id, const char *text); + const char *id, 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_surface(ltk_button *button, ltk_surface *s); @@ -142,12 +142,11 @@ ltk_button_draw(ltk_widget *self, ltk_rect clip) { 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); + ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y); } static void 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 = NULL, *fill = NULL; @@ -181,33 +180,12 @@ ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) { ltk_text_line_get_size(button->tl, &text_w, &text_h); int text_x = (rect.w - text_w) / 2; 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, s, window->gc, text_x, text_y, rect); + ltk_text_line_draw(button->tl, s, &theme.text_color, text_x, text_y); button->widget.dirty = 0; } static void ltk_button_change_state(ltk_widget *self) { - ltk_button *button = (ltk_button *)self; - ltk_color *fill = NULL; - switch (button->widget.state) { - case LTK_NORMAL: - fill = &theme.fill; - break; - case LTK_PRESSED: - fill = &theme.fill_pressed; - break; - case LTK_ACTIVE: - fill = &theme.fill_active; - break; - case LTK_DISABLED: - fill = &theme.fill_disabled; - break; - default: - ltk_fatal("No style found for button!\n"); - } - ltk_text_line_change_colors(button->tl, &theme.text_color, fill); self->dirty = 1; } @@ -220,20 +198,17 @@ ltk_button_mouse_release(ltk_widget *self, XEvent event) { } static ltk_button * -ltk_button_create(ltk_window *window, const char *id, const char *text) { - char *text_copy; +ltk_button_create(ltk_window *window, const char *id, char *text) { ltk_button *button = ltk_malloc(sizeof(ltk_button)); uint16_t font_size = window->theme.font_size; - text_copy = ltk_strdup(text); - button->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1, &theme.text_color, &theme.fill); + button->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1); 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; button->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2; ltk_fill_widget_defaults(&button->widget, id, window, &vtable, button->widget.ideal_w, button->widget.ideal_h); - /* render text */ - ltk_button_change_state((ltk_widget *)button); + button->widget.dirty = 1; return button; } diff --git a/src/color.h b/src/color.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,13 +17,15 @@ #ifndef _LTK_COLOR_H_ #define _LTK_COLOR_H_ -#if USE_PANGO == 1 +#include "compat.h" + +#if USE_XFT == 1 #include <X11/Xft/Xft.h> #endif typedef struct { XColor xcolor; - #if USE_PANGO == 1 + #if USE_XFT == 1 XftColor xftcolor; #endif } ltk_color; diff --git a/src/compat.h b/src/compat.h @@ -0,0 +1,9 @@ +#ifdef _LTK_COMPAT_H_ +#define _LTK_COMPAT_H_ + +#if USE_PANGO == 1 +#undef USE_XFT +#define USE_XFT 1 +#endif + +#endif diff --git a/src/graphics.h b/src/graphics.h @@ -17,30 +17,31 @@ #ifndef _LTK_GRAPHICS_H_ #define _LTK_GRAPHICS_H_ +/* FIXME: make this global and use it elsewhere */ +#define USE_XLIB_GRAPHICS 1 + /* FIXME: Is it faster to take ltk_color* or ltk_color? */ #include <X11/Xft/Xft.h> #include "rect.h" #include "color.h" #include "ltk.h" +#include "compat.h" -typedef struct ltk_surface ltk_surface; +/* 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); +/* returns 0 if successful, 1 if not resizable */ +int ltk_surface_resize(ltk_surface *s, int w, int h); +/* FIXME: kind of hacky */ +void ltk_surface_update_size(ltk_surface *s, int w, int h); +ltk_surface *ltk_surface_from_window(ltk_window *window); void ltk_surface_get_size(ltk_surface *s, int *w, int *h); - -/* 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_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y); 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 */ /* @@ -50,10 +51,12 @@ void ltk_surface_draw_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r void ltk_surface_fill_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r); */ -#if USE_PANGO == 1 +/* FIXME: only generate draw if needed */ +#if USE_XFT == 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); +#if USE_XLIB_GRAPHICS == 1 +Drawable ltk_surface_get_drawable(ltk_surface *s); +#endif #endif /* _LTK_GRAPHICS_H_ */ diff --git a/src/graphics_xlib.c b/src/graphics_xlib.c @@ -23,14 +23,16 @@ #include "widget.h" #include "ltk.h" #include "memory.h" +#include "compat.h" struct ltk_surface { - int w, h; - ltk_window *window; - Pixmap p; - #if USE_PANGO == 1 - XftDraw *xftdraw; - #endif + int w, h; + ltk_window *window; + Drawable d; + #if USE_XFT == 1 + XftDraw *xftdraw; + #endif + char resizable; }; ltk_surface * @@ -39,31 +41,56 @@ ltk_surface_create(ltk_window *window, int w, int h) { 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); + s->d = XCreatePixmap(window->dpy, window->xwindow, w, h, window->depth); + #if USE_XFT == 1 + s->xftdraw = XftDrawCreate(window->dpy, s->d, window->vis, window->cm); #endif + s->resizable = 1; + return s; +} + +ltk_surface * +ltk_surface_from_window(ltk_window *window) { + ltk_surface *s = ltk_malloc(sizeof(ltk_surface)); + s->w = window->rect.w; + s->h = window->rect.h; + s->window = window; + s->d = window->drawable; + #if USE_XFT == 1 + s->xftdraw = XftDrawCreate(window->dpy, s->d, window->vis, window->cm); + #endif + s->resizable = 0; return s; } void ltk_surface_destroy(ltk_surface *s) { - #if USE_PANGO == 1 + #if USE_XFT == 1 XftDrawDestroy(s->xftdraw); #endif - XFreePixmap(s->window->dpy, s->p); + if (s->resizable) + XFreePixmap(s->window->dpy, (Pixmap)s->d); ltk_free(s); } void +ltk_surface_update_size(ltk_surface *s, int w, int h) { + s->w = w; + s->h = h; +} + +int ltk_surface_resize(ltk_surface *s, int w, int h) { + if (!s->resizable) + return 1; 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); + XFreePixmap(s->window->dpy, (Pixmap)s->d); + s->d = XCreatePixmap(s->window->dpy, s->window->xwindow, w, h, s->window->depth); + #if USE_XFT == 1 + XftDrawChange(s->xftdraw, s->d); #endif + return 0; } void @@ -76,13 +103,13 @@ 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); + XDrawRectangle(s->window->dpy, s->d, 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); + XFillRectangle(s->window->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, rect.h); } void @@ -98,6 +125,14 @@ ltk_window_fill_rect(ltk_window *window, ltk_color *c, ltk_rect rect) { XFillRectangle(window->dpy, window->drawable, window->gc, rect.x, rect.y, rect.w, rect.h); } +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->d, dst->d, src->window->gc, + src_rect.x, src_rect.y, src_rect.w, src_rect.h, dst_x, dst_y + ); +} + /* TODO */ /* void @@ -117,30 +152,16 @@ 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 +#if USE_XFT == 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; +#if USE_XLIB_GRAPHICS == 1 +Drawable +ltk_surface_get_drawable(ltk_surface *s) { + return s->d; } +#endif diff --git a/src/label.c b/src/label.c @@ -36,7 +36,7 @@ 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); + const char *id, char *text); static void ltk_label_destroy(ltk_widget *self, int shallow); static void ltk_label_redraw_surface(ltk_label *label, ltk_surface *s); @@ -86,7 +86,7 @@ ltk_label_draw(ltk_widget *self, ltk_rect clip) { 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); + ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y); } static void @@ -100,17 +100,15 @@ ltk_label_redraw_surface(ltk_label *label, ltk_surface *s) { 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, s, label->widget.window->gc, text_x, text_y, r); + ltk_text_line_draw(label->tl, s, &theme.text_color, text_x, text_y); } static ltk_label * -ltk_label_create(ltk_window *window, const char *id, const char *text) { - char *text_copy; +ltk_label_create(ltk_window *window, const char *id, char *text) { ltk_label *label = ltk_malloc(sizeof(ltk_label)); uint16_t font_size = window->theme.font_size; - text_copy = ltk_strdup(text); - label->tl = ltk_text_line_create(window->xwindow, font_size, text_copy, -1, &theme.text_color, &theme.bg_color); + label->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1); 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; diff --git a/src/ltk.h b/src/ltk.h @@ -56,10 +56,16 @@ struct ltk_event_queue { though, so it's just going to stay that way. */ +/* FIXME: fix this ugliness; remove circular dependencies */ +typedef struct ltk_text_context ltk_text_context; +typedef struct ltk_surface ltk_surface; + typedef struct ltk_window { Display *dpy; Visual *vis; ltk_surface_cache *surface_cache; + ltk_text_context *text_context; + ltk_surface *surface; Colormap cm; GC gc; int screen; diff --git a/src/ltkd.c b/src/ltkd.c @@ -506,6 +506,7 @@ ltk_window_other_event(ltk_window *window, XEvent event) { window->rect.w = w; window->rect.h = h; ltk_window_invalidate_rect(window, window->rect); + ltk_surface_update_size(window->surface, w, h); if (ptr) { ptr->rect.w = w; ptr->rect.h = h; @@ -616,10 +617,7 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 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; - window->first_event = window->last_event = NULL; window->rect.w = 0; @@ -630,6 +628,9 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int window->dirty_rect.h = 0; window->dirty_rect.x = 0; window->dirty_rect.y = 0; + window->surface = ltk_surface_from_window(window); + + window->text_context = ltk_text_context_create(window, window->theme.font); XClearWindow(window->dpy, window->xwindow); XMapRaised(window->dpy, window->xwindow); @@ -639,8 +640,8 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int static void ltk_destroy_window(ltk_window *window) { + ltk_text_context_destroy(window->text_context); XDestroyWindow(window->dpy, window->xwindow); - ltk_cleanup_text(); XCloseDisplay(window->dpy); /* FIXME: This doesn't work because it can sometimes be a readonly string from ltk_window_setup_theme_defaults! */ diff --git a/src/scrollbar.c b/src/scrollbar.c @@ -160,7 +160,7 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_rect clip) { } 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); + ltk_surface_copy(s, self->window->surface, ltk_rect_relative(rect, clip_final), clip_final.x, clip_final.y); } static int @@ -200,11 +200,12 @@ static int ltk_scrollbar_motion_notify(ltk_widget *self, XEvent event) { (void)self; (void)event; - /* + ltk_scrollbar *sc = (ltk_scrollbar *)self; double scale; int delta, max_pos; - if (sc->widget.state != LTK_PRESSED) + if (self->state != LTK_PRESSED) { return 1; + } if (sc->orient == LTK_HORIZONTAL) { delta = event.xbutton.x - sc->last_mouse_x; max_pos = sc->virtual_size > sc->widget.rect.w ? sc->virtual_size - sc->widget.rect.w : 0; @@ -223,7 +224,6 @@ ltk_scrollbar_motion_notify(ltk_widget *self, XEvent event) { sc->last_mouse_y = event.xbutton.y; if (delta > 0) sc->callback(sc->callback_data); - */ return 1; } diff --git a/src/text.h b/src/text.h @@ -19,22 +19,39 @@ #include "color.h" #include "graphics.h" +#include "ltk.h" typedef struct ltk_text_line ltk_text_line; +/* typedef struct ltk_text_context ltk_text_context; */ -/* 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, 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); +/* FIXME: something to hold display, etc. to avoid circular reference between window and text context */ + +ltk_text_context *ltk_text_context_create(ltk_window *window, char *default_font); +void ltk_text_context_destroy(ltk_text_context *ctx); + +/* FIXME: allow to give length of text */ +ltk_text_line *ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int take_over_text, int width); 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> -#endif +/* Draw the entire line to a surface. */ +void ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y); + +/* Get the smallest rectangle of the line that can be drawn while covering 'clip'. + * The Pango backend will currently always return the full rectangle for the line + * because it doesn't support clipping. Other backends may just return a slightly + * larger rectangle so they can draw full glyphs. */ +ltk_rect ltk_text_line_get_minimal_clip_rect(ltk_text_line *tl, ltk_rect clip); + +/* Draw a line onto a surface at position x,y and clipped to 'clip'. Note that + * clipping isn't supported properly, so the drawn part of the line may be + * larger than 'clip'. In order to find out the exact size of the drawn section, + * use ltk_rect_line_get_minimal_clip_rect. */ +void ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y, ltk_rect clip); + +/* FIXME: Any way to implement clipping properly? The text would need to be drawn + to an intermediate surface, but then we lose alpha blending with the background + (unless another library like cairo is used, which I want to avoid). */ #endif /* _LTK_TEXT_H_ */ diff --git a/src/text_pango.c b/src/text_pango.c @@ -35,96 +35,87 @@ #include "text.h" struct ltk_text_line { + ltk_text_context *ctx; char *text; - uint16_t font_size; - int w; - int h; - Window window; PangoLayout *layout; - ltk_color *fg; - ltk_color *bg; + uint16_t font_size; }; -struct { +struct ltk_text_context { + ltk_window *window; PangoFontMap *fontmap; PangoContext *context; char *default_font; - Display *dpy; - int screen; - Colormap cm; -} tm = {NULL, NULL, NULL, NULL, 0, 0}; +}; void -ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm) { - tm.fontmap = pango_xft_get_font_map(dpy, screen); - tm.context = pango_font_map_create_context(tm.fontmap); - tm.default_font = ltk_strdup(default_font); - tm.dpy = dpy; - tm.screen = screen; - tm.cm = cm; +ltk_text_context_create(ltk_window *window, const char *default_font) { + ltk_text_context *ctx = ltk_malloc(sizeof(ltk_text_context)); + ctx->window = window; + ctx->fontmap = pango_xft_get_font_map(window->dpy, window->screen); + ctx->context = pango_font_map_create_context(ctx->fontmap); + ctx->default_font = ltk_strdup(default_font); } void -ltk_cleanup_text(void) { - if (tm.default_font) ltk_free(tm.default_font); - /* FIXME: destroy fontmap and context */ +ltk_text_context_destroy(ltk_text_context *ctx) { + ltk_free(ctx->default_font); + g_object_unref(ctx->fontmap); + g_object_unref(ctx->context); + ltk_free(ctx); } 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; -} - -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_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)); - line->text = text; - line->font_size = font_size; - line->layout = pango_layout_new(tm.context); - - PangoFontDescription *desc = pango_font_description_from_string(tm.default_font); +ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int take_over_text, int width) { + ltk_text_line *tl = ltk_malloc(sizeof(ltk_text_line)); + if (take_over_text) + tl->text = text; + else + tl->text = ltk_strdup(text); + tl->font_size = font_size; + tl->layout = pango_layout_new(ctx->context); + + PangoFontDescription *desc = pango_font_description_from_string(ctx->default_font); pango_font_description_set_size(desc, font_size * PANGO_SCALE); - pango_layout_set_font_description(line->layout, desc); + pango_layout_set_font_description(tl->layout, desc); pango_font_description_free(desc); - line->window = window; - 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; + tl->ctx = ctx; + pango_layout_set_wrap(tl->layout, PANGO_WRAP_WORD_CHAR); + pango_layout_set_text(tl->layout, text, -1); + ltk_text_line_set_width(tl, width * PANGO_SCALE); + + return tl; } -/* FIXME: bg isn't used right now */ void -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 */ - (void)gc; +ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y) { XftDraw *d = ltk_surface_get_xft_draw(s); - pango_xft_render_layout(d, &tl->fg->xftcolor, tl->layout, x * PANGO_SCALE, y * PANGO_SCALE); + pango_xft_render_layout(d, &color->xftcolor, tl->layout, x * PANGO_SCALE, y * PANGO_SCALE); +} + +/* FIXME: any way to actually implement clipping with pango? */ +void +ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y, ltk_rect clip) { + (void)clip; + ltk_text_line_draw(tl, s, color, x, y); +} + +ltk_rect +ltk_text_line_get_minimal_clip_rect(ltk_text_line *tl, ltk_rect clip) { + (void)clip; + int w, h; + ltk_text_line_get_size(tl, &w, &h); + return (ltk_rect){0, 0, w, h}; } void ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h) { - *w = tl->w; - *h = tl->h; + pango_layout_get_pixel_size(tl->layout, w, h); } void diff --git a/src/text_stb.c b/src/text_stb.c @@ -1,5 +1,3 @@ -/* 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, 2022 lumidify <nobody@lumidify.org> * @@ -70,7 +68,7 @@ typedef struct { } ltk_glyph; struct ltk_text_line { - XImage *img; + ltk_text_context *ctx; char *text; ltk_glyph *glyphs; size_t glyph_len; @@ -79,18 +77,14 @@ struct ltk_text_line { int lines; int lines_alloc; - Window window; - int w_max; int w; int h; int line_h; int x_min; int y_min; - uint16_t font_size; int dirty; - ltk_color *fg; - ltk_color *bg; + uint16_t font_size; }; /* Hash definitions */ @@ -99,7 +93,8 @@ KHASH_MAP_INIT_INT(glyphinfo, ltk_glyph_info*) /* font path, size -> glyph cache hash */ KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*) -static struct { +struct ltk_text_context { + ltk_window *window; khash_t(glyphcache) *glyph_cache; ltk_font **fonts; int num_fonts; @@ -107,33 +102,28 @@ static struct { FcPattern *fcpattern; ltk_font *default_font; uint16_t font_id_cur; - Display *dpy; - int screen; - Colormap cm; -} tm = {NULL, NULL, 0, 0, NULL, NULL, 1, NULL, 0, 0}; - +}; -static ltk_font *ltk_get_font(char *path, int index); +static ltk_font *ltk_get_font(ltk_text_context *ctx, char *path, int index); static ltk_glyph_info *ltk_create_glyph_info(ltk_font *font, int id, float scale); static void ltk_destroy_glyph_info(ltk_glyph_info *gi); static ltk_glyph_info *ltk_get_glyph_info(ltk_font *font, int id, float scale, khash_t(glyphinfo) *cache); -static khash_t(glyphinfo) *ltk_get_glyph_cache(uint16_t font_id, +static khash_t(glyphinfo) *ltk_get_glyph_cache(ltk_text_context *ctx, uint16_t font_id, uint16_t font_size); -static khint_t ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size); +static khint_t ltk_create_glyph_cache(ltk_text_context *ctx, uint16_t font_id, uint16_t font_size); static void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache); -static void ltk_load_default_font(const char *name); static ltk_font *ltk_create_font(char *path, uint16_t id, int index); static void ltk_destroy_font(ltk_font *font); -static ltk_font *ltk_load_font(char *path, int index); -static ltk_font *ltk_get_font(char *path, int index); -static void ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text, +static ltk_font *ltk_load_font(ltk_text_context *ctx, char *path, int index); +static void ltk_load_default_font(ltk_text_context *ctx, char *default_font); +static void ltk_text_to_glyphs(ltk_text_context *ctx, ltk_glyph *glyphs, int num_glyphs, char *text, uint16_t font_size, int *x_min, int *y_min, int *x_max, int *y_max); static void ltk_text_line_create_glyphs(ltk_text_line *tl); static void ltk_text_line_draw_glyph(ltk_glyph *glyph, int x, int y, XImage *img, XColor fg); -static XImage *ltk_create_ximage(int w, int h, int depth, XColor bg); +/*static XImage *ltk_create_ximage(int w, int h, int depth, XColor bg);*/ /* These unicode routines are taken from @@ -210,29 +200,33 @@ static size_t u8_wc_toutf8(char *dest, uint32_t ch) { } */ -void -ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm) { - tm.fonts_bufsize = 1; - tm.glyph_cache = kh_init(glyphcache); - tm.fonts = ltk_malloc(sizeof(ltk_font *)); - ltk_load_default_font(default_font); - tm.dpy = dpy; - tm.screen = screen; - tm.cm = cm; +ltk_text_context * +ltk_text_context_create(ltk_window *window, char *default_font) { + ltk_text_context *ctx = ltk_malloc(sizeof(ltk_text_context)); + ctx->window = window; + ctx->glyph_cache = kh_init(glyphcache); + ctx->fonts = ltk_malloc(sizeof(ltk_font *)); + ctx->num_fonts = 0; + ctx->fonts_bufsize = 1; + ctx->fcpattern = NULL; + ltk_load_default_font(ctx, default_font); + ctx->font_id_cur = 1; + return ctx; } void -ltk_cleanup_text(void) { - for (int i = 0; i < tm.num_fonts; i++) { - ltk_destroy_font(tm.fonts[i]); +ltk_text_context_destroy(ltk_text_context *ctx) { + /* FIXME: destroy fcpattern */ + for (int i = 0; i < ctx->num_fonts; i++) { + ltk_destroy_font(ctx->fonts[i]); } - if (!tm.glyph_cache) return; - for (khint_t k = kh_begin(tm.glyph_cache); k != kh_end(tm.glyph_cache); k++) { - if (kh_exist(tm.glyph_cache, k)) { - ltk_destroy_glyph_cache(kh_value(tm.glyph_cache, k)); + if (!ctx->glyph_cache) return; + for (khint_t k = kh_begin(ctx->glyph_cache); k != kh_end(ctx->glyph_cache); k++) { + if (kh_exist(ctx->glyph_cache, k)) { + ltk_destroy_glyph_cache(kh_value(ctx->glyph_cache, k)); } } - kh_destroy(glyphcache, tm.glyph_cache); + kh_destroy(glyphcache, ctx->glyph_cache); } static ltk_glyph_info * @@ -274,24 +268,24 @@ ltk_get_glyph_info(ltk_font *font, int id, float scale, khash_t(glyphinfo) *cach } static khash_t(glyphinfo) * -ltk_get_glyph_cache(uint16_t font_id, uint16_t font_size) { +ltk_get_glyph_cache(ltk_text_context *ctx, uint16_t font_id, uint16_t font_size) { khint_t k; uint32_t attr = ((uint32_t)font_id << 16) + font_size; - k = kh_get(glyphcache, tm.glyph_cache, attr); - if (k == kh_end(tm.glyph_cache)) { - k = ltk_create_glyph_cache(font_id, font_size); + k = kh_get(glyphcache, ctx->glyph_cache, attr); + if (k == kh_end(ctx->glyph_cache)) { + k = ltk_create_glyph_cache(ctx, font_id, font_size); } - return kh_value(tm.glyph_cache, k); + return kh_value(ctx->glyph_cache, k); } static khint_t -ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size) { +ltk_create_glyph_cache(ltk_text_context *ctx, uint16_t font_id, uint16_t font_size) { khash_t(glyphinfo) *cache = kh_init(glyphinfo); int ret; khint_t k; /* I guess I can just ignore ret for now */ - k = kh_put(glyphcache, tm.glyph_cache, ((uint32_t)font_id << 16) + font_size, &ret); - kh_value(tm.glyph_cache, k) = cache; + k = kh_put(glyphcache, ctx->glyph_cache, ((uint32_t)font_id << 16) + font_size, &ret); + kh_value(ctx->glyph_cache, k) = cache; return k; } @@ -307,24 +301,24 @@ ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache) { } static void -ltk_load_default_font(const char *name) { +ltk_load_default_font(ltk_text_context *ctx, char *name) { FcPattern *match; FcResult result; char *file; int index; /* FIXME: Get rid of this stupid cast somehow */ - tm.fcpattern = FcNameParse((const FcChar8 *)name); - /*tm.fcpattern = FcPatternCreate();*/ - FcPatternAddString(tm.fcpattern, FC_FONTFORMAT, (const FcChar8 *)"truetype"); - FcConfigSubstitute(NULL, tm.fcpattern, FcMatchPattern); - FcDefaultSubstitute(tm.fcpattern); - match = FcFontMatch(NULL, tm.fcpattern, &result); + ctx->fcpattern = FcNameParse((const FcChar8 *)name); + /*ctx->fcpattern = FcPatternCreate();*/ + FcPatternAddString(ctx->fcpattern, FC_FONTFORMAT, (const FcChar8 *)"truetype"); + FcConfigSubstitute(NULL, ctx->fcpattern, FcMatchPattern); + FcDefaultSubstitute(ctx->fcpattern); + match = FcFontMatch(NULL, ctx->fcpattern, &result); FcPatternGetString(match, FC_FILE, 0, (FcChar8 **) &file); FcPatternGetInteger(match, FC_INDEX, 0, &index); - tm.default_font = ltk_get_font(file, index); + ctx->default_font = ltk_get_font(ctx, file, index); FcPatternDestroy(match); } @@ -354,35 +348,35 @@ ltk_destroy_font(ltk_font *font) { } static ltk_font * -ltk_load_font(char *path, int index) { - ltk_font *font = ltk_create_font(path, tm.font_id_cur++, index); - if (tm.num_fonts == tm.fonts_bufsize) { - ltk_font **new = ltk_realloc(tm.fonts, tm.fonts_bufsize * 2 * sizeof(ltk_font *)); - tm.fonts = new; - tm.fonts_bufsize *= 2; +ltk_load_font(ltk_text_context *ctx, char *path, int index) { + ltk_font *font = ltk_create_font(path, ctx->font_id_cur++, index); + if (ctx->num_fonts == ctx->fonts_bufsize) { + ltk_font **new = ltk_realloc(ctx->fonts, ctx->fonts_bufsize * 2 * sizeof(ltk_font *)); + ctx->fonts = new; + ctx->fonts_bufsize *= 2; } - tm.fonts[tm.num_fonts] = font; - tm.num_fonts++; + ctx->fonts[ctx->num_fonts] = font; + ctx->num_fonts++; return font; } static ltk_font * -ltk_get_font(char *path, int index) { +ltk_get_font(ltk_text_context *ctx, char *path, int index) { ltk_font *font = NULL; - for (int i = 0; i < tm.num_fonts; i++) { - if (tm.fonts[i]->index == index && - strcmp(tm.fonts[i]->path, path) == 0) { - font = tm.fonts[i]; + for (int i = 0; i < ctx->num_fonts; i++) { + if (ctx->fonts[i]->index == index && + strcmp(ctx->fonts[i]->path, path) == 0) { + font = ctx->fonts[i]; break; } } if (!font) - font = ltk_load_font(path, index); + font = ltk_load_font(ctx, path, index); return font; } static void -ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text, uint16_t font_size, +ltk_text_to_glyphs(ltk_text_context *ctx, ltk_glyph *glyphs, int num_glyphs, char *text, uint16_t font_size, int *x_min, int *y_min, int *x_max, int *y_max) { uint32_t c1, c2 = 0; int gid; @@ -396,8 +390,8 @@ ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text, uint16_t font_ *x_min = INT_MAX, *x_max = INT_MIN, *y_min = INT_MAX, *y_max = INT_MIN; ltk_glyph_info *ginfo; - ltk_font *font = tm.default_font; - khash_t(glyphinfo) *glyph_cache = ltk_get_glyph_cache(font->id, font_size); + ltk_font *font = ctx->default_font; + khash_t(glyphinfo) *glyph_cache = ltk_get_glyph_cache(ctx, font->id, font_size); scale = stbtt_ScaleForPixelHeight(&font->info, font_size); stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap); @@ -421,8 +415,8 @@ ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text, uint16_t font_ match = FcFontMatch(NULL, pat, &result); FcPatternGetString(match, FC_FILE, 0, &file); FcPatternGetInteger(match, FC_INDEX, 0, &index); - font = ltk_get_font((char *)file, index); - glyph_cache = ltk_get_glyph_cache(font->id, font_size); + font = ltk_get_font(ctx, (char *)file, index); + glyph_cache = ltk_get_glyph_cache(ctx, font->id, font_size); FcPatternDestroy(match); FcPatternDestroy(pat); gid = stbtt_FindGlyphIndex(&font->info, c1); @@ -477,8 +471,7 @@ ltk_unref_glyphs(ltk_glyph *glyphs, int num_glyphs) { } */ -/* FIXME: Error checking that tm has been initialized */ - +/* static void ltk_fill_ximage(XImage *img, int w, int h, XColor bg) { int b; @@ -502,9 +495,11 @@ ltk_create_ximage(int w, int h, int depth, XColor bg) { return img; } +*/ /* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */ /* FIXME: make this work with other pixel representations */ +/* FIXME: will fail horribly if depth is not 32 */ static void ltk_text_line_draw_glyph(ltk_glyph *glyph, int x, int y, XImage *img, XColor fg) { double a; @@ -562,26 +557,20 @@ ltk_text_line_break_lines(ltk_text_line *tl) { i++; } tl->h = tl->line_h * tl->lines; + tl->dirty = 0; } -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: 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); - } +/* 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_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y) { + if (tl->dirty) + ltk_text_line_break_lines(tl); + Drawable d = ltk_surface_get_drawable(s); + XImage *img = XGetImage(tl->ctx->window->dpy, d, x, y, tl->w, tl->h, 0xFFFFFF, ZPixmap); int last_break = 0; for (int i = 0; i < tl->lines; i++) { @@ -593,55 +582,42 @@ ltk_text_line_render( for (int j = last_break; j < next_break; j++) { int x = tl->glyphs[j].x - tl->glyphs[last_break].x; int y = tl->glyphs[j].y - tl->y_min + tl->line_h * i; - ltk_text_line_draw_glyph(&tl->glyphs[j], x, y, tl->img, fg->xcolor); + ltk_text_line_draw_glyph(&tl->glyphs[j], x, y, img, color->xcolor); } last_break = next_break; } - tl->dirty = 0; + XPutImage(tl->ctx->window->dpy, d, tl->ctx->window->gc, img, 0, 0, x, y, tl->w, tl->h); + XDestroyImage(img); } -/* 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) */ +/* FIXME: ACTUALLY IMPLEMENT!!! */ 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; - +ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, int y, ltk_rect clip) { + (void)clip; + ltk_text_line_draw(tl, s, color, x, y); } -/* FIXME: error checking if img is rendered yet, tm initialized, etc. */ -void -ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, GC gc, int x, int y, ltk_rect clip) { +ltk_rect +ltk_text_line_get_minimal_clip_rect(ltk_text_line *tl, 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; - xoff = xoff >= 0 ? xoff : 0; - yoff = yoff >= 0 ? yoff : 0; - int w = clip.w > tl->w - xoff ? tl->w - xoff : clip.w; - 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); - */ - Pixmap p = ltk_surface_get_pixmap(s); - XPutImage(tm.dpy, p, gc, tl->img, 0, 0, x, y, tl->w, tl->h); + ltk_text_line_break_lines(tl); + return (ltk_rect){0, 0, tl->w, tl->h}; } void ltk_text_line_set_width(ltk_text_line *tl, int width) { /* FIXME: clarify what the difference between w_max and w is */ + /* FIXME: tl->w could be made slightly less than tl->w_max depending on breaking */ tl->w_max = width; tl->w = width; - ltk_text_line_break_lines(tl); tl->dirty = 1; } void ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h) { + if (tl->dirty) + ltk_text_line_break_lines(tl); *w = tl->w; *h = tl->h; } @@ -649,7 +625,7 @@ ltk_text_line_get_size(ltk_text_line *tl, int *w, int *h) { static void ltk_text_line_create_glyphs(ltk_text_line *tl) { int x_min, x_max, y_min, y_max; - ltk_text_to_glyphs(tl->glyphs, tl->glyph_len, tl->text, tl->font_size, + ltk_text_to_glyphs(tl->ctx, tl->glyphs, tl->glyph_len, tl->text, tl->font_size, &x_min, &y_min, &x_max, &y_max); /* for drawing the glyphs at the right position on the image */ tl->x_min = x_min; @@ -659,28 +635,26 @@ 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_color *fg, ltk_color *bg) { - ltk_text_line *line = ltk_malloc(sizeof(ltk_text_line)); - line->window = window; - line->img = NULL; - line->text = text; - line->glyph_len = u8_strlen(text); - line->glyphs = ltk_malloc(line->glyph_len * sizeof(ltk_glyph)); - line->font_size = font_size; - line->w_max = width; - ltk_text_line_create_glyphs(line); - 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; +ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int take_over_text, int width) { + ltk_text_line *tl = ltk_malloc(sizeof(ltk_text_line)); + tl->ctx = ctx; + if (take_over_text) + tl->text = text; + else + tl->text = ltk_strdup(text); + tl->glyph_len = u8_strlen(text); + tl->glyphs = ltk_malloc(tl->glyph_len * sizeof(ltk_glyph)); + tl->font_size = font_size; + tl->w_max = width; + ltk_text_line_create_glyphs(tl); + tl->lines_alloc = tl->lines = 0; + tl->line_indeces = NULL; + tl->dirty = 1; + return tl; } 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);