aboutsummaryrefslogtreecommitdiff
path: root/mu_x11.c
blob: 13b23c2614ef757d2ef706dbdfbcdaa96417bec6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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);
}