<Viewing>BiScrollers.DF>> <> <<>> <> DIRECTORY Cursors, TIPUser, Icons, InputFocus, ViewerClasses, ViewerOps, Graphics, Geom2D, BiScrollers, RealFns, Real, Buttons; BiScrollersImpl2: CEDAR MONITOR IMPORTS Graphics, Cursors, TIPUser, InputFocus, VO:ViewerOps, Geom2D, RealFns, Real, Buttons EXPORTS BiScrollers = BEGIN OPEN BiScrollers; Number: TYPE = Geom2D.Number; ViewerClass: TYPE = ViewerClasses.ViewerClass; Axis: TYPE = Geom2D.Axis; Vec: TYPE = Geom2D.Vec; Area: TYPE = Geom2D.Area; AreaRef: TYPE = REF Area; BiScroller: PUBLIC TYPE = REF BiScrollerObject; BiScrollerObject: PUBLIC TYPE = RECORD [ class: BiScrollerClass, clientData: REF ANY, viewer: Viewer _ NIL, children: Child _ NIL, t, u: Transform _ Geom2D.id, h, v: Viewer _ NIL, --horizontal and vertical BiScrollBars-- s, r: Viewer _ NIL, --scale and rotate Knobs-- hv, sr, a: Buttons.Button, --hor/vert, scale/rot, & all reset buttons-- cl, cr, cb, ct: INTEGER _ 0, --limits of subclass's clipping region-- cw, ch: INTEGER _ 0 --last time we looked in the viewer-- ]; BiScrollerClass: PUBLIC TYPE = REF BiScrollerClassRec; BiScrollerClassRec: PUBLIC TYPE = RECORD [ viewerClass: ViewerClass, notify: ViewerClasses.NotifyProc, paint: ViewerClasses.PaintProc, extrema: ExtremaProc, init: ViewerClasses.InitProc, mayStretch: BOOLEAN]; Child: TYPE = REF ChildObject; ChildObject: TYPE = RECORD [next: Child, where: Vec, it: Viewer]; Range: TYPE = REF RangeObject; RangeObject: TYPE = RECORD [min, max: Number]; BiScrollBar: TYPE = REF BiScrollBarObject; BiScrollBarObject: TYPE = RECORD [viewer: Viewer _ NIL, parent: BiScroller, axis: Axis, cursors: ARRAY State OF Cursors.CursorType]; State: TYPE = {Idle, Increase, Decrease, Random, Reset}; Knob: TYPE = REF KnobObject; KnobObject: TYPE = RECORD [viewer: Viewer _ NIL, parent: BiScroller, axis: Axis, newTransform: NewTransformProc, resetTransform: ResetTransformProc, deltaTransform: DeltaTransformProc, cursors: ARRAY State OF Cursors.CursorType, last: Number _ 0]; NewTransformProc: TYPE = PROC [old: Transform, parm: Number, cx, cy: Number] RETURNS [new: Transform]; ResetTransformProc: TYPE = PROC [old: Transform, cx, cy: Number] RETURNS [new: Transform]; DeltaTransformProc: TYPE = PROC [old: Transform, delta: INTEGER, cx, cy: Number] RETURNS [new: Transform]; awake: BOOLEAN _ FALSE; --true while we have CapturedButtons indent: INTEGER _ 11; --width of BiScrollBars reset: Cursors.CursorType _ bullseye; grayl: CARDINAL = 11110B; graym: CARDINAL = 122645B; grayh: CARDINAL = 102041B; NewBiScrollerClass: PUBLIC PROC [ flavor: ATOM, extrema: ExtremaProc, notify: ViewerClasses.NotifyProc _ NIL, paint: ViewerClasses.PaintProc _ NIL, modify: ViewerClasses.ModifyProc _ NIL, destroy: ViewerClasses.DestroyProc _ NIL, copy: ViewerClasses.CopyProc _ NIL, set: ViewerClasses.SetProc _ NIL, get: ViewerClasses.GetProc _ NIL, init: ViewerClasses.InitProc _ NIL, finish: LIST OF REF ANY _ NIL, <> save: ViewerClasses.SaveProc _ NIL, tipTable: TIPUser.TIPTable _ NIL, icon: Icons.IconFlavor _ document, cursor: Cursors.CursorType _ textPointer, mayStretch: BOOLEAN _ TRUE] RETURNS [bsc: BiScrollerClass] = BEGIN vc: ViewerClass _ NEW [ViewerClasses.ViewerClassRec _ [ flavor: flavor, notify: NotifyBiScroller, paint: PaintBiScroller, modify: modify, destroy: destroy, copy: copy, set: set, get: get, save: save, tipTable: tipTable, icon: icon, cursor: cursor]]; VO.RegisterViewerClass[flavor: flavor, class: vc]; bsc _ NEW [BiScrollerClassRec _ [ viewerClass: vc, notify: notify, paint: paint, extrema: extrema, init: init, mayStretch: mayStretch]]; END; CreateBiScroller: PUBLIC PROC [class: BiScrollerClass, info: ViewerClasses.ViewerRec _ [], paint: BOOLEAN _ TRUE] RETURNS [new: BiScroller] = BEGIN new _ NEW[BiScrollerObject _ [class: class, clientData: info.data, t: Geom2D.id]]; info.data _ new; new.viewer _ VO.CreateViewer[flavor: class.viewerClass.flavor, info: info, paint: FALSE]; MakeBorders[new]; new.t _ Geom2D.id.Translate[new.viewer.cw/2, new.viewer.ch/2]; new.u _ new.t.Inverse[]; [] _ ComputeClientBounds[new, FALSE]; IF class.init # NIL THEN class.init[new.viewer]; IF paint THEN VO.PaintViewer[viewer: new.viewer, hint: all]; END; Destroy: PUBLIC PROC [bs: BiScroller] RETURNS [BiScroller] = BEGIN VO.DestroyViewer[bs.viewer]; bs.viewer _ NIL; RETURN [NIL]; END; AddChild: PUBLIC PROC [to: BiScroller, what: Viewer, x, y: REAL _ 0, useTheseCoords: BOOLEAN _ FALSE, paint: BOOLEAN _ TRUE] = BEGIN my: Vec; c: Child _ NEW [ChildObject _ [next: to.children, it: what, where: IF useTheseCoords THEN [x, y] ELSE [what.wx, what.wy] ]]; to.children _ c; my _ to.t.MapVec[c.where]; VO.MoveViewer[viewer: what, x: RI[my.x], y: RI[my.y], w: what.ww, h: what.wh, paint: paint]; END; DeleteChild: PUBLIC PROC [of: BiScroller, who: Viewer] = BEGIN Filter: PROC [c: Child] RETURNS [Child] = {IF c = NIL THEN RETURN [NIL]; IF c.it = who THEN RETURN [c.next]; c.next _ Filter[c.next]; RETURN [c]}; of.children _ Filter[of.children]; END; IsBiScroller: PUBLIC PROC [ra: REF ANY] RETURNS [BOOLEAN] = {RETURN [ISTYPE[ra, BiScroller]]}; NarrowToBiScroller: PUBLIC PROC [ra: REF ANY] RETURNS [BiScroller] = {RETURN [NARROW[ra]]}; QuaViewer: PUBLIC PROC [bs: BiScroller] RETURNS [Viewer] = {RETURN [bs.viewer]}; QuaBiScroller: PUBLIC PROC [v: Viewer] RETURNS [BiScroller] = {RETURN [NARROW[v.data]]}; ViewerIsABiScroller: PUBLIC PROC [v: Viewer] RETURNS [BOOLEAN] = {RETURN [ISTYPE[v.data, BiScroller]]}; ClientDataOf: PUBLIC PROC [bs: BiScroller] RETURNS [REF ANY] = {RETURN [bs.clientData]}; BoundaryOf: PUBLIC PROC [bs: BiScroller] RETURNS [VecList] = BEGIN RETURN [Geom2D.MapVecs[bs.u, LIST[[bs.cl, bs.cb], [bs.cr, bs.cb], [bs.cr, bs.ct], [bs.cl, bs.ct]]]]; END; BoundaryExtrema: PUBLIC PROC [bs: BiScroller, direction: Vec] RETURNS [min, max: Vec] = BEGIN vl: VecList _ BoundaryOf[bs]; e: Geom2D.ExtremaRec _ direction.Extreme[vl.first, direction.Extreme[vl.rest.first, direction.Extreme[vl.rest.rest.first, direction.StartExtreme[vl.rest.rest.rest.first]]]]; RETURN [e.minV, e.maxV]; END; ComputeClientBounds: PROC [bs: BiScroller, paint: BOOLEAN] RETURNS [news: BOOLEAN] = BEGIN bs.cl _ indent; bs.cb _ indent; <> IF (news _ bs.cw # bs.viewer.cw OR bs.ch # bs.viewer.ch) THEN BEGIN bs.cw _ bs.viewer.cw; bs.ch _ bs.viewer.ch; bs.cr _ bs.cw - indent; bs.ct _ bs.ch - indent; VO.MoveViewer[bs.h, bs.cl, 0, bs.cr-bs.cl, indent, paint]; VO.MoveViewer[bs.v, 0, bs.cb, indent, bs.ct-bs.cb, paint]; VO.MoveViewer[bs.s, bs.cl, bs.ct, bs.cr-bs.cl, indent, paint]; VO.MoveViewer[bs.r, bs.cr, bs.cb, indent, bs.ct-bs.cb, paint]; VO.MoveViewer[bs.a, 0, bs.ct, indent, indent, paint]; VO.MoveViewer[bs.hv, 0, 0, indent, indent, paint]; VO.MoveViewer[bs.sr, bs.cr, bs.ct, indent, indent, paint]; END ELSE BEGIN bs.cr _ bs.cw - indent; bs.ct _ bs.ch - indent; END; END; MakeBorders: PROC [bs: BiScroller] = BEGIN bs.h _ VO.CreateViewer[flavor: $BiScrollBarX, info: [ name: "X scrolling", parent: bs.viewer, wx: indent, wy: 0, ww: bs.viewer.cw-2*indent, wh: indent, data: NEW[BiScrollBarObject _ [parent: bs, axis: X, cursors: [ Increase: scrollRight, Decrease: scrollLeft, Random: pointUp, Reset: reset, Idle: scrollLeftRight]]], scrollable: FALSE, border: FALSE], paint: FALSE]; bs.v _ VO.CreateViewer[flavor: $BiScrollBarY, info: [ name: "Y scrolling", parent: bs.viewer, wx: 0, wy: indent, ww: indent, wh: bs.viewer.ch-2*indent, data: NEW[BiScrollBarObject _ [parent: bs, axis: Y, cursors: [ Increase: scrollUp, Decrease: scrollDown, Random: pointRight, Reset: reset, Idle: scrollUpDown]]], scrollable: FALSE, border: FALSE], paint: FALSE]; bs.s _ VO.CreateViewer[flavor: $ScaleKnob, info: [ name: "Scale Knob", parent: bs.viewer, wx: indent, wy: bs.viewer.ch-indent, ww: bs.viewer.cw-2*indent, wh: indent, data: NEW [KnobObject _ [parent: bs, axis: X, cursors: [ Idle: scrollLeftRight, Random: pointDown, Reset: reset, Increase: pointRight, Decrease: pointLeft], newTransform: NewScale, resetTransform: ResetScale, deltaTransform: DeltaScale]], scrollable: FALSE, border: FALSE], paint: FALSE]; bs.r _ VO.CreateViewer[flavor: $RotateKnob, info: [ name: "Rotate Knob", parent: bs.viewer, wx: bs.viewer.cw-indent, wy: indent, ww: indent, wh: bs.viewer.ch-2*indent, data: NEW [KnobObject _ [parent: bs, axis: Y, cursors: [ Idle: scrollUpDown, Random: pointLeft, Reset: reset, Increase: pointUp, Decrease: pointDown], newTransform: NewRotation, resetTransform: ResetRotation, deltaTransform: DeltaRotation]], scrollable: FALSE, border: FALSE], paint: FALSE]; bs.a _ Buttons.Create[info: [name: "", parent: bs.viewer, wx: 0, wy: bs.viewer.ch-indent, ww: indent, wh: indent], proc: ResetAll, clientData: bs, documentation: "I reset the scaling, rotation, and offsets"]; bs.hv _ Buttons.Create[info: [name: "", parent: bs.viewer, wx: 0, wy: 0, ww: indent, wh: indent], proc: ResetOffsets, clientData: bs, documentation: "I reset the offsets"]; bs.sr _ Buttons.Create[info: [name: "", parent: bs.viewer, wx: bs.viewer.cw-indent, wy: bs.viewer.ch-indent, ww: indent, wh: indent], proc: ResetSR, clientData: bs, documentation: "I reset the scaling and rotation"]; END; NotifyBiScroller: ViewerClasses.NotifyProc = BEGIN bs: BiScroller _ NARROW[self.data]; i, o, l: LIST OF REF ANY _ NIL; IF bs.class.notify = NIL THEN RETURN; FOR i _ input, i.rest WHILE i # NIL DO l _ IF l = NIL THEN o _ CONS[i.first, NIL] ELSE l.rest _ CONS[i.first, NIL]; WITH l.first SELECT FROM z: TIPUser.TIPScreenCoords => {cc: ClientCoords _ NEW [Vec _ bs.u.MapVec[[z.mouseX, z.mouseY]]]; l.first _ cc}; ENDCASE; ENDLOOP; bs.class.notify[self, o]; END; PaintBiScroller: ViewerClasses.PaintProc = BEGIN bs: BiScroller _ NARROW[self.data]; Graphics.ClipBox[context, [bs.cl, bs.cb, bs.cr, bs.ct]]; Graphics.Translate[context, bs.t.e, bs.t.f]; Graphics.Concat[context, bs.t.a, bs.t.b, bs.t.c, bs.t.d]; bs.class.paint[self, context, whatChanged, clear]; END; ResetAll: Buttons.ButtonProc = BEGIN bs: BiScroller _ NARROW[clientData]; IF (mouseButton = red) THEN Fit[bs] ELSE ChangeTransform[bs, [1, 0, 0, 1, 0, 0]]; END; Fit: PUBLIC PROC [bs: BiScroller, paint: BOOLEAN _ TRUE, mayStretch: BOOLEAN _ FALSE] = BEGIN xmin, ymin, xmax, ymax: Vec; dx, dy, sx, sy: Number; [xmin, xmax] _ bs.class.extrema[bs.clientData, [1, 0]]; [ymin, ymax] _ bs.class.extrema[bs.clientData, [0, 1]]; dx _ xmax.x - xmin.x; dy _ ymax.y - ymin.y; sx _ IF ABS[dx] = 0 THEN 1 ELSE (bs.cr-bs.cl)/dx; sy _ IF ABS[dy] = 0 THEN 1 ELSE (bs.ct-bs.cb)/dy; IF NOT mayStretch THEN sx _ sy _ MIN[sx, sy]; ChangeTransform[bs, [sx, 0, 0, sy, (bs.cr+bs.cl)/2 - sx*(xmax.x+xmin.x)/2, (bs.ct+bs.cb)/2 - sy*(ymax.y+ymin.y)/2], paint]; END; ResetOffsets: Buttons.ButtonProc = BEGIN bs: BiScroller _ NARROW[clientData]; IF (mouseButton = red) THEN BEGIN xmin, xmax, ymin, ymax: Number; [xmin, xmax] _ GetLimits[bs, X]; [ymin, ymax] _ GetLimits[bs, Y]; ChangeTransform[bs, bs.t.Translate[(bs.cl+bs.cr)/2-(xmin+xmax)/2, (bs.ct+bs.cb)/2-(ymin+ymax)/2]]; END ELSE ChangeTransform[bs, [bs.t.a, bs.t.b, bs.t.c, bs.t.d, 0, 0]]; END; ResetSR: Buttons.ButtonProc = BEGIN bs: BiScroller _ NARROW[clientData]; s: Number; v: Vec _ [(bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]; u: Transform; u _ bs.t.Translate[-v.x, -v.y]; s _ u.a*u.d - u.b*u.c; u _ IF ABS[s] < 0.0001 THEN [1, 0, 0, 1, 0, 0] ELSE [1, 0, 0, 1, (u.e*u.d-u.f*u.c)/s, (u.f*u.a-u.e*u.b)/s]; ChangeTransform[bs, u.Translate[v.x, v.y]]; END; InitBiScrollBar: ViewerClasses.InitProc = BEGIN bsb: BiScrollBar _ NARROW[self.data]; bsb.viewer _ self; END; PaintBiScrollBar: ViewerClasses.PaintProc = BEGIN bsb: BiScrollBar _ NARROW[self.data]; r: Range; Do: PROCEDURE [c: CARDINAL, xmin, ymin, xmax, ymax: Number] = {Graphics.SetStipple[context, c]; Graphics.DrawBox[context, [xmin, ymin, xmax, ymax]]}; IF whatChanged # NIL THEN BEGIN r _ NARROW[whatChanged]; SELECT bsb.axis FROM X => {Do[grayh, 0, 0, r.min*self.cw, self.ch]; Do[graym, r.min*self.cw, 0, r.max*self.cw, self.ch]; Do[grayl, r.max*self.cw, 0, self.cw, self.ch]}; Y => {Do[grayh, 0, 0, self.cw, r.min*self.ch]; Do[graym, 0, r.min*self.ch, self.cw, r.max*self.ch]; Do[grayl, 0, r.max*self.ch, self.cw, self.ch]}; ENDCASE => ERROR; END; END; GetLimits: PROC [bs: BiScroller, axis: Axis] RETURNS [pmin, pmax: Number] = BEGIN norm, min, max: Vec; SELECT axis FROM X => norm _ [bs.t.d, -bs.t.b]; Y => norm _ [-bs.t.c, bs.t.a]; ENDCASE => ERROR; [min, max] _ bs.class.extrema[bs.clientData, norm]; min _ bs.t.MapVec[min]; max _ bs.t.MapVec[max]; SELECT axis FROM X => RETURN [min.x, max.x]; Y => RETURN [min.y, max.y]; ENDCASE => ERROR; END; NotifyBiScrollBarProc: TYPE = PROC [bsb: BiScrollBar, bs: BiScroller, mouse: TIPUser.TIPScreenCoords, input: LIST OF REF ANY] RETURNS [LIST OF REF ANY]; WakeUpBiScrollBar: PROC [bsb: BiScrollBar, bs: BiScroller, to: State, indicate: BOOLEAN] = BEGIN awake _ TRUE; Cursors.SetCursor[bsb.viewer.class.cursor _ bsb.cursors[to]]; IF indicate THEN BEGIN r: Range _ NEW[RangeObject]; beginW, endW, beginZ, endZ, deltaW: Number; SELECT bsb.axis FROM X => {[beginW, endW] _ GetLimits[bs, X]; beginZ _ bs.cl; endZ _ bs.cr}; Y => {[beginW, endW] _ GetLimits[bs, Y]; beginZ _ bs.cb; endZ _ bs.ct}; ENDCASE => ERROR; deltaW _ endW - beginW; IF ABS[deltaW] # 0 THEN r^ _ [ MAX[0.0, MIN[1.0, (beginZ - beginW)/deltaW]], MAX[0.0, MIN[1.0, (endZ - beginW)/deltaW]]] ELSE r^ _ [ IF beginW > beginZ THEN 0.0 ELSE 1.0, IF beginW < endZ THEN 1.0 ELSE 0.0]; VO.PaintViewer[viewer: bsb.viewer, hint: client, clearClient: FALSE, whatChanged: r]; END; InputFocus.CaptureButtons[proc: bsb.viewer.class.notify, tip: bsb.viewer.tipTable, viewer: bsb.viewer]; END; Sleep: PROC [bsb: BiScrollBar] = BEGIN Cursors.SetCursor[bsb.viewer.class.cursor _ bsb.cursors[Idle]]; InputFocus.ReleaseButtons[]; awake _ FALSE; END; IncreaseBiScrollBar: NotifyBiScrollBarProc = BEGIN IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Increase, TRUE]; IF input.first = $Idle THEN NULL ELSE IF input.first = $Doit THEN BEGIN Sleep[bsb]; Move[bs, SELECT bsb.axis FROM X => [bsb.viewer.cw - mouse.mouseX, 0], Y => [0, bsb.viewer.ch - mouse.mouseY], ENDCASE => ERROR]; END ELSE ERROR; RETURN [input.rest]; END; DecreaseBiScrollBar: NotifyBiScrollBarProc = BEGIN IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Decrease, TRUE]; IF input.first = $Idle THEN NULL ELSE IF input.first = $Doit THEN BEGIN Sleep[bsb]; Move[bs, SELECT bsb.axis FROM X => [-mouse.mouseX, 0], Y => [0, -mouse.mouseY], ENDCASE => ERROR]; END ELSE ERROR; RETURN [input.rest]; END; ThumbBiScrollBar: NotifyBiScrollBarProc = BEGIN IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Random, TRUE]; IF input.first = $Idle THEN NULL ELSE IF input.first = $Doit THEN BEGIN cmin, cmax: Number; Foo: PROCEDURE [low, high, mouse: Number] RETURNS [Number] = {RETURN [(low+high)/2 - (cmin + (mouse-low)*(cmax-cmin)/(high-low))]}; SELECT bsb.axis FROM X => {[cmin, cmax] _ GetLimits[bs, X]; Sleep[bsb]; Move[bs, [Foo[bs.cl, bs.cr, mouse.mouseX], 0]]}; Y => {[cmin, cmax] _ GetLimits[bs, Y]; Sleep[bsb]; Move[bs, [0, Foo[bs.cb, bs.ct, mouse.mouseY]]]}; ENDCASE => ERROR; END ELSE ERROR; RETURN [input.rest]; END; ResetBiScrollBar: NotifyBiScrollBarProc = BEGIN IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Reset, FALSE]; IF input.first = $Idle THEN NULL ELSE IF input.first = $Doit THEN BEGIN nu: Transform _ bs.t; SELECT bsb.axis FROM X => nu.e _ 0; Y => nu.f _ 0; ENDCASE => ERROR; Sleep[bsb]; ChangeTransform[bs, nu]; END ELSE ERROR; RETURN [input.rest]; END; Move: PROCEDURE [bs: BiScroller, by: Vec] = BEGIN ChangeTransform[bs, bs.t.Translate[by.x, by.y]]; END; NotifyBiScrollBar: ViewerClasses.NotifyProc = BEGIN ENABLE UNWIND => InputFocus.ReleaseButtons[]; bsb: BiScrollBar _ NARROW[self.data]; bs: BiScroller _ bsb.parent; mouse: TIPUser.TIPScreenCoords; IF ComputeClientBounds[bs, TRUE] THEN RETURN; WHILE input # NIL DO WITH input.first SELECT FROM x: ATOM => SELECT x FROM $Increase => input _ IncreaseBiScrollBar[bsb, bs, mouse, input.rest]; $Decrease => input _ DecreaseBiScrollBar[bsb, bs, mouse, input.rest]; $Random => input _ ThumbBiScrollBar[bsb, bs, mouse, input.rest]; $Reset => input _ ResetBiScrollBar[bsb, bs, mouse, input.rest]; ENDCASE => ERROR; z: TIPUser.TIPScreenCoords => BEGIN v: Viewer; c: BOOLEAN; mouse _ z; IF awake THEN BEGIN [v, c] _ VO.MouseInViewer[mouse]; IF v # self OR NOT c THEN {Sleep[bsb]; VO.PaintViewer[self, client, TRUE, NIL]; RETURN}; END; input _ input.rest; END; ENDCASE => ERROR; ENDLOOP; END; NewScale: NewTransformProc = BEGIN s: Number _ parm * 2; IF parm < 0.001 THEN RETURN [old]; new _ old.Translate[-cx, -cy].ScaleT[s].Translate[cx, cy]; END; ResetScale: ResetTransformProc = BEGIN s: Number _ RealFns.SqRt[ABS[old.a*old.d - old.b*old.c]]; IF ABS[s] = 0 THEN RETURN [old]; new _ old.Translate[-cx, -cy].ScaleT[1.0/s].Translate[cx, cy]; END; DeltaScale: DeltaTransformProc = BEGIN RETURN [old.Translate[-cx, -cy].ScaleT[SELECT delta FROM -1 => 1.0/2.0, 1 => 2.0, ENDCASE => ERROR].Translate[cx, cy]]; END; NewRotation: NewTransformProc = BEGIN RETURN [old.Translate[-cx, -cy].RotateDegrees[(parm-0.5)*360].Translate[cx, cy]]; END; ResetRotation: ResetTransformProc = BEGIN s: Number _ RealFns.SqRt[ABS[old.a*old.d - old.b*old.c]]; IF ABS[s] = 0 THEN RETURN [[1, 0, 0, 1, old.e, old.f]]; new _ old.Translate[-cx, -cy]; new _ [s, 0, 0, s, new.e, new.f]; new _ new.Translate[cx, cy]; END; DeltaRotation: DeltaTransformProc = BEGIN RETURN [old.Translate[-cx, -cy].RotateBy90s[delta].Translate[cx, cy]]; END; InitKnob: ViewerClasses.InitProc = BEGIN k: Knob _ NARROW[self.data]; k.viewer _ self; END; PaintKnob: ViewerClasses.PaintProc = BEGIN IF whatChanged = NIL THEN RETURN; WITH whatChanged SELECT FROM a: AreaRef => {Graphics.SetStipple[context, graym]; [] _ Graphics.SetPaintMode[context, invert]; Graphics.DrawBox[context, a^]}; ENDCASE => ERROR; END; SleepKnob: PROC [k: Knob] = {Cursors.SetCursor[k.viewer.class.cursor _ k.cursors[Idle]]; InputFocus.ReleaseButtons[]; awake _ FALSE}; WakeKnob: PROC [k: Knob, bs: BiScroller, state: State] = BEGIN awake _ TRUE; Cursors.SetCursor[k.viewer.class.cursor _ k.cursors[state]]; InputFocus.CaptureButtons[proc: k.viewer.class.notify, tip: k.viewer.tipTable, viewer: k.viewer]; END; KnobNotifyProc: TYPE = PROC [k: Knob, bs: BiScroller, mouse: TIPUser.TIPScreenCoords, input: LIST OF REF ANY] RETURNS [LIST OF REF ANY]; ResetKnob: KnobNotifyProc = BEGIN IF NOT awake THEN WakeKnob[k, bs, Reset]; IF input.first = $Idle THEN RETURN [input.rest]; IF input.first # $Doit THEN ERROR; SleepKnob[k]; ChangeTransform[bs, k.resetTransform[bs.t, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]]; RETURN [input.rest]; END; SetKnob: KnobNotifyProc = BEGIN max, mark: Number; Foo: PROC [xa, xb, ya, yb: Number] = BEGIN a: AreaRef _ NEW[Area]; a.xmin _ MIN[xa, xb]; a.xmax _ MAX[xa, xb]; a.ymin _ MIN[ya, yb]; a.ymax _ MAX[ya, yb]; VO.PaintViewer[viewer: k.viewer, hint: client, clearClient: FALSE, whatChanged: a]; END; IF NOT awake THEN {WakeKnob[k, bs, Random]; k.last _ SELECT k.axis FROM X => k.viewer.cw/2, Y => k.viewer.ch/2, ENDCASE => ERROR}; SELECT k.axis FROM X => {max _ k.viewer.cw; mark _ mouse.mouseX; Foo[mark, k.last, 0, indent]}; Y => {max _ k.viewer.ch; mark _ mouse.mouseY; Foo[0, indent, mark, k.last]}; ENDCASE => ERROR; k.last _ mark; IF input.first = $Idle THEN RETURN [input.rest]; IF input.first # $Doit THEN ERROR; SleepKnob[k]; ChangeTransform[bs, k.newTransform[bs.t, mark/max, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]]; RETURN [input.rest]; END; UpKnob: KnobNotifyProc = BEGIN IF NOT awake THEN WakeKnob[k, bs, Increase]; IF input.first = $Idle THEN RETURN [input.rest]; IF input.first # $Doit THEN ERROR; SleepKnob[k]; ChangeTransform[bs, k.deltaTransform[bs.t, 1, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]]; RETURN [input.rest]; END; DownKnob: KnobNotifyProc = BEGIN IF NOT awake THEN WakeKnob[k, bs, Decrease]; IF input.first = $Idle THEN RETURN [input.rest]; IF input.first # $Doit THEN ERROR; SleepKnob[k]; ChangeTransform[bs, k.deltaTransform[bs.t, -1, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]]; RETURN [input.rest]; END; RI: PROC [REAL] RETURNS [INTEGER] = Real.RoundI; GetTransform: PUBLIC PROC [bs: BiScroller] RETURNS [t: Transform] = {t _ bs.t}; ChangeTransform: PUBLIC PROC [bs: BiScroller, new: Transform, paint: BOOLEAN _ TRUE] = BEGIN Doit: ENTRY PROC [t, u: Transform] = {bs.u _ u; bs.t _ t}; inv: Transform _ new.Inverse[]; FOR c: Child _ bs.children, c.next UNTIL c = NIL DO nu: Vec; nu _ new.MapVec[c.where]; VO.EstablishViewerPosition[c.it, RI[nu.x], RI[nu.y], c.it.ww, c.it.wh]; ENDLOOP; Doit[new, inv]; IF paint THEN VO.PaintViewer[viewer: bs.viewer, hint: client]; END; NotifyKnob: ViewerClasses.NotifyProc = BEGIN ENABLE UNWIND => InputFocus.ReleaseButtons[]; mouse: TIPUser.TIPScreenCoords; k: Knob _ NARROW[self.data]; bs: BiScroller _ k.parent; WHILE input # NIL DO WITH input.first SELECT FROM x: ATOM => SELECT x FROM $Increase => input _ UpKnob[k, bs, mouse, input.rest]; $Decrease => input _ DownKnob[k, bs, mouse, input.rest]; $Random => input _ SetKnob[k, bs, mouse, input.rest]; $Reset => input _ ResetKnob[k, bs, mouse, input.rest]; ENDCASE => ERROR; z: TIPUser.TIPScreenCoords => BEGIN v: Viewer; c: BOOLEAN; mouse _ z; IF awake THEN BEGIN [v, c] _ VO.MouseInViewer[mouse]; IF v # self OR NOT c THEN {SleepKnob[k]; VO.PaintViewer[self, client, TRUE, NIL]; RETURN}; END; input _ input.rest; END; ENDCASE => ERROR; ENDLOOP; END; Setup: PROCEDURE = BEGIN VO.RegisterViewerClass[flavor: $BiScrollBarX, class: NEW [ViewerClasses.ViewerClassRec _ [ flavor: $BiScrollBarX, init: InitBiScrollBar, notify: NotifyBiScrollBar, paint: PaintBiScrollBar, tipTable: TIPUser.InstantiateNewTIPTable["KnobH.TIP"], cursor: scrollLeftRight]]]; VO.RegisterViewerClass[flavor: $BiScrollBarY, class: NEW [ViewerClasses.ViewerClassRec _ [ flavor: $BiScrollBarY, init: InitBiScrollBar, notify: NotifyBiScrollBar, paint: PaintBiScrollBar, tipTable: TIPUser.InstantiateNewTIPTable["KnobV.TIP"], cursor: scrollUpDown]]]; VO.RegisterViewerClass[flavor: $ScaleKnob, class: NEW [ViewerClasses.ViewerClassRec _ [ flavor: $ScaleKnob, init: InitKnob, notify: NotifyKnob, paint: PaintKnob, tipTable: TIPUser.InstantiateNewTIPTable["KnobH.TIP"], cursor: scrollLeftRight]]]; VO.RegisterViewerClass[flavor: $RotateKnob, class: NEW [ViewerClasses.ViewerClassRec _ [ flavor: $RotateKnob, init: InitKnob, notify: NotifyKnob, paint: PaintKnob, tipTable: TIPUser.InstantiateNewTIPTable["KnobV.TIP"], cursor: scrollUpDown]]]; END; Setup[]; END.