FILE: BiScrollersImpl.Mesa, from [Ivy]<Spreitzer>Viewing>BiScrollers.DF
Last Edited by: Spreitzer, July 21, 1984 12:28:35 pm PDT
one of two implementations of BiScrollers
DIRECTORY
Cursors, TIPUser, Icons, InputFocus, ViewerClasses, ViewerOps,
Graphics, Geom2D, BiScrollers, RealFns, Real;
BiScrollersImpl: CEDAR MONITOR
IMPORTS Graphics, Cursors, TIPUser, InputFocus, VO:ViewerOps,
Geom2D, Real
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: 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--
];
BiScrollerClass: PUBLIC TYPE = REF BiScrollerClassRec;
BiScrollerClassRec: PUBLIC TYPE = RECORD [
viewerClass: ViewerClass,
notify: ViewerClasses.NotifyProc,
paint: ViewerClasses.PaintProc,
extrema: ExtremaProc,
init: ViewerClasses.InitProc,
finish: LIST OF REF ANY,
cursor: Cursors.CursorType,
mayStretch: BOOLEAN];
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 [mv, bc: Vec, bs: BiScroller] RETURNS [Area, BOOLEAN];
FinishProc: TYPE = PROC [bs: BiScroller] RETURNS [Areas];
CursorProc: TYPE = PROC [mv, bc: Vec] RETURNS [Cursors.CursorType];
TrackProc: TYPE = PROC [mv, bc: Vec, bs: BiScroller] RETURNS [Area];
NewTransformProc: TYPE = PROC [old: Transform, mv, bc: Vec, bs: BiScroller] RETURNS [new: Transform];
Areas: TYPE = REF AreasRec;
AreasRec: TYPE = RECORD [next: Areas, area: Area];
State: TYPE = {Normal, MovingWindow, ToCentering, ScaleDoubling, ScaleHalving, ScaleRandoming, ResettingScale, Centering, Lefting, Righting, Topping, Bottoming, ResettingAndCentering, Client};
ActiveState: TYPE = State[MovingWindow..Client];
awake: BOOLEANFALSE;
state: State ← Normal;
mice: ARRAY ActiveState OF Mouse ← ALL[NIL];
resetScaleCursor, centerCursor, resetAllCursor, pointUpRight, pointUpLeft, pointDownRight, pointDownLeft: Cursors.CursorType;
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 ANYNIL,
save: ViewerClasses.SaveProc ← NIL,
tipTable: TIPUser.TIPTable ← NIL,
icon: Icons.IconFlavor ← document,
cursor: Cursors.CursorType ← textPointer,
mayStretch: BOOLEAN]
RETURNS [bsc: BiScrollerClass] =
BEGIN
vc: ViewerClass;
t: TIPUser.TIPTable ← TIPUser.InstantiateNewTIPTable["BiScroller.TIP"];
IF tipTable # NIL THEN
BEGIN
t.mouseTicks ← MIN[t.mouseTicks, tipTable.mouseTicks];
t.opaque ← FALSE;
t.link ← tipTable;
END;
vc ← NEW [ViewerClasses.ViewerClassRec ← [
flavor: flavor,
notify: NotifyBiScroller,
paint: PaintBiScroller,
modify: modify,
destroy: destroy,
copy: copy,
set: set,
get: get,
save: save,
tipTable: t,
icon: icon,
cursor: cursor]];
VO.RegisterViewerClass[flavor: flavor, class: vc];
bsc ← NEW [BiScrollerClassRec ← [
viewerClass: vc,
notify: notify,
paint: paint,
extrema: extrema,
init: init,
finish: finish,
cursor: cursor,
mayStretch: mayStretch]];
END;
CreateBiScroller: PUBLIC PROC [class: BiScrollerClass,
info: ViewerClasses.ViewerRec ← [],
paint: BOOLEANTRUE] RETURNS [new: BiScroller] =
BEGIN
paintFirst: BOOL = paint AND info.iconic;
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: paintFirst];
new.t ← Geom2D.id.Translate[new.viewer.cw/2, new.viewer.ch/2];
new.u ← new.t.Inverse[];
IF class.init # NIL THEN class.init[new.viewer];
IF NOT paintFirst THEN VO.ComputeColumn[column: VO.ViewerColumn[new.viewer], paint: paint];
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: BOOLEANFALSE,
paint: BOOLEANTRUE] =
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[[0, 0],
[bs.viewer.cw, 0],
[bs.viewer.cw, bs.viewer.ch],
[0, bs.viewer.ch]]]];
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;
NotifyClient: PROC [bs: BiScroller, input: LIST OF REF ANY, giveUp: BOOLEAN] =
BEGIN
i, o, l: LIST OF REF ANYNIL;
IF giveUp THEN GiveUp[bs];
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[bs.viewer, o]};
END;
NotifyBiScroller: ViewerClasses.NotifyProc =
BEGIN ENABLE UNWIND => {InputFocus.ReleaseButtons[]; awake ← FALSE};
bs: BiScroller ← NARROW[self.data];
IF input = NIL THEN NotifyClient[bs, input, TRUE]
ELSE SELECT input.first FROM
$BSscaleRandom => Work[bs, ScaleRandoming, input.rest];
$BSscaleDouble => Work[bs, ScaleDoubling, input.rest];
$BSscaleHalve => Work[bs, ScaleHalving, input.rest];
$BSresetScale => Work[bs, ResettingScale, input.rest];
$BSmoveWindow => Work[bs, MovingWindow, input.rest];
$BStoCenter => Work[bs, ToCentering, input.rest];
$BScenter => Work[bs, Centering, input.rest];
$BSresetCenter => Work[bs, ResettingAndCentering, input.rest];
$BSleft => Work[bs, Lefting, input.rest];
$BSright => Work[bs, Righting, input.rest];
$BStop => Work[bs, Topping, input.rest];
$BSbottom => Work[bs, Bottoming, input.rest];
$BSClient => Work[bs, Client, input.rest];
$BSClientEnd => {Work[bs, Client, input.rest]; Sleep[bs]};
$BSgiveUp => GiveUp[bs];
$BSeatIt => NULL;
ENDCASE => NotifyClient[bs, input, TRUE];
END;
GiveUp: PROCEDURE [bs: BiScroller] =
BEGIN
Hello: ENTRY PROC RETURNS [BOOLEAN] =
{oldState ← state;
IF NOT awake THEN RETURN [TRUE];
DoSleep[bs];
RETURN [FALSE]};
oldState: State;
IF Hello[] THEN RETURN;
IF oldState = Normal THEN RETURN;
Finish[bs, mice[oldState]];
END;
first, next: Area;
Finish: PROC [bs: BiScroller, mouse: Mouse] =
BEGIN
IF mouse.finish # NIL THEN
BEGIN
changed: Areas ← mouse.finish[bs];
IF changed # NIL THEN VO.PaintViewer[
viewer: bs.viewer, hint: client, clearClient: FALSE, whatChanged: changed];
END;
END;
Work: PROC [bs: BiScroller, why: ActiveState, input: LIST OF REF ANY] =
BEGIN
mv, bc: Vec;
at: TIPUser.TIPScreenCoords ← NARROW[input.first];
who: Mouse ← mice[why];
bc ← [bs.viewer.cw/2, bs.viewer.ch/2];
IF NOT WakeUp[bs] THEN
BEGIN
v: Viewer;
c: BOOLEAN;
[v, c] ← VO.MouseInViewer[at];
IF v # bs.viewer OR NOT c THEN {Sleep[bs]; Finish[bs, who]; RETURN};
END;
mv ← [at.mouseX, at.mouseY];
IF why # state THEN
BEGIN
IF state IN ActiveState THEN Finish[bs, mice[state]];
state ← why;
IF who.start # NIL THEN
BEGIN
paint: BOOLEAN;
[first, paint] ← who.start[mv, bc, bs];
IF paint THEN VO.PaintViewer[viewer: bs.viewer,
hint: client, clearClient: FALSE,
whatChanged: NewArea[first]];
next ← first;
END
END;
IF who.cursor # NIL THEN Cursors.SetCursor[bs.viewer.class.cursor ←
who.cursor[mv, bc]];
IF who.track # NIL THEN
BEGIN
new: Area ← who.track[mv, bc, bs];
VO.PaintViewer[viewer: bs.viewer, hint: client, clearClient: FALSE,
whatChanged: AreaDiff[new, next]];
next ← new;
END;
IF why = Client THEN NotifyClient[bs, input, FALSE]
ELSE IF input.rest.first = $Doit THEN
BEGIN
Sleep[bs];
oldT ← bs.t; bcUsed ← bc;
ChangeTransform[bs, nuT ← who.newTransform[bs.t, mv, bc, bs]];
END
ELSE IF input.rest.first # $Idle THEN ERROR;
END;
oldT, nuT: Transform;
bcUsed: Vec;
WakeUp: ENTRY PROC [bs: BiScroller] RETURNS [BOOLEAN] =
BEGIN
IF awake THEN RETURN [FALSE];
{InputFocus.CaptureButtons[proc: bs.viewer.class.notify,
tip: bs.viewer.tipTable, viewer: bs.viewer]};
RETURN [awake ← TRUE];
END;
Sleep: ENTRY PROC [bs: BiScroller] = {DoSleep[bs]};
DoSleep: PROC [bs: BiScroller] =
BEGIN
awake ← FALSE; state ← Normal;
InputFocus.ReleaseButtons[];
Cursors.SetCursor[bs.viewer.class.cursor ← bs.class.cursor];
END;
windowT: Transform;
MoveWindowStart: StartProc =
BEGIN
cxmin, cxmax, cymin, cymax: Number;
cx, cy, dx, dy: Number;
[cxmin, cxmax] ← GetLimits[bs, X];
[cymin, cymax] ← GetLimits[bs, Y];
[windowT.a, windowT.e] ← Squeeze[cxmin, cxmax, 0, bs.viewer.cw];
[windowT.d, windowT.f] ← Squeeze[cymin, cymax, 0, bs.viewer.ch];
windowT.b ← windowT.c ← 0;
cx ← (cxmin+cxmax)/2; cy ← (cymin+cymax)/2;
dx ← bs.viewer.cw/2; dy ← bs.viewer.ch/2;
RETURN [windowT.MapArea[[cx-dx, cy-dy, cx+dx, cy+dy]], TRUE];
END;
MoveWindowTrack: TrackProc =
BEGIN
RETURN [mv.Minus[bc].Displace[first]];
END;
MoveWindowFinish: FinishProc =
{RETURN [AreaDiff[next, first]]};
MoveWindowTransform: NewTransformProc =
BEGIN
u: Transform ← windowT.Inverse[];
RETURN [old.TranslateV[Geom2D.Minus[[bs.viewer.cw/2, bs.viewer.ch/2],
u.MapVec[mv]]]];
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]]]};
PtToCenterStart: StartProc =
{RETURN [Indicate[mv, bc, bs.viewer.cw, bs.viewer.ch], TRUE]};
PtToCenterTrack: TrackProc =
{RETURN [Indicate[mv, bc, bs.viewer.cw, bs.viewer.ch]]};
PtToCenterFinish: FinishProc =
{RETURN [NewArea[next]]};
PtToCenterCursor: CursorProc =
BEGIN
d: Vec ← bc.Minus[mv];
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;
PtToCenterTransform: NewTransformProc =
BEGIN
delta: Vec ← bc.Minus[mv];
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;
ScaleDoubleCursor: CursorProc = {RETURN [pointUp]};
ScaleDoubleTransform: NewTransformProc =
{RETURN [old.Translate[-bc.x, -bc.y].ScaleT[2].TranslateV[bc]]};
ScaleHalveCursor: CursorProc = {RETURN [pointDown]};
ScaleHalveTransform: NewTransformProc =
{RETURN [old.Translate[-bc.x, -bc.y].ScaleT[1.0/2.0].TranslateV[bc]]};
ScaleRandomStart: StartProc =
BEGIN
d: Vec ← mv.Minus[bc];
adx: Number ← ABS[d.x];
ady: Number ← ABS[d.y];
slope ← IF adx = 0 THEN 1 ELSE ady/adx;
RETURN [[bc.x-adx, bc.y-ady, bc.x+adx, bc.y+ady], FALSE];
END;
slope: Number;
ScaleRandomTrack: TrackProc =
BEGIN
d: Vec ← mv.Minus[bc];
adx: Number ← ABS[d.x];
ady: Number ← IF bs.class.mayStretch THEN ABS[d.y] ELSE adx*slope;
RETURN [[bc.x-adx, bc.y-ady, bc.x+adx, bc.y+ady]];
END;
ScaleRandomFinish: FinishProc =
{RETURN [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.mayStretch THEN sx ← sy ← (sx+sy)/2;
RETURN [old.Translate[-bc.x, -bc.y].ScaleTXY[sx, sy].TranslateV[bc]];
END;
ResetScaleCursor: CursorProc = {RETURN [resetScaleCursor]};
ResetScaleTransform: NewTransformProc =
{s, ne, nf: Number;
new ← old.Translate[-bc.x, -bc.y];
s ← new.a*new.d - new.b*new.c;
IF ABS[s] = 0 THEN RETURN [old];
ne ← (new.e*new.d-new.f*new.c)/s;
nf ← (new.f*new.a-new.e*new.b)/s;
new ← [1, 0, 0, 1, ne, nf];
new ← new.TranslateV[bc]};
CenterCursor: CursorProc = {RETURN [centerCursor]};
CenterTransform: NewTransformProc =
BEGIN
xmin, xmax, ymin, ymax: Number;
[xmin, xmax] ← GetLimits[bs, X];
[ymin, ymax] ← GetLimits[bs, Y];
RETURN [old.Translate[bc.x - (xmin+xmax)/2, bc.y - (ymin+ymax)/2]];
END;
LeftingCursor: CursorProc = {RETURN [scrollLeft]};
LeftTransform: NewTransformProc =
BEGIN
xmin: Number;
[xmin, ] ← GetLimits[bs, X];
RETURN [old.Translate[-xmin, 0]];
END;
BottomingCursor: CursorProc = {RETURN [scrollDown]};
BottomTransform: NewTransformProc =
BEGIN
ymin: Number;
[ymin, ] ← GetLimits[bs, Y];
RETURN [old.Translate[0, -ymin]];
END;
RightingCursor: CursorProc = {RETURN [scrollRight]};
RightTransform: NewTransformProc =
BEGIN
xmax: Number;
[, xmax] ← GetLimits[bs, X];
RETURN [old.Translate[bs.viewer.cw - xmax, 0]];
END;
ToppingCursor: CursorProc = {RETURN [scrollUp]};
TopTransform: NewTransformProc =
BEGIN
ymax: Number;
[, ymax] ← GetLimits[bs, Y];
RETURN [old.Translate[0, bs.viewer.ch - ymax]];
END;
ResetCenterCursor: CursorProc = {RETURN [resetAllCursor]};
ResetCenterTransform: NewTransformProc =
BEGIN
xmin, xmax, ymin, ymax: Vec;
[xmin, xmax] ← bs.class.extrema[bs.clientData, [1, 0]];
[ymin, ymax] ← bs.class.extrema[bs.clientData, [0, 1]];
RETURN [Geom2D.id.TranslateV[bc.Minus[
[(xmin.x+xmax.x)/2, (ymin.y+ymax.y)/2]]]];
END;
TextCursor: CursorProc = {RETURN [textPointer]};
ClientFinish: FinishProc = {NotifyClient[bs, bs.class.finish, FALSE]; RETURN [NIL]};
PaintBiScroller: ViewerClasses.PaintProc =
BEGIN
bs: BiScroller ← NARROW[self.data];
IF (IF whatChanged = NIL THEN FALSE ELSE ISTYPE[whatChanged, Areas])
THEN BEGIN
Graphics.SetStipple[context, 122645B];
[] ← Graphics.SetPaintMode[context, invert];
FOR a: Areas ← NARROW[whatChanged], a.next WHILE a # NIL DO
Graphics.DrawBox[context, a.area];
ENDLOOP;
RETURN;
END;
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;
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;
Fit: PUBLIC PROC [bs: BiScroller, paint: BOOLEANTRUE, mayStretch: BOOLEANFALSE] =
BEGIN
new: Transform ← Geom2D.id;
cxmin, cxmax, cymin, cymax: Number;
[cxmin, cxmax] ← GetLimits[bs, X];
[cymin, cymax] ← GetLimits[bs, Y];
[new.a, new.e] ← Squeeze[cxmin, cxmax, 0, bs.viewer.cw];
[new.d, new.f] ← Squeeze[cymin, cymax, 0, bs.viewer.ch];
IF NOT mayStretch THEN
BEGIN
IF new.a < new.d THEN
BEGIN
new.d ← new.a;
new.f ← CenterRange[cymin, cymax, 0, bs.viewer.ch, new.d];
END
ELSE IF new.d < new.a THEN
BEGIN
new.a ← new.d;
new.e ← CenterRange[cxmin, cxmax, 0, bs.viewer.cw, new.a];
END;
END;
ChangeTransform[bs, Geom2D.Concat[bs.t, new], paint];
END;
CenterRange: PROC [umin, umax, tmin, tmax, s: Number] RETURNS [o: Number] =
BEGIN
RETURN [(tmin+tmax - (umin+umax)*s)/2];
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: BOOLEANTRUE] =
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;
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
resetAllCursor ← Cursors.NewCursor[hotX: 8, hotY: 8, bits: [
146667B, 124442B, 146662B, 124242B,
126662B, 000000B, 000000B, 000000B,
004440B, 012440B, 016440B, 012440B,
012660B, 000000B, 000000B, 000000B]];
centerCursor ← Cursors.NewCursor[hotX: 8, hotY: 8, bits: [
071721B, 105031B, 101031B, 101625B,
101023B, 105023B, 071721B, 000000B,
000000B, 175736B, 021021B, 021021B,
021636B, 021022B, 021021B, 021721B]];
resetScaleCursor ← Cursors.NewCursor[hotX: 8, hotY: 8, bits: [
146667B, 124442B, 146662B, 124242B,
126662B, 000000B, 000000B, 000000B,
155646B, 111244B, 151646B, 051244B,
155266B, 000000B, 000000B, 000000B]];
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[MovingWindow] ← NEW [MouseRec ← [start: MoveWindowStart,
track: MoveWindowTrack,
finish: MoveWindowFinish,
cursor: TextCursor,
newTransform: MoveWindowTransform]];
mice[ToCentering] ← NEW [MouseRec ← [start: PtToCenterStart,
track: PtToCenterTrack,
finish: PtToCenterFinish,
cursor: PtToCenterCursor,
newTransform: PtToCenterTransform]];
mice[ScaleDoubling] ← NEW [MouseRec ← [cursor: ScaleDoubleCursor,
newTransform: ScaleDoubleTransform]];
mice[ScaleHalving] ← NEW [MouseRec ← [cursor: ScaleHalveCursor,
newTransform: ScaleHalveTransform]];
mice[ScaleRandoming] ← NEW [MouseRec ← [start: ScaleRandomStart,
track: ScaleRandomTrack,
finish: ScaleRandomFinish,
cursor: TextCursor,
newTransform: ScaleRandomTransform]];
mice[ResettingScale] ← NEW [MouseRec ← [cursor: ResetScaleCursor,
newTransform: ResetScaleTransform]];
mice[Centering] ← NEW [MouseRec ← [cursor: CenterCursor,
newTransform: CenterTransform]];
mice[Lefting] ← NEW [MouseRec ← [cursor: LeftingCursor,
newTransform: LeftTransform]];
mice[Righting] ← NEW [MouseRec ← [cursor: RightingCursor,
newTransform: RightTransform]];
mice[Topping] ← NEW [MouseRec ← [cursor:ToppingCursor,
newTransform: TopTransform]];
mice[Bottoming] ← NEW [MouseRec ← [cursor: BottomingCursor,
newTransform: BottomTransform]];
mice[ResettingAndCentering] ← NEW [MouseRec ← [cursor: ResetCenterCursor,
newTransform: ResetCenterTransform]];
mice[Client] ← NEW [MouseRec ← [finish: ClientFinish]];
END;
Setup[];
END.