aboutsummaryrefslogtreecommitdiff
path: root/mu_x11.c
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukasz@arcofasiagroup.com>2026-08-20 23:16:01 +0200
committerLukasz Kasprzak <lukasz@arcofasiagroup.com>2026-08-20 23:16:01 +0200
commitc453d5c3ba41af61975a83ea553b41e410d88ae2 (patch)
tree7538f3d4f39cf574526d8a0cb2d7ff023ef3f55a /mu_x11.c
parent2fe328e64067f77070de3ba51f5cff3643afc47f (diff)
downloadcolitur-x-c453d5c3ba41af61975a83ea553b41e410d88ae2.tar.gz
colitur-x-c453d5c3ba41af61975a83ea553b41e410d88ae2.zip
colitur-mu: an editable microui front-end, and its Xlib backend
Same data as colitur-x, but the invocation is editable: year, language, book-name form and numbering tradition are buttons that re-run colitur. No calendar logic is reimplemented; the status line shows the exact command that produced what is on screen. microui (MIT, rxi) is vendored rather than packaged -- a copied-in dependency cannot rot, which is the whole reason to prefer it over a toolkit here. mu_x11.c carries one bug worth recording: microui resets its scissor with an unclipped rect of 0x1000000, and XRectangle's w/h are unsigned short, so it truncated to 0 and turned 'clip nothing' into 'clip everything'. A panel drew its first screenful and the rest vanished.
Diffstat (limited to 'mu_x11.c')
-rw-r--r--mu_x11.c148
1 files changed, 148 insertions, 0 deletions
diff --git a/mu_x11.c b/mu_x11.c
new file mode 100644
index 0000000..13b23c2
--- /dev/null
+++ b/mu_x11.c
@@ -0,0 +1,148 @@
+/* mu_x11.c -- an Xlib/Xft backend for microui.
+ *
+ * microui is renderer-agnostic: it emits a command list of clipped
+ * rectangles, text runs and icons, and asks the host two questions about
+ * font metrics. This answers those and draws the commands into an
+ * off-screen pixmap, then blits it, so a frame never appears half-drawn.
+ *
+ * SPDX-License-Identifier: MIT (matching the vendored microui)
+ */
+#define _POSIX_C_SOURCE 200809L
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+#include <stdio.h>
+#include <string.h>
+#include "vendor/microui.h"
+#include "mu_x11.h"
+
+static Display *dpy;
+static Window win;
+static Visual *vis;
+static Colormap cmap;
+static XftFont *fnt;
+static XftDraw *xd;
+static Pixmap buf;
+static GC gc;
+static int bw, bh;
+
+int mux_text_width(mu_Font font, const char *s, int len)
+{
+ (void)font;
+ XGlyphInfo gi;
+ if (len < 0) len = strlen(s);
+ XftTextExtentsUtf8(dpy, fnt, (const FcChar8 *)s, len, &gi);
+ return gi.xOff;
+}
+
+int mux_text_height(mu_Font font)
+{
+ (void)font;
+ return fnt->ascent + fnt->descent;
+}
+
+void mux_init(Display *d, Window w, const char *fontname)
+{
+ dpy = d; win = w;
+ int scr = DefaultScreen(dpy);
+ vis = DefaultVisual(dpy, scr);
+ cmap = DefaultColormap(dpy, scr);
+ fnt = XftFontOpenName(dpy, scr, fontname);
+ gc = XCreateGC(dpy, win, 0, NULL);
+ buf = 0; xd = NULL; bw = bh = 0;
+}
+
+static void ensure_buffer(int w, int h)
+{
+ if (buf && w == bw && h == bh) return;
+ if (xd) { XftDrawDestroy(xd); xd = NULL; }
+ if (buf) { XFreePixmap(dpy, buf); buf = 0; }
+ buf = XCreatePixmap(dpy, win, w, h, DefaultDepth(dpy, DefaultScreen(dpy)));
+ xd = XftDrawCreate(dpy, buf, vis, cmap);
+ bw = w; bh = h;
+}
+
+static void xft_of(mu_Color c, XftColor *out)
+{
+ XRenderColor rc = { c.r * 257, c.g * 257, c.b * 257, c.a * 257 };
+ XftColorAllocValue(dpy, vis, cmap, &rc, out);
+}
+
+static const char *icon_glyph(int id)
+{
+ switch (id) {
+ case MU_ICON_CLOSE: return "x";
+ case MU_ICON_CHECK: return "*";
+ case MU_ICON_COLLAPSED: return ">";
+ case MU_ICON_EXPANDED: return "v";
+ default: return "?";
+ }
+}
+
+/* CLAMP BEFORE CONVERTING. XRectangle's w/h are unsigned SHORT, and microui
+ resets its scissor by emitting an "unclipped" rect of 0x1000000 -- which
+ truncates to 0 in 16 bits, turning "clip nothing" into "clip everything".
+ The symptom is that a panel draws its first screenful and the rest
+ silently vanishes, which reads as a layout or buffer bug and is neither. */
+static void set_clip(mu_Rect r)
+{
+ int x = r.x, y = r.y, w = r.w, h = r.h;
+ if (x < 0) { w += x; x = 0; }
+ if (y < 0) { h += y; y = 0; }
+ if (w > bw - x) w = bw - x;
+ if (h > bh - y) h = bh - y;
+ if (w <= 0 || h <= 0) { w = 0; h = 0; }
+ XRectangle xr = { (short)x, (short)y, (unsigned short)w, (unsigned short)h };
+ Region reg = XCreateRegion();
+ XUnionRectWithRegion(&xr, reg, reg);
+ XftDrawSetClip(xd, reg);
+ XDestroyRegion(reg);
+}
+
+void mux_present(mu_Context *ctx, int w, int h, mu_Color bg)
+{
+ ensure_buffer(w, h);
+ XftDrawSetClip(xd, NULL);
+
+ XftColor col;
+ xft_of(bg, &col);
+ XftDrawRect(xd, &col, 0, 0, w, h);
+ XftColorFree(dpy, vis, cmap, &col);
+
+ mu_Command *cmd = NULL;
+ while (mu_next_command(ctx, &cmd)) {
+ switch (cmd->type) {
+ case MU_COMMAND_RECT:
+ xft_of(cmd->rect.color, &col);
+ XftDrawRect(xd, &col, cmd->rect.rect.x, cmd->rect.rect.y,
+ cmd->rect.rect.w, cmd->rect.rect.h);
+ XftColorFree(dpy, vis, cmap, &col);
+ break;
+ case MU_COMMAND_TEXT:
+ xft_of(cmd->text.color, &col);
+ XftDrawStringUtf8(xd, &col, fnt, cmd->text.pos.x,
+ cmd->text.pos.y + fnt->ascent,
+ (const FcChar8 *)cmd->text.str,
+ strlen(cmd->text.str));
+ XftColorFree(dpy, vis, cmap, &col);
+ break;
+ case MU_COMMAND_ICON: {
+ const char *g = icon_glyph(cmd->icon.id);
+ int tw = mux_text_width(NULL, g, 1);
+ xft_of(cmd->icon.color, &col);
+ XftDrawStringUtf8(xd, &col, fnt,
+ cmd->icon.rect.x + (cmd->icon.rect.w - tw) / 2,
+ cmd->icon.rect.y + fnt->ascent
+ + (cmd->icon.rect.h - mux_text_height(NULL)) / 2,
+ (const FcChar8 *)g, 1);
+ XftColorFree(dpy, vis, cmap, &col);
+ break;
+ }
+ case MU_COMMAND_CLIP:
+ set_clip(cmd->clip.rect);
+ break;
+ }
+ }
+ XftDrawSetClip(xd, NULL);
+ XCopyArea(dpy, buf, win, gc, 0, 0, w, h, 0, 0);
+ XFlush(dpy);
+}