MODULE; <* PRAGMA LL *> IMPORT Animate, Axis, Fmt, Font, MG, MGPublic, MGV, Pts, Rect, R2, Match, MatchIE, MatchSelector, MatchViewClass, Thread, VBT, View, ZeusPanel; REVEAL T = MatchViewClass.T BRANDED OBJECT mgv : MGV.V; hides: ARRAY [1 .. 16] OF MG.Rectangle; clues: ARRAY [1 .. 16] OF MG.Rectangle; OVERRIDES shape := Shape; startrun := Startrun; reactivity := Reactivity; oeSetState := SetState; oeInit := Init; END; PROCEDURE ViewMatch Startrun (t: T) = VAR v := t.mgv; BEGIN LOCK v.mu DO v.displayList := NEW(MG.Group).init(3 * 16); END; END Startrun; PROCEDUREReactivity (<* UNUSED *> t: T; <* UNUSED *> on: BOOLEAN) = BEGIN END Reactivity; PROCEDUREShape (v: T; ax: Axis.T; <* UNUSED *> n: CARDINAL): VBT.SizeRange = BEGIN IF ax = Axis.T.Hor THEN WITH sz = Pts.ToScreenPixels(v, 4.0 * WidthPts, ax) DO RETURN VBT.SizeRange{sz, sz, sz + 1} END; ELSE WITH sz = Pts.ToScreenPixels(v, 4.0 * HeightPts, ax) DO RETURN VBT.SizeRange{sz, sz, sz + 1} END; END; END Shape; PROCEDURESelectee (<* UNUSED *> s : MGV.Selectee; v : MG.V; t : MG.T; READONLY cd: VBT.MouseRec ) = VAR realID: INTEGER; BEGIN IF t.id > 100 THEN realID := t.id MOD 100 ELSE realID := 0 END; TRY MatchIE.Selected(v.view, realID, cd); EXCEPT Thread.Alerted => END; END Selectee; CONST HeightPts = 100.0; WidthPts = 100.0; VAR msg := ARRAY [0..3] OF TEXT { "SRC 1st Annual", "ANIMATION FESTIVAL", "July 20-31, 1992", "Don't miss it!"}; msgColor := MGPublic.ColorFromText("LightBlue"); msgFont := Font.FromName (ARRAY [0..0] OF TEXT{"*helvetica*240*"}); clueColor := MGPublic.ColorFromText("LightRed"); clueFont := Font.FromName (ARRAY [0..0] OF TEXT{"*helvetica*-r-*140*"}); hideColor:= MGPublic.ColorFromText("LightGreen"); hideFont := Font.FromName (ARRAY[0..0] OF TEXT{"*helvetica*180*"}); CONST Border = 3.0; PROCEDURENW (i: INTEGER): R2.T = BEGIN WITH row = (i - 1) DIV 4, col = (i - 1) MOD 4 DO RETURN R2.T{FLOAT(col) * WidthPts + Border, FLOAT(4 - row) * HeightPts - Border} END END NW; PROCEDURESE (i: INTEGER): R2.T = BEGIN WITH row = (i - 1) DIV 4, col = (i - 1) MOD 4 DO RETURN R2.T{FLOAT(col+1) * WidthPts - Border, FLOAT(4 - row - 1) * HeightPts + Border} END END SE; PROCEDURES (k: INTEGER): Sides = VAR sides:= Sides{}; BEGIN DEC(k); IF k MOD 2 = 1 THEN sides := sides + Sides{Side.Top} END; IF (k DIV 2) MOD 2 = 1 THEN sides := sides + Sides{Side.Bottom} END; IF (k DIV 4) MOD 2 = 1 THEN sides := sides + Sides{Side.Left} END; IF (k DIV 8) MOD 2 = 1 THEN sides := sides + Sides{Side.Right} END; IF sides = Sides{} THEN sides := Sides{Side.Left.. Side.Bottom} END; RETURN sides; END S; PROCEDUREInit (t: T; READONLY clues: Match.Clues) = CONST MsgHeight = 4.0 / FLOAT(NUMBER(msg)) * HeightPts; VAR v := t.mgv; BEGIN MGPublic.ResetLookups(v); FOR i := 0 TO LAST(msg) DO WITH nw = R2.T{0.0, FLOAT(NUMBER(msg) - i) * MsgHeight}, se = R2.T{4.0 * WidthPts, nw[1] - MsgHeight} DO EVAL NEW(MG.Rectangle, id := i, color := msgColor, font := msgFont, label := msg[i]).init(nw, se, v); END; END; FOR k := 1 TO 16 DO t.clues[k] := NEW(MG.Rectangle, id := 100 + k, appearance := NEW(ClippedAppearance, sides := S(k)), color := clueColor, font := clueFont, label := clues[k]).init(NW(k), SE(k), v); END; FOR k := 1 TO 16 DO t.hides[k] := NEW(MG.Rectangle, id := 200 + k, color := hideColor, font := hideFont, label := Fmt.Int(k)).init( NW(k), SE(k), v); END; VBT.Mark(v); END Init; <* UNUSED *> PROCEDUREoldSetState (t: T; i,j: INTEGER; state: Match.State) = VAR v := t.mgv; BEGIN CASE state OF | Match.State.Hide => (* restore t.hides[i] *) v.displayList.addBefore (v, t.hides[i]); IF j # i THEN v.displayList.addBefore (v, t.hides[j]) END | Match.State.Clue => (* get rid of t.hides[i] *) v.displayList.remove (v, t.hides[i]); IF j # i THEN v.displayList.remove (v, t.hides[j]) END | Match.State.Reveal => (* t.hides[i] already gone; get rid of t.clues[i] *) v.displayList.remove (v, t.clues[i]); IF j # i THEN v.displayList.remove (v, t.clues[j]) END END; VBT.Mark(v); END oldSetState; PROCEDURESetState (t: T; i, j: INTEGER; state: Match.State) RAISES {Thread.Alerted} = VAR v := t.mgv; BEGIN CASE state OF | Match.State.Hide => (* restore t.hides[i] *) Grow(v, i, t.hides[i]); IF j # i THEN Grow(v, j, t.hides[j]) END | Match.State.Clue => (* get rid of t.hides[i] *) Shrink(v, i, t.hides[i]); IF j # i THEN Shrink(v, j, t.hides[j]) END | Match.State.Reveal => (* t.hides[i] already gone; get rid of t.clues[i] *) AnimDo(v, i, t.clues[i]); IF j # i THEN AnimDo(v, j, t.clues[j]) END END; MGV.Animation(v) END SetState; TYPE MoveCorners = Animate.T OBJECT cell: INTEGER; OVERRIDES length := Length; doStep := DoStep; END; PROCEDURELength (<* UNUSED *> t : MoveCorners; v : MG.V; <* UNUSED *> mg: MG.T ): INTEGER = BEGIN RETURN Pts.ToScreenPixels(v, HeightPts / 2.0, Axis.T.Ver); END Length; CONST InnerW = WidthPts - Border - Border; InnerH = HeightPts - Border - Border; PROCEDUREDoStep ( t : MoveCorners; time : REAL; <* UNUSED *> timePrev: REAL; v : MG.V; mg : MG.T ) = BEGIN WITH nw = NW(t.cell), se = SE(t.cell) DO NARROW(mg, MG.Rectangle).reshape( v, R2.Add(nw, R2.Scale(time / 2.0, R2.T{InnerW, -InnerH})), R2.Add(se, R2.Scale(time / 2.0, R2.T{-InnerW, InnerH}))); END; END DoStep; PROCEDUREShrink (v: MGV.V; i: INTEGER; cell: MG.Rectangle) = BEGIN WITH anim = NEW(MoveCorners, cell := i).init() DO MGV.AddAnimation(v, anim, cell) END END Shrink; PROCEDUREGrow (v: MGV.V; i: INTEGER; cell: MG.Rectangle) = BEGIN WITH anim = NEW(MoveCorners, cell := i).init( Animate.tfInverse) DO MGV.AddAnimation(v, anim, cell) END END Grow; TYPE MoveSide = MoveCorners OBJECT side: Side; OVERRIDES doStep := DoStepMoveSide; END; PROCEDUREDoStepMoveSide ( t : MoveSide; time : REAL; <* UNUSED *> timePrev: REAL; v : MG.V; mg : MG.T ) = VAR nw := NW(t.cell); se := SE(t.cell); BEGIN CASE t.side OF | Side.Top => nw[1] := nw[1] - time * InnerH; | Side.Bottom => se[1] := se[1] + time * InnerH; | Side.Left => nw[0] := nw[0] + time * InnerW; | Side.Right => se[0] := se[0] - time * InnerW; ELSE <* ASSERT FALSE *> END; NARROW(mg, MG.Rectangle).reshape(v, nw, se); END DoStepMoveSide; VAR animHighlight := NEW(Animate.Highlight).init(); PROCEDUREAnimDo ( v : MGV.V; <* UNUSED *> i : INTEGER; cell: MG.Rectangle) = BEGIN MGV.AddAnimation(v, animHighlight, cell) END AnimDo; TYPE Side = {Left, Right, Top, Bottom}; Sides = SET OF Side; CONST LeftRight = Sides{Side.Left,Side.Right}; TopBottom = Sides{Side.Top,Side.Bottom}; TYPE ClippedAppearance = MG.AppearanceDefault OBJECT sides: Sides := Sides{Side.Top}; OVERRIDES paint := ClippedPaint END; PROCEDUREClippedPaint ( self: ClippedAppearance; t : MG.T; v : MG.V; VAR clip: Rect.T ) = VAR rect := t.rgn(v).r; wd := FLOAT(Rect.HorSize(rect)); ht := FLOAT(Rect.VerSize(rect)); hscale, vscale := 1.0; BEGIN IF LeftRight = LeftRight * self.sides THEN hscale := 0.5 END; IF TopBottom = TopBottom * self.sides THEN vscale := 0.5 END; IF Side.Top IN self.sides THEN rect.north := rect.north + ROUND(vscale * t.highlight * ht); END; IF Side.Bottom IN self.sides THEN rect.south := rect.south - ROUND(vscale * t.highlight * ht); END; IF Side.Left IN self.sides THEN rect.west := rect.west + ROUND(hscale * t.highlight * wd); END; IF Side.Right IN self.sides THEN rect.east := rect.east - ROUND(hscale * t.highlight * wd); END; rect := Rect.Meet(clip, rect); MG.AppearanceDefault.paint(self, t, v, rect); END ClippedPaint; PROCEDURENew (): View.T = VAR v := NEW(T); BEGIN v.mgv := NEW(MGV.V, view := v, selectee := NEW(MGV.Selectee, select := Selectee), selector := MatchSelector.closest).init(); RETURN v.init(v.mgv); END New; BEGIN ZeusPanel.RegisterView (New, "Game Board", "Match"); END ViewMatch.