DIRECTORY GGBasicTypes, GGCaret, GGDescribe, GGError, GGGravity, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGMultiGravity, GGObjects, GGOutline, GGRefresh, GGSegmentTypes, GGSelect, GGSequence, GGTraj, GGUtility, GGVector, GGWindow, ImagerTransformation, InputFocus, Menus, Rope; GGMouseEventImplB: CEDAR PROGRAM IMPORTS GGCaret, GGDescribe, GGError, GGGravity, GGMouseEvent, GGMultiGravity, GGObjects, GGOutline, GGRefresh, GGSelect, GGSequence, GGTraj, GGVector, GGWindow, ImagerTransformation, InputFocus EXPORTS GGMouseEvent = BEGIN AlignmentPoint: TYPE = GGInterfaceTypes.AlignmentPoint; BoundBox: TYPE = GGModelTypes.BoundBox; Caret: TYPE = GGInterfaceTypes.Caret; EntityGenerator: TYPE = GGModelTypes.EntityGenerator; FeatureData: TYPE = GGInterfaceTypes.FeatureData; GargoyleData: TYPE = GGInterfaceTypes.GargoyleData; Joint: TYPE = GGModelTypes.Joint; MouseButton: TYPE = Menus.MouseButton; MouseProc: TYPE = GGMouseEvent.MouseProc; ObjectBag: TYPE = GGInterfaceTypes.ObjectBag; Outline: TYPE = GGModelTypes.Outline; OutlineDescriptor: TYPE = REF OutlineDescriptorObj; OutlineDescriptorObj: TYPE = GGModelTypes.OutlineDescriptorObj; Point: TYPE = GGBasicTypes.Point; ResultFeatureType: TYPE = GGModelTypes.ResultFeatureType; Scene: TYPE = GGModelTypes.Scene; SelectMode: TYPE = GGModelTypes.SelectMode; Segment: TYPE = GGSegmentTypes.Segment; Sequence: TYPE = GGModelTypes.Sequence; Slice: TYPE = GGModelTypes.Slice; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceGenerator: TYPE = GGModelTypes.SliceGenerator; SliceClass: TYPE = GGModelTypes.SliceClass; SliceParts: TYPE = GGModelTypes.SliceParts; StartProc: TYPE = GGMouseEvent.StartProc; TouchGroup: TYPE = GGSegmentTypes.TouchGroup; Traj: TYPE = GGModelTypes.Traj; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajGenerator: TYPE = GGModelTypes.TrajGenerator; Vector: TYPE = GGBasicTypes.Vector; Problem: SIGNAL [msg: Rope.ROPE] = GGError.Problem; SaveSavedState: PROC [gargoyleData: GargoyleData] = { GGObjects.SaveSelections[gargoyleData.scene]; GGWindow.SaveCaretPos[gargoyleData]; GGCaret.Copy[from: gargoyleData.caret, to: gargoyleData.drag.savedCaret]; }; DescribeSelectionAction: PUBLIC PROC [gargoyleData: GargoyleData, feature: FeatureData, selectMode: SelectMode, action: Rope.ROPE] = { GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[action]], [rope[GGDescribe.DescribeFeature[feature, gargoyleData]]]]; }; SetCaretAttractorEndpoint: PUBLIC PROC [gargoyleData: GargoyleData, mapPoint: Point, feature: FeatureData] = { IF feature=NIL THEN GGCaret.SetAttractor[gargoyleData.caret, mapPoint, NIL] ELSE { resultType: ResultFeatureType _ feature.resultType; shape: REF ANY _ feature.shape; SELECT resultType FROM outline => { traj: Traj; hitType: GGModelTypes.TrajPartType; segNum, cpNum, jointNum: INT; hitPoint, thisPoint: Point; jointSeq: Sequence; jointParts: SliceParts; jointD: OutlineDescriptor; outlineD: OutlineDescriptor _ NARROW[shape]; [traj, hitType, segNum, cpNum, jointNum, hitPoint] _ GGOutline.UnpackHitData[feature.hitPart]; SELECT hitType FROM joint => { jointSeq _ GGSequence.CreateFromJoint[traj, jointNum]; thisPoint _ mapPoint; }; controlPoint => { jointSeq _ GGSequence.CreateFromControlPoint[traj, segNum, cpNum]; thisPoint _ mapPoint; }; segment => { success: BOOL; jointPoint, cpPoint: Point; seg: Segment _ GGTraj.FetchSegment[traj, segNum]; [jointNum, ----] _ GGOutline.NearestJointToHitData[feature.hitPart]; jointPoint _ GGTraj.FetchJointPos[traj, jointNum]; [cpPoint, cpNum, success] _ seg.class.closestControlPoint[seg, mapPoint, GGUtility.plusInfinity]; IF NOT success THEN { -- its a joint for sure jointSeq _ GGSequence.CreateFromJoint[traj, jointNum]; thisPoint _ jointPoint; } ELSE { -- could be a cp instead of a joint cpDist: REAL _ GGVector.DistanceSquared[cpPoint, mapPoint]; jointDist: REAL _ GGVector.DistanceSquared[jointPoint, mapPoint]; tisAJoint: BOOL _ jointDist <= cpDist; IF tisAJoint THEN { jointSeq _ GGSequence.CreateFromJoint[traj, jointNum]; thisPoint _ jointPoint; } ELSE { jointSeq _ GGSequence.CreateFromControlPoint[traj, segNum, cpNum]; thisPoint _ cpPoint; }; }; }; ENDCASE => ERROR; jointParts _ GGOutline.PartsFromSequence[outlineD.slice, jointSeq]; jointD _ NEW[OutlineDescriptorObj _ [outlineD.slice, jointParts]]; GGCaret.SetAttractor[gargoyleData.caret, thisPoint, jointD]; }; slice => { pos: Point; sliceD: SliceDescriptor _ NARROW[shape]; IF sliceD.slice.class.type = $Outline THEN ERROR Problem[msg: "Outlines are Slices"]; [pos, ----, ----, ----] _ sliceD.slice.class.closestPoint[sliceD, mapPoint, GGUtility.plusInfinity]; GGCaret.SetAttractor[gargoyleData.caret, pos, sliceD]; }; ENDCASE => GGCaret.SetAttractor[gargoyleData.caret, mapPoint, NIL]; }; }; StartDeselectJoint: PUBLIC StartProc = { gargoyleData.drag.selectState _ joint; StartDeselectAux[gargoyleData, worldPt, NIL]; DuringDeselect[NIL, gargoyleData, worldPt]; }; StartDeselectSegment: PUBLIC StartProc = { gargoyleData.drag.selectState _ segment; StartDeselectAux[gargoyleData, worldPt, NIL]; DuringDeselect[NIL, gargoyleData, worldPt]; }; StartDeselectTrajectory: PUBLIC StartProc = { gargoyleData.drag.selectState _ traj; StartDeselectAux[gargoyleData, worldPt, NIL]; DuringDeselect[NIL, gargoyleData, worldPt]; }; StartDeselectTopLevel: PUBLIC StartProc = { gargoyleData.drag.selectState _ topLevel; StartDeselectAux[gargoyleData, worldPt, NIL]; DuringDeselect[NIL, gargoyleData, worldPt]; }; StartDeselectAux: PROC [gargoyleData: GargoyleData, worldPt: Point, startBox: BoundBox] = { IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; SaveSavedState[gargoyleData]; gargoyleData.drag.currentPoint _ worldPt; -- this line moved from individual Start code gargoyleData.drag.transform _ ImagerTransformation.Scale[1.0]; -- needed for DuringDeselect to work properly }; DuringDeselect: PUBLIC MouseProc = { resultPoint: Point; feature: FeatureData; gargoyleData.drag.currentPoint _ worldPt; IF gargoyleData.drag.selectState = joint THEN [resultPoint, feature] _ GGMultiGravity.InnerCircle[worldPt, gargoyleData.hitTest.criticalR, gargoyleData.hitTest.innerR, GGGravity.emptyObjectBag, gargoyleData.hitTest.sceneTriggerBag, gargoyleData, FALSE] ELSE [resultPoint, feature] _ GGMultiGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, GGGravity.emptyObjectBag, gargoyleData.hitTest.sceneTriggerBag, gargoyleData]; SetCaretAttractorEndpoint[gargoyleData, resultPoint, feature]; DescribeSelectionAction[gargoyleData, feature, gargoyleData.drag.selectState, "Deselecting"]; GGObjects.RestoreSelections[gargoyleData.scene]; IF feature = NIL THEN { -- no near trajectories, do nothing } ELSE { DuringDeselectAux[feature, resultPoint, gargoyleData.scene, gargoyleData.drag.selectState]; }; GGWindow.RestoreScreenAndInvariants[paintAction: $DuringSelect, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; -- end DuringDeselect DuringDeselectAux: PROC [feature: FeatureData, caretPt: Point, scene: Scene, mode: GGModelTypes.SelectMode] = { SELECT feature.resultType FROM outline => { [] _ OutlineDeselectAux[feature, mode, scene]; }; slice => { [] _ SliceDeselectAux[feature, mode, scene]; }; midpoint => NULL; -- temporarily ignore midpoints in bags. KAP. June 26, 1986 slopeLine, angleLine, distanceLine, intersectionPoint, radiiCircle, midpoint => ERROR; ENDCASE => ERROR; }; SliceDeselectAux: PROC [feature: FeatureData, mode: GGModelTypes.SelectMode, scene: Scene] RETURNS [deselectedParts: SliceParts] = { sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[NARROW[feature.shape, SliceDescriptor].slice, scene, normal]; IF sliceD#NIL THEN { class: SliceClass _ sliceD.slice.class; newP: SliceParts _ class.newParts[sliceD.slice, feature.hitPart, mode]; diffP: SliceParts _ class.differenceParts[sliceD.slice, sliceD.parts, newP]; GGSelect.DeselectSlice[sliceD.slice, sliceD.parts, scene, normal]; IF NOT class.emptyParts[sliceD.slice, diffP] THEN GGSelect.SelectSlice[sliceD.slice, diffP, scene, normal]; deselectedParts _ newP; }; }; OutlineDeselectAux: PROC [feature: FeatureData, mode: GGModelTypes.SelectMode, scene: Scene] RETURNS [deselectedParts: SliceParts] = { sliceD: OutlineDescriptor _ GGSelect.FindSelectedOutline[NARROW[feature.shape, OutlineDescriptor].slice, scene, normal]; IF sliceD#NIL THEN { class: GGModelTypes.OutlineClass _ sliceD.slice.class; newP: SliceParts _ class.newParts[sliceD.slice, feature.hitPart, mode]; diffP: SliceParts _ class.differenceParts[sliceD.slice, sliceD.parts, newP]; GGSelect.DeselectOutline[sliceD.slice, sliceD.parts, scene, normal]; IF NOT class.emptyParts[sliceD.slice, diffP] THEN GGSelect.SelectOutline[sliceD.slice, diffP, scene, normal]; deselectedParts _ newP; }; }; EndDeselect: PUBLIC MouseProc = { resultPoint: Point; feature: FeatureData; IF gargoyleData.drag.selectState = joint THEN [resultPoint, feature] _ GGMultiGravity.InnerCircle[worldPt, gargoyleData.hitTest.criticalR, gargoyleData.hitTest.innerR, GGGravity.emptyObjectBag, gargoyleData.hitTest.sceneTriggerBag, gargoyleData, FALSE] ELSE [resultPoint, feature] _ GGMultiGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, GGGravity.emptyObjectBag, gargoyleData.hitTest.sceneTriggerBag, gargoyleData]; SetCaretAttractorEndpoint[gargoyleData, resultPoint, feature]; GGWindow.NewCaretPos[gargoyleData]; GGRefresh.MoveOverlayToBackground[gargoyleData]; SELECT gargoyleData.drag.selectState FROM joint => EndDeselectJoint[gargoyleData, resultPoint, feature]; segment => EndDeselectSegment[gargoyleData, resultPoint, feature]; traj => EndDeselectTrajectory[gargoyleData, resultPoint, feature]; topLevel => EndDeselectTopLevel[gargoyleData, resultPoint, feature]; ENDCASE => ERROR; gargoyleData.drag.selectState _ none; -- added to help DescribeFeature work. KAP. }; EndDeselectJoint: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { GGCaret.SitOn[gargoyleData.caret, NIL]; IF feature = NIL THEN { GGError.AppendHerald[gargoyleData.feedback, "No near joint found.", oneLiner]; } ELSE { SELECT feature.resultType FROM outline => { gone: SliceParts _ OutlineDeselectAux[feature, joint, gargoyleData.scene]; sliceD: OutlineDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; slice => { gone: SliceParts _ SliceDeselectAux[feature, joint, gargoyleData.scene]; sliceD: SliceDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; ENDCASE => SIGNAL Problem [msg: "Unexpected feature type for deselect joint."]; }; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; -- end EndDeselectJoint EndDeselectSegment: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { GGCaret.SitOn[gargoyleData.caret, NIL]; IF feature = NIL THEN { -- do nothing GGError.AppendHerald[gargoyleData.feedback, "No near segment found.", oneLiner]; } ELSE { SELECT feature.resultType FROM outline => { gone: SliceParts _ OutlineDeselectAux[feature, segment, gargoyleData.scene]; sliceD: OutlineDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; slice => { gone: SliceParts _ SliceDeselectAux[feature, segment, gargoyleData.scene]; sliceD: SliceDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; ENDCASE => SIGNAL Problem [msg: "Unexpected feature type for deselect segment."]; }; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; -- end EndDeselectSegment EndDeselectTrajectory: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { GGCaret.SitOn[gargoyleData.caret, NIL]; IF feature = NIL THEN { GGError.AppendHerald[gargoyleData.feedback, "No near trajectory found.", oneLiner]; } ELSE { SELECT feature.type FROM outline => { gone: SliceParts _ OutlineDeselectAux[feature, traj, gargoyleData.scene]; sliceD: OutlineDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; slice => { gone: SliceParts _ SliceDeselectAux[feature, traj, gargoyleData.scene]; sliceD: SliceDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; ENDCASE => SIGNAL Problem [msg: "Unexpected feature type for deselect trajectory."]; }; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; -- end EndDeselectTrajectory EndDeselectTopLevel: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { GGCaret.SitOn[gargoyleData.caret, NIL]; IF feature = NIL THEN { GGError.AppendHerald[gargoyleData.feedback, "No near object found.", oneLiner]; } ELSE { SELECT feature.resultType FROM outline => { gone: SliceParts _ OutlineDeselectAux[feature, topLevel, gargoyleData.scene]; sliceD: OutlineDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; slice => { gone: SliceParts _ SliceDeselectAux[feature, topLevel, gargoyleData.scene]; sliceD: SliceDescriptor _ NARROW[feature.shape]; IF gone#NIL THEN GGError.PutFHerald[gargoyleData.feedback, oneLiner, "%g %g", [rope[sliceD.slice.class.describe[sliceD.slice, gone]]], [rope[" deselected"]]]; }; ENDCASE => SIGNAL Problem [msg: "Unexpected feature type for deselect top level."]; }; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; StartExtendSelectJoint: PUBLIC StartProc = { gargoyleData.drag.extendMode _ joint; RETURN[StartExtendSelection[input, gargoyleData, worldPt]]; }; StartExtendSelectSegment: PUBLIC StartProc = { gargoyleData.drag.extendMode _ segment; RETURN[StartExtendSelection[input, gargoyleData, worldPt]]; }; StartExtendSelectTraj: PUBLIC StartProc = { gargoyleData.drag.extendMode _ traj; RETURN[StartExtendSelection[input, gargoyleData, worldPt]]; }; StartExtendSelectTopLevel: PUBLIC StartProc = { gargoyleData.drag.extendMode _ topLevel; RETURN[StartExtendSelection[input, gargoyleData, worldPt]]; }; StartExtendSelection: PUBLIC StartProc = { IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur gargoyleData.drag.currentPoint _ worldPt; gargoyleData.drag.transform _ ImagerTransformation.Scale[1.0]; DuringExtendSelection[NIL, gargoyleData, worldPt]; }; DuringExtendSelection: PUBLIC MouseProc= { resultPoint: Point; feature: FeatureData; IF gargoyleData.drag.extendMode = joint THEN [resultPoint, feature] _ GGMultiGravity.InnerCircle[worldPt, gargoyleData.hitTest.criticalR, gargoyleData.hitTest.innerR, GGGravity.emptyObjectBag, gargoyleData.hitTest.sceneTriggerBag, gargoyleData, FALSE] ELSE [resultPoint, feature] _ GGMultiGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, GGGravity.emptyObjectBag, gargoyleData.hitTest.sceneTriggerBag, gargoyleData]; SetCaretAttractorEndpoint[gargoyleData, resultPoint, feature]; DescribeSelectionAction[gargoyleData, feature, gargoyleData.drag.extendMode, "Extending to"]; GGSelect.DeselectAll[gargoyleData.scene, normal]; -- MUST CLEAR OUT TRANSIENT SELECTIONS! GGObjects.RestoreSelections[gargoyleData.scene]; IF feature = NIL THEN { -- no near trajectories, do nothing } ELSE { SELECT gargoyleData.drag.extendMode FROM joint, segment, segmentRange, traj, topLevel => DuringExtendSelectionFeedback[feature, resultPoint, gargoyleData]; outline, slice, none => NULL; -- NoOp ENDCASE => ERROR; }; GGWindow.RestoreScreenAndInvariants[paintAction: $DuringSelect, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; -- end DuringExtendSelection DuringExtendSelectionFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = { SELECT gargoyleData.drag.extendMode FROM joint => GGMouseEvent.SelectJointOrCP[feature, caretPt, gargoyleData]; segment => GGMouseEvent.SelectSegment[feature, caretPt, gargoyleData]; segmentRange => { IF feature.resultType = outline THEN { traj, trajToExtend: Traj; hitType: GGModelTypes.TrajPartType; segNum, cpNum, jointNum, segToExtendNum: INT; hitPoint: Point; [traj, hitType, segNum, cpNum, jointNum, hitPoint] _ GGOutline.UnpackHitData[feature.hitPart]; IF (hitType = segment OR hitType = controlPoint) THEN { [trajToExtend, segToExtendNum] _ GGOutline.UnpackOneSegmentDescriptorOld[gargoyleData.drag.outlineToExtend]; IF trajToExtend = traj THEN { -- we're extending segments in the same trajectory seq: Sequence _ GGSequence.CreateFromSegments[traj, segToExtendNum, segNum]; [] _ GGSelect.SelectSequence[seq, gargoyleData.scene, normal]; -- select current hit } ELSE GOTO RegularSelectMechanism; } ELSE GOTO RegularSelectMechanism; } ELSE GOTO RegularSelectMechanism; EXITS RegularSelectMechanism => GGMouseEvent.SelectSegment[feature, caretPt, gargoyleData]; }; traj => GGMouseEvent.SelectTraj[feature: feature, caretPt: caretPt, gargoyleData: gargoyleData]; topLevel => GGMouseEvent.SelectTopLevel[feature: feature, caretPt: caretPt, gargoyleData: gargoyleData]; ENDCASE => ERROR; -- should have been weeded about before calling this Proc }; EndExtendSelection: PUBLIC MouseProc = { GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; -- end EndExtendSelection END. €GGMouseEventImplB.mesa Last edited by Pier on November 21, 1986 3:10:19 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. Bier, January 27, 1987 1:07:13 am PST Kurlander July 17, 1986 2:40:45 pm PDT Deselection Procs 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 deselect hightlight the joint which the caret is snapped to. The final result is to deselect a single joint selection. If the final position of the cursor is too far from any line, do nothing. Refresh strategy: the selections are moved to the overlay, then DuringDeselect the hit joints are deselected. Since the selection is on the overlay, it's joints will be highlighted, preserving selection feedback, except for the transiently deselected joint. GGAlign.SetBagsForAction[gargoyleData, $Select]; Common Deselect Operations. 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 a single segment selection. If the final position of the cursor is too far from any line, do nothing. Refresh strategy: the selections are moved to the overlay, then DuringDeselect the hit segments are deselected. Since the selection is on the overlay, it's segments will be highlighted, preserving selection feedback, except for the transiently deselected segments. Common Deselect Operations. 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 a single trajectory selection. If the final position of the cursor is too far from any line, do nothing. Refresh strategy: the selections are moved to the overlay, then DuringDeselect the hit trajectories are deselected. Since the selection is on the overlay, it's trajectories will be highlighted, preserving selection feedback, except for the transiently deselected trajectories. GGAlign.SetBagsForAction[gargoyleData, $Select]; Common Deselect Operations. GGAlign.SetBagsForAction[gargoyleData, $SelectTopLevel]; Common Deselect Operations. Check overlay invariant and grab input focus. Measure caret coordinates. While a joint, cp, segment, traj, or top level object is being deselected, gravity is forced to be StrictDistance. The object bag should consist only of trajectories and slices. 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 removing highlighted joints or cps. Use StrictDistance gravity except for DeselectJoint. Put Caret on a joint (if any). Reselect all. Put Caret on a joint (if any). Clear the overlay plane. And Dispatch to the proper EndDeselect handler. Extend selection procs. Put Caret on a joint (if any). Reselect all. depending on extend mode, check if feature is in that mode, then select the new feature. GGCaret.DoNotSit[gargoyleData.caret]; -- simple but not always correct thing to do simply paint new selections directly Κ<˜Icode™Kšœ7™7™…Kšœ%™%K™&—K™šΟk ˜ Kšœ‘˜‘K˜K˜—šΟnœœ˜ KšœΌ˜ΓKšœ˜—˜Kšœœ#˜7Kšœ œ˜'Kšœœ˜%Kšœœ ˜5Kšœ œ ˜1Kšœœ!˜3Kšœœ˜!Kšœ œ˜&Kšœ œ˜)Kšœ œ˜-Kšœ œœ˜%Kšœœœ˜3Kšœœ%˜?Kšœœ˜!Kšœœ"˜9Kšœœ˜!Kšœ œ˜+Kšœ œ˜'Kšœ œ˜'Kšœœ˜!Kšœœ ˜5Kšœœ˜3Kšœ œ˜+Kšœ œ˜+Kšœ œ˜)Kšœ œ˜-Kšœœ˜Kšœ œ˜%Kšœœ˜1Kšœœ˜#—K˜Kšžœœ œ˜3K˜šžœœ!˜5Kšœ-˜-Kšœ$˜$K˜IK˜K˜—šžœœœYœ˜†Kšœˆ˜ˆK˜K˜—šžœœœH˜nš œ œœ4œœ˜RKšœ3˜3Kšœœœ˜šœ ˜˜ K˜ Kšœ#˜#Kšœœ˜Kšœ˜K˜K˜K˜Kšœœ˜,Kšœ^˜^šœ ˜šœ ˜ Kšœ6˜6Kšœ˜K˜—šœ˜KšœB˜BKšœ˜K˜—šœ ˜ Kšœ œ˜Kšœ˜Kšœ1˜1KšœD˜DKšœ2˜2Kšœa˜ašœœ œΟc˜-Kšœ6˜6Kšœ˜K˜—šœŸ#˜*Kšœœ/˜;Kšœ œ2˜AKšœ œ˜&šœ ˜ šœ˜Kšœ6˜6Kšœ˜K˜—šœ˜KšœB˜BKšœ˜K˜——K˜—K˜—Kšœœ˜—KšœC˜CKšœ œ6˜BKšœ<˜šžœF˜]Kš  ™ —Kšœ  œ˜0šœ œœŸ#˜;K˜—šœ˜Kšœ[˜[K˜—Kšœuœ œœ˜€KšœŸ˜K˜—šžœœX˜ošœ˜˜ Kšœ.˜.K˜—šœ ˜ Kšœ,˜,Kšœ˜—Kšœ œŸ;˜MKšœPœ˜VKšœœ˜—Kšœ˜K˜—šžœœEœ"˜„Kšœ5œ7˜ršœœœ˜Kšœ'˜'KšœG˜GKšœL˜LKšœB˜BKšœœ'œ:˜kKšœ˜K˜—K˜K˜K˜—šžœœEœ"˜†Kšœ9œ9˜xšœœœ˜Kšœ6˜6KšœG˜GKšœL˜LKšœD˜DKšœœ'œ<˜mKšœ˜K˜—K˜K˜—šž œœ˜!Kšœ˜Kšœ˜šœ'˜-KšœΘœ˜Ξ—šœ―˜³Kš ™—Kšžœ%˜>šœ#˜#Kš ™—šœ  œ˜0Kš /™/—šœ˜)Kšœ>˜>KšœB˜BKšœB˜BKšœD˜DKšœœ˜—Kšœ&Ÿ+˜QK˜K˜—šžœœK˜aKšœ"œ˜'šœ œœ˜KšœN˜NKšœ˜—šœ˜šœ˜šœ ˜ KšœJ˜JKšœœ˜2KšœœœŽ˜žKšœ˜—šœ ˜ KšœH˜HKšœœ˜0KšœœœŽ˜žKšœ˜—Kšœœ>˜O—Kšœ˜—Kšœyœ œœ˜¨KšœŸ˜K˜—šžœœK˜cKšœ"œ˜'šœ œœŸ ˜%KšœP˜PK˜—šœ˜šœ˜šœ ˜ KšœL˜LKšœœ˜2KšœœœŽ˜žK˜—šœ ˜ KšœJ˜JKšœœ˜0KšœœœŽ˜žK˜—Kšœœ@˜Q—K˜—Kšœyœ œœ˜¨KšœŸ˜K˜—šžœœK˜fKšœ"œ˜'šœ œœ˜KšœS˜SK˜—šœ˜šœ˜šœ ˜ KšœI˜IKšœœ˜2KšœœœŽ˜žK˜—˜ KšœG˜GKšœœ˜0KšœœœŽ˜žKšœ˜—KšœœC˜T—Kšœ˜—Kšœyœ œœ˜¨KšœŸ˜K˜—šžœœK˜dKšœ"œ˜'šœ œœ˜KšœO˜OK˜—šœ˜šœ˜šœ ˜ KšœM˜MKšœœ˜2KšœœœŽ˜žK˜—˜ KšœK˜KKšœœ˜0KšœœœŽ˜žK˜—KšœœB˜S—K˜—Kšœyœ œœ˜¨Kšœ˜—K™K™šž Πbnžœœ˜,Kšœ%˜%Kšœ5˜;K˜K˜—šž ‘žœœ˜.Kšœ'˜'Kšœ5˜;K˜K˜—šž ‘žœœ˜+Kšœ$˜$Kšœ5˜;K˜K˜—šž ‘žœœ˜/Kšœ(˜(Kšœ5˜;K˜K˜—šžœœ˜*Kšœœ&œœ˜7Kšœ7˜7KšœŸ0˜NKšœ)˜)Kšœ>˜>Kšœœ˜2K˜K˜—šžœœ ˜*Kšœ˜Kšœ˜šœ&˜,KšœΘœ˜Ξ—šœ―˜³Kš ™—Kšžœ%˜>šžœF˜]Kš  ™ —Kšœ2Ÿ'˜YKšœ  œ˜0šœ œœŸ#˜;K˜—šœ˜šœ˜(Kšœs˜sKšœœŸ˜'Kšœœ˜—K˜—Kšœuœ œœ˜€Kšœ˜K˜—šžœœG˜jK™Xšœ˜(KšœF˜FKšœF˜Fšœ˜šœœ˜&Kšœ˜Kšœ#˜#Kšœ)œ˜-Kšœ˜Kšœ^˜^šœœœ˜7Kšœl˜lšœœŸ2˜PKšœL˜LKšœ?Ÿ˜TK˜—Kšœœ˜!K˜—Kšœœ˜!K˜—Kšœœ˜!š˜KšœU˜U—K˜—Kšœ`˜`Kšœh˜hKšœœŸ9˜K—K˜K˜—šž‘œœ˜(Kšœ&Ÿ-™SK™$Kšœyœ œœ˜¨KšœŸ˜—K˜Kšœ˜—…—HteT