<> <> <<>> <> <> DIRECTORY Graphics USING [black, Box, DrawTo, GetBounds, SetColor, SetCP, SetFat, white], PathEditor USING [AddControlPoint, AddKnotToFront, AddKnotToRear, AdjustSegmentSplit, BooleanSpecification, ButtonHitProc, CloseActiveTrajectory, ConfirmSegmentSplit, CreatePathViewer, CurveType, DeleteActiveTrajectory, DeleteVertex, DrawInPathViewer, DrawProc, FixControlPoint, FlushImage, GetTrajectoryFile, HitTestCurves, HitTestVertices, MoveActiveVertex, NewTrajectory, OpenActiveTrajectory, PathData, Point, Redraw, SetActiveTrajectory, SetActiveVertex, SetContinuity, SplitSegment, StoreTrajectoryFile, TrajectoryIsEmpty, VertexType], Real USING [RoundI], Rope USING [ROPE], ViewerTools USING [GetSelectionContents]; PathBuilder: CEDAR PROGRAM IMPORTS Graphics, PathEditor, Real, ViewerTools = BEGIN OPEN PathEditor; handle: Handle _ NEW[SplineToolRec]; Handle: TYPE = REF SplineToolRec; SplineToolRec: TYPE = RECORD [ pathData: PathData, gridConstrain: BOOLEAN _ FALSE, redHeldProc: ButtonProc _ NIL, redUpProc: ButtonProc _ NIL ]; ButtonProc: TYPE = PROCEDURE [point: Point]; ButtonHit: ButtonHitProc = { IF handle.gridConstrain THEN [x, y] _ GridConstrain[x, y]; SELECT event FROM $NewImage => FlushImage[pathData: handle.pathData]; $Redraw => RedrawViewer; $GridOn => SetGridConstraints[TRUE]; $GridOff => SetGridConstraints[FALSE]; $Get => GetTrajectories[]; $Store => StoreTrajectories[]; $NewTraj => NewTrajectory[pathData: handle.pathData]; $CloseTraj => CloseActiveTrajectory[pathData: handle.pathData]; $OpenTraj => OpenActiveTrajectory[pathData: handle.pathData]; $SetActiveVertex => [] _ SetActiveVertex[pathData: handle.pathData, where: [x, y]]; $MoveActiveVertex => MoveActiveVertex[pathData: handle.pathData, newPosition: [x, y]]; $SetConstraints => SetConstraints[point: [x, y]]; $SetActiveTrajectory => [] _ SetActiveTrajectory[pathData: handle.pathData, where: [x, y]]; $SetActiveTrajectory => [] _ SetActiveTrajectory[pathData: handle.pathData, where: [x, y]]; $AddKnot => AddKnot[point: [x, y]]; $MoveKnot => IF handle.redHeldProc # NIL THEN handle.redHeldProc[point: [x, y]]; $ConfirmMove => IF handle.redUpProc # NIL THEN handle.redUpProc[point: [x, y]]; $AddControlPoint => AddControlPoint[pathData: handle.pathData, where: [x, y], position: [x, y]]; $MoveActiveVertex => MoveActiveVertex[pathData: handle.pathData, newPosition: [x, y]]; $AddTrajectory => AddTrajectory[point: [x, y]]; $MoveActiveVertex => MoveActiveVertex[pathData: handle.pathData, newPosition: [x, y]]; $DeleteVertex => DeleteVertex[pathData: handle.pathData, vertex: [x, y]]; $DeleteTrajectory => DeleteTrajectory[point: [x, y]]; ENDCASE; }; RedrawViewer: PROCEDURE [] = { <> DrawInPathViewer[handle.pathData, DrawGrid]; Redraw[pathData: handle.pathData]; }; gridOffsetX: REAL = 0.0; gridOffsetY: REAL = 0.0; gridResolutionX: REAL = 16.0; gridResolutionY: REAL = 16.0; GridConstrain: PROCEDURE [x, y: REAL] RETURNS [newX, newY: REAL] = { <> newX _ gridOffsetX + Real.RoundI[(x - gridOffsetX) / gridResolutionX] * gridResolutionX; newY _ gridOffsetY + Real.RoundI[(y - gridOffsetY) / gridResolutionY] * gridResolutionY; }; DrawGrid: DrawProc = { bounds: Graphics.Box _ Graphics.GetBounds[context]; startX, startY, x, y: REAL; [startX, startY] _ GridConstrain[bounds.xmin, bounds.ymin]; IF startX < bounds.xmin THEN startX _ startX + gridResolutionX; IF startY < bounds.ymin THEN startY _ startY + gridResolutionY; IF handle.gridConstrain THEN Graphics.SetColor[context, Graphics.black] ELSE Graphics.SetColor[context, Graphics.white]; [] _ Graphics.SetFat[context, TRUE]; FOR y _ startY, y + gridResolutionY UNTIL y > bounds.ymax DO FOR x _ startX, x + gridResolutionX UNTIL x > bounds.xmax DO Graphics.SetCP[context, x, y]; Graphics.DrawTo[context, x, y]; ENDLOOP; ENDLOOP; }; SetGridConstraints: PROCEDURE [on: BOOLEAN] = { <> handle.gridConstrain _ on; DrawInPathViewer[handle.pathData, DrawGrid]; }; GetTrajectories: PROCEDURE [] = { <> fileName: Rope.ROPE _ ViewerTools.GetSelectionContents[]; GetTrajectoryFile[pathData: handle.pathData, fileName: fileName]; }; StoreTrajectories: PROCEDURE [] = { <> fileName: Rope.ROPE _ ViewerTools.GetSelectionContents[]; StoreTrajectoryFile[pathData: handle.pathData, fileName: fileName]; }; AddKnot: ButtonProc = { <> closeToVertex, closeToCurve: BOOLEAN; vertexType: VertexType; curveType: CurveType; [closeToVertex, vertexType,] _ HitTestVertices[pathData: handle.pathData, where: point]; SELECT TRUE FROM TrajectoryIsEmpty[handle.pathData] OR (closeToVertex AND vertexType = frontKnot) => { handle.redHeldProc _ MoveVertex; handle.redUpProc _ NIL; AddKnotToFront[pathData: handle.pathData, position: point]; }; closeToVertex AND vertexType = rearKnot => { handle.redHeldProc _ MoveVertex; handle.redUpProc _ NIL; AddKnotToRear[pathData: handle.pathData, position: point]; }; ENDCASE => { [closeToCurve, curveType,] _ HitTestCurves[pathData: handle.pathData, where: point]; IF ~closeToCurve THEN { handle.redHeldProc _ NIL; handle.redUpProc _ NIL; } ELSE { handle.redHeldProc _ MoveSplit; handle.redUpProc _ ConfirmSplit; SplitSegment[pathData: handle.pathData, where: point]; }; }; }; AddTrajectory: ButtonProc = { <> NewTrajectory[pathData: handle.pathData]; AddKnot[point: point]; }; MoveVertex: ButtonProc = { <> MoveActiveVertex[pathData: handle.pathData, newPosition: point]; }; MoveSplit: ButtonProc = { <> AdjustSegmentSplit[pathData: handle.pathData, where: point]; }; ConfirmSplit: ButtonProc = { <> ConfirmSegmentSplit[pathData: handle.pathData]; handle.redHeldProc _ NIL; handle.redUpProc _ NIL; }; SetConstraints: ButtonProc = { <> closeToVertex: BOOLEAN; vertexType: VertexType; [closeToVertex, vertexType,] _ HitTestVertices[pathData: handle.pathData, where: point]; SELECT TRUE FROM closeToVertex AND vertexType = controlPoint => { FixControlPoint[pathData: handle.pathData, controlPoint: point, fixed: toggle]; }; closeToVertex => { SetContinuity[pathData: handle.pathData, knot: point, continuity: toggle]; }; ENDCASE; }; DeleteTrajectory: ButtonProc = { <> [] _ SetActiveTrajectory[pathData: handle.pathData, where: point]; DeleteActiveTrajectory[pathData: handle.pathData]; }; [handle.pathData] _ CreatePathViewer[ name: "Path Tool", menuLabels: LIST[ [$NewImage, TRUE], [$Redraw, FALSE], [$GridOn, FALSE], [$GridOff, FALSE], [$Get, TRUE], [$Store, TRUE], [$NewTraj, FALSE], [$CloseTraj, FALSE], [$OpenTraj, FALSE] ], buttonProc: ButtonHit, redrawProc: DrawGrid ]; END. <<>>