modified on Fri Jul 10 20:45:45 1992 by mhb
MODULEMG EXPORTSMG ,MGPublic ; <* PRAGMA LL *> IMPORT Axis, Color, ColorName, Font, IntRefTbl, Math, Matrix2D, PaintOp, Point, Pts, R2, R2Box, Rect, Region, VBT, CirclePixmapCache, Path, R2Path, R2PathExtra, Fuzzy, (* ShapePixmap, *) Pixmap, Text; REVEAL T = TPublic BRANDED OBJECT OVERRIDES setColor := SetColorT; setFont := SetFontT; setHighlight := SetHighlightT; setVisible := SetVisibleT; setWeight := SetWeightT; setAlignment := SetAlignmentT; setLabel := SetLabelT; (* copies fields of known subtypes: line, lineEnd, ellipse, shape, label, rectangle (not group) *) render := RenderError; transform := TransformDefault; bounds := BoundsError; rgn := RgnError; init := InitT; END; VAR circleCache := CirclePixmapCache.New();
MG's private circle pixmap cache
VAR (* CONST *) RealDefaultFont := Font.FromName( ARRAY OF TEXT{ "-adobe-helvetica-medium-r-normal--*-100-*-*-p-*-iso8859-1"}); PROCEDUREReturn the number of pixels indicated by an MG.T's weight. If the weight <= 0.0, return 0. Otherwise calculate the number of pixels indicated by the weight (with a minimum of 1). We take the average of both axes.PixelsFromWeight (v: V; axis: Axis.T; weight: REAL): INTEGER = BEGIN RETURN Pts.ToScreenPixels(v, weight, axis); END PixelsFromWeight;
<* INLINE *> PROCEDUREConvert a distance in MG space to pixels in screen spaceBorderInPixels (v: V; t: T): INTEGER = BEGIN IF t.weight <= 0.0 THEN RETURN 0; ELSE RETURN MAX(1, (PixelsFromWeight(v, Axis.T.Hor, t.weight) + PixelsFromWeight(v, Axis.T.Ver, t.weight)) DIV 2); END; END BorderInPixels; <* INLINE *> PROCEDUREScreenPointToMGC (v: V; READONLY pScreen: Point.T): R2.T = VAR dom := VBT.Domain(v.paintVBT); ptsToPixelsX := Pts.ToPixels(v, 1.0, Axis.T.Hor); ptsToPixelsY := Pts.ToPixels(v, 1.0, Axis.T.Ver); BEGIN RETURN R2.T{v.nw[0] + FLOAT(pScreen.h - dom.west) / ptsToPixelsX, v.nw[1] + FLOAT(dom.north - pScreen.v) / ptsToPixelsY} END ScreenPointToMGC;
<* INLINE *> PROCEDUREConvert a point in MG space to a point in screen spaceMGCDistToPixels (v: V; READONLY p: R2.T): Point.T = BEGIN RETURN Point.T{Pts.ToScreenPixels(v, p[0], Axis.T.Hor), Pts.ToScreenPixels(v, p[1], Axis.T.Ver)}; END MGCDistToPixels;
<* INLINE *> PROCEDUREHere is a special version of MGCToScreenRect that returns a Rect.T of constant dimensions. In other words it does not depend on the absolute position of the two corners.MGCToScreenPoint (v: V; READONLY p: R2.T): Point.T = VAR dom := VBT.Domain(v.paintVBT); BEGIN RETURN Point.T{ dom.west + Pts.ToScreenPixels(v, p[0] - v.nw[0], Axis.T.Hor), dom.north + Pts.ToScreenPixels(v, v.nw[1] - p[1], Axis.T.Ver)} END MGCToScreenPoint; <* INLINE *> PROCEDUREMGCToScreenRect (v: V; READONLY corner1, corner2: R2.T): Rect.T = BEGIN RETURN Rect.FromCorners( MGCToScreenPoint(v, corner1), MGCToScreenPoint(v, corner2)); END MGCToScreenRect; <* INLINE *> PROCEDUREMGCToScreenRectNonEmpty (v: V; READONLY corner1, corner2: R2.T): Rect.T = BEGIN RETURN RectFromCorners( MGCToScreenPoint(v, corner1), MGCToScreenPoint(v, corner2)); END MGCToScreenRectNonEmpty; <* INLINE *> PROCEDURERectFromCorners (p1, p2: Point.T): Rect.T = BEGIN RETURN Rect.Join(Rect.FromPoint(p1), Rect.FromPoint(p2)) END RectFromCorners;
<* INLINE *> PROCEDUREWe could be smarter here and spot rotations MOD 2 Pi, but it's probably not going to come up the frequenty. <* UNUSED *> PROCEDURE EquivalentRotationAndScaling (READONLY m1, m2: Matrix2D.T): BOOLEAN = BEGIN RETURN m1[0] = m2[0] AND m1[1] = m2[1] AND m1[2] = m2[2] AND m1[3] = m2[3]; END EquivalentRotationAndScaling;MGCToStableScreenRect (v: V; READONLY corner1, corner2: R2.T): Rect.T = VAR size := MGCDistToPixels(v, R2.Sub(corner2, corner1)); reference := MGCToScreenPoint(v, corner1); BEGIN RETURN Rect.FromAbsEdges(reference.h, reference.h + size.h, reference.v, reference.v - size.v); END MGCToStableScreenRect;
EXCEPTION Fatal; <* FATAL Fatal *> PROCEDUREmust make sure that render t doesn't overpaint any other object that is (visually) on top of itRegister (v: V; id: ID; t: T) = BEGIN LOCK v.mu DO IF v.lookup.put(id, t) THEN RAISE Fatal END; END; END Register; PROCEDURELookup (v: V; id: ID): T = VAR res: REFANY; BEGIN LOCK v.mu DO IF id # NoID AND v.lookup.get(id, res) THEN RETURN res; ELSE RETURN NIL; END; END; END Lookup; PROCEDUREResetLookupsLocked (v: V) = BEGIN v.lookup := NEW(IntRefTbl.Default).init(); END ResetLookupsLocked; PROCEDUREResetLookups (v: V) = BEGIN LOCK v.mu DO ResetLookupsLocked(v); END; END ResetLookups; PROCEDUREClear (v: V) = BEGIN LOCK v.mu DO ResetLookupsLocked(v); v.animations := NIL; v.displayList := NEW(Group).init(); END; VBT.NewShape(v); v.mgRedisplay(Region.Empty); END Clear; PROCEDUREInitT (t : T; v : V := NIL; group: Group := NIL ): T = BEGIN IF t.appearance = NIL THEN t.appearance := appearanceDefault END; IF t.color = NIL THEN t.color := PaintOp.bgFg END; IF t.font = DefaultFont THEN t.font := RealDefaultFont END; IF group # NIL THEN group.addBefore(v, t) ELSIF v # NIL THEN v.displayList.addBefore(v, t); END; IF t.id # NoID AND v # NIL THEN Register(v, t.id, t); END; RETURN t END InitT; <* INLINE *> PROCEDUREPos (t: T; v: V): R2.T = BEGIN LOCK v.mu DO RETURN R2.T{t.m[4], t.m[5]}; END; END Pos; <* INLINE *> PROCEDUREPosLocked (t: T; <* UNUSED *> v: V := NIL): R2.T = BEGIN RETURN R2.T{t.m[4], t.m[5]}; END PosLocked; PROCEDURERenderError (<* UNUSED *> self: T; <* UNUSED *> v : V; <* UNUSED *> VAR r : Rect.T) = BEGIN <* ASSERT FALSE *> END RenderError; PROCEDURETransformDefault (self: T; v: V; READONLY m: Matrix2D.T) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.m := Matrix2D.Concat(self.m, m); self.dirty := TRUE; END TransformDefault; PROCEDUREBoundsError (<* UNUSED *> self: T; <* UNUSED *> v: V): R2Box.T = BEGIN <* ASSERT FALSE *> END BoundsError; PROCEDURERgnError (<* UNUSED *> self: T; <* UNUSED *> v: V): Region.T = BEGIN <* ASSERT FALSE *> END RgnError; PROCEDUREPaintLabel (t: T; v: V; READONLY clip, rect: Rect.T) = VAR bnds: Rect.T; BEGIN VBT.PaintText(v.paintVBT, clip, LabelPoint(t, v, rect, bnds), t.font, t.label, t.color.transparentFg); END PaintLabel; PROCEDURELabelRect (t: T; v: V; READONLY rect: Rect.T): Rect.T = VAR bnds: Rect.T; pt := LabelPoint(t, v, rect, bnds); BEGIN RETURN Rect.Move(bnds, pt); END LabelRect; PROCEDURERegionWithLabel (t: T; v: V; READONLY rect: Rect.T): Region.T = BEGIN IF Text.Equal(t.label, "") THEN RETURN Region.FromRect(rect) ELSE RETURN Region.FromRect(Rect.Join(rect, LabelRect(t, v, rect))); END END RegionWithLabel; PROCEDURELabelPoint ( t : T; v : V; READONLY rect: Rect.T; VAR (* OUT *) bnds: Rect.T ): Point.T = VAR b: Rect.T; BEGIN bnds := VBT.BoundingBox(v, t.label, t.font); b := Rect.Inset(bnds, -ROUND(0.15 * FLOAT(Rect.VerSize(bnds)))); CASE t.alignment OF | Alignment.NE => RETURN Point.Add(Rect.NorthEast(rect), Point.Minus(Rect.SouthWest(b))) | Alignment.N => RETURN Point.Add(RectN(rect), Point.Minus(RectS(b))) | Alignment.NW => RETURN Point.Add(Rect.NorthWest(rect), Point.Minus(Rect.SouthEast(b))) | Alignment.W => RETURN Point.Add(RectW(rect), Point.Minus(RectE(b))) | Alignment.SW => RETURN Point.Add(Rect.SouthWest(rect), Point.Minus(Rect.NorthEast(b))) | Alignment.S => RETURN Point.Add(RectS(rect), Point.Minus(RectN(b))) | Alignment.SE => RETURN Point.Add(Rect.SouthEast(rect), Point.Minus(Rect.NorthWest(b))) | Alignment.E => RETURN Point.Add(RectE(rect), Point.Minus(RectW(b))) | Alignment.Center => RETURN Point.Add(Rect.Middle(rect), Point.Minus(Rect.Middle(b))) END END LabelPoint; PROCEDURERectS (r: Rect.T): Point.T = BEGIN RETURN Point.T{(r.east + r.west) DIV 2, r.south} END RectS; PROCEDURERectN (r: Rect.T): Point.T = BEGIN RETURN Point.T{(r.east + r.west) DIV 2, r.north} END RectN; PROCEDURERectE (r: Rect.T): Point.T = BEGIN RETURN Point.T{r.east, (r.north + r.south) DIV 2} END RectE; PROCEDURERectW (r: Rect.T): Point.T = BEGIN RETURN Point.T{r.west, (r.north + r.south) DIV 2} END RectW; REVEAL Group = GroupPublic BRANDED OBJECT OVERRIDES addAfter := GroupAddAfter; addBefore := GroupAddBefore; remove := GroupRemove; top := GroupTop; bottom := GroupBottom; init := InitGroup; render := RenderGroup; setColor := SetColorOfGroup; setFont := SetFontOfGroup; setHighlight := SetHighlightOfGroup; setVisible := SetVisibleOfGroup; setWeight := SetWeightOfGroup; setAlignment := SetAlignmentOfGroup; setLabel := SetLabelOfGroup; bounds := BoundsGroup; rgn := RgnGroup; iterate := GroupIterate; transform := TransformGroup; END; PROCEDUREIterateGroup (group : Group; iter : GroupIterator; recursively := FALSE; fromBack := TRUE ): BOOLEAN = BEGIN LOCK iter.v.mu DO RETURN group.iterate(iter, recursively, fromBack) END END IterateGroup; PROCEDUREGroupIterate (group : Group; iter : GroupIterator; recursively := FALSE; fromBack := TRUE ): BOOLEAN = BEGIN IF fromBack THEN FOR i := 0 TO group.cntElems - 1 DO WITH t = group.elems[i] DO IF recursively THEN TYPECASE t OF | Group (subgroup) => IF NOT subgroup.iterate(iter, recursively) THEN RETURN FALSE END; ELSE END; END; IF NOT iter.proc(t) THEN RETURN FALSE; END; (* if *) END; END; ELSE FOR i := group.cntElems - 1 TO 0 BY -1 DO WITH t = group.elems[i] DO IF recursively THEN TYPECASE t OF | Group (subgroup) => IF NOT subgroup.iterate(iter, recursively) THEN RETURN FALSE END; ELSE END; END; IF NOT iter.proc(t) THEN RETURN FALSE; END; (* if *) END; END; END; RETURN TRUE END GroupIterate; PROCEDUREAddToGroup (group: Group; v: V; t: T; atTop := TRUE) = BEGIN LOCK v.mu DO IF atTop THEN group.addBefore(v, t) ELSE group.addAfter(v, t) END; END END AddToGroup; PROCEDUREAddToGroupAfter (group: Group; v: V; t: T; prev: T := NIL) = BEGIN LOCK v.mu DO group.addAfter(v, t, prev) END END AddToGroupAfter; PROCEDUREAddToGroupBefore (group: Group; v: V; t: T; next: T := NIL) = BEGIN LOCK v.mu DO group.addBefore(v, t, next) END END AddToGroupBefore; PROCEDUREGroupFind (group: Group; t: T): INTEGER = BEGIN FOR i := 0 TO group.cntElems - 1 DO IF group.elems[i] = t THEN RETURN i END; END; <* ASSERT FALSE *> END GroupFind; PROCEDUREGroupInsert (group: Group; v: V; at: INTEGER; t: T) = VAR cnt := group.cntElems; pos := PosLocked(t, v); deltaBack: R2.T; BEGIN IF cnt = NUMBER(group.elems^) THEN VAR new := NEW(REF ARRAY OF T, (3 * group.cntElems) DIV 2); BEGIN SUBARRAY(new^, 0, at) := SUBARRAY(group.elems^, 0, at); SUBARRAY(new^, at + 1, cnt - at) := SUBARRAY(group.elems^, at, cnt - at); group.elems := new; END; ELSE SUBARRAY(group.elems^, at + 1, cnt - at) := SUBARRAY(group.elems^, at, cnt - at) END; group.elems[at] := t; INC(group.cntElems); (* set the child's transformation, but don't move it (a good trick) so transform pos to get the new position (pos') calculate the delta back to the old pos (pos - pos') concat node.m and a translation back by delta *) deltaBack := R2.Sub(pos, Matrix2D.Transform(group.m, pos)); t.transform(v, Matrix2D.Concat(group.m, Matrix2D.Translate( deltaBack[0], deltaBack[1]))); END GroupInsert; PROCEDUREGroupDelete (group: Group; v: V; at: INTEGER; t: T) = VAR cnt := group.cntElems; pos := PosLocked(t, v); inv := Matrix2D.Inverse(group.m); deltaBack: R2.T; BEGIN SUBARRAY(group.elems^, at, cnt - at - 1) := SUBARRAY(group.elems^, at + 1, cnt - at - 1); DEC(group.cntElems); (* remove the parent's matrix transformation without moving child so add back in a translation to the current position after the *) deltaBack := R2.Sub(pos, Matrix2D.Transform(inv, pos)); t.transform(v, Matrix2D.Concat(inv, Matrix2D.Translate(deltaBack[0], deltaBack[1]))); END GroupDelete; PROCEDUREGroupAddAfter (group: Group; v: V; t: T; prev: T := NIL) = VAR i: INTEGER; BEGIN IF t = NIL THEN RETURN END; t.dirty := TRUE; IF prev = NIL THEN i := 0 ELSE i := GroupFind(group, prev) + 1; END; GroupInsert(group, v, i, t); END GroupAddAfter; PROCEDUREGroupAddBefore (group: Group; v: V; t: T; next: T := NIL) = VAR i: INTEGER; BEGIN IF t = NIL THEN RETURN END; t.dirty := TRUE; IF next = NIL THEN i := group.cntElems ELSE i := GroupFind(group, next); END; GroupInsert(group, v, i, t); END GroupAddBefore; PROCEDURERemoveFromGroup (group: Group; v: V; t: T) = BEGIN LOCK v.mu DO group.remove(v, t); END; END RemoveFromGroup; PROCEDUREGroupRemove (group: Group; v: V; t: T) = BEGIN IF t = NIL THEN RETURN END; GroupDelete(group, v, GroupFind(group, t), t); v.dirtyRegion := Rect.Join(v.dirtyRegion, t.appearance.region(t, v).r) END GroupRemove; PROCEDURETopInGroup (group: Group; v: V; t: T) = BEGIN LOCK v.mu DO group.top(v, t) END; END TopInGroup; PROCEDUREGroupTop (group: Group; v: V; t: T) = VAR i := GroupFind(group, t); last := group.cntElems - 1; BEGIN IF i # last THEN group.dirty := TRUE; GroupDelete(group, v, i, t); GroupInsert(group, v, last, t); v.dirtyRegion := Rect.Join( v.dirtyRegion, t.appearance.region(t, v).r); END; END GroupTop; PROCEDUREBottomInGroup (group: Group; v: V; t: T) = BEGIN LOCK v.mu DO group.bottom(v, t) END; END BottomInGroup; PROCEDUREGroupBottom (group: Group; v: V; t: T) = VAR i := GroupFind(group, t); BEGIN IF i # 0 THEN group.dirty := TRUE; SUBARRAY(group.elems^, 1, i) := SUBARRAY(group.elems^, 0, i); group.elems[0] := t; v.dirtyRegion := Rect.Join(v.dirtyRegion, t.appearance.region(t, v).r); END; END GroupBottom; PROCEDUREInitGroup (group : Group; elemsSize := 4; v : V := NIL; parentGroup: Group := NIL ): Group = BEGIN group.elems := NEW(REF ARRAY OF T, elemsSize); EVAL T.init(group, v, parentGroup); RETURN group END InitGroup; TYPE IterRender = GroupIterator OBJECT <* LL = v.mu *> clip: Rect.T; OVERRIDES proc := Renderer; END; PROCEDURERenderGroup (group: Group; v: V; VAR clip: Rect.T) = VAR iter := NEW(IterRender, v := v, clip := clip); BEGIN IF group.visible # 0.0 THEN EVAL group.iterate(iter) END; group.dirty := FALSE; clip := iter.clip; END RenderGroup;
PROCEDURERenderer (iter: IterRender; t: T): BOOLEAN = BEGIN TYPECASE t OF | Group => t.appearance.paint(t, iter.v, iter.clip); ELSE (* render element if it is dirty, or it intersects the clip *) WITH rgn = t.appearance.region(t, iter.v) DO IF t.dirty THEN iter.clip := Rect.Join(iter.clip, rgn.r); t.appearance.paint(t, iter.v, iter.clip); t.dirty := FALSE; ELSIF Region.OverlapRect(iter.clip, rgn) THEN t.appearance.paint(t, iter.v, iter.clip); END; END; END; RETURN TRUE; END Renderer; TYPE IterBB = GroupIterator OBJECT bounds := R2Box.Empty OVERRIDES proc := BBer END; PROCEDUREBoundsGroup (group: Group; v: V): R2Box.T = VAR iter := NEW(IterBB, v := v); BEGIN EVAL group.iterate(iter); RETURN iter.bounds; END BoundsGroup; PROCEDUREBBer (iter: IterBB; t: T): BOOLEAN = BEGIN iter.bounds := R2Box.Join(iter.bounds, t.bounds(iter.v)); RETURN TRUE; END BBer; TYPE IterRgn = GroupIterator OBJECT rgn: Region.T OVERRIDES proc := Rgner END; PROCEDURERgnGroup (group: Group; v: V): Region.T = <* LL = v.mu *> VAR iter := NEW(IterRgn, v := v, rgn := Region.Empty); BEGIN EVAL group.iterate(iter); RETURN iter.rgn; END RgnGroup; PROCEDURERgner (iter: IterRgn; t: T): BOOLEAN = BEGIN IF t.visible # 0.0 THEN iter.rgn := Region.Join(iter.rgn, t.appearance.region(t, iter.v)); END; RETURN TRUE; END Rgner; TYPE IterColor = GroupIterator OBJECT color: PaintOp.ColorScheme OVERRIDES proc := Colorer END; PROCEDURESetColorOfGroup (group: Group; v: V; color: PaintOp.ColorScheme) = <* LL = v.mu *> BEGIN EVAL group.iterate(NEW(IterColor, v := v, color := color)); group.color := color; END SetColorOfGroup; PROCEDUREColorer (iter: IterColor; t: T): BOOLEAN = BEGIN t.setColor(iter.v, iter.color); RETURN TRUE; END Colorer; TYPE IterFont = GroupIterator OBJECT font: Font.T OVERRIDES proc := Fonter END; PROCEDURESetFontOfGroup (group: Group; v: V; font := DefaultFont) = <* LL = v.mu *> BEGIN IF font = DefaultFont THEN font := RealDefaultFont; END; EVAL group.iterate(NEW(IterFont, v := v, font := font)); group.font := font; END SetFontOfGroup; PROCEDUREFonter (iter: IterFont; t: T): BOOLEAN = BEGIN t.setFont(iter.v, iter.font); RETURN TRUE; END Fonter; TYPE IterHighlight = GroupIterator OBJECT highlight: REAL OVERRIDES proc := Highlighter END; PROCEDURESetHighlightOfGroup (group: Group; v: V; highlight := 0.0) = BEGIN EVAL group.iterate(NEW(IterHighlight, v := v, highlight := highlight)); group.highlight := highlight; END SetHighlightOfGroup; PROCEDUREHighlighter (iter: IterHighlight; t: T): BOOLEAN = BEGIN t.setHighlight(iter.v, iter.highlight); RETURN TRUE; END Highlighter; TYPE IterVisible = GroupIterator OBJECT visible: REAL OVERRIDES proc := Visibleer END; PROCEDURESetVisibleOfGroup (group: Group; v: V; visible := 1.0) = BEGIN EVAL group.iterate(NEW(IterVisible, v := v, visible := visible)); group.visible := visible; END SetVisibleOfGroup; PROCEDUREVisibleer (iter: IterVisible; t: T): BOOLEAN = BEGIN t.setVisible(iter.v, iter.visible); RETURN TRUE; END Visibleer; TYPE IterWeight = GroupIterator OBJECT weight: REAL OVERRIDES proc := Weighter END; PROCEDURESetWeightOfGroup (group: Group; v: V; weight := 1.0) = BEGIN EVAL group.iterate(NEW(IterWeight, v := v, weight := weight)); group.weight := weight; END SetWeightOfGroup; PROCEDUREWeighter (iter: IterWeight; t: T): BOOLEAN = BEGIN t.setWeight(iter.v, iter.weight); RETURN TRUE; END Weighter; TYPE IterAlignment = GroupIterator OBJECT alignment: Alignment OVERRIDES proc := Alignmenter END; PROCEDURESetAlignmentOfGroup (group: Group; v : V; alignment := Alignment.Center) = BEGIN EVAL group.iterate(NEW(IterAlignment, v := v, alignment := alignment)); group.alignment := alignment; END SetAlignmentOfGroup; PROCEDUREAlignmenter (iter: IterAlignment; t: T): BOOLEAN = BEGIN t.setAlignment(iter.v, iter.alignment); RETURN TRUE; END Alignmenter; TYPE IterLabel = GroupIterator OBJECT label: TEXT OVERRIDES proc := Labeler END; PROCEDURESetLabelOfGroup (group: Group; v: V; label: TEXT) = BEGIN EVAL group.iterate(NEW(IterLabel, v := v, label := label)); END SetLabelOfGroup; PROCEDURELabeler (iter: IterLabel; t: T): BOOLEAN = BEGIN t.setLabel(iter.v, iter.label); RETURN TRUE; END Labeler; TYPE IterTransform = GroupIterator OBJECT m: Matrix2D.T; OVERRIDES proc := Transformer END; PROCEDURETransformGroup (group: Group; v: V; READONLY m: Matrix2D.T) = BEGIN EVAL group.iterate(NEW(IterTransform, v := v, m := m)); group.m := Matrix2D.Concat(group.m, m); group.dirty := TRUE; END TransformGroup; PROCEDURETransformer (iter: IterTransform; t: T): BOOLEAN = BEGIN t.transform(iter.v, iter.m); RETURN TRUE; END Transformer; REVEAL Line = LinePublic BRANDED OBJECT OVERRIDES init := InitLine; render := RenderLine; bounds := BoundsLine; rgn := RgnLine; reshape := ReshapeLine; setStyle := StyleLine; END; PROCEDUREInitLine ( line : Line; READONLY from, to: R2.T; v : V := NIL; group : Group := NIL ): Line = BEGIN EVAL T.init(line, v, group); line.m := Matrix2D.Translate(from[0], from[1]); line.to := R2.Sub(to, from); RETURN line; END InitLine; PROCEDURESetLineStyle (line: Line; v: V; style := VBT.EndStyle.Round) = BEGIN LOCK v.mu DO line.setStyle(v, style); END; END SetLineStyle; PROCEDURESetLineReshape (line: Line; v: V; READONLY from, to: R2.T) = BEGIN LOCK v.mu DO line.reshape(v, from, to); END; END SetLineReshape; PROCEDUREStyleLine (self: Line; v: V; style := VBT.EndStyle.Round) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.style := style; self.dirty := TRUE; END StyleLine; <* INLINE *> PROCEDURELineFrom (line: Line; v: V): R2.T = BEGIN RETURN PosLocked(line, v) END LineFrom; <* INLINE *> PROCEDURELineTo (line: Line; v: V): R2.T = BEGIN LOCK v.mu DO RETURN Matrix2D.Transform(line.m, line.to); END; END LineTo; <* INLINE *> PROCEDURELineFromLocked (line: Line; v: V := NIL): R2.T = BEGIN RETURN PosLocked(line, v) END LineFromLocked; <* INLINE *> PROCEDURELineToLocked (line: Line; <* UNUSED *> v: V := NIL): R2.T = BEGIN RETURN Matrix2D.Transform(line.m, line.to); END LineToLocked; PROCEDURERenderLine (line: Line; v: V; VAR clip: Rect.T) = BEGIN IF line.visible > 0.0 AND line.weight > 0.0 THEN WITH from = MGCToScreenPoint(v, PosLocked(line)), to = MGCToScreenPoint(v, LineToLocked(line)) DO VBT.Line(v.paintVBT, clip, from, to, PixelsFromWeight(v, Axis.T.Ver, line.weight), op := line.color.fg, end := line.style); PaintLabel(line, v, clip, RectFromCorners(from, to)); END; END; END RenderLine; PROCEDUREBoundsLine (line: Line; <* UNUSED *> v: V): R2Box.T = BEGIN RETURN R2Box.FromCorners(PosLocked(line), LineToLocked(line)); END BoundsLine; PROCEDURERgnLine (line: Line; v: V): Region.T = VAR dw := line.weight / 2.0; from := PosLocked(line); to := LineToLocked(line); n := MAX(from[1], to[1]) + dw; s := MIN(from[1], to[1]) - dw; w := MIN(from[0], to[0]) - dw; e := MAX(from[0], to[0]) + dw; ne := MGCToScreenPoint(v, R2.T{e, n}); sw := MGCToScreenPoint(v, R2.T{w, s}); BEGIN RETURN RegionWithLabel(line, v, RectFromCorners(ne, sw)) END RgnLine; <* INLINE *> PROCEDURESetPosLocked (t: T; READONLY pos: R2.T; <* UNUSED *> v: V := NIL) = BEGIN t.m[4] := pos[0]; t.m[5] := pos[1]; END SetPosLocked; PROCEDUREReshapeLine (line: Line; v: V; READONLY from, to: R2.T) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, line.appearance.region(line, v).r); SetPosLocked(line, from); line.to := R2.Sub(to, from); line.dirty := TRUE; END ReshapeLine; REVEAL LineEnd = LineEndPublic BRANDED OBJECT OVERRIDES render := RenderLineEnd; bounds := BoundsLineEnd; rgn := RgnLineEnd; transform := TransformLineEnd; END; PROCEDURERenderLineEnd (le: LineEnd; v: V; VAR clip: Rect.T) = BEGIN IF le.visible > 0.0 AND (le.controlsFrom OR le.dirty) THEN (* SCG 2 Mar 1993. If the non-controlling end of a line is animation moved in GraphVBT, then the line doesn't get repainted. So repaint the line here even if the non-controlling end is dirty. The worst that happens is an extra VBT.Line *) le.line.render(v, clip); END; END RenderLineEnd; PROCEDUREBoundsLineEnd (le: LineEnd; v: V): R2Box.T = BEGIN RETURN le.line.bounds(v); END BoundsLineEnd; PROCEDURERgnLineEnd (le: LineEnd; v: V): Region.T = BEGIN RETURN le.line.appearance.region(le.line, v) END RgnLineEnd; PROCEDURETransformLineEnd (le: LineEnd; v: V; READONLY m: Matrix2D.T) = VAR to := LineToLocked(le.line); from := PosLocked(le.line); BEGIN le.m := Matrix2D.Concat(le.m, m); v.dirtyRegion := Rect.Join(v.dirtyRegion, le.line.appearance.region(le.line, v).r); IF le.controlsFrom THEN from := Matrix2D.Transform(m, from); SetPosLocked(le.line, from); ELSE to := Matrix2D.Transform(m, to); END; le.line.to := R2.Sub(to, from); le.dirty := TRUE; END TransformLineEnd; REVEAL Rectangle = RectanglePublic BRANDED OBJECT OVERRIDES init := InitRectangle; render := RenderRectangle; bounds := BoundsRectangle; rgn := RgnRectangle; reshape := ReshapeRectangle; END; <* INLINE *> PROCEDURERectangleSW (rectangle: Rectangle; v: V := NIL): R2.T = BEGIN RETURN PosLocked(rectangle, v) END RectangleSW; <* INLINE *> PROCEDURERectangleNE (rectangle: Rectangle; v: V := NIL): R2.T = BEGIN LOCK v.mu DO RETURN RectangleNELocked(rectangle, v); END; END RectangleNE; <* INLINE *> PROCEDURERectangleSWLocked (rectangle: Rectangle; v: V): R2.T = BEGIN RETURN PosLocked(rectangle, v) END RectangleSWLocked; <* INLINE *> PROCEDURERectangleNELocked (rectangle: Rectangle; <* UNUSED *> v: V := NIL): R2.T = BEGIN RETURN Matrix2D.Transform(rectangle.m, rectangle.ne); END RectangleNELocked; PROCEDUREInitRectangle ( t : Rectangle; READONLY corner1, corner2: R2.T; v : V := NIL; group : Group := NIL ): Rectangle = VAR bounds := R2Box.FromCorners(corner1, corner2); BEGIN EVAL T.init(t, v, group); t.m := Matrix2D.Translate(bounds[0].lo, bounds[1].lo); t.ne := R2Box.Size(bounds); RETURN t; END InitRectangle; PROCEDURERenderRectangle (rectangle: Rectangle; v: V; VAR clip: Rect.T) = BEGIN IF rectangle.visible <= 0.0 THEN RETURN END; VAR bounds := MGCToScreenRect(v, PosLocked(rectangle), RectangleNELocked(rectangle)); BEGIN IF rectangle.weight > 0.0 THEN VAR border_pixels := BorderInPixels(v, rectangle); BEGIN IF border_pixels > 0 THEN VBT.PaintTint(v.paintVBT, Rect.Meet(clip, bounds), rectangle.color.fg); bounds := Rect.Inset(bounds, border_pixels); END; END; END; VAR clipped := Rect.Meet(clip, bounds); BEGIN VBT.PaintTint(v.paintVBT, clipped, rectangle.color.bg); PaintLabel(rectangle, v, clipped, bounds); END; END; END RenderRectangle; PROCEDUREBoundsRectangle (rectangle: Rectangle; v: V): R2Box.T = BEGIN RETURN R2Box.Inset(R2Box.FromCorners(PosLocked(rectangle, v), RectangleNELocked(rectangle, v)), -rectangle.weight); END BoundsRectangle; PROCEDURERgnRectangle (rectangle: Rectangle; v: V): Region.T = BEGIN RETURN RegionWithLabel(rectangle, v, Rect.Inset(MGCToScreenRectNonEmpty(v, PosLocked(rectangle), RectangleNELocked(rectangle)), -BorderInPixels(v, rectangle))); END RgnRectangle; PROCEDUREReshapeRectangle ( rectangle : Rectangle; v : V; READONLY corner1, corner2: R2.T ) = VAR bounds := R2Box.FromCorners(corner1, corner2); BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, rectangle.appearance.region(rectangle, v).r); SetPosLocked(rectangle, R2Box.SouthWest(bounds)); rectangle.ne := R2Box.Size(bounds); rectangle.dirty := TRUE; END ReshapeRectangle; PROCEDURESetRectangleReshape (rect: Rectangle; v: V; READONLY c1, c2: R2.T) = BEGIN LOCK v.mu DO rect.reshape(v, c1, c2); END; END SetRectangleReshape; REVEAL Ellipse = EllipsePublic BRANDED OBJECT OVERRIDES init := InitEllipse; render := RenderEllipse; bounds := BoundsEllipse; rgn := RgnEllipse; reshape := ReshapeEllipse; END; <* INLINE *> PROCEDUREEllipseSW (ellipse: Ellipse; v: V := NIL): R2.T = BEGIN RETURN PosLocked(ellipse, v) END EllipseSW; <* INLINE *> PROCEDUREEllipseNE (ellipse: Ellipse; v: V := NIL): R2.T = BEGIN LOCK v.mu DO RETURN EllipseNELocked(ellipse, v); END; END EllipseNE; <* INLINE *> PROCEDUREEllipseSWLocked (ellipse: Ellipse; v: V): R2.T = BEGIN RETURN PosLocked(ellipse, v) END EllipseSWLocked; <* INLINE *> PROCEDUREEllipseNELocked (ellipse: Ellipse; <* UNUSED *> v: V := NIL): R2.T = BEGIN RETURN Matrix2D.Transform(ellipse.m, ellipse.ne); END EllipseNELocked; PROCEDUREInitEllipse ( ellipse : Ellipse; READONLY corner1, corner2: R2.T; v : V := NIL; group : Group := NIL ): Ellipse = VAR bounds := R2Box.FromCorners(corner1, corner2); BEGIN EVAL T.init(ellipse, v, group); ellipse.m := Matrix2D.Translate(bounds[0].lo, bounds[1].lo); ellipse.ne := R2Box.Size(bounds); RETURN ellipse; END InitEllipse; PROCEDURERenderEllipse (ellipse: Ellipse; v: V; VAR clip: Rect.T) = BEGIN IF ellipse.visible <= 0.0 THEN RETURN END; VAR bounds := MGCToStableScreenRect( v, PosLocked(ellipse), EllipseNELocked(ellipse)); delta := Rect.Middle(bounds); width := Rect.HorSize(bounds); height := Rect.VerSize(bounds); border_pixels := 0; BEGIN IF ellipse.weight > 0.0 THEN border_pixels := BorderInPixels(v, ellipse); IF border_pixels > 0 THEN VBT.PaintPixmap(v.paintVBT, clip, ellipse.color.transparentFg, circleCache.create( width, height, border_pixels, fill := FALSE), delta := delta); END; END; VBT.PaintPixmap(v.paintVBT, clip, ellipse.color.transparentBg, circleCache.create( MAX(0, width - 2 * border_pixels), MAX(0, height - 2 * border_pixels), border := 0, fill := TRUE), delta := delta); PaintLabel(ellipse, v, clip, bounds); END; END RenderEllipse; PROCEDUREBoundsEllipse (ellipse: Ellipse; <* UNUSED *> v: V): R2Box.T = BEGIN RETURN R2Box.Inset(R2Box.FromCorners(PosLocked(ellipse), EllipseNELocked(ellipse)), -ellipse.weight); END BoundsEllipse; PROCEDURERgnEllipse (ellipse: Ellipse; v: V): Region.T = BEGIN RETURN RegionWithLabel(ellipse, v, Rect.Inset(MGCToStableScreenRect( v, PosLocked(ellipse), EllipseNELocked(ellipse)), -BorderInPixels(v, ellipse))); END RgnEllipse; PROCEDUREReshapeEllipse ( ellipse : Ellipse; v : V; READONLY corner1, corner2: R2.T ) = VAR bounds := R2Box.FromCorners(corner1, corner2); BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, ellipse.appearance.region(ellipse, v).r); SetPosLocked(ellipse, R2Box.SouthWest(bounds)); ellipse.ne := R2Box.Size(bounds); ellipse.dirty := TRUE; END ReshapeEllipse; REVEAL Shape = ShapePublic BRANDED OBJECT path : R2Path.T := NIL; fill := TRUE; realBounds := R2Box.Empty; integerBounds := Rect.Empty; lastMatrix := Matrix2D.Identity; lastNW := R2.Origin; lastRes := ARRAY Axis.T OF REAL{0.0, 0.0}; filled_pixmap, stroke_pixmap := Pixmap.Solid; invalid := TRUE; integerPath : Path.T; (* !!! SCG 29 July *) OVERRIDES init := InitShape; render := RenderShape; bounds := BoundsShape; rgn := RgnShape; reshape := ReshapeShape; END; PROCEDUREInitShape ( shape : Shape; READONLY origin: R2.T; READONLY path : R2Path.T; READONLY fill := TRUE; v : V := NIL; group : Group := NIL ): Shape = BEGIN shape.m := Matrix2D.Translate(origin[0], origin[1]); shape.path := path; shape.fill := fill; shape.invalid := TRUE; EVAL T.init(shape, v, group); (* must be after shape path is set *) RETURN shape; END InitShape; <* INLINE *> PROCEDUREShapeOrigin (shape: Shape; v: V): R2.T = BEGIN RETURN ShapeOriginLocked(shape, v) END ShapeOrigin; <* INLINE *> PROCEDUREShapePath (shape: Shape; v: V := NIL): R2Path.T = BEGIN LOCK v.mu DO RETURN ShapePathLocked(shape, v); END; END ShapePath; <* INLINE *> PROCEDUREShapeOriginLocked (shape: Shape; v: V := NIL): R2.T = BEGIN RETURN PosLocked(shape, v) END ShapeOriginLocked; <* INLINE *> PROCEDUREShapePathLocked (shape: Shape; <* UNUSED *> v: V := NIL): R2Path.T = BEGIN RETURN shape.path; END ShapePathLocked;
Pixmap caching versionPROCEDURE ValidateShape (shape: Shape; v: V) = BEGIN IF shape.invalid OR NOT EquivalentRotationAndScaling(shape.lastMatrix, shape.m) OR v.nw # shape.lastNW OR VBT.ScreenTypeOf(v).res # shape.lastRes THEN VAR border_pixels := BorderInPixels(v, shape); origin_matrix := shape.m; integer_path: Path.T; BEGIN origin_matrix[4] := 0.0; origin_matrix[5] := 0.0; shape.realBounds := shape.path.bbox(origin_matrix); integer_path := R2PathExtra.R2PathToPath(v, shape.path, origin_matrix);
(* shape.integerBounds are the bounds of the original path
shape.integerBounds := R2PathExtra.PathBounds(integer_path); (* locate shape.integerPath at the origin. This is because the v.shapeVBT must be origin aligned (it's installed off-screen) and we cannot easily translate a Path.T on the fly (in VBT.Fill/Stroke). *) integer_path := R2PathExtra.TranslatePath( integer_path, Point.Minus(Rect.NorthWest(shape.integerBounds))); shape.lastMatrix := origin_matrix; shape.lastNW := v.nw; shape.lastRes := VBT.ScreenTypeOf(v).res; (* Now create the pixmaps for the filled and unfilled paths aligned to the origin *) shape.filled_pixmap := ShapePixmap.New(integer_path, 0, fill := TRUE); shape.stroke_pixmap := ShapePixmap.New(integer_path, border_pixels, fill := FALSE); shape.invalid := FALSE; END; END; END ValidateShape; *)Here we cache some information about a shape. The pixmaps and bounds are normalized to the origin.
PROCEDUREThis is a convenience procedure for calculating the shape's current bounds based on the cached shape's and the current transformation matrix.ValidateShape (shape: Shape; v: V) = VAR st := VBT.ScreenTypeOf(v); BEGIN IF (st # NIL) AND (shape.invalid OR (shape.m # shape.lastMatrix) OR (st.res # shape.lastRes)) THEN shape.realBounds := shape.path.bbox(shape.m); shape.integerPath := R2PathExtra.R2PathToPath(v, shape.path, shape.m); shape.integerBounds := R2PathExtra.PathBounds(shape.integerPath); shape.lastMatrix := shape.m; shape.lastNW := v.nw; shape.lastRes := st.res; shape.invalid := FALSE; END; END ValidateShape;
PROCEDURE!!! Compare old values before setting dirty bit? !!!GetShapeRealBounds (shape: Shape): R2Box.T = BEGIN RETURN R2Box.Translate(shape.realBounds, R2.T{shape.m[4], shape.m[5]}); END GetShapeRealBounds; PROCEDURERenderShape (shape: Shape; v: V; VAR clip: Rect.T) = BEGIN IF shape.visible <= 0.0 THEN RETURN END; ValidateShape(shape, v); WITH realBounds = GetShapeRealBounds(shape), bounds = MGCToScreenRectNonEmpty(v, R2Box.SouthWest(realBounds), R2Box.NorthEast(realBounds)), borderPixels = BorderInPixels(v, shape) DO IF shape.fill THEN VBT.Fill(v.paintVBT, clip, shape.integerPath, wind := VBT.WindingCondition.Odd, op := shape.color.transparentBg); IF shape.weight > 0.0 THEN VBT.Stroke( v.paintVBT, clip, shape.integerPath, width := borderPixels, op := shape.color.transparentFg); END; ELSIF shape.weight > 0.0 THEN VBT.Stroke(v.paintVBT, clip, shape.integerPath, width := borderPixels, op := shape.color.transparentFg); END; (*| !!! SCG 29 July WITH delta = Rect.NorthWest(bounds) DO IF shape.fill THEN VBT.PaintPixmap( v.paintVBT, clip, shape.color.transparentBg, shape.filled_pixmap, delta := delta); IF shape.weight > 0.0 THEN VBT.PaintPixmap(v.paintVBT, clip, shape.color.transparentFg, shape.stroke_pixmap, delta := delta); END; ELSE VBT.PaintPixmap(v.paintVBT, clip, shape.color.transparentBg, shape.stroke_pixmap, delta := delta); END; END; !!! *) PaintLabel(shape, v, clip, bounds); END; END RenderShape; PROCEDUREBoundsShape (shape: Shape; v: V): R2Box.T = BEGIN ValidateShape(shape, v); RETURN R2Box.Inset(GetShapeRealBounds(shape), -shape.weight); END BoundsShape; PROCEDURERgnShape (shape: Shape; v: V): Region.T = BEGIN (* Done in BoundsShape ValidateShape(shape, v); *) (* !!! SCG 28 July WITH realBounds = BoundsShape(shape, v) DO RETURN RegionWithLabel(shape, v, MGCToScreenRectNonEmpty(v, R2Box.SouthWest(realBounds), R2Box.NorthEast(realBounds))); END; *) ValidateShape(shape, v); RETURN RegionWithLabel(shape, v, Rect.Inset(shape.integerBounds, -BorderInPixels(v, shape))) END RgnShape; PROCEDUREReshapeShape ( shape : Shape; v : V; READONLY origin: R2.T; READONLY path : R2Path.T; READONLY fill := TRUE) = BEGIN ValidateShape(shape, v); v.dirtyRegion := Rect.Join(v.dirtyRegion, shape.appearance.region(shape, v).r); SetPosLocked(shape, origin); shape.path := path; shape.fill := fill; shape.invalid := TRUE; shape.dirty := TRUE; END ReshapeShape; REVEAL Label = LabelPublic BRANDED OBJECT OVERRIDES render := RenderLabel; bounds := BoundsLabel; rgn := RgnLabel; END; PROCEDURERenderLabel (label: Label; v: V; VAR clip: Rect.T) = <* LL = v.mu *> BEGIN IF label.visible > 0.0 THEN PaintLabel(label, v, clip, Rect.FromPoint(MGCToScreenPoint(v, PosLocked(label)))); END; END RenderLabel; PROCEDUREBoundsLabel (l: Label; <* UNUSED *> v: V): R2Box.T = VAR pos := PosLocked(l); w := pos[0]; e := w + 1.0; s := pos[1]; n := s + 1.0; BEGIN RETURN R2Box.T{Fuzzy.T{w, e}, Fuzzy.T{s, n}}; END BoundsLabel; PROCEDURERgnLabel (label: Label; v: V): Region.T = BEGIN RETURN Region.FromRect( LabelRect( label, v, Rect.FromPoint(MGCToScreenPoint(v, PosLocked(label))))); END RgnLabel;
PROCEDURESetColorT (self: T; <* UNUSED *> v: V; color: PaintOp.ColorScheme) = BEGIN IF color = NIL THEN color := PaintOp.bgFg END; self.color := color; self.dirty := TRUE; END SetColorT; PROCEDURESetFontT (self: T; v: V; font: Font.T := DefaultFont) = BEGIN IF font = DefaultFont THEN font := RealDefaultFont END; v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.font := font; self.dirty := TRUE; END SetFontT; PROCEDURESetHighlightT (self: T; v: V; highlight := 0.0) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.highlight := highlight; self.dirty := TRUE; END SetHighlightT; PROCEDURESetVisibleT (self: T; v: V; visible := 1.0) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.visible := visible; self.dirty := TRUE; END SetVisibleT; PROCEDURESetWeightT (self: T; v: V; weight := 1.0) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.weight := weight; self.dirty := TRUE; END SetWeightT; PROCEDURESetAlignmentT (self: T; v: V; alignment := Alignment.Center) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.alignment := alignment; self.dirty := TRUE; END SetAlignmentT; PROCEDURESetLabelT (self: T; v: V; label := "") = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); self.label := label; self.dirty := TRUE; END SetLabelT; PROCEDURESetAppearance (self: T; v: V; appearance: Appearance := NIL) = BEGIN v.dirtyRegion := Rect.Join(v.dirtyRegion, self.appearance.region(self, v).r); IF appearance = NIL THEN appearance := appearanceDefault; ELSE appearance.prevAppearance := self.appearance; END; self.appearance := appearance; self.dirty := TRUE; END SetAppearance; PROCEDURESetColor (t: T; v: V; color: PaintOp.ColorScheme) = BEGIN IF color = NIL THEN color := PaintOp.bgFg END; LOCK v.mu DO t.setColor(v, color) END; IF v.nonOverlappingElements THEN VAR r := Rect.Full; BEGIN t.appearance.paint(t, v, r) END; ELSE v.mgRedisplay(Region.Empty); END; END SetColor; PROCEDURESetFont (t: T; v: V; font: Font.T) = BEGIN LOCK v.mu DO t.setFont(v, font) END; IF v.nonOverlappingElements THEN VAR r := Rect.Full; BEGIN t.appearance.paint(t, v, r) END; ELSE v.mgRedisplay(Region.Empty); END; END SetFont; PROCEDURESetHighlight (t: T; v: V; highlight := 1.0) = BEGIN LOCK v.mu DO t.setHighlight(v, highlight) END; IF v.nonOverlappingElements THEN VAR r := Rect.Full; BEGIN t.appearance.paint(t, v, r) END; ELSE v.mgRedisplay(Region.Empty); END; END SetHighlight; PROCEDURESetVisible (t: T; v: V; visible := 1.0) = BEGIN LOCK v.mu DO t.setVisible(v, visible); END; IF v.nonOverlappingElements THEN VAR r := Rect.Full; BEGIN t.appearance.paint(t, v, r) END; ELSE v.mgRedisplay(Region.Empty); END; END SetVisible; PROCEDURESetWeight (t: T; v: V; weight := 1.0) = BEGIN LOCK v.mu DO t.setWeight(v, weight); END; v.mgRedisplay(Region.Empty); END SetWeight; PROCEDURESetAlignment (t: T; v: V; alignment := Alignment.Center) = BEGIN LOCK v.mu DO t.setAlignment(v, alignment) END; IF v.nonOverlappingElements THEN VAR r := Rect.Full; BEGIN t.appearance.paint(t, v, r) END; ELSE v.mgRedisplay(Region.Empty); END; END SetAlignment; PROCEDURESetLabel (t: T; v: V; label := "") = BEGIN LOCK v.mu DO t.setLabel(v, label) END; IF v.nonOverlappingElements THEN VAR r := Rect.Full; BEGIN t.appearance.paint(t, v, r) END; ELSE v.mgRedisplay(Region.Empty); END; END SetLabel; PROCEDUREScale ( t : T; v : V; READONLY factor := R2.Ones; READONLY wrt := R2.Origin) = BEGIN LOCK v.mu DO ScaleLocked(t, v, factor, wrt); END; v.mgRedisplay(Region.Empty); END Scale; PROCEDUREScaleLocked ( t : T; v : V; READONLY factor := R2.Ones; READONLY wrt := R2.Origin) = BEGIN t.transform(v, Matrix2D.Concat3(Matrix2D.Translate(-wrt[0], -wrt[1]), Matrix2D.Scale(factor[0], factor[1]), Matrix2D.Translate(wrt[0], wrt[1]))) END ScaleLocked; PROCEDUREBoundingBox (t: T; v: V): R2Box.T = BEGIN LOCK v.mu DO RETURN BoundingBoxLocked(t, v); END END BoundingBox; <* INLINE *> PROCEDUREBoundingBoxLocked (t: T; v: V): R2Box.T = BEGIN RETURN t.appearance.boundingBox(t, v) END BoundingBoxLocked; PROCEDURETransform (t: T; v: V; READONLY m: Matrix2D.T) = BEGIN IF t = NIL THEN RETURN END; LOCK v.mu DO t.transform(v, m) END; v.mgRedisplay(Region.Empty); END Transform; PROCEDURETransformLocked (t: T; v: V; READONLY m: Matrix2D.T) = BEGIN IF t = NIL THEN RETURN END; t.transform(v, m); END TransformLocked; PROCEDURERTranslate (t: T; v: V; READONLY by: R2.T) = BEGIN IF t = NIL THEN RETURN END; LOCK v.mu DO t.transform(v, Matrix2D.Translate(by[0], by[1])) END; v.mgRedisplay(Region.Empty); END RTranslate; PROCEDURERTranslateLocked (t: T; v: V; READONLY by: R2.T) = BEGIN IF t = NIL THEN RETURN END; t.transform(v, Matrix2D.Translate(by[0], by[1])) END RTranslateLocked; PROCEDURETranslateTo (t: T; v: V; READONLY dest: R2.T; center := FALSE) = BEGIN IF t = NIL THEN RETURN END; LOCK v.mu DO TranslateToLocked(t, v, dest, center); END; v.mgRedisplay(Region.Empty); END TranslateTo; PROCEDURETranslateToLocked ( t : T; v : V; READONLY dest : R2.T; center := FALSE) = BEGIN IF t = NIL THEN RETURN END; IF center THEN VAR bounds := t.bounds(v); BEGIN t.transform(v, Matrix2D.Translate(dest[0] - (bounds[0].lo + bounds[0].hi) / 2.0, dest[1] - (bounds[1].lo + bounds[1].hi) / 2.0)); END; ELSE VAR pos := PosLocked(t, v); BEGIN t.transform( v, Matrix2D.Translate(dest[0] - pos[0], dest[1] - pos[1])); END; END; END TranslateToLocked; PROCEDURERotate (t: T; v: V; angle: REAL; READONLY origin:= R2.Origin) = BEGIN LOCK v.mu DO RotateLocked(t, v, angle, origin) END; v.mgRedisplay(Region.Empty); END Rotate; CONST DToR = FLOAT(Math.Degree, REAL); PROCEDURERotateLocked ( t : T; v : V; angle : REAL; READONLY origin := R2.Origin) = BEGIN IF t = NIL THEN RETURN END; t.transform(v, Matrix2D.Concat3( Matrix2D.Translate(-origin[0], -origin[1]), Matrix2D.Rotate(DToR * angle), Matrix2D.Translate(origin[0], origin[1]))); END RotateLocked; PROCEDUREPaintOpFromRGB (rgb: Color.T; mode: PaintOp.Mode): PaintOp.T = BEGIN RETURN PaintOp.FromRGB(rgb.r, rgb.g, rgb.b, mode); END PaintOpFromRGB; PROCEDUREOpFromName (name: TEXT; mode := PaintOp.Mode.Accurate): PaintOp.T = <* FATAL ColorName.NotFound *> BEGIN RETURN PaintOpFromRGB(ColorName.ToRGB(name), mode := mode); END OpFromName; PROCEDUREColorFromText (bg, fg: TEXT := NIL; mode := PaintOp.Mode.Accurate): PaintOp.ColorScheme = VAR bgOp, fgOp: PaintOp.T; <* FATAL ColorName.NotFound *> BEGIN IF bg = NIL THEN bgOp := PaintOp.Bg; ELSE bgOp := PaintOpFromRGB(ColorName.ToRGB(bg), mode := mode); END; IF fg = NIL THEN fgOp := PaintOp.Fg; ELSE fgOp := PaintOpFromRGB( ColorName.ToRGB(fg), mode := mode); END; RETURN PaintOp.MakeColorScheme(bgOp, fgOp) END ColorFromText; PROCEDUREColorFromRGB (bg, fg: Color.T; mode := PaintOp.Mode.Accurate): PaintOp.ColorScheme = VAR bgOp, fgOp: PaintOp.T; BEGIN bgOp := PaintOpFromRGB(bg, mode := mode); fgOp := PaintOpFromRGB(fg, mode := mode); RETURN PaintOp.MakeColorScheme(bgOp, fgOp) END ColorFromRGB; REVEAL Appearance = AppearancePublic BRANDED OBJECT OVERRIDES paint := AppPaintDefault; boundingBox := AppBBDefault; region := AppRegionDefault; END; AppearanceDefault = Appearance BRANDED OBJECT END; PROCEDUREAppPaintDefault ( app : Appearance; t : T; v : V; VAR clip: Rect.T ) = BEGIN IF app.prevAppearance = NIL THEN t.render(v, clip); ELSE app.prevAppearance.paint(t, v, clip); END; END AppPaintDefault; PROCEDUREAppBBDefault (app: Appearance; t: T; v: V): R2Box.T = BEGIN IF app.prevAppearance = NIL THEN RETURN t.bounds(v); ELSE RETURN app.prevAppearance.boundingBox(t, v); END; END AppBBDefault; PROCEDUREAppRegionDefault (app: Appearance; t: T; v: V): Region.T = BEGIN IF app.prevAppearance = NIL THEN RETURN t.rgn(v); ELSE RETURN app.prevAppearance.region(t, v); END; END AppRegionDefault; BEGIN defaultColor := PaintOp.bgFg; appearanceDefault := NEW(AppearanceDefault); END MG.