fix(popup): fit the current X11 monitor

This commit is contained in:
2026-08-14 22:54:15 +09:00
parent 59482d00f3
commit 38475318db
8 changed files with 505 additions and 115 deletions

View File

@@ -253,4 +253,7 @@ font_render(struct ct *t)
}
checkguards(t, mem);
free(mem);
textclose();
textclose();
CT_EQ_INT(t, 0, textwidth(&a));
}

View File

@@ -1,5 +1,4 @@
#include "test.h"
#include <limits.h>
enum {
Rgbmask = 0xffffff,
@@ -39,6 +38,9 @@ checkpadding(struct ct *t, u32int *img, Popup *p)
void
popup_layout(struct ct *t)
{
static int tinyheight[] = {
1, PopupPad, 2*PopupPad + Fontsz - 1,
};
static const struct {
int first;
int shown;
@@ -47,6 +49,8 @@ popup_layout(struct ct *t)
} markers[] = {
{ 0, 0, 0, "" },
{ 0, 9, 9, "" },
{ 0, 3, 9, "1-3/9" },
{ 8, 1, 9, "9-9/9" },
{ 0, 9, 10, "1-9/10" },
{ 9, 1, 10, "10-10/10" },
{ 0, 9, 18, "1-9/18" },
@@ -56,6 +60,7 @@ popup_layout(struct ct *t)
{ 27, 5, 32, "28-32/32" },
};
char buf[32];
Area area, mon[2], out, work;
Caret caret;
Drawcmd dc;
Popup p;
@@ -70,59 +75,62 @@ popup_layout(struct ct *t)
CT_CHECK(t, strcmp(markers[i].want, buf) == 0);
}
CT_EQ_INT(t, 12*Fontsz, PopupTextw);
CT_EQ_INT(t, 2*PopupPad + PopupNumw + PopupTextw, Imgw);
CT_EQ_INT(t, 2*PopupPad + PopupNumw + PopupTextw, PopupBasew);
CT_EQ_INT(t, 2*PopupPad + (1 + Maxdisp + 1)*Fontsz + PopupSep,
Imgh);
/* Monitor selection retains origins and intersects the EWMH work area. */
mon[0] = (Area){0, 0, 1920, 1080};
mon[1] = (Area){1920, 200, 1280, 1024};
work = (Area){0, 30, 3200, 1070};
popuparea(mon, 2, &work, 2500, 500, &out);
CT_EQ_INT(t, 1920, out.x);
CT_EQ_INT(t, 200, out.y);
CT_EQ_INT(t, 1280, out.w);
CT_EQ_INT(t, 900, out.h);
work = (Area){60, 0, 3140, 1224};
popuparea(mon, 2, &work, 100, 100, &out);
CT_EQ_INT(t, 60, out.x);
CT_EQ_INT(t, 0, out.y);
CT_EQ_INT(t, 1860, out.w);
CT_EQ_INT(t, 1080, out.h);
work = (Area){5000, 0, 100, 100};
popuparea(mon, 2, &work, 2500, 500, &out);
CT_EQ_INT(t, mon[1].x, out.x);
CT_EQ_INT(t, mon[1].y, out.y);
CT_EQ_INT(t, mon[1].w, out.w);
CT_EQ_INT(t, mon[1].h, out.h);
mon[0] = (Area){0, 0, 100, 100};
mon[1] = (Area){200, 0, 100, 100};
popuparea(mon, 2, nil, 150, 50, &out);
CT_EQ_INT(t, 0, out.x);
memset(&caret, 0, sizeof caret);
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 50, x);
area = (Area){1920, 30, 1280, 900};
popupposition(&caret, 2000, 50, &area, 100, 60, &x, &y);
CT_EQ_INT(t, 2010, x);
CT_EQ_INT(t, 60, y);
caret.valid = 1;
caret.x = 70;
caret.x = 2100;
caret.y = 80;
caret.h = 20;
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 70, x);
popupposition(&caret, 0, 0, &area, 100, 60, &x, &y);
CT_EQ_INT(t, 2100, x);
CT_EQ_INT(t, 100, y);
caret.x = 790;
caret.y = 590;
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 700, x);
CT_EQ_INT(t, 530, y);
caret.x = 3150;
caret.y = 850;
popupposition(&caret, 0, 0, &area, 200, 100, &x, &y);
CT_EQ_INT(t, 3000, x);
CT_EQ_INT(t, 750, y);
/* Neither side fits: choose above, then clamp to the screen. */
caret.x = 20;
caret.y = 30;
/* A popup no larger than a small work area stays inside its origin. */
area = (Area){100, 200, 80, 80};
caret.x = 120;
caret.y = 230;
caret.h = 20;
popupposition(&caret, 0, 0, 80, 80, 100, 100, &x, &y);
CT_EQ_INT(t, 0, x);
CT_EQ_INT(t, 0, y);
caret.x = 70;
caret.y = 80;
caret.h = 0;
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 70, x);
CT_EQ_INT(t, 80, y);
caret.h = -20;
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 70, x);
CT_EQ_INT(t, 80, y);
caret.x = INT_MIN;
caret.y = INT_MAX;
caret.h = INT_MAX;
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 0, x);
CT_EQ_INT(t, 540, y);
caret.x = INT_MAX;
caret.y = INT_MIN;
caret.h = INT_MIN;
popupposition(&caret, 0, 0, INT_MAX, INT_MAX,
INT_MAX, INT_MAX, &x, &y);
CT_EQ_INT(t, 0, x);
CT_EQ_INT(t, 0, y);
popupposition(&caret, 0, 0, &area, 80, 80, &x, &y);
CT_EQ_INT(t, 100, x);
CT_EQ_INT(t, 200, y);
if(!CT_CHECK(t, textinit()))
return;
@@ -130,7 +138,7 @@ popup_layout(struct ct *t)
/* Empty sections reserve nothing. */
memset(&dc, 0, sizeof dc);
dc.sel = -1;
popuplayout(&dc, Imgw, &p);
popuplayout(&dc, PopupBasew, Imgh, &p);
CT_EQ_INT(t, 0, p.w);
CT_EQ_INT(t, 0, p.h);
CT_EQ_INT(t, -1, p.prey);
@@ -139,8 +147,8 @@ popup_layout(struct ct *t)
/* Preedit only. */
dc.pre = mkstr("preedit");
popuplayout(&dc, 2*Imgw, &p);
CT_EQ_INT(t, Imgw, p.w);
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, PopupBasew, p.w);
CT_EQ_INT(t, PopupPad, p.prey);
CT_EQ_INT(t, -1, p.sepy);
CT_EQ_INT(t, -1, p.rowsy);
@@ -153,8 +161,8 @@ popup_layout(struct ct *t)
dc.nkouho = 2;
dc.kouho[0] = mkstr("short");
dc.kouho[1] = mkstr("candidate");
popuplayout(&dc, 2*Imgw, &p);
CT_EQ_INT(t, Imgw, p.w);
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, PopupBasew, p.w);
CT_EQ_INT(t, -1, p.prey);
CT_EQ_INT(t, -1, p.sepy);
CT_EQ_INT(t, PopupPad, p.rowsy);
@@ -167,8 +175,8 @@ popup_layout(struct ct *t)
/* Preedit and candidates meet at one separator, with no empty rows. */
dc.pre = mkstr("preedit");
popuplayout(&dc, 2*Imgw, &p);
CT_EQ_INT(t, Imgw, p.w);
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, PopupBasew, p.w);
CT_EQ_INT(t, PopupPad, p.prey);
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy);
@@ -176,6 +184,15 @@ popup_layout(struct ct *t)
CT_EQ_INT(t, p.rowsy + 2*Fontsz + PopupPad, p.h);
CT_EQ_INT(t, 2*PopupPad + 3*Fontsz + PopupSep, p.h);
/* Long preedit expands to the monitor cap just like a candidate. */
sclear(&longrow);
for(i = 0; i < Maxrunes; i++)
sputr(&longrow, 'W');
memset(&dc, 0, sizeof dc);
dc.pre = longrow;
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, 2*PopupBasew, p.w);
/* The largest panel includes all sections and fits its backing image. */
memset(&dc, 0, sizeof dc);
dc.pre = mkstr("preedit");
@@ -183,13 +200,10 @@ popup_layout(struct ct *t)
dc.sel = 4;
dc.first = 0;
dc.total = Maxdisp + 1;
sclear(&longrow);
for(i = 0; i < Maxrunes; i++)
sputr(&longrow, 'W');
for(i = 0; i < Maxdisp; i++)
dc.kouho[i] = mkstr(i == dc.sel ? "M" : "candidate");
dc.kouho[0] = longrow;
popuplayout(&dc, 2*Imgw, &p);
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, Maxdisp, p.n);
CT_EQ_INT(t, PopupPad, p.prey);
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
@@ -197,7 +211,7 @@ popup_layout(struct ct *t)
CT_EQ_INT(t, p.rowsy + Maxdisp*Fontsz, p.marky);
CT_EQ_INT(t, p.marky + Fontsz + PopupPad, p.h);
CT_EQ_INT(t, Imgh, p.h);
CT_EQ_INT(t, Imgw, p.w);
CT_EQ_INT(t, 2*PopupBasew, p.w);
CT_CHECK(t, p.h <= Imgh);
CT_EQ_INT(t, p.w - PopupPad, p.markx + p.markw);
CT_EQ_INT(t, PopupPad, p.selx);
@@ -207,7 +221,7 @@ popup_layout(struct ct *t)
CT_EQ_INT(t, p.numx + PopupNumw, p.textx);
CT_EQ_INT(t, p.w - PopupPad, p.textx + p.textw);
img = emalloc(Imgw*Imgh*sizeof img[0]);
img = emalloc(2*PopupBasew*Imgh*sizeof img[0]);
popupdraw(img, &dc, &p);
checkpadding(t, img, &p);
for(x = 0; x < p.w; x++)
@@ -254,15 +268,15 @@ popup_layout(struct ct *t)
}
CT_CHECK(t, markink > 0);
/* A fitted long row is bounded by the fixed candidate content area. */
/* A long candidate grows from the stable base to the monitor cap. */
memset(&dc, 0, sizeof dc);
dc.sel = -1;
dc.nkouho = 1;
dc.kouho[0] = longrow;
n = textwidth(&longrow);
popuplayout(&dc, 2*Imgw, &p);
CT_EQ_INT(t, Imgw, p.w);
CT_EQ_INT(t, PopupTextw, p.textw);
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, 2*PopupBasew, p.w);
CT_CHECK(t, p.textw > PopupTextw);
CT_CHECK(t, n > p.textw);
CT_EQ_INT(t, PopupPad, p.rowsy);
CT_EQ_INT(t, p.w - PopupPad, p.textx + p.textw);
@@ -281,7 +295,54 @@ popup_layout(struct ct *t)
CT_CHECK(t, ink > 0);
CT_EQ_INT(t, n, textwidth(&longrow));
popuplayout(&dc, Imgw - 1, &p);
CT_EQ_INT(t, Imgw - 1, p.w);
popuplayout(&dc, PopupBasew - 1, Imgh, &p);
CT_EQ_INT(t, PopupBasew - 1, p.w);
/* Height truncation scrolls the row window to the selected candidate. */
memset(&dc, 0, sizeof dc);
dc.nkouho = Maxdisp;
dc.sel = Maxdisp - 1;
dc.total = Maxdisp;
for(i = 0; i < Maxdisp; i++)
dc.kouho[i] = mkstr("candidate");
popuplayout(&dc, PopupBasew, 2*PopupPad + 2*Fontsz, &p);
CT_EQ_INT(t, 1, p.n);
CT_EQ_INT(t, Maxdisp - 1, p.row0);
CT_EQ_INT(t, PopupPad, p.sely);
CT_CHECK(t, p.marky >= 0);
CT_CHECK(t, p.h <= 2*PopupPad + 2*Fontsz);
popupdraw(img, &dc, &p);
numink = 0;
for(y = p.rowsy; y < p.rowsy + Fontsz; y++)
for(x = p.numx; x < p.numx + PopupNumw; x++)
if(pixel(img, &p, x, y) != (Colsel & Rgbmask))
numink++;
markink = 0;
for(y = p.marky; y < p.marky + Fontsz; y++)
for(x = p.markx; x < p.markx + p.markw; x++)
if(pixel(img, &p, x, y) != (Colbg & Rgbmask))
markink++;
CT_CHECK(t, numink > 0); /* full-width */
CT_CHECK(t, markink > 0); /* 9-9/9 */
/* The selected row wins over preedit and footer in one-row height. */
dc.pre = mkstr("preedit");
popuplayout(&dc, PopupBasew, 2*PopupPad + Fontsz, &p);
CT_EQ_INT(t, -1, p.prey);
CT_EQ_INT(t, 1, p.n);
CT_EQ_INT(t, Maxdisp - 1, p.row0);
CT_EQ_INT(t, -1, p.marky);
/* With less than one padded row, selected content starts at the top. */
for(i = 0; i < nelem(tinyheight); i++){
popuplayout(&dc, PopupBasew, tinyheight[i], &p);
CT_EQ_INT(t, min(tinyheight[i], Fontsz), p.h);
CT_EQ_INT(t, 0, p.rowsy);
CT_EQ_INT(t, 0, p.sely);
CT_EQ_INT(t, 1, p.n);
CT_EQ_INT(t, Maxdisp - 1, p.row0);
popupdraw(img, &dc, &p);
CT_EQ_UINT(t, Colsel, pixel(img, &p, p.w - PopupPad - 1, 0));
}
free(img);
}