summaryrefslogtreecommitdiff
path: root/appl/wm/polyhedra.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/polyhedra.b')
-rw-r--r--appl/wm/polyhedra.b800
1 files changed, 800 insertions, 0 deletions
diff --git a/appl/wm/polyhedra.b b/appl/wm/polyhedra.b
new file mode 100644
index 00000000..b6d7088d
--- /dev/null
+++ b/appl/wm/polyhedra.b
@@ -0,0 +1,800 @@
+implement WmPolyhedra;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect, Pointer, Image, Screen, Display: import draw;
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "math.m";
+ math: Math;
+ sin, cos, tan, sqrt: import math;
+include "rand.m";
+ rand: Rand;
+include "daytime.m";
+ daytime: Daytime;
+include "math/polyhedra.m";
+ polyhedra: Polyhedra;
+ Polyhedron: import Polyhedra;
+ scanpolyhedra, getpolyhedron: import polyhedra;
+include "math/polyfill.m";
+ polyfill: Polyfill;
+ initzbuf, clearzbuf, fillpoly: import polyfill;
+include "smenu.m";
+ smenu: Smenu;
+ Scrollmenu: import smenu;
+
+WmPolyhedra : module
+{
+ init : fn(nil : ref Draw->Context, argv : list of string);
+};
+
+WIDTH, HEIGHT: con 400;
+
+mainwin: ref Toplevel;
+Disp, black, white, opaque: ref Image;
+Dispr: Rect;
+pinit := 40;
+
+init(ctxt : ref Draw->Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ bufio = load Bufio Bufio->PATH;
+ math = load Math Math->PATH;
+ rand = load Rand Rand->PATH;
+ daytime = load Daytime Daytime->PATH;
+ polyhedra = load Polyhedra Polyhedra->PATH;
+ polyfill = load Polyfill Polyfill->PATH;
+ smenu = load Smenu Smenu->PATH;
+ rand->init(daytime->now());
+ daytime = nil;
+ polyfill->init();
+ √2 = sqrt(2.0);
+ √3 = sqrt(3.0);
+ cursor := "";
+
+ tkclient->init();
+ if(ctxt == nil){
+ ctxt = tkclient->makedrawcontext();
+ # sys->fprint(sys->fildes(2), "wm not running\n");
+ # exit;
+ }
+ argv = tl argv;
+ while(argv != nil){
+ case hd argv{
+ "-p" =>
+ argv = tl argv;
+ if(argv != nil)
+ pinit = int hd argv;
+ "-r" =>
+ pinit = -1;
+ "-c" =>
+ argv = tl argv;
+ if(argv != nil)
+ cursor = hd argv;
+ }
+ if(argv != nil)
+ argv = tl argv;
+ }
+ (win, wmcmd) := tkclient->toplevel(ctxt, "", "Polyhedra", Tkclient->Resize | Tkclient->Hide);
+ mainwin = win;
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ for(i := 0; i < len win_config; i++)
+ cmd(win, win_config[i]);
+ if(cursor != nil)
+ cmd(win, "cursor -bitmap " + cursor);
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ fittoscreen(win);
+ pid := -1;
+ sync := chan of int;
+ chanθ := chan of real;
+ geo := newgeom();
+ setimage(win, geo);
+ cmd(win, "update");
+ display := win.image.display;
+ white = display.color(Draw->White);
+ black = display.color(Draw->Black);
+ opaque = display.opaque;
+ shade = array[NSHADES] of ref Image;
+ for(i = 0; i < NSHADES; i++){
+ # v := (255*i)/(NSHADES-1); # NSHADES=17
+ v := (192*i)/(NSHADES-1)+32; # NSHADES=13
+ # v := (128*i)/(NSHADES-1)+64; # NSHADES=9
+ shade[i] = display.rgb(v, v, v);
+ # shade[i] = rgba(display, v, v, v, 16r7f);
+ }
+ (geo.npolyhedra, geo.polyhedra, geo.b) = scanpolyhedra("/lib/polyhedra.all");
+ if(geo.npolyhedra == 0){
+ sys->fprint(sys->fildes(2), "cannot open polyhedra database\n");
+ exit;
+ }
+ yieldc := chan of int;
+ # spawn yieldproc(yieldc);
+ # ypid := <- yieldc;
+ initgeom(geo);
+ sm := array[2] of ref Scrollmenu;
+ sm[0] = scrollmenu(win, ".f.menu", geo.polyhedra, geo.npolyhedra, 0);
+ sm[1] = scrollmenu(win, ".f.menud", geo.polyhedra, geo.npolyhedra, 1);
+ # createmenu(win, geo.polyhedra);
+ spawn drawpolyhedron(geo, sync, chanθ, yieldc);
+ pid = <- sync;
+ newproc := 0;
+
+ for(;;){
+ alt{
+ c := <-win.ctxt.kbd =>
+ tk->keyboard(win, c);
+ c := <-win.ctxt.ptr =>
+ tk->pointer(win, *c);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq =>
+ tkclient->wmctl(win, c);
+ c := <- wmcmd =>
+ case c{
+ "exit" =>
+ exits(pid, sm);
+ * =>
+ sync <-= 0;
+ tkclient->wmctl(win, c);
+ if(c[0] == '!'){
+ if(setimage(win, geo) <= 0)
+ exits(pid, sm);
+ }
+ sync <-= 1;
+ }
+ c := <- cmdch =>
+ (nil, toks) := sys->tokenize(c, " ");
+ case hd toks{
+ "prev" =>
+ geo.curpolyhedron = geo.curpolyhedron.prv;
+ getpoly(geo, -1);
+ newproc = 1;
+ "next" =>
+ geo.curpolyhedron = geo.curpolyhedron.nxt;
+ getpoly(geo, 1);
+ newproc = 1;
+ "dual" =>
+ geo.dual = !geo.dual;
+ newproc = 1;
+ "edges" =>
+ edges = !edges;
+ "faces" =>
+ faces = !faces;
+ "clear" =>
+ clear = !clear;
+ "slow" =>
+ if(geo.θ > ε){
+ if(geo.θ < 2.)
+ chanθ <-= geo.θ/2.;
+ else
+ chanθ <-= geo.θ-1.;
+ }
+ "fast" =>
+ if(geo.θ < 45.){
+ if(geo.θ < 1.)
+ chanθ <-= 2.*geo.θ;
+ else
+ chanθ <-= geo.θ+1.;
+ }
+ "axis" =>
+ setaxis(geo);
+ initmatrix(geo);
+ newproc = 1;
+ "menu" =>
+ x := int cmd(win, ".p cget actx");
+ y := int cmd(win, ".p cget acty");
+ w := int cmd(win, ".p cget -actwidth");
+ h := int cmd(win, ".p cget -actheight");
+ sm[geo.dual].post(x+w/8, y+h/8, cmdch, "");
+ # cmd(win, ".f.menu post " + x + " " + y);
+ * =>
+ i = int hd toks;
+ fp := geo.polyhedra;
+ for(p := fp; p != nil; p = p.nxt){
+ if(p.indx == i){
+ geo.curpolyhedron = p;
+ getpoly(geo, 1);
+ newproc = 1;
+ break;
+ }
+ if(p.nxt == fp)
+ break;
+ }
+ }
+ }
+ if(newproc){
+ sync <-= 0; # stop it first
+ kill(pid);
+ spawn drawpolyhedron(geo, sync, chanθ, yieldc);
+ pid = <- sync;
+ newproc = 0;
+ }
+ }
+}
+
+setimage(win: ref Toplevel, geo: ref Geom): int
+{
+ panelw := int tk->cmd(win, ".p cget -actwidth");
+ panelh := int tk->cmd(win, ".p cget -actheight");
+ if(panelw < 3)
+ panelw = 3;
+ if(panelh < 3)
+ panelh = 3;
+ Dispr = Rect((0,0), (panelw, panelh));
+ Disp = win.image.display.newimage(Dispr, win.image.chans, 0, Draw->Black);
+ if(Disp == nil){
+ sys->fprint(sys->fildes(2), "not enough image memory\n");
+ return 0;
+ }
+ tk->putimage(win, ".p", Disp, nil);
+ if(Dispr.dx() > Dispr.dy())
+ h := Dispr.dy();
+ else
+ h = Dispr.dx();
+ rr: Rect = ((0, 0), (h, h));
+ corner := ((Dispr.min.x+Dispr.max.x-rr.max.x)/2, (Dispr.min.y+Dispr.max.y-rr.max.y)/2);
+ geo.r = (rr.min.add(corner), rr.max.add(corner));
+ geo.h = h;
+ geo.sx = real ((3*h)/8);
+ geo.sy = - real ((3*h)/8);
+ geo.tx = h/2+geo.r.min.x;
+ geo.ty = h/2+geo.r.min.y;
+ geo.zstate = initzbuf(geo.r);
+ return 1;
+}
+
+# yieldcpu(c: chan of int)
+# {
+# c <-= 1;
+# <-c;
+# }
+
+# yieldproc(c: chan of int)
+# {
+# c <-= sys->pctl(0, nil);
+# for (;;) {
+# <-c;
+# c <-= 1;
+# }
+# }
+
+π: con Math->Pi;
+√2, √3: real;
+∞: con 1<<30;
+ε: con 0.001;
+
+Axis: adt{
+ λ, μ, ν: int;
+};
+
+Vector: adt{
+ x, y, z: real;
+};
+
+Geom: adt{
+ h: int; # length, breadth of r below
+ r: Rect; # area on screen to update
+ sx, sy: real; # x, y scale
+ tx, ty: int; # x, y translation
+ θ: real; # angle of rotation
+ TM: array of array of real; # rotation matrix
+ axis: Axis; # direction cosines of rotation
+ view: Vector;
+ light: Vector;
+ npolyhedra: int;
+ polyhedra: ref Polyhedron;
+ curpolyhedron: ref Polyhedron;
+ b: ref Iobuf; # of polyhedra file
+ dual: int;
+ zstate: ref Polyfill->Zstate;
+};
+
+NSHADES: con 13; # odd
+shade: array of ref Image;
+
+clear, faces: int = 1;
+edges: int = 0;
+
+setview(geo: ref Geom)
+{
+ geo.view = (0.0, 0.0, 1.0);
+ geo.light = (0.0, -1.0, 0.0);
+}
+
+map(v: Vector, geo: ref Geom): Point
+{
+ return (int (geo.sx*v.x)+geo.tx, int (geo.sy*v.y)+geo.ty);
+}
+
+minus(v1: Vector): Vector
+{
+ return (-v1.x, -v1.y, -v1.z);
+}
+
+add(v1, v2: Vector): Vector
+{
+ return (v1.x+v2.x, v1.y+v2.y, v1.z+v2.z);
+}
+
+sub(v1, v2: Vector): Vector
+{
+ return (v1.x-v2.x, v1.y-v2.y, v1.z-v2.z);
+}
+
+mul(v1: Vector, l: real): Vector
+{
+ return (l*v1.x, l*v1.y, l*v1.z);
+}
+
+div(v1: Vector, l: real): Vector
+{
+ return (v1.x/l, v1.y/l, v1.z/l);
+}
+
+normalize(v1: Vector): Vector
+{
+ return div(v1, sqrt(dot(v1, v1)));
+}
+
+dot(v1, v2: Vector): real
+{
+ return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
+}
+
+cross(v1, v2: Vector): Vector
+{
+ return (v1.y*v2.z-v2.y*v1.z, v1.z*v2.x-v2.z*v1.x, v1.x*v2.y-v2.x*v1.y);
+}
+
+drawpolyhedron(geo: ref Geom, sync: chan of int, chanθ: chan of real, yieldc: chan of int)
+{
+ s: string;
+
+ sync <-= sys->pctl(0, nil);
+ p := geo.curpolyhedron;
+ if(!geo.dual || p.anti){
+ s = p.name;
+ s += " (" + string p.indx + ")";
+ puts(s);
+ drawpolyhedron0(p.V, p.F, p.concave, p.allf || p.anti, p.v, p.f, p.fv, p.inc, geo, sync, chanθ, yieldc);
+ }
+ else{
+ s = p.dname;
+ s += " (" + string p.indx + ")";
+ puts(s);
+ drawpolyhedron0(p.F, p.V, p.concave, p.anti, p.f, p.v, p.vf, 0.0, geo, sync, chanθ, yieldc);
+ }
+}
+
+drawpolyhedron0(V, F, concave, allf: int, v, f: array of Vector, fv: array of array of int, inc: real, geo: ref Geom, sync: chan of int, chanθ: chan of real, yieldc: chan of int)
+{
+ norm : array of array of Vector;
+ newn, oldn : array of Vector;
+
+ yieldc = nil; # not used now
+ θ := geo.θ;
+ totθ := 0.;
+ if(θ != 0.)
+ n := int ((360.+θ/2.)/θ);
+ else
+ n = ∞;
+ p := n;
+ t := 0;
+ vec := array[2] of array of Vector;
+ vec[0] = array[V] of Vector;
+ vec[1] = array[V] of Vector;
+ if(concave){
+ norm = array[2] of array of Vector;
+ norm[0] = array[F] of Vector;
+ norm[1] = array[F] of Vector;
+ }
+ Disp.draw(geo.r, black, opaque, (0, 0));
+ reveal(geo.r);
+ for(i := 0; ; i = (i+1)%p){
+ alt{
+ <- sync =>
+ <- sync;
+ θ = <- chanθ =>
+ geo.θ = θ;
+ initmatrix(geo);
+ if(θ != 0.){
+ n = int ((360.+θ/2.)/θ);
+ p = int ((360.-totθ+θ/2.)/θ);
+ }
+ else
+ n = p = ∞;
+ if(p == 0)
+ i = 0;
+ else
+ i = 1;
+ * =>
+ # yieldcpu(yieldc);
+ sys->sleep(0);
+ }
+ if(concave)
+ clearzbuf(geo.zstate);
+ new := vec[t];
+ old := vec[!t];
+ if(concave){
+ newn = norm[t];
+ oldn = norm[!t];
+ }
+ t = !t;
+ if(i == 0){
+ for(j := 0; j < V; j++)
+ new[j] = v[j];
+ if(concave){
+ for(j = 0; j < F; j++)
+ newn[j] = f[j];
+ }
+ setview(geo);
+ totθ = 0.;
+ p = n;
+ }
+ else{
+ for(j := 0; j < V; j++)
+ new[j] = mulm(geo.TM, old[j]);
+ if(concave){
+ for(j = 0; j < F; j++)
+ newn[j] = mulm(geo.TM, oldn[j]);
+ }
+ else{
+ geo.view = mulmi(geo.TM, geo.view);
+ geo.light = mulmi(geo.TM, geo.light);
+ }
+ totθ += θ;
+ }
+ if(clear)
+ Disp.draw(geo.r, black, opaque, (0, 0));
+ for(j := 0; j < F; j++){
+ if(concave){
+ if(allf || dot(geo.view, newn[j]) < 0.0)
+ polyfilla(fv[j], new, newn[j], dot(geo.light, newn[j]), geo, concave, inc);
+ }
+ else{
+ if(dot(geo.view, f[j]) < 0.0)
+ polyfilla(fv[j], new, f[j], dot(geo.light, f[j]), geo, concave, 0.0);
+ }
+ }
+ reveal(geo.r);
+ }
+}
+
+ZSCALE: con real (1<<20);
+LIMIT: con real (1<<11);
+
+polyfilla(fv: array of int, v: array of Vector, f: Vector, ill: real, geo: ref Geom, concave: int, inc: real)
+{
+ dc, dx, dy: int;
+
+ d := 0.0;
+ n := fv[0];
+ ap := array[n+1] of Point;
+ for(j := 0; j < n; j++){
+ vtx := v[fv[j+1]];
+ # vtx = add(vtx, mul(f, 0.1)); # interesting effects with -/larger factors
+ ap[j] = map(vtx, geo);
+ d += dot(f, vtx);
+ }
+ ap[n] = ap[0];
+ d /= real n;
+ if(concave){
+ if(fv[n+1] != 1)
+ d += inc;
+ if(f.z > -ε && f.z < ε)
+ return;
+ α := geo.sx;
+ β := real geo.tx;
+ γ := geo.sy;
+ δ := real geo.ty;
+ c := f.z;
+ a := -f.x/(c*α);
+ if(a <= -LIMIT || a >= LIMIT)
+ return;
+ b := -f.y/(c*γ);
+ if(b <= -LIMIT || b >= LIMIT)
+ return;
+ d = d/c-β*a-δ*b;
+ if(d <= -LIMIT || d >= LIMIT)
+ return;
+ dx = int (a*ZSCALE);
+ dy = int (b*ZSCALE);
+ dc = int (d*ZSCALE);
+ }
+ edge := white;
+ face := shade[int ((real ((NSHADES-1)/2))*(1.0-ill))];
+ if(concave){
+ if(!faces)
+ face = black;
+ if(!edges)
+ edge = nil;
+ fillpoly(Disp, ap, ~0, face, (0, 0), geo.zstate, dc, dx, dy);
+ }
+ else{
+ if(faces)
+ Disp.fillpoly(ap, ~0, face, (0, 0));
+ if(edges)
+ Disp.poly(ap, Draw->Endsquare, Draw->Endsquare, 0, edge, (0, 0));
+ }
+}
+
+getpoly(geo: ref Geom, dir: int)
+{
+ p := geo.curpolyhedron;
+ if(0){
+ while(p.anti){
+ if(dir > 0)
+ p = p.nxt;
+ else
+ p = p.prv;
+ }
+ }
+ geo.curpolyhedron = p;
+ getpolyhedron(p, geo.b);
+}
+
+degtorad(α: real): real
+{
+ return α*π/180.0;
+}
+
+initmatrix(geo: ref Geom)
+{
+ TM := geo.TM;
+ φ := degtorad(geo.θ);
+ sinθ := sin(φ);
+ cosθ := cos(φ);
+ (l, m, n) := normalize((real geo.axis.λ, real geo.axis.μ, real geo.axis.ν));
+ f := 1.0-cosθ;
+ TM[1][1] = (1.0-l*l)*cosθ + l*l;
+ TM[1][2] = l*m*f-n*sinθ;
+ TM[1][3] = l*n*f+m*sinθ;
+ TM[2][1] = l*m*f+n*sinθ;
+ TM[2][2] = (1.0-m*m)*cosθ + m*m;
+ TM[2][3] = m*n*f-l*sinθ;
+ TM[3][1] = l*n*f-m*sinθ;
+ TM[3][2] = m*n*f+l*sinθ;
+ TM[3][3] = (1.0-n*n)*cosθ + n*n;
+}
+
+mulm(TM: array of array of real, v: Vector): Vector
+{
+ x := v.x;
+ y := v.y;
+ z := v.z;
+ v.x = TM[1][1]*x + TM[1][2]*y + TM[1][3]*z;
+ v.y = TM[2][1]*x + TM[2][2]*y + TM[2][3]*z;
+ v.z = TM[3][1]*x + TM[3][2]*y + TM[3][3]*z;
+ return v;
+}
+
+mulmi(TM: array of array of real, v: Vector): Vector
+{
+ x := v.x;
+ y := v.y;
+ z := v.z;
+ v.x = TM[1][1]*x + TM[2][1]*y + TM[3][1]*z;
+ v.y = TM[1][2]*x + TM[2][2]*y + TM[3][2]*z;
+ v.z = TM[1][3]*x + TM[2][3]*y + TM[3][3]*z;
+ return v;
+}
+
+reveal(r: Rect)
+{
+ cmd := sys->sprint(".p dirty %d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+ tk->cmd(mainwin, cmd);
+ tk->cmd(mainwin, "update");
+}
+
+newgeom(): ref Geom
+{
+ geo := ref Geom;
+ TM := array[4] of array of real;
+ for(i := 0; i < 4; i++)
+ TM[i] = array[4] of real;
+ geo.θ = 10.;
+ geo.TM = TM;
+ geo.axis = (1, 1, 1);
+ geo.view = (1., 1., 1.);
+ geo.light = (1., 1., 1.);
+ geo.dual = 0;
+ return geo;
+}
+
+setaxis(geo: ref Geom)
+{
+ oaxis := geo.axis;
+ # while(geo.axis == Axis (0, 0, 0) || geo.axis = oaxis) not allowed
+ while((geo.axis.λ == 0 && geo.axis.μ == 0 && geo.axis.ν == 0) || (geo.axis.λ == oaxis.λ && geo.axis.μ == oaxis.μ && geo.axis.ν == oaxis.ν))
+ geo.axis = (rand->rand(5) - 2, rand->rand(5) - 2, rand->rand(5) - 2);
+}
+
+initgeom(geo: ref Geom)
+{
+ if(pinit < 0)
+ pn := rand->rand(geo.npolyhedra);
+ else
+ pn = pinit;
+ for(p := geo.polyhedra; --pn >= 0; p = p.nxt)
+ ;
+ geo.curpolyhedron = p;
+ getpoly(geo, 1);
+ setaxis(geo);
+ geo.θ = real (rand->rand(5)+1);
+ geo.dual = 0;
+ initmatrix(geo);
+ setview(geo);
+ Disp.draw(geo.r, black, opaque, (0, 0));
+ reveal(geo.r);
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ if(sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+exits(pid: int, sm: array of ref Scrollmenu)
+{
+ if(pid != -1)
+ kill(pid);
+ # kill(ypid);
+ sm[0].destroy();
+ sm[1].destroy();
+ exit;
+}
+
+cmd(top: ref Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "polyhedra: tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+puts(s: string)
+{
+ cmd(mainwin, ".f1.txt configure -text {" + s + "}");
+ cmd(mainwin, "update");
+}
+
+MENUMAX: con 10;
+
+scrollmenu(top: ref Tk->Toplevel, mname: string, p: ref Polyhedron, n: int, dual: int): ref Scrollmenu
+{
+ labs := array[n] of string;
+ i := 0;
+ for(q := p; q != nil && i < n; q = q.nxt){
+ if(dual)
+ name := q.dname;
+ else
+ name = q.name;
+ labs[i++] = string q.indx + " " + name;
+ }
+ sm := Scrollmenu.new(top, mname, labs, MENUMAX, (n-MENUMAX)/2);
+ cmd(top, mname + " configure -borderwidth 3");
+ return sm;
+}
+
+createmenu(top: ref Tk->Toplevel, p: ref Polyhedron)
+{
+ mn := ".f.menu";
+ cmd(top, "menu " + mn);
+ i := j := 0;
+ for(q := p ; q != nil; q = q.nxt){
+ cmd(top, mn + " add command -label {" + string q.indx + " " + q.name + "} -command {send cmd " + string q.indx + "}");
+ if(q.nxt == p)
+ break;
+ i++;
+ j++;
+ if(j == MENUMAX && q.nxt != nil){
+ cmd(top, mn + " add cascade -label MORE -menu " + mn + ".menu");
+ mn += ".menu";
+ cmd(top, "menu " + mn);
+ j = 0;
+ }
+ }
+}
+
+fittoscreen(win: ref Tk->Toplevel)
+{
+ Point: import draw;
+ if (win.image == nil || win.image.screen == nil)
+ return;
+ r := win.image.screen.image.r;
+ scrsize := Point((r.max.x - r.min.x), (r.max.y - r.min.y));
+ bd := int cmd(win, ". cget -bd");
+ winsize := Point(int cmd(win, ". cget -actwidth") + bd * 2, int cmd(win, ". cget -actheight") + bd * 2);
+ if (winsize.x > scrsize.x)
+ cmd(win, ". configure -width " + string (scrsize.x - bd * 2));
+ if (winsize.y > scrsize.y)
+ cmd(win, ". configure -height " + string (scrsize.y - bd * 2));
+ actr: Rect;
+ actr.min = Point(int cmd(win, ". cget -actx"), int cmd(win, ". cget -acty"));
+ actr.max = actr.min.add((int cmd(win, ". cget -actwidth") + bd*2,
+ int cmd(win, ". cget -actheight") + bd*2));
+ (dx, dy) := (actr.dx(), actr.dy());
+ if (actr.max.x > r.max.x)
+ (actr.min.x, actr.max.x) = (r.max.x - dx, r.max.x);
+ if (actr.max.y > r.max.y)
+ (actr.min.y, actr.max.y) = (r.max.y - dy, r.max.y);
+ if (actr.min.x < r.min.x)
+ (actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
+ if (actr.min.y < r.min.y)
+ (actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
+ cmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
+}
+
+win_config := array[] of {
+ "frame .f",
+ "button .f.prev -text {prev} -command {send cmd prev}",
+ "button .f.next -text {next} -command {send cmd next}",
+ "checkbutton .f.dual -text {dual} -command {send cmd dual} -variable dual",
+ ".f.dual deselect",
+ "pack .f.prev -side left",
+ "pack .f.next -side right",
+ "pack .f.dual -side top",
+
+ "frame .f0",
+ "checkbutton .f0.edges -text {edges} -command {send cmd edges} -variable edges",
+ ".f0.edges deselect",
+ "checkbutton .f0.faces -text {faces} -command {send cmd faces} -variable faces",
+ ".f0.faces select",
+ "checkbutton .f0.clear -text {clear} -command {send cmd clear} -variable clear",
+ ".f0.clear select",
+ "pack .f0.edges -side left",
+ "pack .f0.faces -side right",
+ "pack .f0.clear -side top",
+
+ "frame .f2",
+ "button .f2.slow -text {slow} -command {send cmd slow}",
+ "button .f2.fast -text {fast} -command {send cmd fast}",
+ "button .f2.axis -text {axis} -command {send cmd axis}",
+ "pack .f2.slow -side left",
+ "pack .f2.fast -side right",
+ "pack .f2.axis -side top",
+
+ "frame .f1",
+ "label .f1.txt -text { } -width " + string WIDTH,
+ "pack .f1.txt -side top -fill x",
+
+ "frame .f3",
+ "button .f3.menu -text {menu} -command {send cmd menu}",
+ "pack .f3.menu -side left",
+
+ "frame .pbd -bd 3",
+ "panel .p -width " + string WIDTH + " -height " + string HEIGHT,
+
+ "pack .f -side top -fill x",
+ "pack .f0 -side top -fill x",
+ "pack .f2 -side top -fill x",
+ "pack .f1 -side top -fill x",
+ "pack .f3 -side top -fill x",
+ "pack .p -in .pbd -fill both -expand 1",
+ "pack .pbd -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+
+};
+
+rgba(d: ref Display, r: int, g: int, b: int, α: int): ref Image
+{
+ c := draw->setalpha((r<<24)|(g<<16)|(b<<8), α);
+ return d.newimage(((0, 0), (1, 1)), d.image.chans, 1, c);
+}