FILE: BiScrollersButtonless.Mesa
Last Edited by: Spreitzer, March 28, 1985 3:41:47 pm PST
Implements BiScrollerStyle "Buttonless".
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: BOOLFALSE,
state: State ← Normal
];
ButtonlessClass: TYPE = REF ButtonlessClassRep;
ButtonlessClassRep: TYPE = RECORD [
viewerClass: ViewerClass,
notify: ViewerClasses.NotifyProc,
paint: ViewerClasses.PaintProc,
extrema: ExtremaProc,
init: ViewerClasses.InitProc,
finish: LORA,
cursor: Cursors.CursorType,
mayStretch, offsetsMustBeIntegers: BOOL];
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: Buttonless] RETURNS [Area, BOOLEAN];
FinishProc: TYPE = PROC [bs: Buttonless] RETURNS [Areas];
CursorProc: TYPE = PROC [mv, bc: Vec] RETURNS [Cursors.CursorType];
TrackProc: TYPE = PROC [mv, bc: Vec, bs: Buttonless] RETURNS [Area];
NewTransformProc: TYPE = PROC [old: Transform, mv, bc: Vec, bs: Buttonless] 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};
ActiveState: TYPE = State[MovingWindow .. ResettingAndCentering];
awake: Viewer ← NIL;
mice: ARRAY ActiveState OF Mouse ← ALL[NIL];
resetScaleCursor, centerCursor, resetAllCursor, pointUpRight, pointUpLeft, pointDownRight, pointDownLeft: Cursors.CursorType;
buttonlessStyle: BiScrollerStyle ← NEW [BiScrollerStyleRep ← [
NewBiScrollerClass: NewBiScrollerClass,
CreateBiScroller: CreateBiScroller,
Destroy: Destroy,
Fit: Fit,
GetTransforms: GetTransforms,
ChangeTransform: ChangeTransform,
AddChild: AddChild,
DeleteChild: DeleteChild,
SetButtonsCapturedness: SetButtonsCapturedness,
BoundaryOf: BoundaryOf,
QuaViewer: QuaViewer,
ClientDataOf: ClientDataOf
]];
NewBiScrollerClass: 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: LORANIL,
save: ViewerClasses.SaveProc ← NIL,
tipTable: TIPUser.TIPTable ← NIL,
icon: Icons.IconFlavor ← document,
cursor: Cursors.CursorType ← textPointer,
mayStretch: BOOLTRUE,
offsetsMustBeIntegers: BOOLFALSE]
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]];
ViewerOps.RegisterViewerClass[flavor: flavor, class: vc];
bsc ← NEW [BiScrollerClassRep ← [
style: buttonlessStyle,
rep: NEW [ButtonlessClassRep ← [
viewerClass: vc,
notify: notify,
paint: paint,
extrema: extrema,
init: init,
finish: finish,
cursor: cursor,
mayStretch: mayStretch,
offsetsMustBeIntegers: offsetsMustBeIntegers]]
]];
END;
CreateBiScroller: PROC [class: BiScrollerClass, info: ViewerClasses.ViewerRec ← [], paint: BOOLEANTRUE] RETURNS [new: BiScroller] =
BEGIN
bsc: ButtonlessClass ← NARROW[class.rep];
paintFirst: BOOL = paint AND info.iconic;
bs: Buttonless ← NEW [ButtonlessRep ← [class: bsc,
clientData: info.data,
t: Geom2D.id]];
info.data ← new ← NEW [BiScrollerRep ← [
style: class.style,
class: class,
rep: bs]];
bs.viewer ← ViewerOps.CreateViewer[flavor: bsc.viewerClass.flavor, info: info, paint: paintFirst];
ChangeTransform[new, Geom2D.id.Translate[bs.viewer.cw/2, bs.viewer.ch/2], FALSE];
IF bsc.init # NIL THEN bsc.init[bs.viewer];
IF NOT paintFirst THEN ViewerOps.ComputeColumn[column: ViewerOps.ViewerColumn[bs.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: BOOLEANFALSE, paint: BOOLEANTRUE] =
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: BOOLFALSE] 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]};
BoundaryOf: 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;
NotifyClient: PROC [bl: Buttonless, input: LORA] = {
IF bl.class.notify # NIL THEN bl.class.notify[bl.viewer, input];
};
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 ANYNIL;
mv: Vec;
ToClient: PROC = {
IF bl.state # Normal THEN GiveUp[bl];
IF bl.class.notify # NIL THEN bl.class.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[bl, mice[bl.state]];
Sleep[bl];
IF didWant THEN NotifyClient[bl, bl.class.finish];
RETURN};
};
l.first ← NEW [Vec ← bl.u.MapVec[mv ← [z.mouseX, z.mouseY]]];
};
ENDCASE;
ENDLOOP;
input ← o;
IF input = NIL THEN ToClient[]
ELSE SELECT input.first FROM
$BSscaleRandom => Work[bs, bl, mv, ScaleRandoming, input.rest];
$BSscaleDouble => Work[bs, bl, mv, ScaleDoubling, input.rest];
$BSscaleHalve => Work[bs, bl, mv, ScaleHalving, input.rest];
$BSresetScale => Work[bs, bl, mv, ResettingScale, input.rest];
$BSmoveWindow => Work[bs, bl, mv, MovingWindow, input.rest];
$BStoCenter => Work[bs, bl, mv, ToCentering, input.rest];
$BScenter => Work[bs, bl, mv, Centering, input.rest];
$BSresetCenter => Work[bs, bl, mv, ResettingAndCentering, input.rest];
$BSleft => Work[bs, bl, mv, Lefting, input.rest];
$BSright => Work[bs, bl, mv, Righting, input.rest];
$BStop => Work[bs, bl, mv, Topping, input.rest];
$BSbottom => Work[bs, bl, mv, Bottoming, input.rest];
$BSgiveUp => GiveUp[bl];
$BSeatIt => NULL;
ENDCASE => ToClient[];
END;
first, next: Area;
Work: PROC [bs: BiScroller, bl: Buttonless, mv: Vec, why: ActiveState, input: LORA] =
BEGIN
bc: Vec;
who: Mouse ← mice[why];
oldState: State ← bl.state;
clientWanted: BOOL ← bl.clientWantsActive;
bl.clientWantsActive ← FALSE;
bl.state ← why;
bc ← [bl.viewer.cw/2, bl.viewer.ch/2];
IF clientWanted THEN NotifyClient[bl, bl.class.finish];
WakeUp[bl];
IF why # oldState THEN
BEGIN
IF oldState IN ActiveState THEN Finish[bl, mice[oldState]];
IF who.start # NIL THEN
BEGIN
paint: BOOLEAN;
[first, paint] ← who.start[mv, bc, bl];
IF paint THEN ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: NewArea[first]];
next ← first;
END
END;
IF who.cursor # NIL THEN Cursors.SetCursor[bl.viewer.class.cursor ← who.cursor[mv, bc]];
IF who.track # NIL THEN
BEGIN
new: Area ← who.track[mv, bc, bl];
ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: AreaDiff[new, next]];
next ← new;
END;
IF input.rest.first = $Doit THEN
BEGIN
Sleep[bl];
oldT ← bl.t; bcUsed ← bc;
ChangeTransform[bs, nuT ← who.newTransform[bl.t, mv, bc, bl]];
END
ELSE IF input.rest.first # $Idle THEN ERROR;
END;
oldT, nuT: Transform;
bcUsed: Vec;
GiveUp: PROCEDURE [bs: Buttonless] =
BEGIN
WasIdle: ENTRY PROC RETURNS [BOOLEAN] =
{oldState ← bs.state;
IF bs.state = Normal THEN RETURN [TRUE];
DoSleep[bs];
RETURN [FALSE]};
oldState: State;
IF WasIdle[] THEN RETURN;
IF oldState = Normal THEN ERROR;
Finish[bs, mice[oldState]];
END;
Finish: PROC [bs: Buttonless, mouse: Mouse] =
BEGIN
IF mouse.finish # NIL THEN
BEGIN
changed: Areas ← mouse.finish[bs];
IF changed # NIL THEN ViewerOps.PaintViewer[viewer: bs.viewer, hint: client, clearClient: FALSE, whatChanged: changed];
END;
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;
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]};
PaintBiScroller: PROC [self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL] --ViewerClasses.PaintProc-- =
BEGIN
bs: Buttonless ← NARROW[NARROW[self.data, BiScroller].rep];
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: Buttonless, 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: PROC [bs: BiScroller, paint: BOOLEANTRUE, mayStretch: BOOLEANFALSE] =
BEGIN
bl: Buttonless ← NARROW[bs.rep];
new: Transform ← Geom2D.id;
cxmin, cxmax, cymin, cymax: Number;
[cxmin, cxmax] ← GetLimits[bl, X];
[cymin, cymax] ← GetLimits[bl, Y];
[new.a, new.e] ← Squeeze[cxmin, cxmax, 0, bl.viewer.cw];
[new.d, new.f] ← Squeeze[cymin, cymax, 0, bl.viewer.ch];
IF NOT mayStretch THEN
BEGIN
IF new.a < new.d THEN
BEGIN
new.d ← new.a;
new.f ← CenterRange[cymin, cymax, 0, bl.viewer.ch, new.d];
END
ELSE IF new.d < new.a THEN
BEGIN
new.a ← new.d;
new.e ← CenterRange[cxmin, cxmax, 0, bl.viewer.cw, new.a];
END;
END;
ChangeTransform[bs, Geom2D.Concat[bl.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;
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: BOOLEANTRUE] =
BEGIN
bl: Buttonless ← NARROW[bs.rep];
Doit: ENTRY PROC [t, u: Transform] = {bl.u ← u; bl.t ← t};
inv: Transform;
IF bl.class.offsetsMustBeIntegers THEN {new.e ← RI[new.e]; new.f ← RI[new.f]};
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
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]];
RegisterStyle["Buttonless", buttonlessStyle];
END;
Setup[];
END.