BiScrollersButtonless.Mesa
Copyright Ó 1992 by Xerox Corporation. All rights reserved.
Last tweaked by Mike Spreitzer on March 20, 1992 1:24 pm PST
Chauser, June 2, 1992 3:24 pm PDT
Implements BiScrollerStyle "Buttonless".
DIRECTORY BiScrollers, Cursors, Geom2D, Imager, ImagerBackdoor, ImagerBox, ImagerPath, ImagerTransformation, InputFocus, MultiCursors, PFS, PFSNames, Real, RealFns, TIPLinking, TIPUser, ViewerClasses, Vector2, ViewerOps;
BiScrollersButtonless: CEDAR MONITOR
IMPORTS BiScrollers, Cursors, Geom2D, Imager, ImagerBackdoor, ImagerBox, ImagerPath, ImagerTransformation, InputFocus, MultiCursors, PFS, PFSNames, Real, RealFns, TIPLinking, 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: 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,
data: REF ANY ¬ NIL];
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 ¬ Cursors.CursorType[questionMark];
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;
myWorkingDirectory: PFS.PATH ~ PFS.GetWDir[];
NewBiScrollerClass: PROC [cc: ClassCommon] RETURNS [bsc: BiScrollerClass] =
BEGIN
viewerClass: ViewerClass;
tipFile: PFS.PATH ¬ PFSNames.ExpandName[PFS.PathFromRope["BiScroller.tip"], myWorkingDirectory];
t: TIPUser.TIPTable ¬ TIPUser.InstantiateNewTIPTable[PFS.RopeFromPath[tipFile]];
IF cc.vanilla = NIL THEN cc.vanilla ¬ GenID;
IF cc.bsUserAction = NIL THEN cc.bsUserAction ¬ DoBSUserAction;
IF cc.tipTable # NIL THEN [] ¬ TIPLinking.Append[t, cc.tipTable];
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: 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], 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: 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.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: 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, device, user, display: REF ANY] --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.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
oldState: State ¬ Normal;
WasIdle: ENTRY PROC RETURNS [BOOLEAN] =
{oldState ¬ bl.state;
IF bl.state = Normal THEN RETURN [TRUE];
DoSleep[bl];
RETURN [FALSE]};
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: BOOL ¬ TRUE;
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 ¬ [0, 0];
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: BOOL ¬ FALSE] --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: BOOL ¬ FALSE] --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: BOOL ¬ TRUE] =
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 ¬ MultiCursors.NewCursor[hotX: -15, hotY: 0, bits: [
000037B, 000177B, 000777B, 001477B,
002077B, 000176B, 000346B, 000704B,
001614B, 003410B, 007020B, 016000B,
034000B, 070000B, 160000B, 140000B]];
pointUpLeft ¬ MultiCursors.NewCursor[hotX: 0, hotY: 0, bits: [
174000B, 177000B, 177600B, 176300B,
176040B, 077000B, 063400B, 021600B,
030700B, 010340B, 004160B, 000070B,
000034B, 000016B, 000007B, 000003B]];
pointDownRight ¬ MultiCursors.NewCursor[hotX: -15, hotY: -15, bits: [
140000B, 160000B, 070000B, 034000B,
016000B, 007020B, 003410B, 001614B,
000704B, 000346B, 000176B, 002077B,
001477B, 000777B, 000177B, 000037B]];
pointDownLeft ¬ MultiCursors.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.