mentor/src/logo/Logo.m3


 Copyright (C) 1994, Digital Equipment Corporation         
 All rights reserved.                                      
 See the file COPYRIGHT for a full description.            

MODULE Logo;

IMPORT Algorithm, Animate, Axis, Color, Filter, FormsVBT, GraphVBT,
       GraphVBTExtras, logoAlgClass, logoIE, logoViewClass, MG,
       MGPaintOp, MGV, PaintOp, R2, Random, Text, Thread, VBT, View,
       ZeusPanel;

TYPE
  T = logoViewClass.T OBJECT
        v         : GraphVBT.T;
        colors    : REF ARRAY OF PaintOp.T;
        colorPhase                                := 0;
      OVERRIDES
        startrun       := StartRunV;
        oeInit         := InitV;
        oeRandomStep   := RandomStepV;
        oeShift        := ShiftV;
        oeMarquee      := MarqueeV;
        oeMarqueeSpots := MarqueeSpotsV;
      END;

  T2 = T OBJECT
       OVERRIDES
         oeRandomStep := RandomStep2;
       END;

VAR
  rand := NEW (Random.Default).init ();

PROCEDURE RandomStep2 (view: T2; delta: REAL) RAISES {Thread.Alerted} =
  BEGIN
    T.oeRandomStep(view, delta);
LOCK VBT.mu DO ZeusPanel.PhotographViews(NIL) END;
  END RandomStep2;

TYPE
  MyVertex = GraphVBT.Vertex OBJECT pref: R2.T;  END;
  Spots = GraphVBT.Vertex OBJECT
            sap: SpotAP;
            p  : REAL
          END;

  MyGraphVBT = GraphVBT.T OBJECT
                 marquee           : ARRAY [0 .. 3] OF GraphVBT.Edge;
                 mlengths          : ARRAY Axis.T OF REAL;
                 mcorner0, mcorner1: R2.T;
                 mspots            : ARRAY [0 .. 30] OF Spots;
                 mspotSize         : REAL;
                 spotShifts         := 0.0;
                 spotDelta : REAL;
                 text: REF ARRAY OF Glyph;
               OVERRIDES
                 reshape := ReshapeGraphVBT;
               END;

PROCEDURE ReshapeGraphVBT (v: MyGraphVBT; READONLY cd: VBT.ReshapeRec) =
  BEGIN
    GraphVBT.T.reshape(v, cd);
    v.redisplay();
  END ReshapeGraphVBT;

CONST
  YFloor = 0.4;
  Scale  = 3.0 / 4.0;
  Width  = 0.02;
num is the position number of the letter, x, y are the coordinates assuming a 1, 1 glyph box
PROCEDURE RefPt (v: MyGraphVBT; n: INTEGER; p: R2.T): R2.T =
  VAR
    len := FLOAT(MAX(1, NUMBER(v.text^)));
    xs  := 1.0 / (len + 2.0);
    ys  := 1.5 * xs;
  BEGIN
    RETURN R2.T{(1.0 + FLOAT(n) + p[0] * Scale) * xs,
                1.0 - (YFloor + p[1] * Scale * ys)};
  END RefPt;
num is the position number of the letter, x, y are the coordinates assuming a 1, 1 box
PROCEDURE NewVertex (v: GraphVBT.T; num: INTEGER; p: R2.T): GraphVBT.Vertex =
  BEGIN
    RETURN
      NEW(MyVertex, graph := v, shape := GraphVBT.VertexShape.Rectangle,
          pos := R2.T{rand.real(), rand.real()},
          pref := RefPt(v, num, p), color := PaintOp.Bg).init();
  END NewVertex;

PROCEDURE Edge (v: MyGraphVBT; num: INTEGER; v0, v1, c0, c1: R2.T):
  GraphVBT.Edge =
  BEGIN
    RETURN
      NEW(
        GraphVBT.Edge, vertex0 := NewVertex(v, num, v0),
        vertex1 := NewVertex(v, num, v1),
        control0 := NewVertex(v, num, c0),
        control1 := NewVertex(v, num, c1), width := Width,
        color := NARROW(GraphVBTExtras.GetMG(v).view, T).colors[num]).init();
  END Edge;

PROCEDURE RandomRange (current, pref, range: REAL): REAL =
  BEGIN
    IF ABS(current - pref) > 2.0 * range THEN
      RETURN pref
    ELSE
      VAR
        min := MIN(current, pref);
        max := MAX(current, pref);
        lo  := MAX(0.0, max - range);
        hi  := MIN(1.0, min + range);
      BEGIN
        RETURN lo + rand.real() * (hi - lo);
      END;
    END;
  END RandomRange;

PROCEDURE RandomPos (v: MyVertex; delta: REAL): R2.T =
  BEGIN
    RETURN R2.T{RandomRange(v.pos[0], v.pref[0], delta),
                RandomRange(v.pos[1], v.pref[1], delta)}
  END RandomPos;

PROCEDURE RandomMoveEdge (<* UNUSED *> v    : GraphVBT.T;
                                 e    : GraphVBT.Edge;
                                 delta: REAL           ) =
  VAR v0, v1, c0, c1: MyVertex;
  BEGIN
    v0 := e.vertex0;
    v1 := e.vertex1;
    c0 := e.control0;
    c1 := e.control1;
    v0.move(RandomPos(v0, delta), TRUE);
    v1.move(RandomPos(v1, delta), TRUE);
    IF delta = 0.0 AND R2.Equal(v0.pref, c0.pref) THEN
      e.move(v0, v1, NIL, NIL, TRUE)
    ELSE
      c0.move(RandomPos(c0, delta), TRUE);
      c1.move(RandomPos(c1, delta), TRUE);
    END;
  END RandomMoveEdge;

PROCEDURE RandomMoveEdges (         v    : MyGraphVBT;
                           READONLY a    : ARRAY OF GraphVBT.Edge;
                                    delta: REAL                    ) =
  BEGIN
    FOR i := 0 TO LAST(a) DO RandomMoveEdge(v, a[i], delta) END;
  END RandomMoveEdges;

PROCEDURE RandomMove (v: MyGraphVBT; delta: REAL) =
  BEGIN
    FOR i := 0 TO LAST(v.text^) DO
      IF v.text[i] # NIL THEN RandomMoveEdges(v, v.text[i]^, delta); END;
    END;
  END RandomMove;

TYPE
  ColorAnimation = Animate.T OBJECT
    op: PaintOp.T;
    hsvStart, hsvEnd: Color.HSV;
  OVERRIDES
    length := CALength;
    doStep := CADoStep;
  END;

PROCEDURE CALength (<* UNUSED *> ca: ColorAnimation;
                    <* UNUSED *> v : MG.V;
                    <* UNUSED *> mg: MG.T            ): INTEGER =
  BEGIN
    RETURN 30
  END CALength;

PROCEDURE ScaleHSV (start, end: Color.HSV; time: REAL): Color.HSV =
  PROCEDURE Scale (s, e, t: REAL): REAL =
    BEGIN
      RETURN s + t * (e - s)
    END Scale;
  BEGIN
    RETURN Color.HSV{Scale(start.h, end.h, time), Scale(start.s, end.s, time),
                 Scale(start.v, end.v, time)}
  END ScaleHSV;

PROCEDURE CADoStep (             ca      : ColorAnimation;
                                 time    : REAL;
                    <* UNUSED *> timePrev: REAL;
                                 v       : MG.V;
                    <* UNUSED *> mg      : MG.T            ) =
  VAR rgb := Color.FromHSV(ScaleHSV(ca.hsvStart, ca.hsvEnd, time));
  BEGIN
    MGPaintOp.Set(
      VBT.ScreenTypeOf(v), ca.op, MGPaintOp.RGB{rgb.r, rgb.g, rgb.b});
  END CADoStep;

PROCEDURE NewRandomColor (view: T; op: PaintOp.T; i: CARDINAL; delta: REAL) =
  VAR
    rgb := MGPaintOp.Get(op);
    hue := FLOAT(i) / 6.0;
  BEGIN
    MGV.AddAnimation(
      GraphVBTExtras.GetMG(view.v), NEW(ColorAnimation, op := op,
                  hsvStart := Color.ToHSV(rgb),
                  hsvEnd := Color.HSV{RandomRange(hue, hue, delta),
                                  RandomRange(1.0, 1.0, delta),
                                  RandomRange(0.5, 0.5, delta)}).init(),
      NIL);
  END NewRandomColor;

PROCEDURE RandomColor(v: MyGraphVBT; delta: REAL) =
  VAR view: T := GraphVBTExtras.GetMG(v).view;
  BEGIN
    FOR i := 0 TO LAST(v.text^) DO
      NewRandomColor(view, view.colors[i], i, delta)
    END;
  END RandomColor;

TYPE
  SpotAP = GraphVBT.AnimationPath OBJECT
    spot: Spots;
  OVERRIDES
    pos := SpotAPos;
  END;

PROCEDURE SpotAPos (sap: SpotAP; t: REAL): R2.T =
  BEGIN
    RETURN SpotPos(
             sap.spot.graph,
             sap.spot.p + t * NARROW(sap.spot.graph, MyGraphVBT).spotDelta)
  END SpotAPos;

PROCEDURE ShiftV (view: T) RAISES {Thread.Alerted} =
  VAR v: MyGraphVBT := view.v;
  BEGIN
    FOR i := 0 TO LAST(v.text^) DO
      VAR
        iStart := (i + view.colorPhase) MOD 6;
        iEnd   := (iStart + 1);
      BEGIN
        MGV.AddAnimation(
          GraphVBTExtras.GetMG(v), NEW(ColorAnimation, op := view.colors[i],
                 hsvStart := Color.HSV{FLOAT(iStart) / 6.0, 1.0, 0.5},
                 hsvEnd := Color.HSV{FLOAT(iEnd) / 6.0, 1.0, 0.5}).init(), NIL);
      END;
    END;
    INC(view.colorPhase);

    FOR i := 0 TO LAST(v.mspots) DO
      WITH spot = v.mspots[i] DO
        spot.move (SpotPos(v, spot.p + v.spotDelta), TRUE, path := spot.sap);
      END;
    END;
    view.v.animate(0.0, 1.0);
    v.spotShifts := v.spotShifts + v.spotDelta;
  END ShiftV;
We want spots to just cover the line. Setting the box's center 1/2 spot width out from the line, puts the inner edge of the box right over the middle of the line. So, have to get the line's width in world coordinates (from MM) and then adjust the box accordingly
PROCEDURE SpotPos (v: MyGraphVBT; p: REAL): R2.T =
  VAR
    c0     := v.mcorner0;
    c1     := v.mcorner1;
    hor    := v.mlengths[Axis.T.Hor];
    ver    := v.mlengths[Axis.T.Ver];
    circum := 2.0 * (hor + ver);
  BEGIN
    p := (p + v.spotShifts) MOD circum;

    IF p <= hor THEN
      RETURN R2.Add(c0, R2.T{p, 0.0})
    ELSIF p <= hor + ver THEN
      RETURN R2.Add(c1, R2.T{0.0, (hor + ver) - p})
    ELSIF p <= hor + hor + ver THEN
      RETURN R2.Add(c1, R2.T{(hor + ver - p), 0.0})
    ELSE
      RETURN R2.Add(c0, R2.T{0.0, p - circum})
    END;
  END SpotPos;

PROCEDURE MarqueeV (view: T; side: INTEGER) RAISES {Thread.Alerted} =
  VAR
    v         : MyGraphVBT := view.v;
    start, end: R2.T;
    MDelta := MIN(0.1, 1.0 / FLOAT(LAST(v.text^) + 4));
 BEGIN
    CASE side OF
    | 0 =>
        start := R2.Add(RefPt(v, 0, P0), R2.T{-MDelta, +MDelta});
        end := R2.Add(RefPt(v, LAST(v.text^), P6), R2.T{MDelta, +MDelta});
    | 1 =>
        start :=
          R2.Add(RefPt(v, LAST(v.text^), P6), R2.T{MDelta, +MDelta});
        end := R2.Add(RefPt(v, LAST(v.text^), P4), R2.T{MDelta, -MDelta});
    | 2 =>
        start :=
          R2.Add(RefPt(v, LAST(v.text^), P4), R2.T{MDelta, -MDelta});
        end := R2.Add(RefPt(v, 0, P2), R2.T{-MDelta, -MDelta});
    | 3 =>
        start := R2.Add(RefPt(v, 0, P2), R2.T{-MDelta, -MDelta});
        end := R2.Add(RefPt(v, 0, P0), R2.T{-MDelta, +MDelta});
    ELSE
    END;
    v.marquee[side] :=
      NEW(GraphVBT.Edge, width := Width,
          vertex0 :=
            NEW(MyVertex, graph := v,
                shape := GraphVBT.VertexShape.Rectangle, pos := start,
                pref := start, size := R2.Origin).init(),
          vertex1 :=
            NEW(MyVertex, graph := v,
                shape := GraphVBT.VertexShape.Rectangle, pos := start,
                pref := end, size := R2.Origin).init()).init();
    LOCK v.mu DO v.marquee[side].vertex1.move(end, TRUE) END;
    v.animate(0.0, 1.0);
  END MarqueeV;

PROCEDURE MarqueeSpotsV (view: T) =
  VAR
    v               : MyGraphVBT := view.v;
    length, delta, p: REAL;
    MDelta := MIN(0.1, 1.0 / FLOAT(LAST(v.text^) + 4));
    c0 := R2.Add(RefPt(v, 0, P0), R2.T{-MDelta, +MDelta});
    c1 := R2.Add(RefPt(v, LAST(v.text^), P4), R2.T{+MDelta, -MDelta});
  BEGIN
    FOR i := 0 TO 3 DO v.marquee[i].setColor(PaintOp.Bg); END;
    v.mcorner0 := c0;
    v.mcorner1 := c1;
    v.mlengths[Axis.T.Hor] := ABS(c1[0] - c0[0]);
    v.mlengths[Axis.T.Ver] := ABS(c1[1] - c0[1]);
    length := 2.0 * (v.mlengths[Axis.T.Hor] + v.mlengths[Axis.T.Ver]);
    delta := length / FLOAT(NUMBER(v.mspots));
    v.mspotSize := 1.0 * delta / 2.0;
    v.spotDelta := delta;
    p := 0.0;
    FOR i := 0 TO LAST(v.mspots) DO
      v.mspots[i] := NEW(Spots, graph := v, p := p,
                         shape := GraphVBT.VertexShape.Rectangle,
                         size := R2.T{v.mspotSize, v.mspotSize},
                         pos := SpotPos(v, p), color := PaintOp.Fg).init();
      v.mspots[i].sap := NEW(SpotAP, spot := v.mspots[i]);
      p := p + delta;
    END;
  END MarqueeSpotsV;

CONST
  P0 = R2.T{0.0, 0.0};
  P1 = R2.T{0.0, 0.5};
  P2 = R2.T{0.0, 1.0};
  P3 = R2.T{0.5, 1.0};
  P4 = R2.T{1.0, 1.0};
  P5 = R2.T{1.0, 0.5};
  P6 = R2.T{1.0, 0.0};
  P7 = R2.T{0.5, 0.0};
  P8 = R2.T{0.5, 0.5};
  P9 = R2.T{1.0, 0.75};
  P10 = R2.T{1.0, 0.25};
  P11 = R2.T{0.25, 0.5};
  P12 = R2.T{0.75, 0.5};
  P13 = R2.T{0.75, 0.25};
  P14 = R2.T{0.25, 0.0};
  (*P15 = R2.T{0.75, 1.0};*)
  P16 = R2.T{0.75, 0.0};
  P17 = R2.T{0.0, 0.75};
  P18 = R2.T{0.0, 0.25};

TYPE
  Glyph = REF ARRAY OF GraphVBT.Edge;
  OEdge = RECORD v0, v1, c0, c1: R2.T END;
  Outline = REF ARRAY OF OEdge;

VAR
  font: ARRAY CHAR OF Outline;

PROCEDURE InitFont () =
  VAR NullOutline := NEW(Outline, 0);
  BEGIN
    FOR i := FIRST(font) TO LAST(font) DO
      font[i] := NullOutline;
    END;
    font['A'] := NEW(Outline, 3);
    font['A'][0] := OEdge{P0, P3, P0, P3};
    font['A'][1] := OEdge{P6, P3, P6, P3};
    font['A'][2] := OEdge{P11, P12, P11, P12};
    font['B'] := NEW(Outline, 5);
    font['B'][0] := OEdge{P0, P2, P0, P2};
    font['B'][1] := OEdge{P2, P9, P4, P4};
    font['B'][2] := OEdge{P9, P1, P5, P5};
    font['B'][3] := OEdge{P1, P10, P5, P5};
    font['B'][4] := OEdge{P10, P0, P6, P6};
    font['C'] := NEW(Outline, 4);
    font['C'][0] := OEdge{P1, P3, P2, P2};
    font['C'][1] := OEdge{P3, P9, P4, P4};
    font['C'][2] := OEdge{P10, P7, P6, P6};
    font['C'][3] := OEdge{P7, P1, P0, P0};
    font['D'] := NEW(Outline, 3);
    font['D'][0] := OEdge{P0, P2, P0, P2};
    font['D'][1] := OEdge{P2, P5, P4, P4};
    font['D'][2] := OEdge{P5, P0, P6, P6};
    font['E'] := NEW(Outline, 4);
    font['E'][0] := OEdge{P6, P0, P6, P0};
    font['E'][1] := OEdge{P0, P2, P0, P2};
    font['E'][2] := OEdge{P2, P4, P2, P4};
    font['E'][3] := OEdge{P1, P5, P1, P5};
    font['F'] := NEW(Outline, 3);
    font['F'][0] := OEdge{P4, P2, P4, P2};
    font['F'][1] := OEdge{P0, P2, P0, P2};
    font['F'][2] := OEdge{P1, P5, P1, P5};
    font['G'] := NEW(Outline, 5);
    font['G'][0] := OEdge{P1, P3, P2, P2};
    font['G'][1] := OEdge{P3, P9, P4, P4};
    font['G'][2] := OEdge{P5, P7, P6, P6};
    font['G'][3] := OEdge{P7, P1, P0, P0};
    font['G'][4] := OEdge{P5, P12, P5, P12};
    font['H'] := NEW(Outline, 3);
    font['H'][0] := OEdge{P0, P2, P0, P2};
    font['H'][1] := OEdge{P4, P6, P4, P6};
    font['H'][2] := OEdge{P1, P5, P1, P5};
    font['I'] := NEW(Outline, 1);
    font['I'][0] := OEdge{P7, P3, P7, P3};
    font['J'] := NEW(Outline, 3);
    font['J'][0] := OEdge{P4, P5, P4, P5};
    font['J'][1] := OEdge{P5, P7, P6, P6};
    font['J'][2] := OEdge{P7, P1, P0, P0};
    font['K'] := NEW(Outline, 3);
    font['K'][0] := OEdge{P0, P2, P0, P2};
    font['K'][1] := OEdge{P1, P4, P1, P4};
    font['K'][2] := OEdge{P1, P6, P1, P6};
    font['L'] := NEW(Outline, 2);
    font['L'][0] := OEdge{P0, P2, P0, P2};
    font['L'][1] := OEdge{P0, P6, P0, P6};
    font['M'] := NEW(Outline, 4);
    font['M'][0] := OEdge{P0, P2, P0, P2};
    font['M'][1] := OEdge{P2, P8, P2, P8};
    font['M'][2] := OEdge{P8, P4, P8, P4};
    font['M'][3] := OEdge{P4, P6, P4, P6};
    font['N'] := NEW(Outline, 3);
    font['N'][0] := OEdge{P0, P2, P0, P2};
    font['N'][1] := OEdge{P2, P6, P2, P6};
    font['N'][2] := OEdge{P6, P4, P6, P4};
    font['O'] := NEW(Outline, 4);
    font['O'][0] := OEdge{P1, P3, P2, P2};
    font['O'][1] := OEdge{P3, P5, P4, P4};
    font['O'][2] := OEdge{P5, P7, P6, P6};
    font['O'][3] := OEdge{P7, P1, P0, P0};
    font['P'] := NEW(Outline, 3);
    font['P'][0] := OEdge{P0, P2, P0, P2};
    font['P'][1] := OEdge{P9, P1, P5, P5};
    font['P'][2] := OEdge{P2, P9, P4, P4};
    font['Q'] := NEW(Outline, 5);
    font['Q'][0] := OEdge{P1, P3, P2, P2};
    font['Q'][1] := OEdge{P3, P5, P4, P4};
    font['Q'][2] := OEdge{P5, P7, P6, P6};
    font['Q'][3] := OEdge{P7, P1, P0, P0};
    font['Q'][4] := OEdge{P13, P6, P13, P6};
    font['R'] := NEW(Outline, 4);
    font['R'][0] := OEdge{P0, P2, P0, P2};
    font['R'][1] := OEdge{P1, P6, P1, P6};
    font['R'][2] := OEdge{P2, P9, P4, P4};
    font['R'][3] := OEdge{P9, P1, P5, P5};
    font['S'] := NEW(Outline, 6);
    font['S'][0] := OEdge{P9, P3, P4, P4};
    font['S'][1] := OEdge{P3, P17, P2, P2};
    font['S'][2] := OEdge{P17, P8, P1, P1};
    font['S'][3] := OEdge{P8, P10, P5, P5};
    font['S'][4] := OEdge{P10, P7, P6, P6};
    font['S'][5] := OEdge{P7, P18, P0, P0};
    font['T'] := NEW(Outline, 2);
    font['T'][0] := OEdge{P7, P3, P7, P3};
    font['T'][1] := OEdge{P2, P4, P2, P4};
    font['U'] := NEW(Outline, 4);
    font['U'][0] := OEdge{P2, P1, P2, P1};
    font['U'][1] := OEdge{P1, P7, P0, P0};
    font['U'][2] := OEdge{P7, P5, P6, P6};
    font['U'][3] := OEdge{P5, P4, P5, P4};
    font['V'] := NEW(Outline, 2);
    font['V'][0] := OEdge{P7, P2, P7, P2};
    font['V'][1] := OEdge{P7, P4, P7, P4};
    font['W'] := NEW(Outline, 4);
    font['W'][0] := OEdge{P2, P14, P2, P14};
    font['W'][1] := OEdge{P14, P8, P14, P8};
    font['W'][2] := OEdge{P16, P8, P16, P8};
    font['W'][3] := OEdge{P16, P4, P16, P4};
    font['X'] := NEW(Outline, 2);
    font['X'][0] := OEdge{P6, P2, P6, P2};
    font['X'][1] := OEdge{P0, P4, P0, P4};
    font['Y'] := NEW(Outline, 3);
    font['Y'][0] := OEdge{P7, P8, P7, P8};
    font['Y'][1] := OEdge{P8, P2, P8, P2};
    font['Y'][2] := OEdge{P8, P4, P8, P4};
    font['Z'] := NEW(Outline, 3);
    font['Z'][0] := OEdge{P2, P4, P2, P4};
    font['Z'][1] := OEdge{P4, P0, P4, P0};
    font['Z'][2] := OEdge{P0, P6, P0, P6};
    FOR i := 'a' TO 'z' DO
      font[i] := font[VAL(ORD(i) - ORD('a') + ORD('A'), CHAR)]
    END;
  END InitFont;

PROCEDURE New (): View.T =
  VAR view := NEW(T);
  BEGIN
    view.v := NEW(MyGraphVBT).init();
    RETURN view.init(view.v);
  END New;

PROCEDURE NewWAlbum (): View.T =
  VAR view := NEW(T2);
  BEGIN
    view.v := NEW(MyGraphVBT).init();
    RETURN view.init(view.v);
  END NewWAlbum;

PROCEDURE StartRunV (view: T) =
  BEGIN
    view.v := NEW(MyGraphVBT).init();
    GraphVBTExtras.GetMG(view.v).view := view;
    EVAL Filter.Replace(view, view.v);
  END StartRunV;

PROCEDURE AdjustGlyph (v: MyGraphVBT; outline: Outline; num: INTEGER):
  Glyph =
  VAR
    glyph := NEW(Glyph, NUMBER(outline^));
  BEGIN
    FOR i := 0 TO LAST(outline^) DO
      glyph[i] := Edge(v, num, outline[i].v0, outline[i].v1, outline[i].c0,
                       outline[i].c1);
    END;
    RETURN glyph;
  END AdjustGlyph;

PROCEDURE InitText(v: MyGraphVBT; text: TEXT) =
  BEGIN
    v.text := NEW(REF ARRAY OF Glyph, Text.Length(text));
    FOR i := 0 TO LAST(v.text^) DO
      v.text[i] := AdjustGlyph(v, font[Text.GetChar(text, i)], i);
    END;
  END InitText;

PROCEDURE InitV (view: T; text: TEXT) RAISES {Thread.Alerted} =
  VAR v: MyGraphVBT := view.v;
  BEGIN
    LOCK v.mu DO v.clear() END;
    InitFont();
    view.colors := NEW(REF ARRAY OF PaintOp.T, Text.Length(text));
    FOR i := 0 TO LAST(view.colors^) DO
      view.colors[i] :=
        MGPaintOp.New(
          MGPaintOp.RGB{rand.real(), rand.real(), rand.real()});
    END;
    InitText(view.v, text);
    view.oeRandomStep(0.5);
  END InitV;

PROCEDURE RandomStepV (view: T; delta: REAL) RAISES {Thread.Alerted} =
  BEGIN
    LOCK view.v.mu DO RandomMove(view.v, delta); END;
    RandomColor(view.v, delta);
    view.v.animate(0.0, 1.0);
  END RandomStepV;

TYPE
  Alg = logoAlgClass.T OBJECT
  OVERRIDES
    run := AlgRun
  END;

PROCEDURE NewA (): Algorithm.T =
  VAR fv := ZeusPanel.NewForm("logoinput.fv");
  BEGIN
    RETURN NEW(Alg, data := fv).init()
  END NewA;

PROCEDURE AlgRun (alg: Alg) RAISES {Thread.Alerted} =
  VAR
    delta: REAL;
    text: TEXT;                 <* FATAL FormsVBT.Error, FormsVBT.Unimplemented *>
  BEGIN
    LOCK VBT.mu DO text := FormsVBT.GetText(alg.data, "text"); END;
    LOOP
      logoIE.Init(alg, text);
      delta := 0.25;
      WHILE delta > 0.01 DO
        logoIE.RandomStep(alg, delta);
        delta := delta - 0.02;
      END;
      logoIE.RandomStep(alg, 0.0);
      FOR i := 0 TO 3 DO logoIE.Marquee(alg, i); END;
      logoIE.MarqueeSpots(alg);
      FOR i := 1 TO 15 DO logoIE.Shift(alg) END
    END;
  END AlgRun;

BEGIN
  ZeusPanel.RegisterAlg(NewA, "Mentor Demo", "logo");
  ZeusPanel.RegisterView(New, "Mentor Demo", "logo");
  ZeusPanel.RegisterView(NewWAlbum, "Mentor Demo w/ album", "logo");
END Logo.

interface GraphVBT is in:


interface View is in: