BiScrollersButtonless.Mesa
Mike Spreitzer January 23, 1987 2:54:36 pm PST
Last tweaked by Mike Spreitzer on November 4, 1987 3:14:10 pm PST
Implements BiScrollerStyle "Buttonless".
DIRECTORY BiScrollers, Cursors, Geom2D, Imager, ImagerBackdoor, ImagerBox, ImagerPath, ImagerTransformation, InputFocus, Real, RealFns, TIPUser, ViewerClasses, Vector2, ViewerOps;
BiScrollersButtonless: CEDAR MONITOR
IMPORTS BiScrollers, Cursors, Geom2D, Imager, ImagerBackdoor, ImagerBox, ImagerPath, ImagerTransformation, InputFocus, Real, RealFns, TIPUser, Vector2, ViewerOps
= BEGIN OPEN BiScrollers;
Number: TYPE = Geom2D.Number;
ViewerClass: TYPE = ViewerClasses.ViewerClass;
Axis: TYPE = Geom2D.Axis;
Vec: TYPE = Geom2D.Vec;
Rect: TYPE = Geom2D.Rect;
RectRef: TYPE = REF Rect;
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
tp, up: Transform ← Geom2D.id, --previous transforms
cw, ch: INTEGER ← 0,  --last time we looked in the viewer--
clientWantsActive, initialized: BOOLFALSE,
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,
data: REF ANYNIL];
StartProc: TYPE = PROC [viewMouse, viewCenter: Vec, bs: BiScroller, bl: Buttonless, data: REF ANY];
FinishProc: TYPE = PROC [bs: BiScroller, bl: Buttonless, data: REF ANY];
CursorProc: TYPE = PROC [viewMouse, viewCenter: Vec, data: REF ANY] RETURNS [Cursors.CursorType];
TrackProc: TYPE = PROC [viewMouse, viewCenter: Vec, bs: BiScroller, bl: Buttonless, data: REF ANY];
NewTransformProc: TYPE = PROC [viewMouse, viewCenter: Vec, bs: BiScroller, bl: Buttonless, diff: BOOL, data: REF ANY];
Rects: TYPE = REF RectsRec;
RectsRec: TYPE = RECORD [next: Rects, rect: Rect];
Paths: TYPE = REF PathsRep;
PathsRep: TYPE = RECORD [next: Paths, path: ImagerPath.Trajectory, op: {fill, stroke}];
State: TYPE = {Normal, Thumbing, ScrollToCentering, ScrollAlongClicksing, Expanding, Contracting};
ActiveState: TYPE = State[Thumbing .. Contracting];
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
]];
first, next: Rect;
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.bsUserAction = NIL THEN cc.bsUserAction ← DoBSUserAction;
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,
caption: cc.caption,
adjust: AdjustBiScroller,
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: BOOLEANTRUE] 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], ignore, FALSE];
bl.cw ← bl.viewer.cw;
bl.ch ← bl.viewer.ch;
SetBS[bl.viewer, new];
IF class.common.init # NIL THEN class.common.init[bl.viewer];
bl.initialized ← TRUE;
IF paintFirst THEN NULL
ELSE IF bl.viewer.parent=NIL THEN ViewerOps.ComputeColumn[column: ViewerOps.ViewerColumn[bl.viewer], paint: paint]
ELSE IF paint THEN ViewerOps.PaintViewer[bl.viewer, all];
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.Transform[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]};
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 ANYNIL;
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.Transform[viewMouse ← [z.mouseX, z.mouseY]]];
};
ENDCASE;
ENDLOOP;
input ← o;
IF input = NIL THEN ToClient[]
ELSE SELECT input.first FROM
$BSexpand => Work[bs, bl, viewMouse, Expanding, FALSE, input.rest];
$BSexpandXY => Work[bs, bl, viewMouse, Expanding, TRUE, input.rest];
$BScontract => Work[bs, bl, viewMouse, Contracting, FALSE, input.rest];
$BScontractXY => Work[bs, bl, viewMouse, Contracting, TRUE, input.rest];
$BSthumb => Work[bs, bl, viewMouse, Thumbing, FALSE, input.rest];
$BSscrollToCenter => Work[bs, bl, viewMouse, ScrollToCentering, FALSE, input.rest];
$BSscrollAlongClicks => Work[bs, bl, viewMouse, ScrollAlongClicksing, FALSE, input.rest];
$BSgiveUp => GiveUp[bs, bl];
$BSeatIt => NULL;
ENDCASE => ToClient[];
END;
Work: PROC [bs: BiScroller, bl: Buttonless, viewMouse: Vec, why: ActiveState, diff: BOOL, 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, who.data];
END;
IF who.cursor # NIL THEN Cursors.SetCursor[bl.viewer.class.cursor ← who.cursor[viewMouse, viewCenter, who.data]];
IF who.track # NIL THEN who.track[viewMouse, viewCenter, bs, bl, who.data];
IF input.rest.first = $Doit THEN
BEGIN
Sleep[bl];
oldT ← bl.t; bcUsed ← viewCenter;
who.newTransform[viewMouse, viewCenter, bs, bl, diff, who.data];
END
ELSE IF input.rest.first # $Idle THEN ERROR;
END;
oldT: 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, mouse.data];
END;
Awake: ENTRY PROC [bl: Buttonless] RETURNS [b: BOOL] = {b ← awake = bl.viewer};
WakeUp: ENTRY PROC [bl: Buttonless] = {ENABLE UNWIND => {}; SetActive[bl]};
Sleep: ENTRY PROC [bl: Buttonless] = {ENABLE UNWIND => {}; DoSleep[bl]};
DoSleep: INTERNAL PROC [bl: Buttonless] = {
bl.state ← Normal;
Cursors.SetCursor[bl.viewer.class.cursor ← bl.class.cursor];
SetActive[bl];
};
SetActive: INTERNAL PROC [bl: Buttonless] = {
wasAwake: Viewer ← awake;
IF bl.clientWantsActive OR (bl.state # Normal) THEN {
IF awake = NIL THEN {
awake ← bl.viewer;
InputFocus.CaptureButtons[proc: bl.viewer.class.notify, tip: bl.viewer.tipTable, viewer: bl.viewer];
};
}
ELSE {
IF awake = bl.viewer THEN {
awake ← NIL;
InputFocus.ReleaseButtons[];
};
};
IF NOT (wasAwake = bl.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[]};
outline: Rect;
ThumbStart: StartProc =
BEGIN
cxmin, cxmax, cymin, cymax: Number;
cx, cy, dx, dy: Number;
windowT: Geom2D.Trans;
[cxmin, cxmax] ← ViewLimitsOfImage[bs, X];
[cymin, cymax] ← ViewLimitsOfImage[bs, Y];
[windowT.dxdx, windowT.dx] ← Squeeze[cxmin, cxmax, 0, bl.viewer.cw];
[windowT.dydy, windowT.dy] ← Squeeze[cymin, cymax, 0, bl.viewer.ch];
windowT.dydx ← windowT.dxdy ← 0;
cx ← (cxmin+cxmax)/2; cy ← (cymin+cymax)/2;
dx ← bl.viewer.cw/2; dy ← bl.viewer.ch/2;
outline ← windowT.FromTrans[].TransformRectangle[[cx-dx, cy-dy, 2*dx, 2*dy]];
first ← Geom2D.Displace[viewMouse.Sub[viewCenter], outline];
ViewerOps.PaintViewer[
viewer: bl.viewer,
hint: client,
clearClient: FALSE,
whatChanged: NewRect[first]
];
next ← first;
END;
ThumbTrack: TrackProc =
BEGIN
new: Rect ← Geom2D.Displace[viewMouse.Sub[viewCenter], outline];
ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: RectDiff[new, next]];
next ← new;
END;
ThumbFinish: FinishProc = {
ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: NewRect[next]];
};
ThumbTransform: NewTransformProc ~ {
bs.class.common.bsUserAction[bs, LIST[$AlignFracs, NEW [Vec ← [viewMouse.x/bl.viewer.cw, viewMouse.y/bl.viewer.ch]], NEW [Vec ← [0.5, 0.5]], $TRUE, $TRUE]]};
arWidth: REAL ← 0.1;
arHalfWidth: REAL ← arWidth/2;
arHeadLen: REAL ← 0.4;
rootTwo: REAL ← RealFns.SqRt[2.0];
rootHalf: REAL ← RealFns.SqRt[0.5];
MakeArrow: PROC [from, to: Vec, also: Paths] RETURNS [alles: Paths] = {
first: BOOLTRUE;
p: ImagerPath.Trajectory ← NIL;
diff: Vec;
t: Transform;
Do: PROC [x, y: REAL] = {
v: Vec ← t.Transform[[x, y]];
p ← IF first THEN ImagerPath.MoveTo[v] ELSE ImagerPath.LineTo[p, v];
first ← FALSE;
};
[from, to, ] ← AdjustScroll[from, to];
diff ← from.Sub[to];
t ← Geom2D.id
.PostRotate[RealFns.ArcTanDeg[diff.y, diff.x] - 90]
.PostScale[diff.Length[]]
.PostTranslate[to];
Do[arHalfWidth, 1.0];
Do[arHalfWidth, arWidth*rootTwo+arHalfWidth];
Do[(arHeadLen - arWidth)*rootHalf, (arHeadLen + arWidth)*rootHalf];
Do[arHeadLen*rootHalf, arHeadLen*rootHalf];
Do[0, 0];
Do[-arHeadLen*rootHalf, arHeadLen*rootHalf];
Do[(arWidth-arHeadLen)*rootHalf, (arWidth+arHeadLen)*rootHalf];
Do[-arHalfWidth, arWidth*rootTwo+arHalfWidth];
Do[-arHalfWidth, 1.0];
alles ← NEW [PathsRep ← [next: also, path: p, op: fill]];
};
AdjustScroll: PROC [from, to: Vec] RETURNS [nFrom, nTo: Vec, changed: BOOL] = {
delta: Vec ← to.Sub[from];
adx: Number ← ABS[delta.x];
ady: Number ← ABS[delta.y];
IF changed ← (adx < 2 OR adx < ady/10) THEN delta.x ← 0 ELSE
IF changed ← (ady < 2 OR ady < adx/10) THEN delta.y ← 0;
nTo ← to;
nFrom ← nTo.Sub[delta];
};
scrollDown: Vec;
oldArrow: Paths;
ScrollFrom: PROC [viewMouse, viewCenter: Vec, data: REF ANY] RETURNS [from: Vec] =
{from ← SELECT data FROM
$toCenter => viewMouse,
$alongClicks => scrollDown,
ENDCASE => ERROR};
ScrollTo: PROC [viewMouse, viewCenter: Vec, data: REF ANY] RETURNS [to: Vec] =
{to ← SELECT data FROM
$toCenter => viewCenter,
$alongClicks => viewMouse,
ENDCASE => ERROR};
ScrollStart: StartProc = {
scrollDown ← viewMouse;
oldArrow ← NIL;
};
ScrollTrack: TrackProc = {
ViewerOps.PaintViewer[
viewer: bl.viewer,
hint: client,
clearClient: FALSE,
whatChanged: oldArrow ← MakeArrow[
ScrollFrom[viewMouse, viewCenter, data],
ScrollTo[viewMouse, viewCenter, data],
oldArrow]
];
oldArrow.next ← NIL;
};
ScrollFinish: FinishProc = {
IF oldArrow # NIL THEN ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: oldArrow];
};
ScrollCursor: CursorProc =
BEGIN
d: Vec ← Vector2.Sub[
ScrollTo[viewMouse, viewCenter, data],
ScrollFrom[viewMouse, viewCenter, data]];
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 ~ {
from, to: Vec;
[from, to, ] ← AdjustScroll[
ScrollFrom[viewMouse, viewCenter, data],
ScrollTo[viewMouse, viewCenter, data]];
bs.class.common.bsUserAction[bs, LIST[$Shift, NEW [Vec ← to.Sub[from]]]];
RETURN};
slope: Number;
scaleCenter: Vec;
ScaleRandomStart: StartProc =
BEGIN
viewer: Viewer ← bs.style.QuaViewer[bs, TRUE];
slope ← IF viewer.cw = 0 THEN 1 ELSE REAL[viewer.ch]/viewer.cw;
scaleCenter ← viewMouse;
next ← first ← [viewMouse.x, viewMouse.y, 0, 0];
END;
ScaleRandomTrack: TrackProc =
BEGIN
d: Vec ← viewMouse.Sub[scaleCenter];
adx: Number ← ABS[d.x];
ady: Number ← IF bs.class.common.mayStretch THEN ABS[d.y] ELSE adx*slope;
new: Rect ← [scaleCenter.x-adx, scaleCenter.y-ady, 2*adx, 2*ady];
ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: RectDiff[new, next]];
next ← new;
END;
ScaleRandomFinish: FinishProc = {
ViewerOps.PaintViewer[viewer: bl.viewer, hint: client, clearClient: FALSE, whatChanged: NewRect[next]];
};
ScaleRandomTransform: NewTransformProc ~ {
viewer: Viewer ← bs.style.QuaViewer[bs, TRUE];
fromA, toA: Rect;
SELECT data FROM
$Expand => {fromA ← next; toA ← [0, 0, viewer.cw, viewer.ch]};
$Contract => {toA ← next; fromA ← [0, 0, viewer.cw, viewer.ch]};
ENDCASE => ERROR;
bs.class.common.bsUserAction[bs, LIST[$BoxScale, NEW[Rect ← fromA], NEW[Rect ← toA], IF diff THEN $FALSE ELSE $TRUE]];
};
TextCursor: CursorProc = {RETURN [textPointer]};
AdjustBiScroller: PROC [self: Viewer] RETURNS [adjusted: BOOLFALSE] --ViewerClasses.AdjustProc-- ~ {
bs: BiScroller ~ NARROW[self.data];
bl: Buttonless ~ NARROW[bs.rep];
IF adjusted ← bl.initialized AND (self.cw # bl.cw OR self.ch # bl.ch) THEN {
client: Vec --the point that stays fixed, in client coords--;
client ← bl.u.Transform[[
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 bs.class.common.adjust#NIL AND bs.class.common.adjust[self] THEN adjusted ← TRUE;
RETURN};
PaintBiScroller: PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOLFALSE] --ViewerClasses.PaintProc-- =
BEGIN
bs: BiScroller ← NARROW[self.data];
bl: Buttonless ← NARROW[bs.rep];
IF whatChanged # NIL THEN WITH whatChanged SELECT FROM
as: Rects => {
Imager.SetColor[context, invertingGray];
FOR a: Rects ← as, a.next WHILE a # NIL DO
Imager.MaskRectangle[context, a.rect];
ENDLOOP;
RETURN [FALSE];
};
ps: Paths => {
Imager.SetColor[context, invertingGray];
FOR p: Paths ← ps, p.next WHILE p # NIL DO
SELECT p.op FROM
fill => Imager.MaskFillTrajectory[context, p.path];
stroke => Imager.MaskStrokeTrajectory[context, p.path];
ENDCASE => ERROR;
ENDLOOP;
RETURN [FALSE];
};
ENDCASE;
Imager.ConcatT[context, bl.t];
quit ← bs.class.common.paint[self, context, whatChanged, clear];
END;
invertingGray: Imager.Color ← ImagerBackdoor.MakeStipple[0A5A5H, TRUE];
RI: PROC [REAL] RETURNS [INT] = Real.Round;
GetTransforms: PROC [bs: BiScroller, age: TransformsAge ← current] RETURNS [clientToViewer, viewerToClient: Transform] = {
bl: Buttonless ← NARROW[bs.rep];
SELECT age FROM
current => RETURN [clientToViewer: bl.t, viewerToClient: bl.u];
previous => RETURN [clientToViewer: bl.tp, viewerToClient: bl.up];
ENDCASE => ERROR;
};
ChangeTransform: PROC [bs: BiScroller, new: Transform, ageOp: AgeOp, paint: BOOLTRUE] =
BEGIN
bl: Buttonless ← NARROW[bs.rep];
inv: Transform;
Doit: ENTRY PROC = {
ENABLE UNWIND => NULL;
SELECT ageOp FROM
remember => {bl.up ← bl.u; bl.tp ← bl.t};
ignore => NULL;
ENDCASE => ERROR;
bl.u ← inv; bl.t ← new;
};
IF bs.class.common.offsetsMustBeIntegers THEN new ← new.TranslateTo[[RI[new.c], RI[new.f]]];
IF new.a*new.e - new.b*new.d = 0 THEN RETURN;
inv ← new.Invert[];
FOR c: Child ← bl.children, c.next UNTIL c = NIL DO
nu: Vec;
nu ← new.Transform[c.where];
IF paint
THEN ViewerOps.EstablishViewerPosition[c.it, RI[nu.x], RI[nu.y], c.it.ww, c.it.wh]
ELSE SetViewerPosition[c.it, RI[nu.x], RI[nu.y], c.it.ww, c.it.wh];
ENDLOOP;
Doit[];
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;
NewRect: PROC [a: Rect, next: Rects ← NIL] RETURNS [Rects] =
{RETURN [NEW [RectsRec ← [next: next, rect: a]]]};
NewBox: PROC [b: ImagerBox.Box, next: Rects ← NIL] RETURNS [Rects] =
{RETURN [NEW [RectsRec ← [next: next, rect: ImagerBox.RectangleFromBox[b]]]]};
RectDiff: PROC [a, b: Rect] RETURNS [d: Rects] =
BEGIN
y: Number;
A, B: ImagerBox.Box;
IF b.y < a.y THEN {c: Rect ← a; a ← b; b ← c};
A ← ImagerBox.BoxFromRectangle[a];
B ← ImagerBox.BoxFromRectangle[b];
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 [NewRect[a, NewRect[b]]];
IF A.ymax > B.ymax THEN d ← NewBox[[A.xmin, y ← B.ymax, A.xmax, A.ymax]]
ELSE d ← NewBox[[B.xmin, y ← A.ymax, B.xmax, B.ymax]];
RETURN [NewBox[[B.xmin, B.ymin, A.xmin, y],
NewBox[[B.xmax, B.ymin, A.xmax, y],
NewBox[[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[ScrollToCentering] ← NEW [MouseRec ← [
start: ScrollStart,
track: ScrollTrack,
finish: ScrollFinish,
cursor: ScrollCursor,
newTransform: ScrollTransform,
data: $toCenter]];
mice[ScrollAlongClicksing] ← NEW [MouseRec ← [
start: ScrollStart,
track: ScrollTrack,
finish: ScrollFinish,
cursor: ScrollCursor,
newTransform: ScrollTransform,
data: $alongClicks]];
mice[Expanding] ← NEW [MouseRec ← [
start: ScaleRandomStart,
track: ScaleRandomTrack,
finish: ScaleRandomFinish,
cursor: TextCursor,
newTransform: ScaleRandomTransform,
data: $Expand]];
mice[Contracting] ← NEW [MouseRec ← [
start: ScaleRandomStart,
track: ScaleRandomTrack,
finish: ScaleRandomFinish,
cursor: TextCursor,
newTransform: ScaleRandomTransform,
data: $Contract]];
RegisterStyle["Buttonless", buttonlessStyle];
END;
Setup[];
END.