GGMouseEventImpl.mesa
Last edited by Bier on November 7, 1985 11:53:33 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
DIRECTORY
Atom,
GGAlign,
GGCaret,
GGError,
GGGravity,
GGInterfaceTypes,
GGModelTypes,
GGMouseEvent,
GGObjects,
GGRefresh,
GGSegment,
GGSelect,
GGTouch,
GGTransform,
GGVector,
GGWindow,
Imager,
ImagerTransformation,
InputFocus,
IO,
Menus,
Rope,
Rosary;
GGMouseEventImpl: CEDAR PROGRAM
IMPORTS Atom, GGAlign, GGCaret, GGError, GGGravity, GGObjects, GGRefresh, GGSegment, GGSelect, GGTouch, GGTransform, GGVector, GGWindow, ImagerTransformation, InputFocus, IO, Rosary
EXPORTS GGMouseEvent =
BEGIN

Caret: TYPE = GGInterfaceTypes.Caret;
Cluster: TYPE = GGModelTypes.Cluster;
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 = GGModelTypes.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;
TrajGenerator: TYPE = GGObjects.TrajGenerator;
Vector: TYPE = GGModelTypes.Vector;
NotYetImplemented: PUBLIC SIGNAL = CODE;
SittingOnEnd: PROC [caret: Caret] RETURNS [BOOL] = {
chair: Traj;
isJoint: BOOL;
jointNum: NAT;
[chair, isJoint, ----, jointNum, ----] ← GGCaret.GetChair[caret];
IF chair = NIL THEN RETURN[FALSE];
IF NOT isJoint THEN RETURN[FALSE];
RETURN[GGObjects.IsEndJoint[chair, jointNum]];
};
ExtendTrajToMouse: PRIVATE PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj, new: BOOL, success: BOOL] = {
caret: Caret ← gargoyleData.caret;
newLine: Segment;
caretPoint: Point;
jointNum: NAT;
caretPoint ← GGCaret.GetPoint[caret];
IF 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;
GGCaret.Update[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;
GGCaret.Update[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.OutlineFromTraj[traj];
GGObjects.AddOutline[scene, newOutline, -1];
GGCaret.MakeChairTouchTrajJoint[gargoyleData.caret, gargoyleData, traj, 0];
GGCaret.Update[gargoyleData, worldPt, NIL];
GGCaret.SitOnJoint[gargoyleData.caret, traj, 1];
};
};
UpdateCaretToFeature: PROC [gargoyleData: GargoyleData, mapPoint: Point, feature: FeatureData] = {
IF feature = NIL THEN GGCaret.Update[gargoyleData, mapPoint, NIL]
ELSE
SELECT feature.resultType FROM
joint => GGCaret.Update[gargoyleData, mapPoint, feature.tseq.traj, TRUE, feature.jointNum];
segment => GGCaret.Update[gargoyleData, mapPoint, feature.tseq.traj, FALSE,,feature.segNum];
ENDCASE => GGCaret.Update[gargoyleData, mapPoint, NIL];
ReportFeature[feature, gargoyleData];
};
StartAdd: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
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.
jointSeq: Sequence;
trajUnderCaret: Traj;
objectBag: ObjectBag;
jointNum: NAT;
new, success: BOOL;
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
gargoyleData.currentAction ← $StartAdd;
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
GGWindow.SaveCaretPos[gargoyleData];
Add a new segment.
[trajUnderCaret, new, success] ← ExtendTrajToMouse[gargoyleData.scene, worldPt, gargoyleData];
IF NOT success THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
Choose appropriate alignment lines.
objectBag ← GGGravity.CreateObjectBag[];
gargoyleData.hitTest.environ ← objectBag;
GGAlign.AddItemsForAction[gargoyleData, objectBag, $Add];
GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, objectBag];
Move the trajectory's outline to the overlay plane.
[----, ----, ----, jointNum, ----] ← GGCaret.GetChair[gargoyleData.caret];
jointSeq ← GGObjects.CreateSimpleSequence[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];
};
FirstDuringAdd: PRIVATE PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
IF NOT CheckProperAction[gargoyleData, $StartAdd] THEN RETURN;
gargoyleData.currentAction ← $Add;
GGRefresh.StoreBackground[gargoyleData]; -- all but caret and traj on background
DuringAdd[input, gargoyleData, worldPt];
};
DuringAdd: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
Map the endpoint and the caret and reposition them.
totalDragVector: Vector;
mapPoint: Point;
feature: FeatureData;
environ: ObjectBag ← NARROW[gargoyleData.hitTest.environ];
IF gargoyleData.currentAction = $StartAdd THEN {
FirstDuringAdd[input, gargoyleData, worldPt];
RETURN;
};
IF NOT CheckProperAction[gargoyleData, $Add] THEN RETURN;
GGWindow.Painter[$EraseOverlay, gargoyleData]; -- Draw the background
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, environ, gargoyleData];
UpdateCaretToFeature[gargoyleData, mapPoint, feature];
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];
};
EndAdd: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
The dragging is done. Update the endpoint with the current transform.
selSeq: Sequence;
IF NOT CheckProperActions[gargoyleData, $Add, $StartAdd] THEN RETURN;
selSeq ← GGCaret.GetSequence[gargoyleData.caret];
GGObjects.TransformSequence[selSeq, gargoyleData.drag.transform];
GGCaret.MakeChairTouchAttractor[gargoyleData.caret, gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
gargoyleData.currentAction ← $None;
GGWindow.Painter[$PaintEntireScene, gargoyleData];
};
ReportFeature: PROC [feature: FeatureData, gargoyleData: GargoyleData] = {
IF feature = NIL THEN {
GGError.Append["", oneLiner];
}
ELSE {
SELECT feature.resultType FROM
joint => GGError.Append["Caret on joint", oneLiner];
segment => GGError.Append["Caret on segment", oneLiner];
distanceLine => GGError.Append["Caret on distance line", oneLiner];
slopeLine => GGError.Append["Caret on slope line", oneLiner];
symmetryLine => GGError.Append["Caret on symmetry line", oneLiner];
radiiCircle => GGError.Append["Caret on compass circle", oneLiner];
intersectionPoint => GGError.Append["Caret on intersection point", oneLiner];
ENDCASE => ERROR;
};
};
CheckProperAction: PROC [gargoyleData: GargoyleData, properState: ATOM] RETURNS [BOOL] = {
msgRope: Rope.ROPE;
IF gargoyleData.currentAction = properState THEN RETURN[TRUE];
IF gargoyleData.currentAction = $None THEN RETURN[FALSE]; -- but don't complain
msgRope ← IO.PutFR["User switched from %g to %g. Gargoyle confused.", [rope[Atom.GetPName[properState]]], [rope[Atom.GetPName[gargoyleData.currentAction]]]];
GGError.Append[msgRope, oneLiner];
GGError.Blink[];
ResetMouseMachinery[gargoyleData];
RETURN[FALSE];
};
CheckProperActions: PROC [gargoyleData: GargoyleData, properState1, properState2: ATOM] RETURNS [BOOL] = {
msgRope: Rope.ROPE;
IF gargoyleData.currentAction = properState1 OR gargoyleData.currentAction = properState2 THEN RETURN[TRUE];
msgRope ← IO.PutFR["User switched from %g or %g to %g. Gargoyle confused.", [rope[Atom.GetPName[properState1]]], [rope[Atom.GetPName[properState2]]], [rope[Atom.GetPName[gargoyleData.currentAction]]]];
GGError.Append[msgRope, oneLiner];
GGError.Blink[];
ResetMouseMachinery[gargoyleData];
RETURN[FALSE];
};
ResetMouseMachinery: PUBLIC PROC [gargoyleData: GargoyleData] = {
gargoyleData.currentAction ← $None;
gargoyleData.hitTest.responsibleFor ← NIL;
GGSelect.DeselectAll[gargoyleData, normal];
GGSelect.DeselectAll[gargoyleData, copy];
GGSelect.DeselectAll[gargoyleData, hot];
GGSelect.DeselectAll[gargoyleData, active];
GGRefresh.MoveOverlayToBackground[gargoyleData];
};
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];
};
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 ← GGObjects.CreateSimpleSequence[feature.tseq.traj, feature.jointNum, feature.jointNum];
[----,----] ← GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
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 ← GGObjects.CreateSequenceFromJoint[thisItem.traj, jointNum];
[----,----] ← GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
};
segment => {
segNum ← GGObjects.IndexOfSegment[thisItem.seg, thisItem.traj];
segSeq ← GGObjects.CreateSequenceFromSegment[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;
};
UpdateCaretSelectionsAndOverlay: PRIVATE PROC [gargoyleData: GargoyleData, worldPt: Point] = {
resultPoint: Point;
feature: FeatureData;
environ: ObjectBag;
environ ← NARROW[gargoyleData.hitTest.environ];
[resultPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, environ, gargoyleData];
UpdateCaretToFeature[gargoyleData, resultPoint, feature];
GGSelect.DeselectAll[gargoyleData, normal];
IF feature = NIL THEN { -- no near trajectories, place caret in free space
And deselect anything we're responsible for.
IF gargoyleData.hitTest.responsibleFor # NIL THEN {
GGRefresh.RemoveJointsFromOverlay[gargoyleData.hitTest.responsibleFor.traj, gargoyleData];
gargoyleData.hitTest.responsibleFor ← NIL;
};
}
ELSE {
SELECT feature.resultType FROM
joint => {
jointSeq: Sequence ← GGObjects.CreateSimpleSequence[feature.tseq.traj, feature.jointNum, feature.jointNum];
IF gargoyleData.hitTest.responsibleFor # NIL THEN {
GGRefresh.RemoveJointsFromOverlay[gargoyleData.hitTest.responsibleFor.traj, gargoyleData];
};
gargoyleData.hitTest.responsibleFor ← jointSeq;
[----,----] ← GGSelect.SelectSequence[jointSeq, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
segment => { -- we are in the middle of a segment
seq: Sequence ← GGObjects.CreateSimpleSequence[feature.tseq.traj, feature.segNum, GGObjects.FollowingJoint[feature.tseq.traj, feature.segNum]];
IF gargoyleData.hitTest.responsibleFor # NIL THEN {
GGRefresh.RemoveJointsFromOverlay[gargoyleData.hitTest.responsibleFor.traj, gargoyleData];
};
gargoyleData.hitTest.responsibleFor ← seq;
[----,----] ← GGSelect.SelectSequence[seq, gargoyleData, normal];
GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData];
};
slopeLine, distanceLine, intersectionPoint, radiiCircle => {
};
ENDCASE => ERROR NotYetImplemented;
};
GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, environ];
};
StartSelectPoint: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
The user is about to try to pick a trajectory point. While he holds the mouse down, we provide selection feedback in the form of new caret positions.
environ: ObjectBag;
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGWindow.SaveCaretPos[gargoyleData];
gargoyleData.currentAction ← $StartSelectPoint;
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
Build the alignment lines bag.
environ ← GGGravity.CreateObjectBag[];
gargoyleData.hitTest.environ ← environ;
GGAlign.AddItemsForAction[gargoyleData, environ, $SelectPoint];
GGSelect.DeselectAll[gargoyleData, normal];
};
FirstDuringSelectPoint: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
Eventually, this procedure should be replaced by putting optimizations into GGRefreshImpl (e.g. the paint queue). In the mean while, we want the user to be able to select a point without waiting to long. So, if the mouse goes down-up without moving, we do not save the background, draw the alignment lines or any such hurrendous things. These wait until the first mouse motion, which is handled by this procedure.
IF NOT CheckProperAction[gargoyleData, $StartSelectPoint] THEN RETURN;
gargoyleData.currentAction ← $SelectPoint;
GGRefresh.StoreBackground[gargoyleData]; -- all but caret is background
GGWindow.Painter[$EraseOverlay, gargoyleData]; -- show all but caret
UpdateCaretSelectionsAndOverlay[gargoyleData, worldPt];
GGWindow.Painter[$PaintDragOverlay, gargoyleData]; -- show caret in new position
};
DuringSelectPoint: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
IF gargoyleData.currentAction = $StartSelectPoint THEN {
FirstDuringSelectPoint[input, gargoyleData, worldPt];
RETURN;
};
IF NOT CheckProperAction[gargoyleData, $SelectPoint] THEN RETURN;
GGWindow.Painter[$EraseOverlay, gargoyleData]; -- show all but caret
UpdateCaretSelectionsAndOverlay[gargoyleData, worldPt];
GGWindow.Painter[$PaintDragOverlay, gargoyleData]; -- show caret in new position
};
EndSelectPoint: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
This procedure has several purposes. First off, we try to find a joint which is near the mouse and put the caret on it. If the joint is a trajectory endpoint, then it can serve as a site for extending a trajectory. Otherwise, it can serve as the beginning of a new trajectory. If no joints are near the mouse, then we position the caret in free space. If a near joint is found, make it selected.
resultPoint: Point;
feature: FeatureData;
environ: ObjectBag;
IF NOT CheckProperActions[gargoyleData, $SelectPoint, $StartSelectPoint] THEN RETURN;
GGWindow.Painter[$EraseOverlay, gargoyleData]; -- show all but caret
environ ← NARROW[gargoyleData.hitTest.environ];
[resultPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, environ, gargoyleData];
UpdateCaretToFeature[gargoyleData, resultPoint, feature];
SitOnFeature[gargoyleData.caret, feature];
GGSelect.DeselectAll[gargoyleData, normal];
IF gargoyleData.hitTest.responsibleFor # NIL THEN {
GGRefresh.RemoveJointsFromOverlay[gargoyleData.hitTest.responsibleFor.traj, gargoyleData];
gargoyleData.hitTest.responsibleFor ← NIL;
};
IF feature = NIL THEN { -- no near trajectories, place caret in free space
}
ELSE {
GGSelect.DeselectAll[gargoyleData, normal];
SELECT feature.resultType FROM
joint => {
SelectAllTouchingFeature[feature, gargoyleData];
gargoyleData.extendMode ← joint;
};
segment => { -- we are in the middle of a segment
seq: Sequence ← GGObjects.CreateSimpleSequence[feature.tseq.traj, feature.segNum, GGObjects.FollowingJoint[feature.tseq.traj, feature.segNum]];
[----,----] ← GGSelect.SelectSequence[seq, gargoyleData, normal];
GGError.Append["Segment selected", oneLiner];
gargoyleData.extendMode ← segment;
};
slopeLine => {
GGError.Append["Direction line hit.", oneLiner];
};
intersectionPoint => {
GGError.Append["Intersection point hit.", oneLiner];
};
radiiCircle => {
GGError.Append["Compass circle hit.", oneLiner];
};
distanceLine => {
GGError.Append["Distance line hit.", oneLiner];
};
ENDCASE => ERROR NotYetImplemented;
};
GGRefresh.MoveOverlayToBackground[gargoyleData]; -- caret is back in background
gargoyleData.currentAction ← $None;
GGWindow.Painter[$PaintEntireScene, gargoyleData]; -- caret in proper place
};
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;
};
cluster: Cluster => ERROR NotYetImplemented;
ENDCASE => ERROR NotYetImplemented;
ENDLOOP;
};
StartDrag: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
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 SelectPoint: 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.
startUpBag, duringBag: ObjectBag;
mapPoint: Point;
feature: FeatureData;
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
IF GGSelect.NoSelections[gargoyleData, normal] THEN {
GGError.Append["Select some objects to drag.", oneLiner];
GGError.Blink[];
gargoyleData.currentAction ← $None;
RETURN;
};
GGWindow.SaveCaretPos[gargoyleData];
gargoyleData.currentAction ← $Drag;
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay
Build the alignment lines bag.
startUpBag ← GGGravity.CreateObjectBag[];
GGAlign.AddItemsForAction[gargoyleData, startUpBag, $DragStartUp];
Time to move the nearby object (if any) to the cursor.
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, startUpBag, gargoyleData];
IF feature = NIL THEN gargoyleData.drag.startPoint ← worldPt
ELSE gargoyleData.drag.startPoint ← mapPoint;
GGCaret.Update[gargoyleData, gargoyleData.drag.startPoint, NIL];
SitOnFeature[gargoyleData.caret, feature];
Build the alignment lines bag.
duringBag ← GGGravity.CreateObjectBag[];
gargoyleData.hitTest.environ ← duringBag;
GGAlign.AddItemsForAction[gargoyleData, duringBag, $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.
GGRefresh.StoreBackground[gargoyleData]; -- all but selected and caret are on the background
};
DuringDrag: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
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;
environ: ObjectBag ← NARROW[gargoyleData.hitTest.environ];
IF NOT CheckProperAction[gargoyleData, $Drag] THEN RETURN;
GGWindow.Painter[$EraseOverlay, gargoyleData]; -- Draw the background
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, environ, gargoyleData];
UpdateCaretToFeature[gargoyleData, mapPoint, feature];
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 PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
The dragging is done. Update all of the drag entities with the total drag vector and repaint the entire scene.
IF NOT CheckProperAction[gargoyleData, $Drag] THEN RETURN;
UpdateSelectedAfterMove[gargoyleData];
GGCaret.MakeChairTouchAttractor[gargoyleData.caret, gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
gargoyleData.currentAction ← $None;
GGWindow.Painter[$PaintSelectedRegion, gargoyleData];
};
StartRotate: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
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 SelectPoint: 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.
startUpBag, duringBag: ObjectBag;
mapPoint: Point;
feature: FeatureData;
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
IF GGSelect.NoSelections[gargoyleData, normal] THEN {
GGError.Append["Select some objects to rotate.", oneLiner];
GGError.Blink[];
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGCaret.Exists[gargoyleData.anchor] THEN {
GGError.Append["Place an anchor to rotate around.", oneLiner];
GGError.Blink[];
gargoyleData.currentAction ← $None;
RETURN;
};
GGWindow.SaveCaretPos[gargoyleData];
gargoyleData.currentAction ← $Rotate;
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay
Build the alignment lines bag.
startUpBag ← GGGravity.CreateObjectBag[];
GGAlign.AddItemsForAction[gargoyleData, startUpBag, $DragStartUp];
Time to move the nearby object (if any) to the cursor.
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, startUpBag, gargoyleData];
IF feature = NIL THEN gargoyleData.drag.startPoint ← worldPt
ELSE gargoyleData.drag.startPoint ← mapPoint;
Build the alignment lines bag.
duringBag ← GGGravity.CreateObjectBag[];
gargoyleData.hitTest.environ ← duringBag;
GGAlign.AddItemsForAction[gargoyleData, duringBag, $Drag];
GGCaret.Update[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.
GGRefresh.StoreBackground[gargoyleData]; -- all but selected and caret are on the background
};
DuringRotate: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
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;
environ: ObjectBag ← NARROW[gargoyleData.hitTest.environ];
degrees: REAL;
anchorPoint: Point;
IF NOT CheckProperAction[gargoyleData, $Rotate] THEN RETURN;
anchorPoint ← GGCaret.GetPoint[gargoyleData.anchor];
GGWindow.Painter[$EraseOverlay, gargoyleData];
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, environ, gargoyleData];
UpdateCaretToFeature[gargoyleData, mapPoint, feature];
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 PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
The rotating is done. Update all of the drag entities with the full rotation and repaint the entire scene.
IF NOT CheckProperAction[gargoyleData, $Rotate] THEN RETURN;
UpdateSelectedAfterMove[gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
gargoyleData.currentAction ← $None;
GGWindow.Painter[$PaintSelectedRegion, gargoyleData];
};
StartScale: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
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 SelectPoint: 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.
startUpBag, duringBag: ObjectBag;
mapPoint: Point;
feature: FeatureData;
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
IF GGSelect.NoSelections[gargoyleData, normal] THEN {
GGError.Append["Select some objects to scale.", oneLiner];
GGError.Blink[];
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGCaret.Exists[gargoyleData.anchor] THEN {
GGError.Append["Place an anchor to scale around.", oneLiner];
GGError.Blink[];
gargoyleData.currentAction ← $None;
RETURN;
};
GGWindow.SaveCaretPos[gargoyleData];
gargoyleData.currentAction ← $Scale;
GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay
GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay
Build the alignment lines bag.
startUpBag ← GGGravity.CreateObjectBag[];
GGAlign.AddItemsForAction[gargoyleData, startUpBag, $DragStartUp];
Time to move the nearby object (if any) to the cursor.
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, startUpBag, gargoyleData];
IF feature = NIL THEN gargoyleData.drag.startPoint ← worldPt
ELSE gargoyleData.drag.startPoint ← mapPoint;
Build the alignment lines bag.
duringBag ← GGGravity.CreateObjectBag[];
gargoyleData.hitTest.environ ← duringBag;
GGAlign.AddItemsForAction[gargoyleData, duringBag, $Drag];
GGCaret.Update[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.
GGRefresh.StoreBackground[gargoyleData]; -- all but selected and caret are on the background
};
DuringScale: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
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;
environ: ObjectBag ← NARROW[gargoyleData.hitTest.environ];
ratio: REAL;
ratioX, ratioY: REAL;
anchorPoint: Point;
IF NOT CheckProperAction[gargoyleData, $Scale] THEN RETURN;
anchorPoint ← GGCaret.GetPoint[gargoyleData.anchor];
GGWindow.Painter[$EraseOverlay, gargoyleData];
[mapPoint, feature] ← GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, environ, gargoyleData];
UpdateCaretToFeature[gargoyleData, mapPoint, feature];
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 PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
The rotating is done. Update all of the drag entities with the full rotation and repaint the entire scene.
IF NOT CheckProperAction[gargoyleData, $Scale] THEN RETURN;
UpdateSelectedAfterMove[gargoyleData];
GGRefresh.MoveOverlayToBackground[gargoyleData];
gargoyleData.currentAction ← $None;
GGWindow.Painter[$PaintSelectedRegion, gargoyleData];
};
NearestTrajectory: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj] = {
objectBag: ObjectBag;
feature: FeatureData;
resultPoint: Point;
objectBag ← GGGravity.CreateObjectBag[];
GGAlign.AddItemsForAction[gargoyleData, objectBag, $SelectTrajectory];
[resultPoint, feature] ← GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, objectBag];
traj ← IF feature = NIL THEN NIL ELSE feature.tseq.traj;
};
StartSelectTrajectory: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
Deselect all selections. Locate the nearest trajectory and make it the one and only selected entity.
nearTraj: Traj;
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR;
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGSelect.DeselectAll[gargoyleData, normal];
nearTraj ← NearestTrajectory[gargoyleData.scene, worldPt, gargoyleData];
IF nearTraj # NIL THEN {
GGSelect.SelectTraj[nearTraj, gargoyleData, normal];
GGError.Append["Trajectory Selected", oneLiner];
gargoyleData.extendMode ← traj;
}
ELSE {
GGError.Append["No near trajectory found.", oneLiner];
};
GGWindow.Painter[$PaintEntireScene, gargoyleData];
};
StartCopySelectTrajectory: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
Deselect all cpy selections. Locate the nearest trajectory and make it the one and only copy selected entity.
nearTraj, newTraj: Traj;
newOutline, oldOutline: Outline;
translatePoint, caretPos, firstJointPos: Point;
translate: ImagerTransformation.Transformation;
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR;
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
GGSelect.DeselectAll[gargoyleData, copy];
nearTraj ← NearestTrajectory[gargoyleData.scene, worldPt, gargoyleData];
IF nearTraj # NIL THEN {
GGError.Append["Trajectory Copy Selected", oneLiner];
GGSelect.SelectTraj[nearTraj, gargoyleData, copy];
oldOutline ← GGObjects.OutlineOfTraj[nearTraj];
newTraj ← GGObjects.CopyTraj[nearTraj];
newOutline ← GGObjects.OutlineFromTraj[newTraj, oldOutline.lineEnds, oldOutline.fillColor];
GGObjects.AddOutline[gargoyleData.scene, newOutline, -1];
firstJointPos ← GGObjects.FetchJointPos[newTraj, 0];
caretPos ← GGCaret.GetPoint[gargoyleData.caret];
translatePoint ← GGVector.Sub[caretPos, firstJointPos];
translate ← ImagerTransformation.Translate[[translatePoint[1], translatePoint[2]]];
GGObjects.TransformTraj[newTraj, translate];
}
ELSE {
GGError.Append["No near trajectory found.", oneLiner];
};
GGWindow.Painter[$PaintEntireScene, gargoyleData];
};
StartExtend: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = {
nearTraj: Traj;
IF NOT CheckProperAction[gargoyleData, $None] THEN {
gargoyleData.currentAction ← $None;
RETURN;
};
IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR;
[] ← InputFocus.SetInputFocus[gargoyleData.actionArea];
SELECT gargoyleData.extendMode FROM
controlPoint, outline, cluster, joint, segment => {
GGError.Append["Extend selection is only implemented for trajectories.", oneLiner];
RETURN;
};
traj => {
nearTraj ← NearestTrajectory[gargoyleData.scene, worldPt, gargoyleData];
IF nearTraj # NIL THEN {
GGSelect.SelectTraj[nearTraj, gargoyleData, normal];
GGError.Append["Additional Trajectory Selected", oneLiner];
}
ELSE {
GGError.Append["No near trajectory found.", oneLiner];
};
};
ENDCASE => ERROR;
GGWindow.Painter[$PaintEntireScene, gargoyleData];
};
END.