engine: redraw when the picture changes, not when the state might have

Every key copied the whole Im and Search rune by rune, compared them
after the transition, and combined the answer with 'eaten', a commit
count that is always zero, and a force flag for caret and owner changes,
while redraw() kept a separate 'visible' bit to know when to send an
empty popup. redraw() now compares the snapshot with the last one sent
and treats two empty popups as equal, so imhandlekey just ends in
redraw(). keystroke() was only a test entry point and moves there.
This commit is contained in:
2026-08-16 16:06:42 +09:00
parent a557fb114a
commit 68869e2589
2 changed files with 67 additions and 116 deletions

166
strans.c
View File

@@ -2,11 +2,11 @@
#include "fn.h" #include "fn.h"
static Im im; static Im im;
static int visible = 0;
static void *activeowner; static void *activeowner;
static int activecap; static int activecap;
static Caret caret; static Caret caret;
static int candidatechosen; static int candidatechosen;
static Drawcmd lastdraw;
static Emit transjp(Im*, Rune); static Emit transjp(Im*, Rune);
static void backjp(Im*); static void backjp(Im*);
static int maplookup(Trie*, Str*, Str*); static int maplookup(Trie*, Str*, Str*);
@@ -167,18 +167,48 @@ snapshot(Drawcmd *dc)
dc->kouho[i] = im.kouho[first + i]; dc->kouho[i] = im.kouho[first + i];
} }
static int
careteq(Caret *a, Caret *b)
{
return a->valid == b->valid && a->x == b->x && a->y == b->y &&
a->h == b->h;
}
static int
shown(Drawcmd *dc)
{
return dc->pre.n != 0 || dc->nkouho != 0;
}
static int
samedraw(Drawcmd *a, Drawcmd *b)
{
int i;
if(!shown(a) && !shown(b))
return 1;
if(a->nkouho != b->nkouho || a->sel != b->sel ||
a->first != b->first || a->total != b->total ||
scmp(&a->pre, &b->pre) != 0 || !careteq(&a->caret, &b->caret))
return 0;
for(i = 0; i < a->nkouho; i++)
if(scmp(&a->kouho[i], &b->kouho[i]) != 0)
return 0;
return 1;
}
/* Sends the popup a new picture only when it would look different. */
static void static void
redraw(void) redraw(void)
{ {
static Drawcmd old; Drawcmd dc, junk;
Drawcmd dc;
snapshot(&dc); snapshot(&dc);
if(dc.pre.n == 0 && dc.nkouho == 0 && !visible) if(samedraw(&dc, &lastdraw))
return; return;
visible = dc.pre.n != 0 || dc.nkouho != 0; lastdraw = dc;
/* imthread is the sole producer; keep only the newest pending draw. */ /* imthread is the sole producer; keep only the newest pending draw. */
while(channbrecv(drawc, &old) > 0) while(channbrecv(drawc, &junk) > 0)
; ;
channbsend(drawc, &dc); channbsend(drawc, &dc);
} }
@@ -744,63 +774,6 @@ transition(u32int ks, u32int mod, Str *com)
return n; return n;
} }
static int
samestate(Im *a, Search *as, Im *b, Search *bs)
{
int i;
if(a->l != b->l || scmp(&a->pre, &b->pre) != 0 ||
scmp(&a->raw, &b->raw) != 0 || a->nkouho != b->nkouho ||
a->sel != b->sel || as->lang != bs->lang ||
scmp(&as->raw, &bs->raw) != 0 || scmp(&as->text, &bs->text) != 0)
return 0;
for(i = 0; i < a->nkouho; i++)
if(scmp(&a->kouho[i], &b->kouho[i]) != 0)
return 0;
return 1;
}
static void
savestate(Im *p, Search *s)
{
int i;
memset(p, 0, sizeof *p);
memset(s, 0, sizeof *s);
p->l = im.l;
sappend(&p->pre, &im.pre);
sappend(&p->raw, &im.raw);
p->nkouho = im.nkouho;
p->sel = im.sel;
for(i = 0; i < im.nkouho; i++)
sappend(&p->kouho[i], &im.kouho[i]);
s->lang = search.lang;
sappend(&s->raw, &search.raw);
sappend(&s->text, &search.text);
}
static int
keystrokeforce(u32int ks, u32int mod, Str *com, int force)
{
Im oldim;
Search oldsearch;
int eaten, ncom;
savestate(&oldim, &oldsearch);
ncom = com->n;
eaten = transition(ks, mod, com);
if(force || eaten || com->n != ncom ||
!samestate(&oldim, &oldsearch, &im, &search))
redraw();
return eaten;
}
static int
keystroke(u32int ks, u32int mod, Str *com)
{
return keystrokeforce(ks, mod, com, 0);
}
static void static void
init(void) init(void)
{ {
@@ -812,7 +785,7 @@ init(void)
activeowner = nil; activeowner = nil;
activecap = 0; activecap = 0;
candidatechosen = 0; candidatechosen = 0;
visible = 0; memset(&lastdraw, 0, sizeof lastdraw);
} }
static int static int
@@ -821,30 +794,16 @@ meaningful(Keyreq *kr)
return kr->ks != 0 && !ismodkey(kr->ks); return kr->ks != 0 && !ismodkey(kr->ks);
} }
static int /*
setactivecap(int cap) * The engine belongs to whichever context last typed a real key; only
{ * the owner's requests change state. Every request ends in redraw(),
int old; * which sends the popup a picture only if something visible changed.
*/
old = activecap & Cclientpreedit;
activecap &= ~Cclientpreedit;
activecap |= cap & Cclientpreedit;
return old != (activecap & Cclientpreedit);
}
static int
careteq(Caret *a, Caret *b)
{
return a->valid == b->valid && a->x == b->x && a->y == b->y &&
a->h == b->h;
}
static void static void
imhandlekey(Keyreq *kr) imhandlekey(Keyreq *kr)
{ {
Keyres res; Keyres res;
void *oldowner; void *oldowner;
int changed;
oldowner = activeowner; oldowner = activeowner;
sclear(&res.commit); sclear(&res.commit);
@@ -856,57 +815,38 @@ imhandlekey(Keyreq *kr)
reset(); reset();
activeowner = nil; activeowner = nil;
activecap = 0; activecap = 0;
redraw();
} }
break; break;
case Keyreset: case Keyreset:
if(kr->owner == activeowner){ if(kr->owner == activeowner)
reset(); reset();
redraw();
}
break; break;
case Keycaret: case Keycaret:
if(kr->owner == activeowner && !careteq(&caret, &kr->caret)){ if(kr->owner == activeowner)
caret = kr->caret; caret = kr->caret;
if(visible)
redraw();
}
break; break;
case Keycap: case Keycap:
res.eaten = 0; res.eaten = activeowner != nil && kr->owner == activeowner;
if(activeowner != nil && kr->owner == activeowner){ if(res.eaten)
res.eaten = 1; activecap = kr->cap & Cclientpreedit;
if(setactivecap(kr->cap))
redraw();
}
break; break;
case Keypress: case Keypress:
res.eaten = 0; res.eaten = 0;
changed = 0; if(activeowner != nil && kr->owner == activeowner)
if(activeowner != nil && kr->owner == activeowner && activecap = kr->cap & Cclientpreedit;
setactivecap(kr->cap))
changed = 1;
if(meaningful(kr)){ if(meaningful(kr)){
if(kr->owner != activeowner){ if(kr->owner != activeowner){
reset(); reset();
activeowner = kr->owner; activeowner = kr->owner;
activecap = kr->cap & Cclientpreedit; activecap = kr->cap & Cclientpreedit;
caret = kr->caret; caret = kr->caret;
changed = 1; }else if(kr->caret.valid)
}else if(kr->caret.valid &&
!careteq(&caret, &kr->caret)){
caret = kr->caret; caret = kr->caret;
changed = 1; res.eaten = transition(kr->ks, kr->mod, &res.commit);
} }
if(changed)
res.eaten = keystrokeforce(kr->ks, kr->mod,
&res.commit, 1);
else
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
}else if(changed)
redraw();
break; break;
} }
redraw();
if(activeowner != oldowner) if(activeowner != oldowner)
ximownernotify(); ximownernotify();
if(kr->owner == activeowner){ if(kr->owner == activeowner){

View File

@@ -10,6 +10,17 @@ Str shownpre(Im*);
static int typekeys(struct ct*, char*, Str*); static int typekeys(struct ct*, char*, Str*);
static int draindraw(Drawcmd*); static int draindraw(Drawcmd*);
/* One key from the owner, as imhandlekey drives the engine. */
static int
keystroke(u32int ks, u32int mod, Str *com)
{
int eaten;
eaten = transition(ks, mod, com);
redraw();
return eaten;
}
void void
testengineinit(int lang) testengineinit(int lang)
{ {
@@ -675,7 +686,7 @@ struct Searchfix
Lang *dictlang; Lang *dictlang;
Trie *dict; Trie *dict;
int activecap; int activecap;
int visible; Drawcmd lastdraw;
int candidatechosen; int candidatechosen;
}; };
@@ -893,7 +904,7 @@ searchsave(Searchfix *f, int lang)
f->dictlang = getlang(lang); f->dictlang = getlang(lang);
f->dict = f->dictlang->dict; f->dict = f->dictlang->dict;
f->activecap = activecap; f->activecap = activecap;
f->visible = visible; f->lastdraw = lastdraw;
f->candidatechosen = candidatechosen; f->candidatechosen = candidatechosen;
draindraw(nil); draindraw(nil);
} }
@@ -936,7 +947,7 @@ searchend(Searchfix *f)
im = f->im; im = f->im;
search = f->search; search = f->search;
activecap = f->activecap; activecap = f->activecap;
visible = f->visible; lastdraw = f->lastdraw;
candidatechosen = f->candidatechosen; candidatechosen = f->candidatechosen;
} }