diff --git a/dat.h b/dat.h index 876adb8..fb9e978 100644 --- a/dat.h +++ b/dat.h @@ -18,6 +18,9 @@ enum LangVI = 0x16, Fontsz = 32, + PopupPad = 4, + PopupSep = 1, + PopupNumw = Fontsz, Maxrunes = 64, Maxutf = Maxrunes * UTFmax + 1, }; @@ -28,11 +31,13 @@ enum Maxkouho = 32, Maxdisp = 9, Imgw = (Maxrunes + 3) * Fontsz, - Imgh = (Maxdisp + 2) * Fontsz, + Imgh = 2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep, Colfg = 0x000000, Colbg = 0xffffff, - Colsel = 0xcccccc, + Colsep = 0xd0d0d0, + Colsel = 0x333333, + Colselfg = 0xffffff, }; typedef struct Str Str; @@ -137,6 +142,26 @@ struct Drawcmd Caret caret; }; +typedef struct Popup Popup; +struct Popup +{ + int w; + int h; + int n; + int prey; + int sepy; + int rowsy; + int numx; + int textx; + int textw; + int markx; + int marky; + int markw; + int selx; + int sely; + int selw; +}; + typedef struct Keyreq Keyreq; typedef struct Keyres Keyres; enum diff --git a/fn.h b/fn.h index d212b26..3f930e5 100644 --- a/fn.h +++ b/fn.h @@ -30,6 +30,8 @@ void dictlookup(Dictreq*, Dictres*); void drawthread(void*); void popupposition(Caret*, int, int, int, int, int, int, int*, int*); int pagemarker(char*, int, int, int, int); +void popuplayout(Drawcmd*, int, Popup*); +void popupdraw(u32int*, Drawcmd*, Popup*); void imthread(void*); Emit transmap(Im*, Rune); Emit transko(Im*, Rune); @@ -50,3 +52,4 @@ int textinit(void); int textwidth(Str*); void textdraw(u32int*, int, int, int, int, Str*); void textdrawfit(u32int*, int, int, int, int, int, Str*); +void textdrawfitcolor(u32int*, int, int, int, int, int, u32int, Str*); diff --git a/font.c b/font.c index 29b573e..41cab15 100644 --- a/font.c +++ b/font.c @@ -117,12 +117,13 @@ textwidth(Str *s) } static void -drawtext(u32int *buf, int w, int h, int x, int y, int fit, Str *s) +drawtext(u32int *buf, int w, int h, int x, int y, int fit, u32int color, + Str *s) { PangoRectangle r; cairo_surface_t *surface; cairo_t *cr; - int stride; + int b, g, red, stride; if(layout == nil) return; @@ -156,7 +157,10 @@ drawtext(u32int *buf, int w, int h, int x, int y, int fit, Str *s) cairo_rectangle(cr, 0, y, w, Fontsz); cairo_clip(cr); cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y); - cairo_set_source_rgb(cr, 0, 0, 0); + red = color >> 16 & 0xff; + g = color >> 8 & 0xff; + b = color & 0xff; + cairo_set_source_rgb(cr, red / 255.0, g / 255.0, b / 255.0); pango_cairo_show_layout(cr, layout); } cairo_destroy(cr); @@ -167,11 +171,18 @@ drawtext(u32int *buf, int w, int h, int x, int y, int fit, Str *s) void textdraw(u32int *buf, int w, int h, int x, int y, Str *s) { - drawtext(buf, w, h, x, y, -1, s); + drawtext(buf, w, h, x, y, -1, Colfg, s); } void textdrawfit(u32int *buf, int w, int h, int x, int y, int fit, Str *s) { - drawtext(buf, w, h, x, y, fit, s); + drawtext(buf, w, h, x, y, fit, Colfg, s); +} + +void +textdrawfitcolor(u32int *buf, int w, int h, int x, int y, int fit, + u32int color, Str *s) +{ + drawtext(buf, w, h, x, y, fit, color, s); } diff --git a/popup_layout.c b/popup_layout.c index 856f6a9..a47b25a 100644 --- a/popup_layout.c +++ b/popup_layout.c @@ -1,6 +1,33 @@ #include "dat.h" #include "fn.h" +enum { + Asciitofull = 0xFEE0, +}; + +static void +fill(u32int *buf, int n, u32int color) +{ + int i; + + for(i = 0; i < n; i++) + buf[i] = color; +} + +static void +fillrect(u32int *buf, int w, int h, int x, int y, int rw, int rh, + u32int color) +{ + int x0, x1, y0, y1; + + x0 = min(max(x, 0), w); + x1 = min(max(x + max(rw, 0), 0), w); + y0 = min(max(y, 0), h); + y1 = min(max(y + max(rh, 0), 0), h); + for(; y0 < y1; y0++) + fill(buf + y0*w + x0, max(x1 - x0, 0), color); +} + int pagemarker(char *buf, int nbuf, int first, int shown, int total) { @@ -13,6 +40,111 @@ pagemarker(char *buf, int nbuf, int first, int shown, int total) first + shown, total); } +void +popuplayout(Drawcmd *dc, int screenw, Popup *p) +{ + char buf[32]; + vlong width; + Str mark; + int i, maxw, nmark, npre, roww, y; + + memset(p, 0, sizeof *p); + p->prey = -1; + p->sepy = -1; + p->rowsy = -1; + p->markx = -1; + p->marky = -1; + p->sely = -1; + p->n = min(max(dc->nkouho, 0), Maxdisp); + npre = dc->pre.n != 0; + if(p->n == 0 && !npre) + return; + + maxw = npre ? textwidth(&dc->pre) : 0; + roww = 0; + for(i = 0; i < p->n; i++) + roww = max(roww, textwidth(&dc->kouho[i])); + if(p->n != 0) + maxw = max(maxw, PopupNumw + roww); + nmark = pagemarker(buf, sizeof buf, dc->first, p->n, dc->total); + if(nmark != 0){ + sinit(&mark, buf, nmark); + maxw = max(maxw, textwidth(&mark)); + } + width = (vlong)maxw + 2*PopupPad; + p->w = min(min(max(width, 1), Imgw), max(screenw, 0)); + + y = PopupPad; + if(npre){ + p->prey = y; + y += Fontsz; + } + if(npre && p->n != 0){ + p->sepy = y; + y += PopupSep; + } + if(p->n != 0){ + p->rowsy = y; + y += p->n*Fontsz; + } + if(nmark != 0){ + p->marky = y; + y += Fontsz; + } + p->h = y + PopupPad; + p->numx = PopupPad; + p->textx = PopupPad + PopupNumw; + p->textw = max(p->w - PopupPad - p->textx, 0); + if(nmark != 0){ + p->markw = min(textwidth(&mark), max(p->w - 2*PopupPad, 0)); + p->markx = p->w - PopupPad - p->markw; + } + p->selx = PopupPad; + p->selw = max(p->w - 2*PopupPad, 0); + if(dc->sel >= 0 && dc->sel < p->n) + p->sely = p->rowsy + dc->sel*Fontsz; +} + +void +popupdraw(u32int *img, Drawcmd *dc, Popup *p) +{ + char buf[32]; + Str mark, num; + u32int color; + int i, nmark, y; + + if(img == nil || p->n < 0 || p->n > Maxdisp || + p->w <= 0 || p->w > Imgw || + p->h <= 0 || p->h > Imgh) + return; + fill(img, p->w*p->h, Colbg); + if(p->prey >= 0) + textdrawfit(img, p->w, p->h, PopupPad, p->prey, + max(p->w - 2*PopupPad, 0), &dc->pre); + if(p->sepy >= 0) + fillrect(img, p->w, p->h, PopupPad, p->sepy, + p->w - 2*PopupPad, PopupSep, Colsep); + if(p->sely >= 0) + fillrect(img, p->w, p->h, p->selx, p->sely, + p->selw, Fontsz, Colsel); + for(i = 0, y = p->rowsy; i < p->n; i++, y += Fontsz){ + color = i == dc->sel ? Colselfg : Colfg; + sclear(&num); + sputr(&num, '1' + i + Asciitofull); + textdrawfitcolor(img, p->w, p->h, p->numx, y, + min(PopupNumw, + max(p->w - PopupPad - p->numx, 0)), color, &num); + textdrawfitcolor(img, p->w, p->h, p->textx, y, + p->textw, color, &dc->kouho[i]); + } + nmark = pagemarker(buf, sizeof buf, dc->first, p->n, dc->total); + if(nmark != 0 && p->marky >= 0){ + sinit(&mark, buf, nmark); + textdrawfit(img, p->w, p->h, p->markx, p->marky, + p->markw, &mark); + } +} + void popupposition(Caret *caret, int pointerx, int pointery, int screenw, int screenh, int w, int h, int *x, int *y) diff --git a/tests/font_test.c b/tests/font_test.c index 4faf5c2..f57d9e6 100644 --- a/tests/font_test.c +++ b/tests/font_test.c @@ -179,7 +179,7 @@ font_render(struct ct *t) static char *plain[] = { "A", "가", "あ", "漢", "☆", "𠀋" }; u32int *buf, *mem; Str a, heart, longrow, missing; - int i, natural, w, x, y; + int i, maxx, maxy, minx, miny, natural, w, x, y; mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]); fillpixels(mem, Testn + 2 * Guard, guardcolor); @@ -231,6 +231,21 @@ font_render(struct ct *t) } CT_EQ_INT(t, natural, textwidth(&longrow)); + fillpixels(buf, Testn, Colsel); + textdrawfitcolor(buf, Testw, Testh, 2*Fontsz, Fontsz, + 2*Fontsz, Colselfg, &longrow); + CT_CHECK(t, (Colselfg & Rgbmask) != (Colsel & Rgbmask)); + if(CT_CHECK(t, inkbounds(buf, Colsel, + &minx, &miny, &maxx, &maxy))){ + CT_CHECK(t, minx >= 2*Fontsz); + CT_CHECK(t, maxx < 4*Fontsz); + CT_CHECK(t, miny >= Fontsz); + CT_CHECK(t, maxy < 2*Fontsz); + } + checkoutside(t, buf, Colsel, Fontsz); + checkguards(t, mem); + CT_EQ_INT(t, natural, textwidth(&longrow)); + missing = mkstr("\xF4\x8F\xBF\xBF"); for(i = 0; i < 32; i++){ textdraw(buf, Testw, Testh, Fontsz, Fontsz, &heart); diff --git a/tests/popup_test.c b/tests/popup_test.c index 08df9bf..40c8454 100644 --- a/tests/popup_test.c +++ b/tests/popup_test.c @@ -1,6 +1,41 @@ #include "test.h" #include +enum { + Rgbmask = 0xffffff, +}; + +static u32int +pixel(u32int *img, Popup *p, int x, int y) +{ + return img[y*p->w + x] & Rgbmask; +} + +static int +rectcolor(u32int *img, Popup *p, int x0, int y0, int x1, int y1, + u32int color) +{ + int x, y; + + color &= Rgbmask; + for(y = y0; y < y1; y++) + for(x = x0; x < x1; x++) + if(pixel(img, p, x, y) != color) + return 0; + return 1; +} + +static void +checkpadding(struct ct *t, u32int *img, Popup *p) +{ + CT_CHECK(t, rectcolor(img, p, 0, 0, p->w, PopupPad, Colbg)); + CT_CHECK(t, rectcolor(img, p, 0, p->h - PopupPad, + p->w, p->h, Colbg)); + CT_CHECK(t, rectcolor(img, p, 0, 0, PopupPad, p->h, Colbg)); + CT_CHECK(t, rectcolor(img, p, p->w - PopupPad, 0, + p->w, p->h, Colbg)); +} + void popup_layout(struct ct *t) { @@ -22,7 +57,11 @@ popup_layout(struct ct *t) }; char buf[32]; Caret caret; - int i, n, x, y; + Drawcmd dc; + Popup p; + Str longrow; + u32int c, *img; + int i, ink, markink, n, numink, selbg, textink, x, y; for(i = 0; i < nelem(markers); i++){ n = pagemarker(buf, sizeof buf, markers[i].first, @@ -30,7 +69,8 @@ popup_layout(struct ct *t) CT_EQ_INT(t, strlen(markers[i].want), n); CT_CHECK(t, strcmp(markers[i].want, buf) == 0); } - CT_EQ_INT(t, (1 + Maxdisp + 1) * Fontsz, Imgh); + CT_EQ_INT(t, 2*PopupPad + (1 + Maxdisp + 1)*Fontsz + PopupSep, + Imgh); memset(&caret, 0, sizeof caret); popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y); @@ -81,4 +121,157 @@ popup_layout(struct ct *t) INT_MAX, INT_MAX, &x, &y); CT_EQ_INT(t, 0, x); CT_EQ_INT(t, 0, y); + + if(!CT_CHECK(t, textinit())) + return; + + /* Empty sections reserve nothing. */ + memset(&dc, 0, sizeof dc); + dc.sel = -1; + popuplayout(&dc, Imgw, &p); + CT_EQ_INT(t, 0, p.w); + CT_EQ_INT(t, 0, p.h); + CT_EQ_INT(t, -1, p.prey); + CT_EQ_INT(t, -1, p.rowsy); + CT_EQ_INT(t, -1, p.marky); + + /* Preedit only. */ + dc.pre = mkstr("preedit"); + popuplayout(&dc, Imgw, &p); + CT_EQ_INT(t, PopupPad, p.prey); + CT_EQ_INT(t, -1, p.sepy); + CT_EQ_INT(t, -1, p.rowsy); + CT_EQ_INT(t, -1, p.marky); + CT_EQ_INT(t, 2*PopupPad + Fontsz, p.h); + + /* Candidates only. */ + memset(&dc, 0, sizeof dc); + dc.sel = -1; + dc.nkouho = 2; + dc.kouho[0] = mkstr("short"); + dc.kouho[1] = mkstr("candidate"); + popuplayout(&dc, Imgw, &p); + CT_EQ_INT(t, -1, p.prey); + CT_EQ_INT(t, -1, p.sepy); + CT_EQ_INT(t, PopupPad, p.rowsy); + CT_EQ_INT(t, -1, p.marky); + CT_EQ_INT(t, 2*PopupPad + 2*Fontsz, p.h); + CT_EQ_INT(t, PopupPad, p.numx); + CT_EQ_INT(t, p.numx + PopupNumw, p.textx); + CT_EQ_INT(t, p.w - PopupPad, p.textx + p.textw); + + /* Preedit and candidates meet at one separator, with no empty rows. */ + dc.pre = mkstr("preedit"); + popuplayout(&dc, Imgw, &p); + 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); + CT_EQ_INT(t, -1, p.marky); + CT_EQ_INT(t, p.rowsy + 2*Fontsz + PopupPad, p.h); + CT_EQ_INT(t, 2*PopupPad + 3*Fontsz + PopupSep, p.h); + + /* The largest panel includes all sections and fits its backing image. */ + memset(&dc, 0, sizeof dc); + dc.pre = mkstr("preedit"); + dc.nkouho = Maxdisp; + 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, Imgw, &p); + CT_EQ_INT(t, Maxdisp, p.n); + 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); + 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_CHECK(t, p.w > 2*PopupPad + PopupNumw); + CT_CHECK(t, p.w <= Imgw); + CT_CHECK(t, p.h <= Imgh); + CT_EQ_INT(t, p.w - PopupPad, p.markx + p.markw); + CT_EQ_INT(t, PopupPad, p.selx); + CT_EQ_INT(t, p.w - 2*PopupPad, p.selw); + CT_EQ_INT(t, p.rowsy + dc.sel*Fontsz, p.sely); + CT_EQ_INT(t, PopupPad, p.numx); + 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]); + popupdraw(img, &dc, &p); + checkpadding(t, img, &p); + for(x = 0; x < p.w; x++) + CT_EQ_UINT(t, + x >= PopupPad && x < p.w - PopupPad ? Colsep : Colbg, + pixel(img, &p, x, p.sepy)); + + /* Selection paint and both foreground columns stay inside the row. */ + selbg = numink = textink = 0; + for(y = p.sely; y < p.sely + Fontsz; y++){ + for(x = 0; x < p.w; x++){ + c = pixel(img, &p, x, y); + if(x < p.selx || x >= p.selx + p.selw){ + CT_EQ_UINT(t, Colbg, c); + continue; + } + if(c == (Colsel & Rgbmask)){ + selbg++; + continue; + } + CT_CHECK(t, + (x >= p.numx && x < p.numx + PopupNumw) || + (x >= p.textx && x < p.textx + p.textw)); + if(x < p.numx + PopupNumw) + numink++; + else + textink++; + } + } + CT_CHECK(t, Colsel != Colselfg); + CT_CHECK(t, selbg > p.selw*Fontsz/2); + CT_CHECK(t, numink > 0); + CT_CHECK(t, textink > 0); + + /* The marker occupies only its compact, right-aligned footer box. */ + markink = 0; + for(y = p.marky; y < p.marky + Fontsz; y++){ + for(x = 0; x < p.w; x++){ + if(pixel(img, &p, x, y) == (Colbg & Rgbmask)) + continue; + markink++; + CT_CHECK(t, x >= p.markx && x < p.markx + p.markw); + } + } + CT_CHECK(t, markink > 0); + + /* A fitted long row is bounded by the fixed candidate content area. */ + memset(&dc, 0, sizeof dc); + dc.sel = -1; + dc.nkouho = 1; + dc.kouho[0] = longrow; + n = textwidth(&longrow); + popuplayout(&dc, 6*Fontsz, &p); + CT_CHECK(t, n > p.textw); + CT_EQ_INT(t, PopupPad, p.rowsy); + CT_EQ_INT(t, p.w - PopupPad, p.textx + p.textw); + popupdraw(img, &dc, &p); + ink = 0; + for(y = p.rowsy; y < p.rowsy + Fontsz; y++){ + for(x = 0; x < p.w; x++){ + c = pixel(img, &p, x, y); + if(x < PopupPad || x >= p.w - PopupPad) + CT_EQ_UINT(t, Colbg, c); + if(x >= p.textx && x < p.textx + p.textw && + c != (Colbg & Rgbmask)) + ink++; + } + } + CT_CHECK(t, ink > 0); + CT_EQ_INT(t, n, textwidth(&longrow)); + free(img); } diff --git a/win.c b/win.c index ba7a9f2..3876721 100644 --- a/win.c +++ b/win.c @@ -2,10 +2,6 @@ #include "dat.h" #include "fn.h" -enum { - Asciitofull = 0xFEE0, -}; - static xcb_connection_t *conn; static xcb_screen_t *scr; static xcb_window_t win; @@ -146,46 +142,6 @@ wininit(void) return 1; } -static void -fill(u32int *buf, int n, u32int color) -{ - int i; - - for(i = 0; i < n; i++) - buf[i] = color; -} - -static void -drawkouho(Drawcmd *dc, int n, int w, int h) -{ - char buf[32]; - int nmark, npre, sely, y, i; - Str mark, num, *s; - - if(w <= 0 || w > Imgw || h <= 0 || h > Imgh) - return; - fill(img, w * h, Colbg); - npre = dc->pre.n != 0; - if(npre) - textdraw(img, w, h, 0, 0, &dc->pre); - if(dc->sel >= 0 && dc->sel < n){ - sely = (npre + dc->sel) * Fontsz; - fill(img + sely * w, Fontsz * w, Colsel); - } - for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){ - s = &dc->kouho[i]; - sclear(&num); - sputr(&num, '1' + i + Asciitofull); - textdraw(img, w, h, 0, y, &num); - textdrawfit(img, w, h, 2*Fontsz, y, w - 2*Fontsz, s); - } - nmark = pagemarker(buf, sizeof buf, dc->first, n, dc->total); - if(nmark != 0){ - sinit(&mark, buf, nmark); - textdraw(img, w, h, 0, y, &mark); - } -} - static void putimage(int w, int h) { @@ -207,32 +163,20 @@ winhide(void) static int winshow(Drawcmd *dc) { - char buf[32]; - vlong width; - int nmark, npre, px, py, w, h, i, n, maxw; - Str mark; + Popup p; + int px, py; u32int vals[4]; xcb_query_pointer_reply_t *ptr; xcb_query_pointer_cookie_t cookie; - n = min(max(dc->nkouho, 0), Maxdisp); - npre = dc->pre.n != 0; - if(n == 0 && npre == 0){ + if(dc->nkouho <= 0 && dc->pre.n == 0){ return winhide(); } - maxw = textwidth(&dc->pre); - for(i = 0; i < n; i++) - maxw = max(maxw, textwidth(&dc->kouho[i])); - nmark = pagemarker(buf, sizeof buf, dc->first, n, dc->total); - if(nmark != 0){ - sinit(&mark, buf, nmark); - maxw = max(maxw, textwidth(&mark)); - } - vals[3] = h = (n + npre + (nmark != 0)) * Fontsz; - width = (vlong)maxw + 3*Fontsz; - vals[2] = w = min(min(max(width, 1), Imgw), scr->width_in_pixels); - if(w <= 0 || w > Imgw || h <= 0 || h > Imgh) + popuplayout(dc, scr->width_in_pixels, &p); + if(p.w <= 0 || p.w > Imgw || p.h <= 0 || p.h > Imgh) return 0; + vals[2] = p.w; + vals[3] = p.h; px = py = 0; if(!dc->caret.valid){ cookie = xcb_query_pointer(conn, scr->root); @@ -244,7 +188,7 @@ winshow(Drawcmd *dc) free(ptr); } popupposition(&dc->caret, px, py, scr->width_in_pixels, - scr->height_in_pixels, w, h, &px, &py); + scr->height_in_pixels, p.w, p.h, &px, &py); vals[1] = py; vals[0] = px; xcb_configure_window(conn, win, @@ -252,8 +196,8 @@ winshow(Drawcmd *dc) XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, vals); xcb_map_window(conn, win); - drawkouho(dc, n, w, h); - putimage(w, h); + popupdraw(img, dc, &p); + putimage(p.w, p.h); return xcb_flush(conn) > 0; }