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.