<> <> <> <> <<>> 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: 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 { <> master: Interpress.OpenMaster _ NIL; masterName: ROPE _ NIL; <> <> 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 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.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: 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 ~ { <> [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[]; END.