<> <> <<>> <> DIRECTORY BiScrollers, Cursors, Geom2D, Graphics, Icons, InputFocus, Real, RealFns, TIPUser, ViewerClasses, ViewerOps; BiScrollersButtonless: CEDAR MONITOR IMPORTS BiScrollers, Cursors, Geom2D, Graphics, InputFocus, Real, TIPUser, ViewerOps = 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; Buttonless: TYPE = REF ButtonlessRep; ButtonlessRep: TYPE = RECORD [ class: ButtonlessClass, clientData: REF ANY, viewer: Viewer _ NIL, children: Child _ NIL, t: Transform _ Geom2D.id, --Client to Viewer Transform u: Transform _ Geom2D.id, --Viewer coords*u = Client coords cw, ch: INTEGER _ 0, --last time we looked in the viewer-- clientWantsActive: BOOL _ FALSE, state: State _ Normal ]; ButtonlessClass: TYPE = REF ButtonlessClassRep; ButtonlessClassRep: TYPE = RECORD [ viewerClass: ViewerClass, cursor: Cursors.CursorType ]; Child: TYPE = REF ChildObject; ChildObject: TYPE = RECORD [next: Child, where: Vec, it: Viewer]; Mouse: TYPE = REF MouseRec; MouseRec: TYPE = RECORD [ start: StartProc _ NIL, finish: FinishProc _ NIL, cursor: CursorProc _ NIL, track: TrackProc _ NIL, newTransform: NewTransformProc]; StartProc: TYPE = PROC [viewMouse, viewCenter: Vec, bs: BiScroller, bl: Buttonless]; FinishProc: TYPE = PROC [bs: BiScroller, bl: Buttonless]; CursorProc: TYPE = PROC [viewMouse, viewCenter: Vec] RETURNS [Cursors.CursorType]; TrackProc: TYPE = PROC [viewMouse, viewCenter: Vec, bs: BiScroller, bl: Buttonless]; NewTransformProc: TYPE = PROC [old: Transform, viewMouse, viewCenter: Vec, bs: BiScroller, bl: Buttonless] RETURNS [new: Transform]; Areas: TYPE = REF AreasRec; AreasRec: TYPE = RECORD [next: Areas, area: Area]; State: TYPE = {Normal, Thumbing, Scrolling, ScaleRandoming}; ActiveState: TYPE = State[Thumbing .. ScaleRandoming]; awake: Viewer _ NIL; mice: ARRAY ActiveState OF Mouse _ ALL[NIL]; pointUpRight, pointUpLeft, pointDownRight, pointDownLeft: Cursors.CursorType; buttonlessStyle: BiScrollerStyle _ NEW [BiScrollerStyleRep _ [ NewBiScrollerClass: NewBiScrollerClass, CreateBiScroller: CreateBiScroller, Destroy: Destroy, GetTransforms: GetTransforms, ChangeTransform: ChangeTransform, AddChild: AddChild, DeleteChild: DeleteChild, SetButtonsCapturedness: SetButtonsCapturedness, ViewportOf: ViewportOf, QuaViewer: QuaViewer, ClientDataOf: ClientDataOf ]]; NewBiScrollerClass: PROC [cc: ClassCommon] RETURNS [bsc: BiScrollerClass] = BEGIN viewerClass: ViewerClass; t: TIPUser.TIPTable _ TIPUser.InstantiateNewTIPTable["BiScroller.TIP"]; IF cc.vanilla = NIL THEN cc.vanilla _ GenID; IF cc.tipTable # NIL THEN BEGIN t.mouseTicks _ MIN[t.mouseTicks, cc.tipTable.mouseTicks]; t.opaque _ FALSE; t.link _ cc.tipTable; END; viewerClass _ NEW [ViewerClasses.ViewerClassRec _ [ flavor: cc.flavor, notify: NotifyBiScroller, paint: PaintBiScroller, modify: cc.modify, destroy: cc.destroy, copy: cc.copy, set: cc.set, get: cc.get, save: cc.save, menu: cc.menu, tipTable: t, icon: cc.icon, cursor: cc.cursor]]; ViewerOps.RegisterViewerClass[flavor: viewerClass.flavor, class: viewerClass]; bsc _ NEW [BiScrollerClassRep _ [ style: buttonlessStyle, common: cc, rep: NEW [ButtonlessClassRep _ [viewerClass: viewerClass, cursor: cc.cursor]] ]]; END; CreateBiScroller: PROC [class: BiScrollerClass, info: ViewerClasses.ViewerRec _ [], paint: BOOLEAN _ TRUE] RETURNS [new: BiScroller] = BEGIN bsc: ButtonlessClass _ NARROW[class.rep]; paintFirst: BOOL = paint AND info.iconic; bl: Buttonless _ NEW [ButtonlessRep _ [class: bsc, clientData: info.data]]; info.data _ new _ NEW [BiScrollerRep _ [ style: class.style, class: class, rep: bl]]; bl.viewer _ ViewerOps.CreateViewer[flavor: bsc.viewerClass.flavor, info: info, paint: paintFirst]; ChangeTransform[new, class.common.vanilla[new], FALSE]; bl.cw _ bl.viewer.cw; bl.ch _ bl.viewer.ch; IF class.common.init # NIL THEN class.common.init[bl.viewer]; IF NOT paintFirst THEN ViewerOps.ComputeColumn[column: ViewerOps.ViewerColumn[bl.viewer], paint: paint]; END; Destroy: PROC [bs: BiScroller] RETURNS [BiScroller] = BEGIN bl: Buttonless _ NARROW[bs.rep]; ViewerOps.DestroyViewer[bl.viewer]; bl.viewer _ NIL; RETURN [NIL]; END; AddChild: PROC [to: BiScroller, what: Viewer, x, y: REAL _ 0, useTheseCoords: BOOLEAN _ FALSE, paint: BOOLEAN _ TRUE] = BEGIN bl: Buttonless _ NARROW[to.rep]; my: Vec; c: Child _ NEW [ChildObject _ [next: bl.children, it: what, where: IF useTheseCoords THEN [x, y] ELSE [what.wx, what.wy] ]]; bl.children _ c; my _ bl.t.MapVec[c.where]; ViewerOps.MoveViewer[viewer: what, x: RI[my.x], y: RI[my.y], w: what.ww, h: what.wh, paint: paint]; END; DeleteChild: PROC [of: BiScroller, who: Viewer] = BEGIN bl: Buttonless _ NARROW[of.rep]; 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]}; bl.children _ Filter[bl.children]; END; QuaViewer: PROC [bs: BiScroller, inner: BOOL _ FALSE] RETURNS [Viewer] = {bl: Buttonless _ NARROW[bs.rep]; RETURN [bl.viewer]}; ClientDataOf: PROC [bs: BiScroller] RETURNS [REF ANY] = {bl: Buttonless _ NARROW[bs.rep]; RETURN [bl.clientData]}; ViewportOf: PROC [bs: BiScroller] RETURNS [VecList] = BEGIN bl: Buttonless _ NARROW[bs.rep]; RETURN [Geom2D.MapVecs[bl.u, LIST[[0, 0], [bl.viewer.cw, 0], [bl.viewer.cw, bl.viewer.ch], [0, bl.viewer.ch]]]]; END; FinishClient: PROC [bs: BiScroller, bl: Buttonless] = { IF bs.class.common.notify # NIL THEN bs.class.common.notify[bl.viewer, bs.class.common.finish]; }; NotifyBiScroller: PROC [self: Viewer, input: LORA] --ViewerClasses.NotifyProc-- = BEGIN bs: BiScroller _ NARROW[self.data, BiScroller]; bl: Buttonless _ NARROW[bs.rep]; i, o, l: LIST OF REF ANY _ NIL; viewMouse: Vec; ToClient: PROC = { IF bl.state # Normal THEN GiveUp[bs, bl]; IF bs.class.common.notify # NIL THEN bs.class.common.notify[bl.viewer, input]}; 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 => { IF Awake[bl] THEN { v: Viewer; inClient: BOOL; [v, inClient] _ ViewerOps.MouseInViewer[z]; IF v # bl.viewer OR NOT inClient THEN { didWant: BOOL _ bl.clientWantsActive; bl.clientWantsActive _ FALSE; IF bl.state IN ActiveState THEN Finish[bs, bl, mice[bl.state]]; Sleep[bl]; IF didWant THEN FinishClient[bs, bl]; RETURN}; }; l.first _ NEW [Vec _ bl.u.MapVec[viewMouse _ [z.mouseX, z.mouseY]]]; }; ENDCASE; ENDLOOP; input _ o; IF input = NIL THEN ToClient[] ELSE SELECT input.first FROM $BSscaleRandom => Work[bs, bl, viewMouse, ScaleRandoming, input.rest]; $BSthumb => Work[bs, bl, viewMouse, Thumbing, input.rest]; $BSscroll => Work[bs, bl, viewMouse, Scrolling, input.rest]; $BSgiveUp => GiveUp[bs, bl]; $BSeatIt => NULL; ENDCASE => ToClient[]; END; first, next: Area; Work: PROC [bs: BiScroller, bl: Buttonless, viewMouse: Vec, why: ActiveState, input: LORA] = BEGIN viewCenter: Vec; who: Mouse _ mice[why]; oldState: State _ bl.state; clientWanted: BOOL _ bl.clientWantsActive; bl.clientWantsActive _ FALSE; bl.state _ why; viewCenter _ [bl.viewer.cw/2, bl.viewer.ch/2]; IF clientWanted THEN FinishClient[bs, bl]; WakeUp[bl]; IF why # oldState THEN BEGIN IF oldState IN ActiveState THEN Finish[bs, bl, mice[oldState]]; IF who.start # NIL THEN who.start[viewMouse, viewCenter, bs, bl]; END; IF who.cursor # NIL THEN Cursors.SetCursor[bl.viewer.class.cursor _ who.cursor[viewMouse, viewCenter]]; IF who.track # NIL THEN who.track[viewMouse, viewCenter, bs, bl]; IF input.rest.first = $Doit THEN BEGIN Sleep[bl]; oldT _ bl.t; bcUsed _ viewCenter; ChangeTransform[bs, nuT _ who.newTransform[bl.t, viewMouse, viewCenter, bs, bl]]; END ELSE IF input.rest.first # $Idle THEN ERROR; END; oldT, nuT: Transform; bcUsed: Vec; GiveUp: PROCEDURE [bs: BiScroller, bl: Buttonless] = BEGIN WasIdle: ENTRY PROC RETURNS [BOOLEAN] = {oldState _ bl.state; IF bl.state = Normal THEN RETURN [TRUE]; DoSleep[bl]; RETURN [FALSE]}; oldState: State; IF WasIdle[] THEN RETURN; IF oldState = Normal THEN ERROR; Finish[bs, bl, mice[oldState]]; END; Finish: PROC [bs: BiScroller, bl: Buttonless, mouse: Mouse] = BEGIN IF mouse.finish # NIL THEN mouse.finish[bs, bl]; END; Awake: ENTRY PROC [bs: Buttonless] RETURNS [b: BOOL] = {b _ awake = bs.viewer}; WakeUp: ENTRY PROC [bs: Buttonless] = {ENABLE UNWIND => {}; SetActive[bs]}; Sleep: ENTRY PROC [bs: Buttonless] = {ENABLE UNWIND => {}; DoSleep[bs]}; DoSleep: INTERNAL PROC [bs: Buttonless] = { bs.state _ Normal; Cursors.SetCursor[bs.viewer.class.cursor _ bs.class.cursor]; SetActive[bs]; }; SetActive: INTERNAL PROC [bs: Buttonless] = { wasAwake: Viewer _ awake; IF bs.clientWantsActive OR (bs.state # Normal) THEN { IF awake = NIL THEN { awake _ bs.viewer; InputFocus.CaptureButtons[proc: bs.viewer.class.notify, tip: bs.viewer.tipTable, viewer: bs.viewer]; }; } ELSE { IF awake = bs.viewer THEN { awake _ NIL; InputFocus.ReleaseButtons[]; }; }; IF NOT (wasAwake = bs.viewer OR wasAwake = NIL) THEN ERROR; }; SetButtonsCapturedness: PROC [bs: BiScroller, captured: BOOL] = { bl: Buttonless _ NARROW[bs.rep]; Doit: ENTRY PROC = { bl.clientWantsActive _ captured; SetActive[bl]}; Doit[]}; windowT: Transform; ThumbStart: StartProc = BEGIN cxmin, cxmax, cymin, cymax: Number; cx, cy, dx, dy: Number; [cxmin, cxmax] _ ViewLimitsOfImage[bs, X]; [cymin, cymax] _ ViewLimitsOfImage[bs, Y]; [windowT.a, windowT.e] _ Squeeze[cxmin, cxmax, 0, bl.viewer.cw]; [windowT.d, windowT.f] _ Squeeze[cymin, cymax, 0, bl.viewer.ch]; windowT.b _ windowT.c _ 0; cx _ (cxmin+cxmax)/2; cy _ (cymin+cymax)/2; dx _ bl.viewer.cw/2; dy _ bl.viewer.ch/2; first _ windowT.MapArea[[cx-dx, cy-dy, cx+dx, cy+dy]]; ViewerOps.PaintViewer[ viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: NewArea[first] ]; next _ first; END; ThumbTrack: TrackProc = BEGIN new: Area _ viewMouse.Minus[viewCenter].Displace[first]; ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: AreaDiff[new, next]]; next _ new; END; ThumbFinish: FinishProc = { ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: AreaDiff[next, first]]; }; ThumbTransform: NewTransformProc = BEGIN u: Transform _ windowT.Inverse[]; RETURN [old.TranslateV[Geom2D.Minus[[bl.viewer.cw/2, bl.viewer.ch/2], u.MapVec[viewMouse]]]]; END; Indicate: PROC [a, b: Vec, w, h: REAL] RETURNS [Area] = BEGIN adx: Number _ ABS[a.x - b.x]; ady: Number _ ABS[a.y - b.y]; IF a.Minus[b] = [0, 0] THEN RETURN [[a.x, a.y, a.x, a.y]]; IF adx < 2 THEN RETURN [SortArea[[0, a.y, w, b.y]]]; IF adx < ady/10 THEN RETURN [SortArea[[0, a.y, w, b.y]]]; IF ady < 2 THEN RETURN [SortArea[[a.x, 0, b.x, h]]]; IF ady < adx/10 THEN RETURN [SortArea[[a.x, 0, b.x, h]]]; RETURN [SortArea[[a.x, a.y, b.x, b.y]]]; END; SortArea: PROC [in: Area] RETURNS [out: Area] = INLINE {RETURN [[MIN[in.xmin, in.xmax], MIN[in.ymin, in.ymax], MAX[in.xmin, in.xmax], MAX[in.ymin, in.ymax]]]}; ScrollStart: StartProc = { first _ Indicate[viewMouse, viewCenter, bl.viewer.cw, bl.viewer.ch]; ViewerOps.PaintViewer[ viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: NewArea[first] ]; next _ first; }; ScrollTrack: TrackProc = { new: Area _ Indicate[viewMouse, viewCenter, bl.viewer.cw, bl.viewer.ch]; ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: AreaDiff[new, next]]; next _ new; }; ScrollFinish: FinishProc = { ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: NewArea[next]]; }; ScrollCursor: CursorProc = BEGIN d: Vec _ viewCenter.Minus[viewMouse]; adx: Number _ ABS[d.x]; ady: Number _ ABS[d.y]; RETURN [IF d = [0, 0] THEN Cursors.CursorType[bullseye] ELSE IF adx < 2 OR adx < ady/10 THEN (IF d.y < 0 THEN pointDown ELSE pointUp) ELSE IF ady < 2 OR ady < adx/10 THEN (IF d.x < 0 THEN pointLeft ELSE pointRight) ELSE IF d.x > 0 THEN (IF d.y > 0 THEN pointUpRight ELSE pointDownRight) ELSE (IF d.y > 0 THEN pointUpLeft ELSE pointDownLeft)]; END; ScrollTransform: NewTransformProc = BEGIN delta: Vec _ viewCenter.Minus[viewMouse]; adx: Number _ ABS[delta.x]; ady: Number _ ABS[delta.y]; IF adx < 2 OR adx < ady/10 THEN delta.x _ 0 ELSE IF ady < 2 OR ady < adx/10 THEN delta.y _ 0; RETURN [old.TranslateV[delta]]; END; ScaleRandomStart: StartProc = BEGIN d: Vec _ viewMouse.Minus[viewCenter]; adx: Number _ ABS[d.x]; ady: Number _ ABS[d.y]; slope _ IF adx = 0 THEN 1 ELSE ady/adx; first _ [viewCenter.x-adx, viewCenter.y-ady, viewCenter.x+adx, viewCenter.y+ady]; next _ first; END; slope: Number; ScaleRandomTrack: TrackProc = BEGIN d: Vec _ viewMouse.Minus[viewCenter]; adx: Number _ ABS[d.x]; ady: Number _ IF bs.class.common.mayStretch THEN ABS[d.y] ELSE adx*slope; new: Area _ [viewCenter.x-adx, viewCenter.y-ady, viewCenter.x+adx, viewCenter.y+ady]; ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: AreaDiff[new, next]]; next _ new; END; ScaleRandomFinish: FinishProc = { ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: AreaDiff[next, first]]; }; ScaleRandomTransform: NewTransformProc = BEGIN ndx, ndy, odx, ody, sx, sy: Number; ndx _ next.xmax - next.xmin; ndy _ next.ymax - next.ymin; odx _ first.xmax - first.xmin; ody _ first.ymax - first.ymin; sx _ IF ndx=0 OR odx=0 THEN 1 ELSE ndx/odx; sy _ IF ndy=0 OR ody=0 THEN 1 ELSE ndy/ody; IF NOT bs.class.common.mayStretch THEN sx _ sy _ (sx+sy)/2; RETURN [old.Translate[-viewCenter.x, -viewCenter.y].ScaleTXY[sx, sy].TranslateV[viewCenter]]; END; TextCursor: CursorProc = {RETURN [textPointer]}; PaintBiScroller: PROC [self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL] --ViewerClasses.PaintProc-- = BEGIN bs: BiScroller _ NARROW[self.data]; bl: Buttonless _ NARROW[bs.rep]; IF clear AND (self.cw # bl.cw OR self.ch # bl.ch) THEN { client: Vec --the point that stays fixed, in client coords--; client _ bl.u.MapVec[[ bs.class.common.preserve[X]*bl.cw, bs.class.common.preserve[Y]*bl.ch]]; Align[ bs: bs, client: [coord[client.x, client.y]], viewer: [fraction[bs.class.common.preserve[X], bs.class.common.preserve[Y]]], paint: FALSE]; bl.cw _ self.cw; bl.ch _ self.ch; }; IF whatChanged # NIL THEN WITH whatChanged SELECT FROM as: Areas => { Graphics.SetStipple[context, 122645B]; [] _ Graphics.SetPaintMode[context, invert]; FOR a: Areas _ as, a.next WHILE a # NIL DO Graphics.DrawBox[context, a.area]; ENDLOOP; RETURN; }; ENDCASE; Graphics.Translate[context, bl.t.e, bl.t.f]; Graphics.Concat[context, bl.t.a, bl.t.b, bl.t.c, bl.t.d]; bs.class.common.paint[self, context, whatChanged, clear]; END; RI: PROC [REAL] RETURNS [INTEGER] = Real.RoundI; GetTransforms: PROC [bs: BiScroller] RETURNS [t, u: Transform] = {bl: Buttonless _ NARROW[bs.rep]; t _ bl.t; u _ bl.u}; ChangeTransform: PROC [bs: BiScroller, new: Transform, paint: BOOLEAN _ TRUE] = BEGIN bl: Buttonless _ NARROW[bs.rep]; Doit: ENTRY PROC [t, u: Transform] = {bl.u _ u; bl.t _ t}; inv: Transform; IF bs.class.common.offsetsMustBeIntegers THEN {new.e _ RI[new.e]; new.f _ RI[new.f]}; IF bs.class.common.preferIntegerCoefficients THEN { dxdx: INTEGER _ RI[new.a]; dydx: INTEGER _ RI[new.b]; dxdy: INTEGER _ RI[new.c]; dydy: INTEGER _ RI[new.d]; IF ((dxdx=0) = (new.a=0)) AND ((dydx=0) = (new.b=0)) AND ((dxdy=0) = (new.c=0)) AND ((dydy=0) = (new.d=0)) THEN {new.a _ dxdx; new.b_ dydx; new.c _ dxdy; new.d _ dydy}; }; IF new = bl.t THEN RETURN; IF new.a*new.d - new.b*new.c = 0 THEN RETURN; inv _ new.Inverse[]; FOR c: Child _ bl.children, c.next UNTIL c = NIL DO nu: Vec; nu _ new.MapVec[c.where]; ViewerOps.EstablishViewerPosition[c.it, RI[nu.x], RI[nu.y], c.it.ww, c.it.wh]; ENDLOOP; Doit[new, inv]; IF paint THEN ViewerOps.PaintViewer[viewer: bl.viewer, hint: client]; END; Squeeze: PROC [fromMin, fromMax, toMin, toMax: Number] RETURNS [scale, offset: Number] = BEGIN dFrom: Number _ fromMax - fromMin; IF dFrom = 0 THEN RETURN [1, (toMin+toMax)/2]; scale _ (toMax - toMin)/dFrom; offset _ toMin - scale*fromMin; END; NewArea: PROC [a: Area, next: Areas _ NIL] RETURNS [Areas] = {RETURN [NEW [AreasRec _ [next: next, area: a]]]}; AreaDiff: PROC [a, b: Area] RETURNS [d: Areas] = BEGIN y: Number; IF b.ymin < a.ymin THEN {c: Area _ a; a _ b; b _ c}; IF (IF a.ymax <= b.ymin THEN TRUE ELSE IF a.xmin >= b.xmax THEN TRUE ELSE IF a.xmax <= b.xmin THEN TRUE ELSE FALSE) THEN RETURN [NewArea[a, NewArea[b]]]; IF a.ymax > b.ymax THEN d _ NewArea[[a.xmin, y _ b.ymax, a.xmax, a.ymax]] ELSE d _ NewArea[[b.xmin, y _ a.ymax, b.xmax, b.ymax]]; RETURN [NewArea[[b.xmin, b.ymin, a.xmin, y], NewArea[[b.xmax, b.ymin, a.xmax, y], NewArea[[a.xmin, a.ymin, a.xmax, b.ymin], d]]]]; END; Setup: PROCEDURE = BEGIN pointUpRight _ Cursors.NewCursor[hotX: -15, hotY: 0, bits: [ 000037B, 000177B, 000777B, 001477B, 002077B, 000176B, 000346B, 000704B, 001614B, 003410B, 007020B, 016000B, 034000B, 070000B, 160000B, 140000B]]; pointUpLeft _ Cursors.NewCursor[hotX: 0, hotY: 0, bits: [ 174000B, 177000B, 177600B, 176300B, 176040B, 077000B, 063400B, 021600B, 030700B, 010340B, 004160B, 000070B, 000034B, 000016B, 000007B, 000003B]]; pointDownRight _ Cursors.NewCursor[hotX: -15, hotY: -15, bits: [ 140000B, 160000B, 070000B, 034000B, 016000B, 007020B, 003410B, 001614B, 000704B, 000346B, 000176B, 002077B, 001477B, 000777B, 000177B, 000037B]]; pointDownLeft _ Cursors.NewCursor[hotX: 0, hotY: -15, bits: [ 000003B, 000007B, 000016B, 000034B, 000070B, 004160B, 010340B, 030700B, 021600B, 063400B, 077000B, 176040B, 176300B, 177600B, 177000B, 174000B]]; mice[Thumbing] _ NEW [MouseRec _ [start: ThumbStart, track: ThumbTrack, finish: ThumbFinish, cursor: TextCursor, newTransform: ThumbTransform]]; mice[Scrolling] _ NEW [MouseRec _ [start: ScrollStart, track: ScrollTrack, finish: ScrollFinish, cursor: ScrollCursor, newTransform: ScrollTransform]]; mice[ScaleRandoming] _ NEW [MouseRec _ [start: ScaleRandomStart, track: ScaleRandomTrack, finish: ScaleRandomFinish, cursor: TextCursor, newTransform: ScaleRandomTransform]]; RegisterStyle["Buttonless", buttonlessStyle]; END; Setup[]; END.