123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- #include <lib9.h>
- #include <draw.h>
- #include <interp.h>
- #include <isa.h>
- #include "../libinterp/runt.h"
- #include <drawif.h>
- #include <prefab.h>
- #include <kernel.h>
- typedef struct State State;
- struct State
- {
- Prefab_Environ *env;
- List *list;
- char word[Maxchars+UTFmax];
- char *s;
- char *pending;
- Draw_Font *font;
- Draw_Image *color;
- Draw_Image *icon;
- Draw_Image *mask;
- String *tag;
- Point p;
- int mainkind;
- int kind;
- int wid;
- int newelem;
- int ascent;
- int descent;
- };
- static
- char*
- advword(char *s, char *word)
- {
- char *e;
- int w;
- Rune r;
- e = s+Maxchars-1;
- switch(*word++ = *s){
- case '\t': /* BUG: what to do about tabs? */
- strcpy(word-1, " ");
- return s+1;
- case '\n':
- case ' ':
- *word = 0;
- return s+1;
- case '\0':
- return s;
- }
- s++;
- while(s<e && *s && *s!=' ' && *s!='\t' && *s!='\n'){
- if(*(uchar*)s < Runeself)
- *word++ = *s++;
- else{
- w = chartorune(&r, s);
- memmove(word, s, w);
- word += w;
- s += w;
- }
- }
- *word = 0;
- return s;
- }
- static
- int
- ismore(State *state)
- {
- Prefab_Style *style;
- Prefab_Layout *lay;
- int text, icon;
- state->newelem = 0;
- if(state->kind==EIcon || (state->s && state->s[0]) || state->pending)
- return 1;
- if(state->list == H)
- return 0;
- lay = (Prefab_Layout*)state->list->data;
- text = (lay->text!=H && lay->text->len != 0);
- icon = (lay->icon!=H && lay->mask!=H);
- if(!text && !icon)
- return 0;
- state->newelem = 1;
- state->s = string2c(lay->text);
- state->font = lay->font;
- state->color = lay->color;
- state->icon = lay->icon;
- state->mask = lay->mask;
- state->tag = lay->tag;
- style = state->env->style;
- if(icon) /* has precedence; if lay->icon is set, we ignore the text */
- state->kind = EIcon;
- else{
- if(state->mainkind == ETitle){
- if(state->font == H)
- state->font = style->titlefont;
- if(state->color == H)
- state->color = style->titlecolor;
- }else{
- if(state->font == H)
- state->font = style->textfont;
- if(state->color == H)
- state->color = style->textcolor;
- }
- state->kind = state->mainkind;
- }
- state->list = state->list->tail;
- return 1;
- }
- PElement*
- growtext(PElement *pline, State *state, char *w, int minx, int maxx)
- {
- String *s;
- PElement *pe, *plist;
- Prefab_Element *e;
- List *atom;
- Point size;
- Image *image;
- if(state->newelem || pline==H) {
- pe = mkelement(state->env, state->kind);
- e = &pe->e;
- e->r.min.x = minx;
- if(state->kind == EIcon){
- e->image = state->icon;
- D2H(e->image)->ref++;
- e->mask = state->mask;
- D2H(e->mask)->ref++;
- }else{
- e->image = state->color;
- D2H(e->image)->ref++;
- e->font = state->font;
- D2H(e->font)->ref++;
- }
- e->tag = state->tag;
- if(e->tag != H)
- D2H(e->tag)->ref++;
- if(pline == H)
- pline = pe;
- else{
- if(pline->pkind != EHorizontal){
- /* promote pline to list encapsulating current contents */
- atom = prefabwrap(pline);
- plist = mkelement(state->env, EHorizontal);
- destroy(pline);
- /* rest of plist->e.r will be set later */
- plist->e.r.min.x = state->p.x;
- plist->drawpt = state->p;
- plist->e.kids = atom;
- plist->first = atom;
- plist->last = atom;
- plist->vfirst = atom;
- plist->vlast = atom;
- pline = plist;
- }
- /* add e to line */
- atom = prefabwrap(e);
- destroy(e); /* relevant data now in wrapper */
- e = *(Prefab_Element**)atom->data;
- pline->last->tail = atom;
- pline->last = atom;
- pline->vlast = atom;
- pline->nkids++;
- }
- state->newelem = 0;
- }else{
- pe = pline;
- if(pe->pkind == EHorizontal)
- pe = *(PElement**)pe->last->data;
- e = &pe->e;
- }
- if(state->kind == EIcon){
- /* guaranteed OK by buildine */
- image = lookupimage(state->icon);
- size = iconsize(image);
- /* put one pixel on each side */
- e->r.max.x = e->r.min.x+1+size.x+1;
- pline->e.r.max.x = e->r.max.x;
- if(state->ascent < size.y)
- state->ascent = size.y;
- state->kind = -1; /* consume EIcon from state */
- return pline;
- }
- e->r.max.x = maxx;
- pline->e.r.max.x = maxx;
- if(*w == '\n') {
- pline->newline = 1;
- return pline;
- }
- s = addstring(e->str, c2string(w, strlen(w)), 0);
- destroy(e->str);
- e->str = s;
- if(state->ascent < e->font->ascent)
- state->ascent = e->font->ascent;
- if(state->descent < e->font->height-e->font->ascent)
- state->descent = e->font->height-e->font->ascent;
- return pline;
- }
- PElement*
- buildline(State *state, int *ok)
- {
- int wordwid, linewid, nb, rwid, x;
- char tmp[UTFmax+1], *w, *t;
- PElement *pl, *pe;
- Rune r;
- Font *f;
- List *l;
- Image *icon;
- Point size;
- *ok = 1;
- linewid = 0;
- pl = H;
- state->ascent = 0;
- state->descent = 0;
- x = state->p.x;
- while(ismore(state)){
- f = nil;
- if(state->kind == EIcon){
- icon = lookupimage(state->icon);
- if(icon == nil){
- Error:
- destroy(pl);
- *ok = 0;
- return H;
- }
- size = iconsize(icon);
- wordwid = 1+size.x+1;
- }else{
- if(state->pending == 0){
- state->s = advword(state->s, state->word);
- state->pending = state->word;
- }
- if(*(state->pending) == '\n'){
- pl = growtext(pl, state, state->pending, x, x);
- if(pl == H){
- *ok = 0;
- return H;
- }
- state->pending = 0;
- break;
- }
- f = lookupfont(state->font);
- if(f == nil)
- goto Error;
- wordwid = stringwidth(f, state->pending);
- }
- if(linewid+wordwid<=state->wid){
- Easy:
- pl = growtext(pl, state, state->pending, x, x+wordwid);
- if(pl == H){
- *ok = 0;
- return H;
- }
- linewid += wordwid;
- state->pending = 0;
- x += wordwid;
- continue;
- }
- /* this word doesn't fit on this line */
- /* if it's white space or an icon, just generate a line break */
- if(state->word[0]==' ' || state->kind==EIcon){
- if(linewid == 0) /* it's just too wide; emit it and it'll get clipped */
- goto Easy;
- state->pending = 0;
- break;
- }
- /* if word would fit were we to break the line now, do so */
- if(wordwid <= state->wid)
- break;
- /* worst case: bite off the biggest piece that fits */
- w = state->pending;
- while(*w){
- nb = chartorune(&r, w);
- memmove(tmp, w, nb);
- tmp[nb] = 0;
- rwid = stringwidth(f, tmp);
- if(linewid+rwid > state->wid)
- break;
- linewid += rwid;
- w += nb;
- }
- if(w == state->pending){
- /* first char too wide for remaining space */
- if(linewid > 0)
- break;
- /* remaining space is all we'll get */
- kwerrstr("can't handle wide word in textelement\n");
- goto Error;
- }
- nb = w-state->pending;
- t = malloc(nb+1);
- if(t == nil)
- goto Error;
- memmove(t, state->pending, nb);
- t[nb] = 0;
- pl = growtext(pl, state, t, x, state->p.x+linewid);
- free(t);
- if(pl == H){
- *ok = 0;
- return H;
- }
- state->pending = w;
- break;
- }
- pl->e.r.min.y = state->p.y;
- pl->e.r.max.y = state->p.y+state->ascent+state->descent;
- P2P(pl->drawpt, pl->e.r.min);
- if(pl->pkind==EHorizontal){
- for(l=pl->first; l!=H; l=l->tail){
- pe = *(PElement**)l->data;
- pe->e.r.min.y = state->p.y;
- pe->e.r.max.y = state->p.y+state->ascent+state->descent;
- pe->drawpt.x = pe->e.r.min.x;
- if(pe->e.kind == EIcon){
- /* add a pixel on the left; room was left in growtext */
- pe->drawpt.x += 1;
- pe->drawpt.y = pe->e.r.min.y+(state->ascent-Dy(pe->e.image->r));
- }else
- pe->drawpt.y = pe->e.r.min.y+(state->ascent-pe->e.font->ascent);
- }
- }
- return pl;
- }
- PElement*
- layoutelement(Prefab_Environ *env, List *laylist, Draw_Rect rr, enum Elementtype kind)
- {
- PElement *pline, *plist, *firstpline;
- List *lines, *atom, *tail;
- State state;
- int nlines, linewid, maxwid, wid, trim, maxy, ok;
- Point p;
- Rectangle r;
- Screen *screen;
- nlines = 0;
- trim = 0;
- wid = Dx(rr);
- if(wid < 25){
- if(wid <= 0)
- trim = 1;
- screen = lookupscreen(env->screen);
- if(screen == nil)
- return H;
- wid = Dx(screen->display->image->r)-32;
- if(wid < 100)
- wid = 100;
- }
- wid -= 3+3; /* three pixels left and right */
- gchalt++;
- state.env = env;
- state.list = laylist;
- state.s = 0;
- state.pending = 0;
- state.font = H;
- state.color = H;
- state.tag = H;
- p = IPOINT(rr.min);
- p.x += 3;
- state.p = p;
- state.kind = EText; /* anything but EIcon */
- state.mainkind = kind;
- state.wid = wid;
- lines = H;
- tail = H;
- firstpline = H;
- maxwid = 0;
- maxy = 0;
- while(ismore(&state)){
- pline = buildline(&state, &ok);
- if(ok == 0){
- plist = H;
- goto Return;
- }
- if(pline == H)
- break;
- linewid = Dx(pline->e.r);
- if(linewid > maxwid)
- maxwid = linewid;
- if(firstpline == H)
- firstpline = pline;
- else{
- atom = prefabwrap(pline);
- destroy(pline); /* relevant data now in wrapper */
- pline = *(PElement**)atom->data;
- if(lines == H){
- lines = prefabwrap(firstpline);
- destroy(firstpline);
- firstpline = 0; /* never used again; this proves it! */
- tail = lines;
- }
- tail->tail = atom;
- tail = atom;
- }
- nlines++;
- state.p.y = pline->e.r.max.y;
- if(maxy==0 || state.p.y<=rr.max.y)
- maxy = state.p.y;
- }
- if(trim == 0)
- maxwid = wid;
- if(nlines == 0){
- plist = H;
- goto Return;
- }
- if(nlines == 1){
- if(trim == 0){ /* restore clipping around element */
- firstpline->e.r.min.x = rr.min.x;
- firstpline->e.r.max.x = rr.min.x+3+maxwid+3;
- }
- plist = firstpline;
- goto Return;
- }
- plist = mkelement(env, EVertical);
- plist->e.r.min.x = rr.min.x;
- plist->e.r.min.y = p.y;
- plist->e.r.max.x = rr.min.x+3+maxwid+3;
- plist->e.r.max.y = (*(Prefab_Element**)tail->data)->r.max.y;
- plist->drawpt = p;
- plist->e.kids = lines;
- plist->first = lines;
- plist->last = tail;
- plist->vfirst = lines;
- plist->vlast = tail;
- plist->nkids = nlines;
- /* if asked for a fixed size and list is too long, clip */
- if(Dy(rr)>0 && rr.max.y<plist->e.r.max.y){
- R2R(r, plist->e.r);
- r.max.y = maxy;
- clipelement(&plist->e, r);
- }
- Return:
- gchalt--;
- return plist;
- }
- /*
- * Create List with one Layout in it, using malloc instead of heap to
- * keep it out of the eyes of the garbage collector
- */
- List*
- listoflayout(Prefab_Style *style, String *text, int kind)
- {
- List *listp;
- Prefab_Layout *layp;
- listp = malloc(sizeof(List) + TLayout->size);
- if(listp == nil)
- return H;
- listp->tail = H;
- layp = (Prefab_Layout*)listp->data;
- if(kind == EText){
- layp->font = style->textfont;
- layp->color = style->textcolor;
- }else{
- layp->font = style->titlefont;
- layp->color = style->titlecolor;
- }
- layp->text = text;
- layp->icon = H;
- layp->mask = H;
- layp->tag = H;
- return listp;
- }
- PElement*
- textelement(Prefab_Environ *env, String *str, Draw_Rect rr, enum Elementtype kind)
- {
- PElement *pe;
- List *l;
- l = listoflayout(env->style, str, kind);
- pe = layoutelement(env, l, rr, kind);
- free(l);
- return pe;
- }
|