implement MDisplay; # # Copyright © 1998 Vita Nuova Limited. All rights reserved. # # - best viewed with acme! include "sys.m"; include "draw.m"; include "mdisplay.m"; sys : Sys; draw : Draw; Context, Point, Rect, Font, Image, Display, Screen : import draw; # len cell == number of lines # len cell[0] == number of cellmap cells per char # (x,y)*cellsize == font glyph clipr cellS := array [] of {array [] of {(0, 0)}}; cellW := array [] of {array [] of {(0, 0), (1, 0)}}; cellH := array [] of {array [] of {(0, 1)}, array [] of {(0, 0)}}; cellWH := array [] of {array [] of {(0, 1), (1, 1)}, array [] of {(0, 0), (1, 0)}}; Cellinfo : adt { font : ref Font; ch, attr : int; clipmod : (int, int); }; # current display attributes display : ref Display; window : ref Image; frames := array [2] of ref Image; update : chan of int; colours : array of ref Image; bright : ref Image; # current mode attributes cellmap : array of Cellinfo; nrows : int; ncols : int; ulheight : int; curpos : Point; winoff : Point; cellsize : Point; modeattr : con fgWhite | bgBlack; showC := 0; delims := 0; modbbox := Rect((0,0),(0,0)); blankrow : array of Cellinfo; ctxt : ref Context; font : ref Font; # g0 videotex font - extended with unicode g2 syms fonth : ref Font; # double height version of font fontw : ref Font; # double width fonts : ref Font; # double size fontg1 : ref Font; # semigraphic videotex font (ch+128=separated) fontfr : ref Font; # french character set fontusa : ref Font; # american character set Init(c : ref Context) : string { sys = load Sys Sys->PATH; draw = load Draw Draw->PATH; if (c == nil || c.display == nil) return "no display context"; ctxt = c; disp := ctxt.display; black := disp.rgb2cmap(0, 0, 0); blue := disp.rgb2cmap(0, 0, 255); red := disp.rgb2cmap(255, 0, 0); magenta := disp.rgb2cmap(255, 0, 255); green := disp.rgb2cmap(0, 255, 0); cyan := disp.rgb2cmap(0, 255, 255); yellow := disp.rgb2cmap(255, 255, 0); white := disp.rgb2cmap(240, 240, 240); iblack := disp.color(black); iblue := disp.color(blue); ired := disp.color(red); imagenta := disp.color(magenta); igreen := disp.color(green); icyan := disp.color(cyan); iyellow := disp.color(yellow); iwhite := disp.color(white); colours = array [] of { iblack, iblue, ired, imagenta, igreen, icyan, iyellow, iwhite}; bright = disp.color(disp.rgb2cmap(255, 255, 255)); update = chan of int; spawn Update(update); display = disp; return nil; } Quit() { if (update != nil) update <- = QuitUpdate; update = nil; window = nil; frames[0] = nil; frames[1] = nil; cellmap = nil; display = nil; } Mode(r : Draw->Rect, w, h, ulh, d : int, fontpath : string) : (string, ref Draw->Image) { if (display == nil) # module not properly Init()'d return ("not initialized", nil); curpos = Point(-1, -1); if (window != nil) update <- = Pause; cellmap = nil; window = nil; (dx, dy) := (r.dx(), r.dy()); if (dx == 0 || dy == 0) { return (nil, nil); } black := display.rgb2cmap(0, 0, 0); window = ctxt.screen.newwindow(r, Draw->Refbackup, black); if (window == nil) return ("cannot create window", nil); window.origin(Point(0,0), r.min); winr := Rect((0,0), (dx, dy)); frames[0] = display.newimage(winr, window.chans, 0, black); frames[1] = display.newimage(winr, window.chans, 0, black); if (window == nil || frames[0] == nil || frames[1] == nil) { window = nil; return ("cannot allocate display resources", nil); } ncols = w; nrows = h; ulheight = ulh; delims = d; showC = 0; cellmap = array [ncols * nrows] of Cellinfo; font = Font.open(display, fontpath); fontw = Font.open(display, fontpath + "w"); fonth = Font.open(display, fontpath + "h"); fonts = Font.open(display, fontpath + "s"); fontg1 = Font.open(display, fontpath + "g1"); fontfr = Font.open(display, fontpath + "fr"); fontusa = Font.open(display, fontpath + "usa"); if (font != nil) cellsize = Point(font.width(" "), font.height); else cellsize = Point(dx/ncols, dy / nrows); winoff.x = (dx - (cellsize.x * ncols)) / 2; winoff.y = (dy - (cellsize.y * nrows)) /2; if (winoff.x < 0) winoff.x = 0; if (winoff.y < 0) winoff.y = 0; blankrow = array [ncols] of {* => Cellinfo(font, ' ', modeattr | fgWhite, (0,0))}; for (y := 0; y < nrows; y++) { col0 := y * ncols; cellmap[col0:] = blankrow; } # frames[0].clipr = frames[0].r; # frames[1].clipr = frames[1].r; # frames[0].draw(frames[0].r, colours[0], nil, Point(0,0)); # frames[1].draw(frames[1].r, colours[0], nil, Point(0,0)); # window.draw(window.r, colours[0], nil, Point(0,0)); update <- = Continue; return (nil, window); } Cursor(pt : Point) { if (update == nil || cellmap == nil) # update thread (cursor/character flashing) not running return; # normalize pt pt.x--; curpos = pt; update <- = CursorSet; } Put(str : string, pt : Point, charset, attr, insert : int) { if (cellmap == nil || str == nil) # nothing to do return; # normalize pt pt.x--; f : ref Font; cell := cellS; case charset { videotex => if (!(attr & attrD)) attr &= (fgMask | attrF | attrH | attrW | attrP); if (attr & attrW && attr & attrH) { cell = cellWH; f = fonts; } else if (attr & attrH) { cell = cellH; f = fonth; } else if (attr & attrW) { cell = cellW; f = fontw; } else { f = font; } semigraphic => f = fontg1; if (attr & attrL) { # convert to "separated" newstr := ""; for (ix := 0; ix < len str; ix++) newstr[ix] = str[ix] + 16r80; str = newstr; } # semigraphic charset does not support size / polarity attributes # attrD always set later once field attr established attr &= ~(attrD | attrH | attrW | attrP | attrL); french => f = fontfr; american => f = fontusa; * => f = font; } update <- = Pause; txty := pt.y - (len cell - 1); for (cellix := len cell - 1; cellix >= 0; cellix--) { y := pt.y - cellix; if (y < 0) continue; if (y >= nrows) break; col0 := y * ncols; colbase := pt.y * ncols; if (delims && !(attr & attrD)) { # seek back for a delimiter mask : int; delimattr := modeattr; # semigraphics only inherit attrC from current field if (charset == semigraphic) mask = attrC; else mask = bgMask | attrC | attrL; for (ix := pt.x-1; ix >= 0; ix--) { cix := ix + col0; if (cellmap[cix].attr & attrD) { if (cellmap[cix].font == fontg1 && f != fontg1) # don't carry over attrL from semigraphic field mask &= ~attrL; delimattr = cellmap[cix].attr; break; } } attr = (attr & ~mask) | (delimattr & mask); # semigraphics validate background colour if (charset == semigraphic) attr |= attrD; } strlen := len cell[0] * len str; gfxwidth := cellsize.x * strlen; srco := Point(pt.x*cellsize.x, y*cellsize.y); if (insert) { # copy existing cells and display to new position if (pt.x + strlen < ncols) { for (destx := ncols -1; destx > pt.x; destx--) { srcx := destx - strlen; if (srcx < 0) break; cellmap[col0 + destx] = cellmap[col0 + srcx]; } # let draw() do the clipping for us dsto := Point(srco.x + gfxwidth, srco.y); dstr := Rect((dsto.x, srco.y), (ncols * cellsize.x, srco.y + cellsize.y)); frames[0].clipr = frames[0].r; frames[1].clipr = frames[1].r; frames[0].draw(dstr, frames[0], nil, srco); frames[1].draw(dstr, frames[1], nil, srco); if (modbbox.dx() == 0) modbbox = dstr; else modbbox = boundingrect(modbbox, dstr); } } # copy-in new string x := pt.x; for (strix := 0; x < ncols && strix < len str; strix++) { for (clipix := 0; clipix < len cell[cellix]; (x, clipix) = (x+1, clipix+1)) { if (x < 0) continue; if (x >= ncols) break; cmix := col0 + x; cellmap[cmix].font = f; cellmap[cmix].ch = str[strix]; cellmap[cmix].attr = attr; cellmap[cmix].clipmod = cell[cellix][clipix]; } } # render the new string txto := Point(srco.x, txty * cellsize.y); strr := Rect(srco, (srco.x + gfxwidth, srco.y + cellsize.y)); if (strr.max.x > ncols * cellsize.x) strr.max.x = ncols * cellsize.x; drawstr(str, f, strr, txto, attr); # redraw remainder of line until find cell not needing redraw # this could be optimised by # spotting strings with same attrs, font and clipmod pairs # and write out whole string rather than processing # a char at a time attr2 := attr; mask := bgMask | attrC | attrL; s := ""; for (; delims && x < ncols; x++) { if (x < 0) continue; newattr := cellmap[col0 + x].attr; if (cellmap[col0 + x].font == fontg1) { # semigraphics act as bg colour delimiter attr2 = (attr2 & ~bgMask) | (newattr & bgMask); mask &= ~attrL; } else if (newattr & attrD) break; if ((attr2 & mask) == (newattr & mask)) break; newattr = (newattr & ~mask) | (attr2 & mask); cellmap[col0 + x].attr = newattr; s[0] = cellmap[col0 + x].ch; (cx, cy) := cellmap[col0 + x].clipmod; f2 := cellmap[col0 + x].font; cellpos := Point(x * cellsize.x, y * cellsize.y); clipr := Rect(cellpos, cellpos.add(Point(cellsize.x, cellsize.y))); drawpt := cellpos.sub(Point(cx*cellsize.x, cy*cellsize.y)); drawstr(s, f2, clipr, drawpt, newattr); } } update <- = Continue; } Scroll(topline, nlines : int) { if (cellmap == nil || nlines == 0) return; blankr : Rect; scr := Rect((0,topline * cellsize.y), (ncols * cellsize.x, nrows * cellsize.y)); update <- = Pause; frames[0].clipr = scr; frames[1].clipr = scr; dstr := scr.subpt(Point(0, nlines * cellsize.y)); frames[0].draw(dstr, frames[0], nil, frames[0].clipr.min); frames[1].draw(dstr, frames[1], nil, frames[1].clipr.min); if (nlines > 0) { # scroll up - copy up from top if (nlines > nrows - topline) nlines = nrows - topline; for (y := nlines + topline; y < nrows; y++) { srccol0 := y * ncols; dstcol0 := (y - nlines) * ncols; cellmap[dstcol0:] = cellmap[srccol0:srccol0+ncols]; } for (y = nrows - nlines; y < nrows; y++) { col0 := y * ncols; cellmap[col0:] = blankrow; } blankr = Rect(Point(0, scr.max.y - (nlines * cellsize.y)), scr.max); } else { # scroll down - copy down from bottom nlines = -nlines; if (nlines > nrows - topline) nlines = nrows - topline; for (y := (nrows - 1) - nlines; y >= topline; y--) { srccol0 := y * ncols; dstcol0 := (y + nlines) * ncols; cellmap[dstcol0:] = cellmap[srccol0:srccol0+ncols]; } for (y = topline; y < nlines; y++) { col0 := y * ncols; cellmap[col0:] = blankrow; } blankr = Rect(scr.min, (scr.max.x, scr.min.y + (nlines * cellsize.y))); } frames[0].draw(blankr, colours[0], nil, Point(0,0)); frames[1].draw(blankr, colours[0], nil, Point(0,0)); if (modbbox.dx() == 0) modbbox = scr; else modbbox = boundingrect(modbbox, scr); update <- = Continue; } Reveal(show : int) { showC = show; if (cellmap == nil) return; update <- = Pause; for (y := 0; y < nrows; y++) { col0 := y * ncols; for (x := 0; x < ncols; x++) { attr := cellmap[col0+x].attr; if (!(attr & attrC)) continue; s := ""; s[0] = cellmap[col0 + x].ch; (cx, cy) := cellmap[col0 + x].clipmod; f := cellmap[col0 + x].font; cellpos := Point(x * cellsize.x, y * cellsize.y); clipr := Rect(cellpos, cellpos.add(Point(cellsize.x, cellsize.y))); drawpt := cellpos.sub(Point(cx*cellsize.x, cy*cellsize.y)); drawstr(s, f, clipr, drawpt, attr); } } update <- = Continue; } # expects that pt.x already normalized wordchar(pt : Point) : int { if (pt.x < 0 || pt.x >= ncols) return 0; if (pt.y < 0 || pt.y >= nrows) return 0; col0 := pt.y * ncols; c := cellmap[col0 + pt.x]; if (c.attr & attrC && !showC) # don't let clicking on screen 'reveal' concealed chars! return 0; if (c.font == fontg1) return 0; if (c.attr & attrW) { # check for both parts of character (modx, nil) := c.clipmod; if (modx == 1) { # rhs of char - check lhs is the same if (pt.x <= 0) return 0; lhc := cellmap[col0 + pt.x-1]; (lhmodx, nil) := lhc.clipmod; if (!((lhc.attr & attrW) && (lhc.font == c.font) && (lhc.ch == c.ch) && (lhmodx == 0))) return 0; } else { # lhs of char - check rhs is the same if (pt.x >= ncols - 1) return 0; rhc := cellmap[col0 + pt.x + 1]; (rhmodx, nil) := rhc.clipmod; if (!((rhc.attr & attrW) && (rhc.font == c.font) && (rhc.ch == c.ch) && (rhmodx == 1))) return 0; } } if (c.ch >= 16r30 && c.ch <= 16r39) # digits return 1; if (c.ch >= 16r41 && c.ch <= 16r5a) # capitals return 1; if (c.ch >= 16r61 && c.ch <= 16r7a) # lowercase return 1; if (c.ch == '*' || c.ch == '/') return 1; return 0; } GetWord(gfxpt : Point) : string { if (cellmap == nil) return nil; scr := Rect((0,0), (ncols * cellsize.x, nrows * cellsize.y)); gfxpt = gfxpt.sub(winoff); if (!gfxpt.in(scr)) return nil; x := gfxpt.x / cellsize.x; y := gfxpt.y / cellsize.y; col0 := y * ncols; s := ""; # seek back for (sx := x; sx >= 0; sx--) if (!wordchar(Point(sx, y))) break; if (sx++ == x) return nil; # seek forward, constructing s for (; sx < ncols; sx++) { if (!wordchar(Point(sx, y))) break; c := cellmap[col0 + sx]; s[len s] = c.ch; if (c.attr & attrW) sx++; } return s; } Refresh() { if (window == nil || modbbox.dx() == 0) return; if (update != nil) update <- = Redraw; } framecolours(attr : int) : (ref Image, ref Image, ref Image, ref Image) { fg : ref Image; fgcol := attr & fgMask; if (fgcol == fgWhite && attr & attrB) fg = bright; else fg = colours[fgcol / fgBase]; bg : ref Image; bgcol := attr & bgMask; if (bgcol == bgWhite && attr & attrB) bg = bright; else bg = colours[bgcol / bgBase]; (fg0, fg1) := (fg, fg); (bg0, bg1) := (bg, bg); if (attr & attrP) (fg0, bg0, fg1, bg1) = (bg1, fg1, bg0, fg0); if (attr & attrF) { fg0 = fg; fg1 = bg; } if ((attr & attrC) && !showC) (fg0, fg1) = (bg0, bg1); return (fg0, bg0, fg1, bg1); } kill(pid : int) { prog := "/prog/" + string pid + "/ctl"; fd := sys->open(prog, Sys->OWRITE); if (fd != nil) { cmd := array of byte "kill"; sys->write(fd, cmd, len cmd); } } timer(ms : int, pc, tick : chan of int) { pc <- = sys->pctl(0, nil); for (;;) { sys->sleep(ms); tick <- = 1; } } # Update() commands Redraw, Pause, Continue, CursorSet, QuitUpdate : con iota; Update(cmd : chan of int) { flashtick := chan of int; cursortick := chan of int; pc := chan of int; spawn timer(1000, pc, flashtick); flashpid := <- pc; spawn timer(500, pc, cursortick); cursorpid := <- pc; cursor : Point; showcursor := 0; cursoron := 0; quit := 0; nultick := chan of int; flashchan := nultick; pcount := 1; fgframe := 0; for (;!quit ;) alt { c := <- cmd => case c { Redraw => frames[0].clipr = frames[0].r; frames[1].clipr = frames[1].r; r := modbbox.addpt(winoff); window.draw(r.addpt(window.r.min), frames[fgframe], nil, modbbox.min); if (showcursor && cursoron) drawcursor(cursor, fgframe, 1); modbbox = Rect((0,0),(0,0)); Pause => if (pcount++ == 0) flashchan = nultick; Continue => pcount--; if (pcount == 0) flashchan = flashtick; QuitUpdate => quit++; CursorSet => frames[0].clipr = frames[0].r; frames[1].clipr = frames[1].r; if (showcursor && cursoron) drawcursor(cursor, fgframe, 0); cursoron = 0; if (curpos.x < 0 || curpos.x >= ncols || curpos.y < 0 || curpos.y >= nrows) showcursor = 0; else { cursor = curpos; showcursor = 1; drawcursor(cursor, fgframe, 1); cursoron = 1; } } <- flashchan => # flip displays... fgframe = (fgframe + 1 ) % 2; modbbox = Rect((0,0),(0,0)); frames[0].clipr = frames[0].r; frames[1].clipr = frames[1].r; window.draw(window.r.addpt(winoff), frames[fgframe], nil, Point(0,0)); if (showcursor && cursoron) drawcursor(cursor, fgframe, 1); <- cursortick => if (showcursor) { cursoron = !cursoron; drawcursor(cursor, fgframe, cursoron); } } kill(flashpid); kill(cursorpid); } drawstr(s : string, f : ref Font, clipr : Rect, drawpt : Point, attr : int) { (fg0, bg0, fg1, bg1) := framecolours(attr); frames[0].clipr = clipr; frames[1].clipr = clipr; frames[0].draw(clipr, bg0, nil, Point(0,0)); frames[1].draw(clipr, bg1, nil, Point(0,0)); ulrect : Rect; ul := (attr & attrL) && ! (attr & attrD); if (f != nil) { if (ul) ulrect = Rect((drawpt.x, drawpt.y + f.height - ulheight), (drawpt.x + clipr.dx(), drawpt.y + f.height)); if (fg0 != bg0) { frames[0].text(drawpt, fg0, Point(0,0), f, s); if (ul) frames[0].draw(ulrect, fg0, nil, Point(0,0)); } if (fg1 != bg1) { frames[1].text(drawpt, fg1, Point(0,0), f, s); if (ul) frames[1].draw(ulrect, fg1, nil, Point(0,0)); } } if (modbbox.dx() == 0) modbbox = clipr; else modbbox = boundingrect(modbbox, clipr); } boundingrect(r1, r2 : Rect) : Rect { if (r2.min.x < r1.min.x) r1.min.x = r2.min.x; if (r2.min.y < r1.min.y) r1.min.y = r2.min.y; if (r2.max.x > r1.max.x) r1.max.x = r2.max.x; if (r2.max.y > r1.max.y) r1.max.y = r2.max.y; return r1; } drawcursor(pt : Point, srcix, show : int) { col0 := pt.y * ncols; c := cellmap[col0 + pt.x]; s := ""; s[0] = c.ch; (cx, cy) := c.clipmod; cellpos := Point(pt.x * cellsize.x, pt.y * cellsize.y); clipr := Rect(cellpos, cellpos.add(Point(cellsize.x, cellsize.y))); clipr = clipr.addpt(winoff); clipr = clipr.addpt(window.r.min); drawpt := cellpos.sub(Point(cx*cellsize.x, cy*cellsize.y)); drawpt = drawpt.add(winoff); drawpt = drawpt.add(window.r.min); if (!show) { # copy from appropriate frame buffer window.draw(clipr, frames[srcix], nil, cellpos); return; } # invert colours attr := c.attr ^ (fgMask | bgMask); fg, bg : ref Image; f := c.font; if (srcix == 0) (fg, bg, nil, nil) = framecolours(attr); else (nil, nil, fg, bg) = framecolours(attr); prevclipr := window.clipr; window.clipr = clipr; window.draw(clipr, bg, nil, Point(0,0)); ulrect : Rect; ul := (attr & attrL) && ! (attr & attrD); if (f != nil) { if (ul) ulrect = Rect((drawpt.x, drawpt.y + f.height - ulheight), (drawpt.x + clipr.dx(), drawpt.y + f.height)); if (fg != bg) { window.text(drawpt, fg, Point(0,0), f, s); if (ul) window.draw(ulrect, fg, nil, Point(0,0)); } } window.clipr = prevclipr; }