GGMouseEventImpl.mesa
Last edited by Bier on March 18, 1986 4:27:27 pm PST
Contents: Once a mouse event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module.
Stone, August 5, 1985 4:14:05 pm PDT
Pier, April 24, 1986 4:09:47 pm PST
DIRECTORY
GGAlign, GGBasicTypes, GGBoundBox, GGBoxCluster, GGCaret, GGDeselectEvent, GGEvent, GGError, GGGravity, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGObjects, GGRefresh, GGSegment, GGSelect, GGSequence, GGSlackProcess, GGTouch, GGTransform, GGVector, GGWindow, Imager, ImagerTransformation, InputFocus, IO, Menus, Rope, Rosary;
GGMouseEventImpl: CEDAR PROGRAM
IMPORTS GGAlign, GGBoundBox, GGBoxCluster, GGCaret, GGDeselectEvent, GGError, GGEvent, GGGravity, GGObjects, GGRefresh, GGSegment, GGSelect, GGSequence, GGSlackProcess, GGTouch, GGTransform, GGVector, GGWindow, Imager, ImagerTransformation, InputFocus, IO, Rosary
EXPORTS GGMouseEvent = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
Caret: TYPE = GGInterfaceTypes.Caret;
Cluster: TYPE = GGModelTypes.Cluster;
ClusterParts: TYPE = GGModelTypes.ClusterParts;
ClusterGenerator: TYPE = GGModelTypes.ClusterGenerator;
ClusterDescriptor: TYPE = GGModelTypes.ClusterDescriptor;
EntityGenerator: TYPE = GGModelTypes.EntityGenerator;
FeatureData: TYPE = GGGravity.FeatureData;
Joint: TYPE = GGModelTypes.Joint;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
MouseButton: TYPE = Menus.MouseButton;
ObjectBag: TYPE = GGGravity.ObjectBag;
Outline: TYPE = GGModelTypes.Outline;
Point: TYPE = GGBasicTypes.Point;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGModelTypes.Segment;
Sequence: TYPE = GGModelTypes.Sequence;
TouchGroup: TYPE = GGModelTypes.TouchGroup;
TouchItem: TYPE = GGModelTypes.TouchItem;
TouchItemGenerator: TYPE = GGTouch.TouchItemGenerator;
Traj: TYPE = GGModelTypes.Traj;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajGenerator: TYPE = GGObjects.TrajGenerator;
TriggerBag: TYPE = GGAlign.TriggerBag;
Vector: TYPE = GGBasicTypes.Vector;
MouseProc: TYPE = GGMouseEvent.MouseProc;
MouseProc: TYPE = PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point];
StartProc: TYPE = GGMouseEvent.StartProc;
StartProc: TYPE = PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] RETURNS [success: BOOL ← TRUE];
UnexpectedType: PUBLIC ERROR = CODE;
NotYetImplemented: PUBLIC SIGNAL = CODE;
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE;
EasyAbort: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
Many actions don't change the scene at all until the End proc is called. For these cases, we just restore the selections and caret position.
normalSelectedList: LIST OF REF ANY ← gargoyleData.drag.savedSelected.normal;
GGSelect.DeselectAll[gargoyleData, normal]; -- get rid of any transient selections
FOR list: LIST OF REF ANY ← normalSelectedList, list.rest UNTIL list = NIL DO
GGSelect.SelectEntity[list.first, gargoyleData, normal]; -- restore original selections
ENDLOOP;
FOR list: LIST OF REF ANY ← normalSelectedList, list.rest UNTIL list = NIL DO
WITH list.first SELECT FROM
cD: ClusterDescriptor => GGSelect.SelectCluster[cD.cluster, cD.parts, gargoyleData, normal];
ENDCASE => GGSelect.SelectEntity[list.first, gargoyleData, normal];
ENDLOOP;
GGCaret.Copy[from: gargoyleData.drag.savedCaret, to: gargoyleData.caret]; --restore original caret
RestoreInvariants[gargoyleData];
GGError.AppendHerald[". . . Aborted.", FALSE];
GGWindow.Painter[$PaintEntireScene, gargoyleData]; -- only way to accurately restore picture
};
AbortAdd: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
FixupAbortedAdd[gargoyleData];
RestoreInvariants[gargoyleData];
GGError.AppendHerald[". . . Aborted.", TRUE];
};
AbortBox: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
FixupAbortedBox[gargoyleData];
RestoreInvariants[gargoyleData];
GGError.AppendHerald[". . . Aborted.", TRUE];
};
NoOpAbort: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
No abort action is provided, yet.
rope: Rope.ROPE;
rope ← Atom.GetPName[gargoyleData.mouseMode];
GGError.PutF[oneLiner, "%g cannot be aborted yet.", [rope[rope]]];
GGError.Blink[];
};
NoOpDuring: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
};
NoOpEnd: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
};
RestoreInvariants: PROC [gargoyleData: GargoyleData] = {
gargoyleData.hitTest.responsibleFor ← NIL; -- NO LONGER USED
GGRefresh.MoveOverlayToBackground[gargoyleData];
};
ResetMouseMachinery: PUBLIC PROC [gargoyleData: GargoyleData] = {
gargoyleData.mouseMode ← $None;
gargoyleData.state ← $None;
gargoyleData.drag.state ← $None;
RestoreInvariants[gargoyleData];
};
SaveSavedState: PROC [gargoyleData: GargoyleData] = {
gargoyleData.drag.savedSelected ← gargoyleData.selected;
GGCaret.Copy[from: gargoyleData.caret, to: gargoyleData.drag.savedCaret];
};
The FSM
HandleMouse: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
atom: ATOM;
SELECT gargoyleData.mouseMode FROM
$CaretPos => HandleGuarded[StartCaretPos, DuringCaretPos, EndCaretPos, EasyAbort, input, gargoyleData, worldPt];
$Add => HandleGuarded[StartAdd, DuringAdd, EndAdd, AbortAdd, input, gargoyleData, worldPt];
$Box => HandleGuarded[StartBox, DuringBox, EndBox, AbortBox, input, gargoyleData, worldPt];
$Drag => HandleGuarded[StartDrag, DuringDrag, EndDrag, EasyAbort, input, gargoyleData, worldPt];
$CopyAndDrag => HandleGuarded[CopySelected, DuringDrag, EndDrag, EasyAbort, input, gargoyleData, worldPt];
$Rotate => HandleGuarded[StartRotate, DuringRotate, EndRotate, EasyAbort, input, gargoyleData, worldPt];
$Scale => HandleGuarded[StartScale, DuringScale, EndScale, EasyAbort, input, gargoyleData, worldPt];
$SelectJoint => HandleUnGuarded[StartSelectJoint, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt];
$SelectSegment => HandleUnGuarded[StartSelectSegment, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt];
$SelectTrajectory => HandleUnGuarded[StartSelectTrajectory, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt];
$SelectTopLevel => HandleUnGuarded[StartSelectTopLevel, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt];
$Extend => HandleUnGuarded[GGDeselectEvent.StartExtendSelection, GGDeselectEvent.DuringExtendSelection, GGDeselectEvent.EndExtendSelection, EasyAbort, input, gargoyleData, worldPt];
$DeselectJoint => HandleGuarded[GGDeselectEvent.StartDeselectJoint, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt];
$DeselectSegment => HandleGuarded[GGDeselectEvent.StartDeselectSegment, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt];
$DeselectTrajectory => HandleGuarded[GGDeselectEvent.StartDeselectTrajectory, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt];
$DeselectTopLevel => HandleGuarded[GGDeselectEvent.StartDeselectTopLevel, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt];
$None => {
WITH input.first SELECT FROM
refChar: REF CHAR => {
GGSlackProcess.QueueInputActionNoPoint[GGEvent.AddChar, input, gargoyleData];
RETURN;
};
ENDCASE;
atom ← NARROW[input.first];
SELECT atom FROM
$StartCaretPos => {
gargoyleData.mouseMode ← $CaretPos; HandleMouse[input, gargoyleData, worldPt];
};
$StartAdd => {
gargoyleData.mouseMode ← $Add; HandleMouse[input, gargoyleData, worldPt];
};
$StartBox => {
gargoyleData.mouseMode ← $Box; HandleMouse[input, gargoyleData, worldPt];
};
$StartDrag => {
gargoyleData.mouseMode ← $Drag; HandleMouse[input, gargoyleData, worldPt];
};
$StartCopyAndDrag => {
gargoyleData.mouseMode ← $CopyAndDrag; HandleMouse[input, gargoyleData, worldPt];
};
$StartRotate => {
gargoyleData.mouseMode ← $Rotate; HandleMouse[input, gargoyleData, worldPt];
};
$StartScale => {
gargoyleData.mouseMode ← $Scale; HandleMouse[input, gargoyleData, worldPt];
};
$StartSelectJoint => {
gargoyleData.mouseMode ← $SelectJoint; HandleMouse[input, gargoyleData, worldPt];
};
$StartSelectSegment => {
gargoyleData.mouseMode ← $SelectSegment; HandleMouse[input, gargoyleData, worldPt];
};
$StartSelectTrajectory => {
gargoyleData.mouseMode ← $SelectTrajectory; HandleMouse[input, gargoyleData, worldPt];
};
$StartSelectTopLevel => {
gargoyleData.mouseMode ← $SelectTopLevel; HandleMouse[input, gargoyleData, worldPt];
};
$StartExtendSelection => {
gargoyleData.mouseMode ← $Extend; HandleMouse[input, gargoyleData, worldPt];
};
$StartDeselectJoint => {
gargoyleData.mouseMode ← $DeselectJoint; HandleMouse[input, gargoyleData, worldPt];
};
$StartDeselectSegment => {
gargoyleData.mouseMode ← $DeselectSegment; HandleMouse[input, gargoyleData, worldPt];
};
$StartDeselectTrajectory => {
gargoyleData.mouseMode ← $DeselectTrajectory; HandleMouse[input, gargoyleData, worldPt];
};
$StartDeselectTopLevel => {
gargoyleData.mouseMode ← $DeselectTopLevel; HandleMouse[input, gargoyleData, worldPt];
};
ENDCASE; -- ignore other actions
};
ENDCASE => SIGNAL Problem[msg: "Unimplemented Mode"];
};
HandleGuarded: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
genericAction, atom: ATOM;
WITH input.first SELECT FROM
refChar: REF CHAR => RETURN; -- ignore characters during mouse actions
ENDCASE;
atom ← NARROW[input.first];
SELECT atom FROM
$StartCaretPos, $StartAdd, $StartBox, $StartDrag, $StartCopyAndDrag, $StartRotate, $StartScale, $StartSelectJoint, $StartSelectSegment, $StartSelectTrajectory, $StartSelectTopLevel, $StartExtendSelection, $StartDeselectJoint, $StartDeselectSegment, $StartDeselectTrajectory, $StartDeselectTopLevel => genericAction ← $Start;
ENDCASE => genericAction ← atom;
HandleGuardedAux[startProc, duringProc, endProc, abortProc, genericAction, input, gargoyleData, worldPt];
};
HandleUnGuarded: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
genericAction, atom: ATOM;
WITH input.first SELECT FROM
refChar: REF CHAR => RETURN; -- ignore characters during mouse actions
ENDCASE;
atom ← NARROW[input.first];
SELECT atom FROM
$StartCaretPos, $StartAdd, $StartDrag, $StartCopyAndDrag, $StartRotate, $StartScale, $StartSelectJoint, $StartSelectSegment, $StartSelectTrajectory, $StartSelectTopLevel, $StartExtendSelection => genericAction ← $Start;
ENDCASE => genericAction ← atom;
HandleUnGuardedAux[startProc, duringProc, endProc, abortProc, genericAction, input, gargoyleData, worldPt];
};
HandleGuardedAux: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, genericAction: ATOM, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
SELECT gargoyleData.state FROM
$None => {
SELECT genericAction FROM
$Start => {IF startProc[input, gargoyleData, worldPt] THEN gargoyleData.state ← $Main ELSE {abortProc[input, gargoyleData, worldPt]; gargoyleData.state ← $Aborted;}; };
ENDCASE;
};
$Main => {
SELECT genericAction FROM
$During => duringProc[input, gargoyleData, worldPt];
$Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state ← $Aborted};
$GuardUp => gargoyleData.state ← $GuardUp;
$MouseUp => gargoyleData.state ← $MouseUp;
ENDCASE;
};
$GuardUp => {
SELECT genericAction FROM
$AllUp => {
endProc[input, gargoyleData, gargoyleData.drag.currentPoint];
gargoyleData.state ← $None;
gargoyleData.mouseMode ← $None;
};
$Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state ← $Aborted};
ENDCASE;
};
$MouseUp => {
SELECT genericAction FROM
$AllUp => {
endProc[input, gargoyleData, gargoyleData.drag.currentPoint];
gargoyleData.state ← $None;
gargoyleData.mouseMode ← $None;
};
$Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state ← $Aborted};
$Start => { -- we may be starting another action of this mode or some other mode.
endProc[input, gargoyleData, gargoyleData.drag.currentPoint];
gargoyleData.state ← $None;
gargoyleData.mouseMode ← $None;
HandleMouse[input, gargoyleData, worldPt];
};
ENDCASE;
};
$Aborted => {
SELECT genericAction FROM
$AllUp => {gargoyleData.state ← $None; gargoyleData.mouseMode ← $None};
ENDCASE;
};
ENDCASE => SIGNAL Problem[msg: "Unknown generic state"];
};
HandleUnGuardedAux: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, genericAction: ATOM, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
SELECT gargoyleData.state FROM
$None => {
SELECT genericAction FROM
$Start => {IF startProc[input, gargoyleData, worldPt] THEN gargoyleData.state ← $Main ELSE {abortProc[input, gargoyleData, worldPt]; gargoyleData.state ← $Aborted;}; };
ENDCASE;
};
$Main => {
SELECT genericAction FROM
$During => duringProc[input, gargoyleData, worldPt];
$MouseUp, $AllUp => { -- CODE REMOVED DUE TO COMPILER BUG
endProc[input, gargoyleData, worldPt];
gargoyleData.state ← $None;
gargoyleData.mouseMode ← $None;
};
$MouseUp => {
endProc[input, gargoyleData, worldPt];
gargoyleData.state ← $None;
gargoyleData.mouseMode ← $None;
};
$AllUp => {
endProc[input, gargoyleData, worldPt];
gargoyleData.state ← $None;
gargoyleData.mouseMode ← $None;
};
$Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state ← $Aborted};
ENDCASE;
};
$Aborted => {
SELECT genericAction FROM
$AllUp => {gargoyleData.state ← $None; gargoyleData.mouseMode ← $None};
$MouseUp => {gargoyleData.state ← $None; gargoyleData.mouseMode ← $None};
ENDCASE;
};
ENDCASE => SIGNAL Problem[msg: "Unknown generic state"];
};
Caret Procs
StartCaretPos: PUBLIC StartProc = {
The user wishes to place the caret without changing the current selection. Only hot objects trigger alignment lines.
gargoyleData.drag.currentPoint ← worldPt;
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $CaretPos];
Common Select Operations.
StartSelectAux[gargoyleData, worldPt, NIL];
DuringCaretPos[NIL, gargoyleData, worldPt];
};
DuringCaretPos: PUBLIC MouseProc = {
While the caret is being placed, the normal gravity scheme applies. The only feedback is the appearance of text strings and the position of the caret itself.
resultPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
gargoyleData.drag.currentPoint ← worldPt;
[resultPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- show all but caret
SetCaretAttractor[gargoyleData, resultPoint, feature]; -- move caret to feature point
GGWindow.Painter[$PaintDragOverlay, gargoyleData]; -- show caret in new position
}; -- end of DuringCaretPos
EndCaretPos: PUBLIC MouseProc = {
resultPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
Don't Paint Background.
Use User's choice of gravity.
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[resultPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
Move caret to new position.
SetCaretAttractor[gargoyleData, resultPoint, feature]; -- move caret to feature point
GGCaret.DoNotSit[gargoyleData.caret];
The caret doesn't know what it is sitting on. For now, this is our way of ensuring that a subsequent $Add operation will start a NEW trajectory. Later, we should remember the chair to be used to construct touching information.
GGError.PutF[oneLiner, "Final caret on %g.", [rope[DescribeFeature[feature, gargoyleData]]]];
GGRefresh.MoveOverlayToBackground[gargoyleData]; -- caret is back in background
GGWindow.Painter[$MergeCaret, gargoyleData]; -- caret in proper place.
};
SitOnFeature: PROC [caret: Caret, feature: FeatureData] = {
IF feature = NIL THEN {
GGCaret.DoNotSit[caret];
RETURN;
};
SELECT feature.resultType FROM
joint => GGCaret.SitOnJoint[caret, feature.tseq.traj, feature.jointNum];
segment => GGCaret.SitOnSegment[caret, feature.tseq.traj, feature.segNum];
ENDCASE => GGCaret.DoNotSit[caret];
};
SetCaretAttractorEndpoint: PROC [gargoyleData: GargoyleData, mapPoint: Point, feature: FeatureData] = {
IF feature = NIL THEN GGCaret.SetAttractor[gargoyleData, mapPoint, NIL]
ELSE
SELECT feature.resultType FROM
joint => GGCaret.SetAttractor[gargoyleData, mapPoint, feature.tseq.traj, TRUE, feature.jointNum];
segment => {
jointNum: NAT;
jointPos: Point;
jointNum ← NearestJoint[feature, mapPoint];
jointPos ← GGObjects.FetchJointPos[feature.tseq.traj, jointNum];
GGCaret.SetAttractor[gargoyleData, jointPos, feature.tseq.traj, TRUE, jointNum];
};
ENDCASE => GGCaret.SetAttractor[gargoyleData, mapPoint, NIL];
GGError.PutF[oneLiner, "Caret on %g.", [rope[DescribeFeature[feature, gargoyleData]]]];
};
SetCaretAttractor: PROC [gargoyleData: GargoyleData, mapPoint: Point, feature: FeatureData] = {
Called by DuringSelectFeedback, EndSelect, DuringDrag, DuringRotate, and DuringScale. Moves the caret and records the name of the attractor.
IF feature = NIL THEN GGCaret.SetAttractor[gargoyleData, mapPoint, NIL]
ELSE
SELECT feature.resultType FROM
joint => GGCaret.SetAttractor[gargoyleData, mapPoint, feature.tseq.traj, TRUE, feature.jointNum];
segment => GGCaret.SetAttractor[gargoyleData, mapPoint, feature.tseq.traj, FALSE,,feature.segNum];
ENDCASE => GGCaret.SetAttractor[gargoyleData, mapPoint, NIL];
GGError.PutF[oneLiner, "Caret on %g.", [rope[DescribeFeature[feature, gargoyleData]]]];
};
Selection Procs
CopySelected: PUBLIC StartProc = {
Make copies of all selected objects. Then drags the originals. Then pretend that we are doing a normal drag operation.
clusGen: ClusterGenerator;
outSeqGen: GGSelect.OutlineSequenceGenerator;
newTraj: Traj;
newOutline, outline: Outline;
FOR clus: Cluster ← GGObjects.NextCluster[clusGen], GGObjects.NextCluster[clusGen] UNTIL clus = NIL DO
newClus ← GGObjects.CopyCluster[clus];
GGObjects.AddCluster[scene, newClus, -1];
ENDLOOP;
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGWindow.SaveCaretPos[gargoyleData];
SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur
outSeqGen ← GGSelect.SelectedOutlineSequences[gargoyleData, normal];
FOR outSeq: GGSelect.OutlineSequence ← GGSelect.NextOutlineSequences[outSeqGen], GGSelect.NextOutlineSequences[outSeqGen] UNTIL outSeq = NIL DO
IF outSeq.fenceSeq # NIL THEN {
IF GGSequence.NextSegment[GGSequence.SegmentsInSequence[outSeq.fenceSeq]]=NIL THEN {
-- no segments, only joints selected
GGError.Append[". . . Cannot Copy Joints", oneLiner];
GGError.Blink[];
RETURN[FALSE];
};
outline ← GGObjects.OutlineOfTraj[outSeq.fenceSeq.traj];
IF GGSelect.IsSelectedInFull[outline, gargoyleData, normal] THEN {
newOutline ← GGObjects.CopyOutline[outline];
GGObjects.AddOutline[gargoyleData.scene, newOutline, -1];
LOOP;
}
ELSE {
newTraj ← GGObjects.CopyTrajFromSequence[outSeq.fenceSeq];
newOutline ← GGObjects.CreateOutline[traj: newTraj, lineEnds: outline.lineEnds, fillColor: outline.fillColor];
GGObjects.AddOutline[gargoyleData.scene, newOutline, -1];
};
};
FOR holeSeq: Sequence ← GGSequence.NextSequence[outSeq.holeSeqs], GGSequence.NextSequence[outSeq.holeSeqs] UNTIL holeSeq = NIL DO
outline ← GGObjects.OutlineOfTraj[holeSeq.traj];
newTraj ← GGObjects.CopyTrajFromSequence[holeSeq];
newOutline ← GGObjects.CreateOutline[traj: newTraj, lineEnds: outline.lineEnds, fillColor: outline.fillColor];
GGObjects.AddOutline[gargoyleData.scene, newOutline, -1];
ENDLOOP;
ENDLOOP;
[] ← StartDrag[NIL, gargoyleData, worldPt];
};
StartSelectJoint: PUBLIC StartProc = {
Use the StrictDistance gravity function. Find the nearest segment to the cursor. Snap the caret to the nearest endpoint of that segment. Feedback during the operation will be to select hightlight the joint which the caret is snapped to. The final result is to deselect all previous selections and make a single joint selection. If the final position of the cursor is too far from any line, deselect all.
Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected joint will have to be drawn.
gargoyleData.drag.selectState ← joint;
gargoyleData.drag.currentPoint ← worldPt;
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $Select];
Common Select Operations.
StartSelectAux[gargoyleData, worldPt, NIL];
DuringSelect[NIL, gargoyleData, worldPt];
};
StartSelectSegment: PUBLIC StartProc = {
Use the StrictDistance gravity function. Find the nearest segment to the cursor. Snap the caret to the nearest endpoint of that segment. Feedback during the operation will be to select hightlight the endjoints of the segment which the caret is snapped to. The final result is to deselect all previous selections and make a single segment selection. If the final position of the cursor is too far from any line, deselect all.
Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected joint will have to be drawn.
gargoyleData.drag.selectState ← segment;
gargoyleData.drag.currentPoint ← worldPt;
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $Select];
Common Select Operations.
StartSelectAux[gargoyleData, worldPt, NIL];
DuringSelect[NIL, gargoyleData, worldPt];
};
StartSelectTrajectory: PUBLIC StartProc = {
Use the StrictDistance gravity function. Find the nearest trajectory to the cursor. Snap the caret to the nearest endjoint of that trajectory. Feedback during the operation will be to select hightlight the endjoints of the trajectory which the caret is snapped to. The final result is to deselect all previous selections and make a single trajectory selection. If the final position of the cursor is too far from any line, deselect all.
Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected trajectory will have to be drawn.
gargoyleData.drag.selectState ← traj;
gargoyleData.drag.currentPoint ← worldPt;
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $Select];
Common Select Operations.
StartSelectAux[gargoyleData, worldPt, NIL];
DuringSelect[NIL, gargoyleData, worldPt];
};
StartSelectTopLevel: PUBLIC StartProc = {
Use the StrictDistance gravity function. Find the nearest top level object to the cursor. Snap the caret to the nearest point of that object. Feedback during the operation is cluster dependent. The final result is to deselect all previous selections and make a single top level object selected. If the final position of the cursor is too far from any object, deselect all.
Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected object will have to be drawn with selection feedback.
gargoyleData.drag.selectState ← topLevel;
gargoyleData.drag.currentPoint ← worldPt;
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $SelectTopLevel];
Common Select Operations.
StartSelectAux[gargoyleData, worldPt, NIL];
DuringSelect[NIL, gargoyleData, worldPt];
};
StartSelectAux: PROC [gargoyleData: GargoyleData, worldPt: Point, startBox: BoundBox] = {
Check overlay invariant and grab input focus.
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
Measure caret coordinates.
GGWindow.SaveCaretPos[gargoyleData];
SaveSavedState[gargoyleData];
Lift caret.
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
Store the whole scene in the background plane.
GGRefresh.StoreBackground[gargoyleData, GGBoundBox.emptyBoundBox]; --all but caret is bkgnd
};
DuringSelect: PUBLIC MouseProc = {
While a joint, segment, traj, or top level object is being selected, gravity is forced to be StrictDistance. The object bag should consist only of trajectories and clusters. The caret is moved to the segment endpoint of the nearest segment or traj as appropriate or tracks the cursor if none are nearby. Feedback is in the form of highlighted joints.
resultPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
Paint Background.
GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- show all but caret
Use StrictDistance gravity except for SelectJoint.
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
gargoyleData.drag.currentPoint ← worldPt;
IF gargoyleData.drag.selectState = joint THEN
[resultPoint, feature] ← GGGravity.InnerCircle[worldPt, gargoyleData.hitTest.criticalR, gargoyleData.hitTest.innerR, activeObjects, sensitiveObjects]
ELSE [resultPoint, feature] ← GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects];
Put Caret on a joint (if any).
SetCaretAttractorEndpoint[gargoyleData, resultPoint, feature];
Deselect all.
GGRefresh.MoveOverlayToBackground[gargoyleData];
GGSelect.DeselectAll[gargoyleData, normal];
IF feature = NIL THEN { -- no near trajectories, caret in free space
}
ELSE {
SELECT gargoyleData.drag.selectState FROM
joint => DuringSelectJointFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData];
segment => DuringSelectSegmentFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData];
traj => DuringSelectTrajFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData];
topLevel => DuringSelectTopLevelFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData];
ENDCASE => ERROR;
};
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret back on overlay
GGWindow.Painter[$PaintSelectionFeedback, gargoyleData]; -- show caret in new position
};
DuringSelectJointFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = {
Find out which joint is being selected and highlight it.
SELECT feature.resultType FROM
joint => {
jointSeq: Sequence ← GGSequence.CreateJointToJoint[feature.tseq.traj, feature.jointNum, feature.jointNum];
GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
controlPoint => {
controlPointSeq: Sequence ← GGSequence.CreateFromControlPoint[feature.tseq.traj, feature.segNum, feature.controlPointNum];
GGSelect.SelectSequence[controlPointSeq, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
segment => { -- we are in the middle of a segment
jointSeq: Sequence;
jointNum: NAT;
jointNum ← NearestJoint[feature, caretPt];
jointSeq ← GGSequence.CreateJointToJoint[feature.tseq.traj, jointNum, jointNum];
GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
cluster => {
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, joint];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
};
slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR;
There shouldn't be any alignment lines in the object bag.
ENDCASE => ERROR NotYetImplemented;
}; -- end of DuringSelectJointFeedback
DuringSelectSegmentFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = {
Find out which segment is being selected and highlight it.
SELECT feature.resultType FROM
joint => { -- joints are ambiguous if they are not end joints. Do nothing.
};
segment, controlPoint => { -- we are in the middle of a segment. Select it.
seq: Sequence ← GGSequence.CreateJointToJoint[feature.tseq.traj, feature.segNum, GGObjects.FollowingJoint[feature.tseq.traj, feature.segNum]];
GGSelect.SelectSequence[seq, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
cluster => {
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, segment];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
};
slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR;
There shouldn't be any alignment lines in the object bag.
ENDCASE => ERROR NotYetImplemented;
}; -- end of DuringSelectSegmentFeedback
DuringSelectTrajFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = {
Find out which traj is being selected and highlight it.
SELECT feature.resultType FROM
joint, controlPoint, segment => { -- we are in the middle of a segment. Select its traj.
seq: Sequence ← GGSequence.CreateComplete[feature.tseq.traj];
GGSelect.SelectSequence[seq, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
cluster => {
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, traj];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
};
slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR;
There shouldn't be any alignment lines in the object bag.
ENDCASE => ERROR NotYetImplemented;
};
DuringSelectTopLevelFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = {
Find out which traj is being selected and highlight it.
SELECT feature.resultType FROM
joint, controlPoint, segment => {
GGSelect.SelectOutline[feature.tseq.traj.parent, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
cluster => {
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, topLevel];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
};
slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR;
There shouldn't be any alignment lines in the object bag.
ENDCASE => ERROR NotYetImplemented;
};
EndSelect: PUBLIC MouseProc = {
resultPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
Don't Paint Background.
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
IF gargoyleData.drag.selectState = joint THEN
[resultPoint, feature] ← GGGravity.InnerCircle[worldPt, gargoyleData.hitTest.criticalR, gargoyleData.hitTest.innerR, activeObjects, sensitiveObjects]
ELSE [resultPoint, feature] ← GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects];
Deselect all.
GGRefresh.MoveOverlayToBackground[gargoyleData];
GGSelect.DeselectAll[gargoyleData, normal];
Dispatch to the proper EndSelect handler for final selection.
SELECT gargoyleData.drag.selectState FROM
joint => EndSelectJoint[gargoyleData, resultPoint, feature];
segment => EndSelectSegment[gargoyleData, resultPoint, feature];
traj => EndSelectTrajectory[gargoyleData, resultPoint, feature];
topLevel => EndSelectTopLevel[gargoyleData, resultPoint, feature];
ENDCASE => ERROR;
};
EndSelectJoint: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = {
jointSeq, controlPointSeq: Sequence;
jointNum: NAT;
Prepare for a subsequent Add operation.
IF feature # NIL AND (feature.resultType = joint OR feature.resultType = segment) THEN {
jointNum ← NearestJoint[feature, resultPoint];
GGCaret.SitOnJoint[gargoyleData.caret, feature.tseq.traj, jointNum];
}
ELSE GGCaret.DoNotSit[gargoyleData.caret];
Perform the final selection.
IF feature = NIL THEN { -- no preparation needed
GGError.Append["No near joint found.", oneLiner];
}
ELSE {
SELECT feature.resultType FROM
joint, segment => {
jointSeq ← GGSequence.CreateJointToJoint[feature.tseq.traj, jointNum, jointNum];
GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
IF GGObjects.IsEndJoint[feature.tseq.traj, feature.jointNum] THEN GGError.Append["End ", begin]; -- an end joint
GGError.PutF[end, "Joint %g selected", [integer[feature.jointNum]] ];
Prepare for a subsequent Extend operation.
gargoyleData.drag.extendMode ← joint;
gargoyleData.drag.trajToExtend ← feature.tseq.traj;
};
controlPoint => {
controlPointSeq ← GGSequence.CreateFromControlPoint[feature.tseq.traj, feature.segNum, feature.controlPointNum];
GGSelect.SelectSequence[controlPointSeq, gargoyleData, normal];
GGError.PutF[oneLiner, "Control point %g selected", [integer[feature.controlPointNum]] ];
gargoyleData.drag.extendMode ← controlPoint;
gargoyleData.drag.trajToExtend ← feature.tseq.traj;
};
cluster => {
GGSelect.SelectEntity[feature.cluster, gargoyleData, normal];
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, joint];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
GGError.Append["Cluster selected", oneLiner];
gargoyleData.drag.extendMode ← joint;
};
ENDCASE => ERROR UnexpectedType;
};
GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData];
};
EndSelectSegment: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = {
jointNum: NAT;
seq: Sequence;
Prepare for a subsequent Add operation.
IF feature # NIL AND (feature.resultType = joint OR feature.resultType = segment) THEN {
jointNum ← NearestJoint[feature, resultPoint];
GGCaret.SitOnJoint[gargoyleData.caret, feature.tseq.traj, jointNum];
}
ELSE GGCaret.DoNotSit[gargoyleData.caret];
Prepare for a subsequent extend operation.
IF feature = NIL THEN { -- no preparation needed
GGError.Append["No near segment found.", oneLiner];
}
ELSE {
SELECT feature.resultType FROM
joint => {}; -- actually if it is the last joint of a trajectory, this isn't ambiguous, but for now do nothing.
segment, controlPoint => {
seq ← GGSequence.CreateFromSegment[feature.tseq.traj, feature.segNum];
GGSelect.SelectSequence[seq, gargoyleData, normal];
GGError.PutF[oneLiner, "Segment %g selected", [integer[feature.segNum]] ];
gargoyleData.drag.extendMode ← segment;
gargoyleData.drag.trajToExtend ← feature.tseq.traj;
gargoyleData.drag.segToExtendNum ← feature.segNum;
};
cluster => {
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, segment];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
GGError.Append["Cluster selected", oneLiner];
gargoyleData.drag.extendMode ← segment;
};
ENDCASE => ERROR UnexpectedType; -- nothing else should be in the object bag
};
GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData];
};
EndSelectTrajectory: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = {
GGCaret.DoNotSit[gargoyleData.caret];
IF feature = NIL THEN {
GGError.Append["No near trajectory found.", oneLiner];
}
ELSE {
SELECT feature.type FROM
sequence => {
jointNum: NAT ← NearestJoint[feature, resultPoint];
Prepare for a subsequent Add or extend operation.
GGCaret.SitOnJoint[gargoyleData.caret, feature.tseq.traj, jointNum];
GGSelect.SelectTraj[feature.tseq.traj, gargoyleData, normal];
GGError.Append["Trajectory Selected", oneLiner];
gargoyleData.drag.extendMode ← traj;
};
cluster => {
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, traj];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
GGError.Append["Cluster selected", oneLiner];
gargoyleData.drag.extendMode ← traj;
};
ENDCASE => ERROR UnexpectedType;
};
GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData];
}; -- end SelectTrajectory
EndSelectTopLevel: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = {
GGCaret.DoNotSit[gargoyleData.caret]; -- Don't prepare for a subsequent Add operation.
Perform the selection.
IF feature = NIL THEN { -- no selection
GGError.Append["No near object found.", oneLiner];
}
ELSE {
SELECT feature.resultType FROM
joint, segment, controlPoint => {
GGSelect.SelectOutline[feature.tseq.traj.parent, gargoyleData, normal];
GGError.Append["Top level trajectory selected.", oneLiner];
gargoyleData.drag.extendMode ← topLevel;
};
cluster => {
parts: ClusterParts ← feature.cluster.class.endSelect[feature.cluster, topLevel];
GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal];
GGError.Append["Cluster selected", oneLiner];
gargoyleData.drag.extendMode ← topLevel;
};
ENDCASE => ERROR; -- nothing else should be in the object bag
};
GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData];
}; -- end SelectTopLevel
UpdateSelectedAfterMove: PROC [gargoyleData: GargoyleData] = {
entityGen: EntityGenerator;
GGTouch.InitializeTouching[gargoyleData];
entityGen ← GGSelect.SelectedEntities[gargoyleData, normal];
FOR entity: REF ANY ← GGObjects.NextEntity[entityGen], GGObjects.NextEntity[entityGen] UNTIL entity = NIL DO
WITH entity SELECT FROM
selSeq: Sequence => {
GGObjects.TransformSequence[selSeq, gargoyleData.drag.transform];
GGTouch.SequenceMoved[selSeq, gargoyleData];
};
outline: Outline => {
FOR trajs: LIST OF Traj ← outline.children, trajs.rest UNTIL trajs = NIL DO
GGObjects.TransformTraj[trajs.first, gargoyleData.drag.transform];
GGTouch.TrajMoved[trajs.first, gargoyleData];
ENDLOOP;
};
clusD: ClusterDescriptor => {
clusD.cluster.class.transform[clusD.cluster, gargoyleData.drag.transform];
};
ENDCASE => ERROR NotYetImplemented;
ENDLOOP;
};
Motion Procs
StartDrag: PUBLIC StartProc = {
Use gravity to find a nearby point on the selected objects. Move the selected objects so that this point coincides with the cursor. Then we can use the same gravity paradigm that we used for Select: The caret is drawn toward interesting lines.
We should also keep track of which object point the caret is on so we can create fragile touching constraints.
The caret and the selected objects will be on the overlay plane.
restoreBox: BoundBox;
mapPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGWindow.SaveCaretPos[gargoyleData];
SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur
IF GGSelect.NoSelections[gargoyleData, normal] THEN {
GGError.Append["Select some objects to drag", oneLiner];
GGError.Blink[];
RETURN[FALSE];
};
restoreBox ← GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below
gargoyleData.drag.state ← $Drag;
gargoyleData.drag.currentPoint ← worldPt;
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $DragStartUp];
Time to move the nearby object (if any) to the cursor.
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
IF feature = NIL THEN gargoyleData.drag.startPoint ← worldPt
ELSE gargoyleData.drag.startPoint ← mapPoint;
GGCaret.SetAttractor[gargoyleData, gargoyleData.drag.startPoint, NIL];
SitOnFeature[gargoyleData.caret, feature];
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $Drag];
gargoyleData.drag.transform ← ImagerTransformation.Scale[1.0];
We initialize the transform to the identity. Later, it will describe how far the mappoint has moved from the startpoint.
We had to wait until AFTER the duringBag was built before calling StoreBackground
restore the OLD caret bitmap and the selected items bitmap
GGBoundBox.EnlargeByBox[restoreBox, GGBoundBox.BoundBoxOfMoving[gargoyleData] ];
gargoyleData.refresh.startBoundBox^ ← restoreBox^;
GGRefresh.StoreBackground[gargoyleData, restoreBox ]; -- all but selected and caret are on the background
DuringDrag[NIL, gargoyleData, worldPt];
};
DuringDrag: PUBLIC MouseProc = {
We keep track of the total vector of motion since the drag began (i.e. the difference between the initial mouse position and the current mouse position. We transform all of the dragged objects before redrawing them. This style should be better than changing the actual data since there is less accumulated error, and the undo transformation is easily derived.
totalDragVector: Vector;
mapPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects: TriggerBag ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
gargoyleData.drag.currentPoint ← worldPt;
GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- Draw the background
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
SetCaretAttractor[gargoyleData, mapPoint, feature];
IF gargoyleData.hitTest.linesAlwaysOn.state = off THEN -- Bier February 3, 1986 GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, environ];
totalDragVector ← GGVector.Sub[mapPoint, gargoyleData.drag.startPoint];
gargoyleData.drag.transform ← ImagerTransformation.Translate[[totalDragVector[1], totalDragVector[2]]];
GGWindow.Painter[$PaintDragOverlay, gargoyleData];
};
EndDrag: PUBLIC MouseProc = {
The dragging is done. Update all of the drag entities with the total drag vector and repaint the entire scene.
UpdateSelectedAfterMove[gargoyleData];
GGCaret.MakeChairTouchAttractor[gargoyleData.caret, gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
GGWindow.Painter[$MergeAfterDragging, gargoyleData];
GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited
};
StartRotate: PUBLIC StartProc = {
Use gravity to find a nearby point on the selected objects. Move the selected objects so that this point coincides with the cursor. Then we can use the same gravity paradigm that we used for Select: The caret is drawn toward interesting lines.
We should also keep track of which object point the caret is on so we can create fragile touching constraints.
The caret and the selected objects will be on the overlay plane.
restoreBox: BoundBox;
mapPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGWindow.SaveCaretPos[gargoyleData];
SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur
IF GGSelect.NoSelections[gargoyleData, normal] THEN {
GGError.Append["Select some objects to rotate", oneLiner];
GGError.Blink[];
RETURN[FALSE];
};
IF NOT GGCaret.Exists[gargoyleData.anchor] THEN {
GGError.Append["Place an anchor to rotate around", oneLiner];
GGError.Blink[];
RETURN[FALSE];
};
restoreBox ← GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below
gargoyleData.drag.currentPoint ← worldPt;
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $DragStartUp];
Time to move the nearby object (if any) to the cursor.
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
IF feature = NIL THEN gargoyleData.drag.startPoint ← worldPt
ELSE gargoyleData.drag.startPoint ← mapPoint;
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $Drag];
GGCaret.SetAttractor[gargoyleData, gargoyleData.drag.startPoint, NIL];
gargoyleData.drag.transform ← ImagerTransformation.Scale[1.0];
We initialize the transform to the identity. Later, it will describe how far the mappoint has moved from the startpoint.
We had to wait until AFTER the duringBag was built before calling StoreBackground
restore the OLD caret bitmap and the selected items bitmap
GGBoundBox.EnlargeByBox[restoreBox, GGBoundBox.BoundBoxOfMoving[gargoyleData] ];
gargoyleData.refresh.startBoundBox^ ← restoreBox^;
GGRefresh.StoreBackground[gargoyleData, restoreBox ]; -- all but selected and caret are on the background
DuringRotate[NIL, gargoyleData, worldPt];
};
DuringRotate: PUBLIC MouseProc = {
We keep track of the total vector of motion since the drag began (i.e. the difference between the initial mouse position and the current mouse position. We transform all of the dragged objects before redrawing them. This style should be better than changing the actual data since there is less accumulated error, and the undo transformation is easily derived.
originalVector, newVector: Vector;
mapPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects: TriggerBag ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
degrees: REAL;
anchorPoint: Point;
anchorPoint ← GGCaret.GetPoint[gargoyleData.anchor];
GGWindow.Painter[$BitcopyBackground, gargoyleData];
gargoyleData.drag.currentPoint ← worldPt;
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
SetCaretAttractor[gargoyleData, mapPoint, feature];
IF gargoyleData.hitTest.linesAlwaysOn.state = off THEN -- Bier February 3, 1986 7:59:50 pm PST GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, environ];
originalVector ← GGVector.Sub[gargoyleData.drag.startPoint, anchorPoint];
newVector ← GGVector.Sub[mapPoint, anchorPoint];
degrees ← GGVector.AngleCCWBetweenVectors[originalVector, newVector];
gargoyleData.drag.transform ← GGTransform.RotateAboutPoint[anchorPoint, degrees];
GGWindow.Painter[$PaintDragOverlay, gargoyleData];
};
EndRotate: PUBLIC MouseProc = {
The rotating is done. Update all of the drag entities with the full rotation and repaint the entire scene.
UpdateSelectedAfterMove[gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
GGWindow.Painter[$MergeAfterDragging, gargoyleData];
GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited
};
StartScale: PUBLIC StartProc = {
Use gravity to find a nearby point on the selected objects. Move the selected objects so that this point coincides with the cursor. Then we can use the same gravity paradigm that we used for Select: The caret is drawn toward interesting lines.
We should also keep track of which object point the caret is on so we can create fragile touching constraints.
The caret and the selected objects will be on the overlay plane.
restoreBox: BoundBox;
mapPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGWindow.SaveCaretPos[gargoyleData];
SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur
IF GGSelect.NoSelections[gargoyleData, normal] THEN {
GGError.Append["Select some objects to scale", oneLiner];
GGError.Blink[];
RETURN[FALSE];
};
IF NOT GGCaret.Exists[gargoyleData.anchor] THEN {
GGError.Append["Place an anchor to scale around", oneLiner];
GGError.Blink[];
RETURN[FALSE];
};
restoreBox ← GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below
gargoyleData.drag.currentPoint ← worldPt;
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $DragStartUp];
Time to move the nearby object (if any) to the cursor.
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
IF feature = NIL THEN gargoyleData.drag.startPoint ← worldPt
ELSE gargoyleData.drag.startPoint ← mapPoint;
Build the alignment lines bag.
GGAlign.SetBagsForAction[gargoyleData, $Drag];
GGCaret.SetAttractor[gargoyleData, gargoyleData.drag.startPoint, NIL];
gargoyleData.drag.transform ← ImagerTransformation.Scale[1.0];
We initialize the transform to the identity. Later, it will describe how far the mappoint has moved from the startpoint.
We had to wait until AFTER the duringBag was built before calling StoreBackground
restore the OLD caret bitmap and the selected items bitmap
GGBoundBox.EnlargeByBox[restoreBox, GGBoundBox.BoundBoxOfMoving[gargoyleData] ];
gargoyleData.refresh.startBoundBox^ ← restoreBox^;
GGRefresh.StoreBackground[gargoyleData, restoreBox ]; -- all but selected and caret are on the background
DuringScale[NIL, gargoyleData, worldPt];
};
DuringScale: PUBLIC MouseProc = {
We keep track of the total vector of motion since the drag began (i.e. the difference between the initial mouse position and the current mouse position. We transform all of the dragged objects before redrawing them. This style should be better than changing the actual data since there is less accumulated error, and the undo transformation is easily derived.
epsilon: REAL = 1.0e-3;
originalVector, newVector: Vector;
mapPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects: TriggerBag ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
ratio: REAL;
ratioX, ratioY: REAL;
anchorPoint: Point;
anchorPoint ← GGCaret.GetPoint[gargoyleData.anchor];
GGWindow.Painter[$BitcopyBackground, gargoyleData];
gargoyleData.drag.currentPoint ← worldPt;
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
SetCaretAttractor[gargoyleData, mapPoint, feature];
IF gargoyleData.hitTest.linesAlwaysOn.state = off THEN -- Bier February 3, 1986 8:00:00 pm PST GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, environ];
originalVector ← GGVector.Sub[gargoyleData.drag.startPoint, anchorPoint];
newVector ← GGVector.Sub[mapPoint, anchorPoint];
ratio ← GGVector.Magnitude[newVector]/GGVector.Magnitude[originalVector];
gargoyleData.drag.transform ← GGTransform.ScaleAboutPoint[anchorPoint, ratio];
IF ABS[originalVector[1]] > epsilon THEN ratioX ← ABS[newVector[1]/originalVector[1]]
ELSE ratioX ← 1.0;
IF ABS[originalVector[2]] > epsilon THEN ratioY ← ABS[newVector[2]/originalVector[2]]
ELSE ratioY ← 1.0;
IF ratioX > epsilon AND ratioY > epsilon THEN
gargoyleData.drag.transform ← GGTransform.ScaleUnevenAboutPoint[anchorPoint, ratioX, ratioY];
GGWindow.Painter[$PaintDragOverlay, gargoyleData];
};
EndScale: PUBLIC MouseProc = {
The scaling is done. Update all of the drag entities with the full rotation and repaint the entire scene.
UpdateSelectedAfterMove[gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
GGWindow.Painter[$MergeAfterDragging, gargoyleData];
GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited
};
Addition and Extension Procs
ExtendTrajToMouse: PRIVATE PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj, success: BOOL] = {
caret: Caret ← gargoyleData.caret;
caretPoint: Point;
jointNum: NAT;
newLine: Segment;
caretPoint ← GGCaret.GetPoint[caret];
IF GGCaret.SittingOnEnd[caret] THEN { -- extend the existing trajectory
new ← FALSE;
[traj, ----, ----, jointNum, ----] ← GGCaret.GetChair[caret];
IF jointNum = 0 THEN { -- add to the low end of the trajectory
newLine ← GGSegment.MakeLine[worldPt, caretPoint];
success ← GGObjects.AddSegment[traj, lo, newLine, hi];
IF NOT success THEN RETURN;
GGSelect.ReselectTraj[traj, lo, gargoyleData, TRUE];
The trajectory is about to change size. Any sequences stored on the selection lists are obsolete. Update them.
GGCaret.SetAttractor[gargoyleData, worldPt, NIL];
GGCaret.SitOnJoint[gargoyleData.caret, traj, 0];
}
ELSE IF jointNum = GGObjects.HiJoint[traj] THEN { -- add to the high end of the trajectory
newLine ← GGSegment.MakeLine[caretPoint, worldPt];
success ← GGObjects.AddSegment[traj, hi, newLine, lo];
IF NOT success THEN RETURN;
GGSelect.ReselectTraj[traj, hi, gargoyleData, TRUE];
The trajectory is about to change size. Any sequences stored on the selection lists are obsolete. Update them.
GGCaret.SetAttractor[gargoyleData, worldPt, NIL];
GGCaret.SitOnJoint[gargoyleData.caret, traj, jointNum + 1];
}
ELSE ERROR;
}
ELSE { -- Create a new trajectory starting at the caret (making touching constraints, if any).
newOutline: Outline;
new ← TRUE;
newLine ← GGSegment.MakeLine[caretPoint, worldPt];
traj ← GGObjects.CreateTraj[caretPoint];
success ← GGObjects.AddSegment[traj, hi, newLine, lo];
IF NOT success THEN RETURN;
newOutline ← GGObjects.CreateOutline[traj];
GGObjects.AddOutline[scene, newOutline, -1];
GGCaret.MakeChairTouchTrajJoint[gargoyleData.caret, gargoyleData, traj, 0];
GGCaret.SetAttractor[gargoyleData, worldPt, NIL];
GGCaret.SitOnJoint[gargoyleData.caret, traj, 1];
};
};
StartAdd: PUBLIC StartProc = {
If the caret is on the end of a trajectory, then extend the trajectory from that end. Otherwise, create a new simple outline with a segment from the caret to the current mouse position. In either case, move the trajectory to the overlay plane so we can rubberband the new line. This is a little strange, since the new line is not selected. The caret sticks to the new endpoint.
restoreBox: BoundBox;
jointSeq: Sequence;
trajUnderCaret: Traj;
jointNum: NAT;
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGWindow.SaveCaretPos[gargoyleData];
SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur
gargoyleData.drag.currentPoint ← worldPt;
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
restoreBox ← GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below
Add a new segment.
[trajUnderCaret, success] ← ExtendTrajToMouse[gargoyleData.scene, worldPt, gargoyleData];
IF NOT success THEN RETURN[FALSE];
Choose appropriate alignment lines.
GGAlign.SetBagsForAction[gargoyleData, $Add];
GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, NARROW[gargoyleData.hitTest.activeObjectBag]];
Move the trajectory's outline to the overlay plane.
[----, ----, ----, jointNum, ----] ← GGCaret.GetChair[gargoyleData.caret];
jointSeq ← GGSequence.CreateJointToJoint[trajUnderCaret, jointNum, jointNum];
GGCaret.SetSequence[gargoyleData.caret, jointSeq];
GGRefresh.MoveToOverlay[jointSeq, gargoyleData]; -- traj on overlay (why = joint).
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
Initialize the transformation and remember the starting position.
gargoyleData.drag.startPoint ← worldPt;
gargoyleData.drag.transform ← ImagerTransformation.Scale[1.0];
gargoyleData.drag.state ← $Add;
GGRefresh.StoreBackground[gargoyleData, restoreBox]; -- all but caret and traj on background
DuringAdd[NIL, gargoyleData, worldPt]; -- Bier February 10, 1986
As long as we are making the user pay for the alignment lines, we might as well draw them once.
};
DuringAdd: PUBLIC MouseProc = {
Map the endpoint and the caret and reposition them.
totalDragVector: Vector;
mapPoint: Point;
feature: FeatureData;
activeObjects: ObjectBag ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects: TriggerBag ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- Draw the background
gargoyleData.drag.currentPoint ← worldPt;
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData];
SetCaretAttractor[gargoyleData, mapPoint, feature];
GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, activeObjects];
totalDragVector ← GGVector.Sub[mapPoint, gargoyleData.drag.startPoint];
gargoyleData.drag.transform ← ImagerTransformation.Translate[[totalDragVector[1], totalDragVector[2]]];
GGWindow.Painter[$PaintDragOverlay, gargoyleData];
};
EndAdd: PUBLIC MouseProc = {
The dragging is done. Update the endpoint with the current transform.
selSeq: Sequence;
selSeq ← GGCaret.GetSequence[gargoyleData.caret];
GGObjects.TransformSequence[selSeq, gargoyleData.drag.transform];
GGCaret.MakeChairTouchAttractor[gargoyleData.caret, gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
gargoyleData.refresh.startBoundBox^ ← selSeq.traj.boundBox^;
GGWindow.Painter[$MergeBoundBox, gargoyleData];
GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited
};
FixupAbortedAdd: PROC [gargoyleData: GargoyleData] = {
GGEvent.DeleteCaretSegment[NIL, gargoyleData];
gargoyleData.drag.newOutline ← FALSE; -- no longer used
};
AddNewBoxCluster: PROC [from, to: Point, gargoyleData: GargoyleData] RETURNS [clus: Cluster] = {
box: BoundBox;
corner: GGBoxCluster.Corner ← none;
loX: REALMIN[from[1], to[1] ];
loY: REALMIN[from[2], to[2] ];
hiX: REALMAX[from[1], to[1] ];
hiY: REALMAX[from[2], to[2] ];
IF to[1]=loX THEN IF to[2]=loY THEN corner ← ll ELSE corner ← ul;
IF to[1]=hiX THEN IF to[2]=loY THEN corner ← lr ELSE corner ← ur;
box ← GGBoundBox.CreateBoundBox[loX, loY, hiX, hiY];
clus ← GGBoxCluster.MakeBoxCluster[box, corner];
GGObjects.AddCluster[gargoyleData.scene, clus, -1];
do no painting yet
GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited
};
StartBox: PUBLIC StartProc = {
restoreBox: BoundBox;
clus: Cluster;
caretPos: Point ← GGCaret.GetPoint[gargoyleData.caret];
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGWindow.SaveCaretPos[gargoyleData];
SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur
gargoyleData.drag.currentPoint ← worldPt;
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
restoreBox ← GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below
clus ← AddNewBoxCluster[caretPos, worldPt, gargoyleData];
Choose appropriate alignment lines.
GGAlign.SetBagsForAction[gargoyleData, $Drag]; -- same alignment as for Drag
Move new box to overlay
GGRefresh.MoveToOverlay[clus, gargoyleData]; -- clus on overlay (why ?? ).
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
Initialize the transformation and remember the starting position.
gargoyleData.drag.startPoint ← worldPt;
gargoyleData.drag.transform ← ImagerTransformation.Scale[1.0];
gargoyleData.drag.state ← $Box;
GGRefresh.StoreBackground[gargoyleData, restoreBox]; -- all but caret and box on bkgnd
DuringBox[NIL, gargoyleData, worldPt];
};
DuringBox: PUBLIC MouseProc = {
not sure this does the right thing with the caret, but it's close
DuringAdd[input, gargoyleData, worldPt];
};
EndBox: PUBLIC MouseProc = {
The dragging is done. Update the box cluster with the current transform. Unfortunately, we've lost track of the box cluster. Find it for now by looking on the overlay plane for the first (hopefully ONLY) box cluster on it.
cluster: Cluster;
foundFirst: BOOLFALSE;
clusGen: ClusterGenerator ← GGObjects.ClustersInScene[gargoyleData.scene];
FOR clus: Cluster ← GGObjects.NextCluster[clusGen], GGObjects.NextCluster[clusGen] UNTIL clus=NIL DO
IF clus.onOverlay THEN {
IF NOT foundFirst THEN {
clus.class.transform[clus, gargoyleData.drag.transform]; -- update the cluster
foundFirst ← TRUE;
cluster ← clus;
}
ELSE ERROR; -- for now
};
ENDLOOP;
GGRefresh.MoveOverlayToBackground[gargoyleData];
gargoyleData.refresh.startBoundBox^ ← cluster.boundBox^; --newly updated by cluster.class.transform
GGWindow.Painter[$MergeBoundBox, gargoyleData];
GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited
};
FixupAbortedBox: PROC [gargoyleData: GargoyleData] = {
What is going on: basically, this routine is called instead of EndBox. Sooo, we find the box that is being added and delete it. Find it for now by looking on the overlay plane for the first (hopefully ONLY) box cluster on it.
repaintBox: BoundBox ← GGCaret.BoundBoxOfCaret[gargoyleData.caret, gargoyleData]; -- start with caret
foundFirst: BOOLFALSE;
clusGen: ClusterGenerator ← GGObjects.ClustersInScene[gargoyleData.scene];
FOR clus: Cluster ← GGObjects.NextCluster[clusGen], GGObjects.NextCluster[clusGen] UNTIL clus=NIL DO
IF clus.onOverlay THEN {
GGObjects.DeleteCluster[gargoyleData.scene, clus];
GGBoundBox.EnlargeByBox[repaintBox, clus.boundBox]; -- repaint deleted box cluster
EXIT;
};
ENDLOOP;
gargoyleData.refresh.startBoundBox^ ← repaintBox^;
GGWindow.Painter[$PaintBoundBox, gargoyleData];
};
Nearest Procs
NearestJoint: PROC [feature: FeatureData, caretPt: Point] RETURNS [jointNum: NAT] = {
nextNum: NAT;
traj: Traj;
p1, p2: Point;
d1, d2: REAL;
traj ← feature.tseq.traj;
SELECT feature.resultType FROM
joint => {
jointNum ← feature.jointNum;
};
segment, controlPoint => {
nextNum ← GGObjects.FollowingJoint[traj, feature.segNum];
p1 ← GGObjects.FetchJointPos[traj, feature.segNum];
p2 ← GGObjects.FetchJointPos[traj, nextNum];
d1 ← GGVector.DistanceSquared[p1, caretPt];
d2 ← GGVector.DistanceSquared[p2, caretPt];
IF d1 <= d2 THEN jointNum ← feature.segNum
ELSE jointNum ← nextNum;
};
ENDCASE => ERROR NotYetImplemented;
}; -- end of NearestJoint
NearestTrajectory: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj] = {
feature: FeatureData;
resultPoint: Point;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
GGAlign.SetBagsForAction[gargoyleData, $Select];
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[resultPoint, feature] ← GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects];
traj ← IF feature = NIL THEN NIL ELSE feature.tseq.traj;
The following doesn't seem to take care of all the cases properly ??. KAP. March 24, 1986
RETURN[IF feature=NIL OR feature.type=cluster THEN NIL ELSE feature.tseq.traj];
};
NearestSegment: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj, segNum: NAT] = {
feature: FeatureData;
resultPoint: Point;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
GGAlign.SetBagsForAction[gargoyleData, $Select];
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[resultPoint, feature] ← GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects];
traj ← IF feature = NIL THEN NIL ELSE feature.tseq.traj;
IF feature # NIL THEN segNum ← feature.segNum;
The following doesn't seem to take care of all the cases properly ??. KAP. March 24, 1986
IF (feature=NIL OR feature.type=cluster) THEN RETURN[NIL, 0] ELSE RETURN[feature.tseq.traj, feature.segNum];
};
NearestCluster: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [cluster: Cluster] = {
feature: FeatureData;
resultPoint: Point;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
GGAlign.SetBagsForAction[gargoyleData, $SelectCluster];
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[resultPoint, feature] ← GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects];
cluster ← IF feature = NIL THEN NIL ELSE feature.cluster;
};
NearestTopLevel: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [topLevel: REF ANY] = {
feature: FeatureData;
resultPoint: Point;
activeObjects: ObjectBag;
sensitiveObjects: TriggerBag;
GGAlign.SetBagsForAction[gargoyleData, $SelectTopLevel];
activeObjects ← NARROW[gargoyleData.hitTest.activeObjectBag];
sensitiveObjects ← NARROW[gargoyleData.hitTest.sensitiveTriggerBag];
[resultPoint, feature] ← GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects];
IF feature = NIL THEN topLevel ← NIL
ELSE {
SELECT feature.resultType FROM
joint, segment, controlPoint => {
topLevel ← feature.tseq.traj.parent;
};
cluster => {
topLevel ← feature.cluster;
};
ENDCASE => ERROR;
};
};
Touching Procs
SelectAllTouchingFeature: PROC [feature: FeatureData, gargoyleData: GargoyleData] = {
item: TouchItem;
jointSeq, segSeq: Sequence;
SELECT feature.resultType FROM
joint => {
item ← TouchItemOfJoint[feature.tseq.traj, feature.jointNum];
IF item = NIL THEN {
jointSeq ← GGSequence.CreateJointToJoint[feature.tseq.traj, feature.jointNum, feature.jointNum];
GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
IF GGObjects.IsEndJoint[feature.tseq.traj, feature.jointNum] THEN GGError.Append["End ", begin]; -- an end joint
GGError.PutF[end, "Joint %g deselected", [integer[feature.jointNum]] ];
IF GGObjects.IsEndJoint[feature.tseq.traj, feature.jointNum] THEN { -- an end joint
GGError.Append["End Joint selected", oneLiner];
}
ELSE { -- a middle joint
After this time, a Splice will create a new trajectory.
GGError.Append["Middle Joint selected", oneLiner];
};
}
ELSE {
group: TouchGroup ← GGTouch.TouchGroupOfItem[item];
jointNum, segNum: NAT;
itemGen: TouchItemGenerator;
itemGen ← GGTouch.AllTouchItems[group];
FOR thisItem: TouchItem ← GGTouch.NextTouchItem[itemGen], GGTouch.NextTouchItem[itemGen] UNTIL thisItem = NIL DO
SELECT thisItem.touchingPartType FROM
joint => {
jointNum ← GGObjects.IndexOfJoint[thisItem.joint, thisItem.traj];
jointSeq ← GGSequence.CreateFromJoint[thisItem.traj, jointNum];
GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
};
segment => {
segNum ← GGObjects.IndexOfSegment[thisItem.seg, thisItem.traj];
segSeq ← GGSequence.CreateFromSegment[thisItem.traj, segNum];
GGSelect.SelectSequence[segSeq, gargoyleData, normal];
};
ENDCASE;
ENDLOOP;
GGError.Append["Multiple entities selected", oneLiner];
};
};
ENDCASE => ERROR NotYetImplemented;
};
TouchItemOfJoint: PROC [traj: Traj, jointNum: NAT] RETURNS [item: TouchItem] = {
joint: Joint ← NARROW[Rosary.Fetch[traj.joints, jointNum]];
item ← joint.touchItem;
};
Feature description Procs
DescribeFeature: PROC [feature: FeatureData, gargoyleData: GargoyleData] RETURNS [rope: Rope.ROPE] = {
IF feature = NIL THEN RETURN[" nothing"]
ELSE {
SELECT feature.resultType FROM
joint => rope ← "joint";
controlPoint => rope ← "control point";
segment => {
rope ← IO.PutFR["segment %g", [integer[feature.segNum]]];
};
cluster => rope ← "cluster";
distanceLine => rope ← "distance line";
slopeLine => rope ← "slope line";
symmetryLine => rope ← "symmetry line";
radiiCircle => rope ← "compass circle";
intersectionPoint => {
firstObj, secondObj: Rope.ROPE;
firstObj ← IF feature.line1Feature # NIL THEN DescribeSourceFeature[feature.line1Feature, gargoyleData]
ELSE "unknown";
secondObj ← IF feature.line2Feature # NIL THEN DescribeSourceFeature[feature.line2Feature, gargoyleData]
ELSE "unknown";
rope ← IO.PutFR["a %g/%g intersection point", [rope[firstObj]], [rope[secondObj]] ];
};
midpoint => rope ← IO.PutFR["midpoint of segment %g", [integer[feature.segNum]]];
ENDCASE => ERROR;
};
};
DescribeSourceFeature: PROC [feature: FeatureData, gargoyleData: GargoyleData] RETURNS [rope: Rope.ROPE] = {
IF feature = NIL THEN RETURN[" nothing"]
ELSE {
SELECT feature.type FROM
sequence => rope ← "sequence";
cluster => rope ← "cluster";
distanceLine => rope ← "distance line";
slopeLine => rope ← "slope line";
symmetryLine => rope ← "symmetry line";
radiiCircle => rope ← "compass circle";
intersectionPoint => {
firstObj, secondObj: Rope.ROPE;
firstObj ← IF feature.line1Feature # NIL THEN DescribeSourceFeature[feature.line1Feature, gargoyleData]
ELSE "unknown";
secondObj ← IF feature.line2Feature # NIL THEN DescribeSourceFeature[feature.line2Feature, gargoyleData]
ELSE "unknown";
rope ← IO.PutFR["a %g/%g intersection point", [rope[firstObj]], [rope[secondObj]] ];
};
midpoint => rope ← IO.PutFR["midpoint of segment %g", [integer[feature.segNum]]];
ENDCASE => ERROR;
};
};
END.
Pier, November 27, 1985 2:44:20 pm PST
changes to: ReportFeature was sending CR to typescript for null reports. Not sure what was intended, so took it out. -- The idea was to clear the MessageWindow when the caret wasn't being attracted to anything anymore.
Pier, January 7, 1986 8:45:29 pm PST
changes to: EndAdd, EndSelectPoint, EndDrag, EndRotate, EndScale: implement MergeAfterDragging
Pier, January 20, 1986 11:20:08 am PST
changes to many to implement faster painting. Added $PaintCopySelectedRegion