MickeyMouseImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Michael Plass, March 12, 1986 3:40:22 pm PST
Last edited by: Mik Lamming - April 25, 1986 3:47:49 pm PST
DIRECTORY Basics, BiScrollers, Commander, Convert, FS, Geom2D, Icons, Imager, ImagerBackdoor, ImagerBetween, ImagerGraphCapture, ImagerOps, Interpress, IO, List, Menus, MickeyMouse, Process, Real, Rope, RuntimeError, ViewerClasses, ViewerOps, ViewerTools;
MickeyMouseImpl: CEDAR MONITOR
IMPORTS BiScrollers, Commander, Convert, FS, Geom2D, Icons, Imager, ImagerBackdoor, ImagerBetween, ImagerGraphCapture, ImagerOps, Interpress, IO, List, Menus, Process, Real, Rope, RuntimeError, ViewerOps, ViewerTools
EXPORTS MickeyMouse
~ BEGIN OPEN MickeyMouse;
Viewer: TYPE ~ ViewerClasses.Viewer;
ROPE: TYPE ~ Rope.ROPE;
Graph: TYPE ~ ImagerGraphCapture.Graph;
Specification: PUBLIC ERROR [msg: Rope.ROPE] ~ CODE;
Numeric: PROC [rope: ROPE] RETURNS [BOOL] ~ {
s: INT ~ Rope.Size[rope];
FOR i: INT IN [0..s) DO
IF Rope.Fetch[rope, i] NOT IN ['0..'9] THEN RETURN [FALSE]
ENDLOOP;
RETURN [s > 0]
};
Create: PUBLIC PROC [chartName: ROPE] RETURNS [Ref] ~ {
graph: Graph ~ GraphFromInterpress[chartName];
ref: Ref ~ NEW[Rep];
yStart, yEnd: REAL ← 0.0;
timeLine: NAT ← 0;
cp: FS.ComponentPositions;
wd: ROPENIL;
[chartName, cp] ← FS.ExpandName[chartName];
wd ← Rope.Substr[chartName, 0, cp.base.start];
FOR i: NAT IN [0..graph.size) DO
IF graph[i].edgesIn = NIL AND Numeric[graph[i].label] THEN {
end: NAT ~ graph[i].edgesOut.first;
IF Numeric[graph[end].label] AND graph[end].edgesOut = NIL THEN {
yStart ← graph[i].pos.y;
ref.initialTick ← ref.tick ← Convert.IntFromRope[graph[i].label];
yEnd ← graph[end].pos.y;
ref.finalTick ← Convert.IntFromRope[graph[end].label];
timeLine ← i;
EXIT;
};
};
ENDLOOP;
IF yStart <= yEnd OR ref.finalTick <= ref.initialTick THEN ERROR Specification["No valid time line specified"];
FOR i: NAT IN [0..graph.size) DO
IF i # timeLine AND graph[i].edgesIn = NIL THEN {
p: LIST OF KeyFrame ← LIST[[0,FIRST[INT],NIL]];
last: LIST OF KeyFrame ← p;
j: NAT ← i;
activeRecord: ActiveRecord ~ NEW[ActiveRecordRep];
prevTick: INT ← 0;
DO
t: INT ~ Real.Round[(graph[j].pos.y-yStart)/(yEnd-yStart) * (ref.finalTick-ref.initialTick)]+ref.initialTick;
IF graph[j].label # NIL AND t >= last.first.initialTick THEN {
prevTick ← last.first.initialTick;
last.rest ← LIST[[z: graph[j].pos.x, initialTick: t, name: FS.ExpandName[name: graph[j].label, wDir: wd].fullFName]];
last ← last.rest;
};
IF graph[j].edgesOut = NIL THEN EXIT;
j ← graph[j].edgesOut.first;
ENDLOOP;
IF last # p AND last # p.rest THEN {
must have at least two entries.
master: Interpress.OpenMaster ← NIL;
masterName: ROPENIL;
last.rest ← LIST[last.first];
last.rest.first.initialTick ← last.rest.first.initialTick + (last.first.initialTick-prevTick);
p.first ← p.rest.first;
p.first.initialTick ← LAST[INT];
activeRecord.frames ← p;
activeRecord.between ← ImagerBetween.Create[];
FOR pass: NAT IN [0..4) WHILE p # NIL DO
IF master = NIL OR NOT Rope.Equal[p.first.name, masterName, FALSE] THEN {
master ← Interpress.Open[p.first.name, NIL, NIL];
masterName ← p.first.name;
};
ImagerBetween.SetPass[activeRecord.between, pass];
Interpress.DoPage[master, 1, activeRecord.between];
p ← p.rest;
ENDLOOP;
ref.active ← CONS[activeRecord, ref.active];
};
};
ENDLOOP;
RETURN [ref];
};
GraphFromInterpress: PROC [chartName: ROPE] RETURNS [Graph] ~ {
master: Interpress.OpenMaster ~ Interpress.Open[chartName, NIL, NIL];
capture: Imager.Context ~ ImagerGraphCapture.CreateContext[72/0.0254];
Interpress.DoPage[master, 1, capture];
RETURN [ImagerGraphCapture.GetGraph[capture]];
};
ZAt: PROC [activeRecord: ActiveRecord, time: INT] RETURNS [REAL] ~ {
initialTick: ARRAY[0..3) OF INTALL[LAST[INT]];
z: ARRAY[0..3) OF REALALL[0];
i: NAT ← 0;
FOR f: LIST OF KeyFrame ← activeRecord.frames, f.rest UNTIL f = NIL OR i >= 3 DO
z[i] ← f.first.z;
initialTick[i] ← f.first.initialTick;
i ← i + 1;
ENDLOOP;
IF initialTick[1]#LAST[INT] AND initialTick[2]#LAST[INT] AND time IN [initialTick[1]..initialTick[2]] THEN {
delta: INT ~ MAX[initialTick[2]-initialTick[1], 1];
s: REAL ~ REAL[time-initialTick[1]]/REAL[delta];
RETURN [z[1]*(1.0-s) + z[2]*s];
};
RETURN [0];
};
Sort: PROC [active: LIST OF ActiveRecord, time: INT] RETURNS [LIST OF ActiveRecord] ~ {
compareProc: PROC[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] ~ {
a1: ActiveRecord ~ NARROW[ref1];
a2: ActiveRecord ~ NARROW[ref2];
RETURN [Real.CompareREAL[ZAt[a1, time], ZAt[a2, time]]]
};
TRUSTED {active ← LOOPHOLE[List.Sort[LOOPHOLE[active], compareProc]]};
RETURN [active];
};
DrawFrame: PUBLIC PROC [self: Ref, context: Imager.Context] ~ {
self.active ← Sort[self.active, self.tick];
FOR p: LIST OF ActiveRecord ← self.active, p.rest UNTIL p=NIL DO
tick: ARRAY [0..4) OF INTALL[LAST[INT]];
i: NAT ← 0;
FOR f: LIST OF KeyFrame ← p.first.frames, f.rest UNTIL f = NIL OR i >= 4 DO
tick[i] ← f.first.initialTick;
i ← i + 1;
ENDLOOP;
IF tick[1] = LAST[INT] OR tick[2] = LAST[INT] THEN ERROR;
IF self.tick IN [tick[1]..tick[2]] THEN {
action: PROC ~ {
d0: NAT ~ IF tick[0] = LAST[INT] THEN LAST[NAT] ELSE NAT[tick[1]-tick[0]];
d1: NAT ~ MAX[tick[2]-tick[1], 1];
d2: NAT ~ IF tick[3] = LAST[INT] THEN LAST[NAT] ELSE NAT[tick[3]-tick[2]];
ImagerBetween.Replay[self: p.first.between, into: context, t: [d0, d1, d2, self.tick-tick[1]]];
};
Imager.DoSave[context, action];
};
ENDLOOP;
};
NextFrame: PUBLIC PROC [self: Ref] ~ {
self.tick ← self.tick + 1;
FOR p: LIST OF ActiveRecord ← self.active, p.rest UNTIL p=NIL DO
tick: ARRAY [0..4] OF INTALL[LAST[INT]];
name: ARRAY [0..4] OF ROPEALL[NIL];
i: NAT ← 0;
last: NAT ← 0;
FOR f: LIST OF KeyFrame ← p.first.frames, f.rest UNTIL f = NIL OR i > 4 DO
tick[i] ← f.first.initialTick;
name[i] ← f.first.name;
last ← i;
i ← i + 1;
ENDLOOP;
IF self.tick = tick[2] AND tick[3] # LAST[INT] THEN {
ImagerBetween.Roll[p.first.between];
IF name[4] # NIL THEN {
master: Interpress.OpenMaster ~ Interpress.Open[name[4], NIL, NIL];
ImagerBetween.SetPass[p.first.between, 3];
Interpress.DoPage[master, 1, p.first.between];
p.first.frames ← p.first.frames.rest;
};
p.first.frames ← p.first.frames.rest;
};
ENDLOOP;
};
Play: PUBLIC PROC [chartName: ROPE, context: Imager.Context, pause: PROCNIL] ~ {
r: Ref ~ Create[chartName];
UNTIL r.tick > r.finalTick DO
IF pause # NIL THEN pause[];
Imager.SetGray[context, 0];
Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]];
Imager.SetGray[context, 1];
DrawFrame[r, context];
NextFrame[r];
ENDLOOP;
};
CmdTokenBreak: PROC [char: CHAR] RETURNS [IO.CharClass] = {
IF char = '← THEN RETURN [break];
IF char = ' OR char = '\t OR char = ', OR char = '; OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
GetCmdToken: PROC [stream: IO.STREAM] RETURNS [rope: ROPE] = {
rope ← NIL;
rope ← stream.GetTokenRope[CmdTokenBreak ! IO.EndOfStream => CONTINUE].token;
};
MickeyMouseCommand: Commander.CommandProc ~ {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
inner: PROC ~ {
stream: IO.STREAM ~IO.RIS[cmd.commandLine];
fileName: ROPE ~ FS.ExpandName[GetCmdToken[stream]].fullFName;
cp: FS.ComponentPositions ~ FS.ExpandName[fileName].cp;
viewerData: ViewerData ~ NEW[ViewerDataRep];
viewerData.mickeyMouse ← Create[fileName];
[] ← bsStyle.CreateBiScroller[bsClass, [name: fileName, file: fileName, data: viewerData, label: Rope.Substr[fileName, cp.base.start, cp.base.length]]]
};
inner[ ! FS.Error => {msg ← error.explanation; result ← $Failure; CONTINUE}];
};
Extrema: BiScrollers.ExtremaProc ~ {
PROC [clientData: REF ANY, direction: VEC] RETURNS [min, max: VEC];
[min, max] ← Geom2D.ExtremaOfRect[[0, 0, 72*8.5, 72*11], direction];
};
ViewerData: TYPE ~ REF ViewerDataRep;
ViewerDataRep: TYPE ~ RECORD [
run: INT ← 0,
frameDelay: INT ← 0,
stop, reset: BOOLFALSE,
mickeyMouse: Ref ← NIL
];
GetMsec: PROC RETURNS [INT] ~ {
RETURN [Real.RoundLI[1000.0*Convert.RealFromRope[ViewerTools.GetSelectionContents[]]]]
};
MenuAction: Menus.ClickProc ~ {
[parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
viewer: ViewerClasses.Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[BiScrollers.ClientDataOfViewer[viewer]];
doIt: BOOLFALSE;
Locked: ENTRY PROC ~ {
viewerData.frameDelay ← 0;
SELECT clientData FROM
$Reset => {
IF viewerData.run # 0 THEN {viewerData.stop ← viewerData.reset ← TRUE; doIt ← FALSE}
ELSE doIt ← TRUE;
};
$Run => {
msec: INT ← 0;
IF mouseButton # red THEN msec ← GetMsec[ ! RuntimeError.UNCAUGHT => {ViewerOps.BlinkDisplay[]; CONTINUE}];
doIt ← viewerData.run = 0;
viewerData.run ← LAST[INT];
viewerData.frameDelay ← Process.MsecToTicks[msec];
};
$Stop => {IF viewerData.run > 0 THEN viewerData.stop ← TRUE};
$Step => {
doIt ← viewerData.run = 0;
viewerData.run ← viewerData.run + 1;
};
ENDCASE => NULL;
};
IF viewerData = NIL THEN RETURN;
Locked[];
IF doIt THEN {
SELECT clientData FROM
$Reset => Reset[viewer];
$Run => Run[viewer];
$Stop => NULL;
$Step => Run[viewer];
ENDCASE => NULL;
};
};
Reset: PROC [viewer: Viewer] ~ {
viewerData: ViewerData ~ NARROW[BiScrollers.ClientDataOfViewer[viewer]];
IF viewerData = NIL THEN RETURN;
viewerData.reset ← FALSE;
viewerData.mickeyMouse ← Create[viewer.file];
ViewerOps.PaintViewer[viewer, client];
};
Run: PROC [viewer: Viewer] ~ {
viewerData: ViewerData ~ NARROW[BiScrollers.ClientDataOfViewer[viewer]];
reset: BOOLFALSE;
CountDown: ENTRY PROC RETURNS [quit: BOOL ← TRUE] ~ {
reset ← viewerData.reset;
IF viewerData.stop THEN {viewerData.stop ← FALSE; viewerData.run ← 0; RETURN [TRUE]};
IF viewerData.mickeyMouse.tick > viewerData.mickeyMouse.finalTick THEN viewerData.run ← 0;
IF viewerData.run = 0 THEN RETURN [TRUE];
viewerData.run ← viewerData.run - 1;
IF viewerData.run = 0 THEN RETURN [TRUE] ELSE RETURN [FALSE];
};
IF viewerData = NIL THEN RETURN;
DO
ViewerOps.PaintViewer[viewer, client, FALSE];
NextFrame[viewerData.mickeyMouse];
Process.Pause[viewerData.frameDelay];
IF CountDown[] THEN EXIT;
ENDLOOP;
IF reset THEN Reset[viewer];
};
CreateMenu: PROC RETURNS [Menus.Menu] ~ {
menu: Menus.Menu ← Menus.CopyMenu[BiScrollers.bsMenu];
Menus.AppendMenuEntry[
menu: menu, line: 0,
entry: Menus.CreateEntry[
name: "Reset",
proc: MenuAction,
clientData: $Reset,
documentation: "Reset the animation"
]
];
Menus.AppendMenuEntry[
menu: menu, line: 0,
entry: Menus.CreateEntry[
name: "Run",
proc: MenuAction,
clientData: $Run,
documentation: "Run the animation"
]
];
Menus.AppendMenuEntry[
menu: menu, line: 0,
entry: Menus.CreateEntry[
name: "Stop",
proc: MenuAction,
clientData: $Stop,
documentation: "Stop the animation"
]
];
Menus.AppendMenuEntry[
menu: menu, line: 0,
entry: Menus.CreateEntry[
name: "Step",
proc: MenuAction,
clientData: $Step,
documentation: "Single-step the animation"
]
];
RETURN [menu];
};
bsStyle: BiScrollers.BiScrollerStyle;
bsClass: BiScrollers.BiScrollerClass;
PaintProc: ViewerClasses.PaintProc ~ {
[self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL ← FALSE]
viewerData: ViewerData ~ NARROW[BiScrollers.ClientDataOfViewer[self]];
Imager.ConcatT[context, bsStyle.GetTransforms[BiScrollers.QuaBiScroller[self]].viewerToClient];
BEGIN
bounds: Imager.Rectangle ~ ImagerBackdoor.GetBounds[context];
x0: NAT ~ Real.Fix[MIN[MAX[bounds.x, 0], 10000]];
x1: NAT ~ Real.Fix[MIN[MAX[bounds.x+bounds.w+1, 0], 10000]];
y0: NAT ~ Real.Fix[MIN[MAX[bounds.y, 0], 10000]];
y1: NAT ~ Real.Fix[MIN[MAX[bounds.y+bounds.h+1, 0], 10000]];
action: PROC ~ {
Imager.SetGray[context, 0];
Imager.MaskRectangle[context, bounds];
Imager.SetGray[context, 1];
Imager.ConcatT[context, bsStyle.GetTransforms[BiScrollers.QuaBiScroller[self]].clientToViewer];
Imager.ScaleT[context, 72/0.0254];
DrawFrame[viewerData.mickeyMouse, context];
};
ImagerOps.DoWithBuffer[context, action, x0, y0, x1-x0, y1-y0];
END
};
Init: PROC ~ {
bsStyle ← BiScrollers.GetStyle[]; -- default gets BiScrollersButtonned
bsClass ← bsStyle.NewBiScrollerClass[[
flavor: $MickeyMouse,
extrema: Extrema,
notify: NIL,
paint: PaintProc,
destroy: NIL,
get: NIL,
init: NIL,
save: NIL,
menu: CreateMenu[],
tipTable: NIL,
icon: Icons.NewIconFromFile["MickeyMouse.icons", 0],
mayStretch: FALSE, -- NOT OK to scale X and Y differently
preserve: [X: 0, Y: 0] --this specifies point that stays fixed when viewer size changes
]];
Commander.Register["MickeyMouse", MickeyMouseCommand, "Play a key-frame animation"];
};
Init[];
END.