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;
Viewer:
TYPE ~ ViewerClasses.Viewer;
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: ROPE ← NIL;
[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.Master ← NIL;
masterName: ROPE ← NIL;
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];
masterName ← p.first.name;
};
ImagerBetween.SetPass[activeRecord.between, pass];
Interpress.DoPage[master, 1, activeRecord.between, NIL];
p ← p.rest;
ENDLOOP;
ref.active ← CONS[activeRecord, ref.active];
};
};
ENDLOOP;
RETURN [ref];
};
GraphFromInterpress:
PROC [chartName:
ROPE]
RETURNS [Graph] ~ {
master: Interpress.Master ~ Interpress.Open[chartName, NIL];
capture: Imager.Context ~ ImagerGraphCapture.CreateContext[72/0.0254];
Interpress.DoPage[master, 1, capture, NIL];
RETURN [ImagerGraphCapture.GetGraph[capture]];
};
ZAt:
PROC [activeRecord: ActiveRecord, time:
INT]
RETURNS [
REAL] ~ {
initialTick: ARRAY[0..3) OF INT ← ALL[LAST[INT]];
z: ARRAY[0..3) OF REAL ← ALL[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 INT ← ALL[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 INT ← ALL[LAST[INT]];
name: ARRAY [0..4] OF ROPE ← ALL[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.Master ~ Interpress.Open[name[4], NIL];
ImagerBetween.SetPass[p.first.between, 3];
Interpress.DoPage[master, 1, p.first.between, NIL];
p.first.frames ← p.first.frames.rest;
};
p.first.frames ← p.first.frames.rest;
};
ENDLOOP;
};
Play:
PUBLIC
PROC [chartName:
ROPE, context: Imager.Context, pause:
PROC ←
NIL] ~ {
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: BOOL ← FALSE,
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: BOOL ← FALSE;
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: BOOL ← FALSE;
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[];