diff --git a/src/App/TopPanel.tsx b/src/App/TopPanel.tsx index 0dc8fab..df25360 100644 --- a/src/App/TopPanel.tsx +++ b/src/App/TopPanel.tsx @@ -52,6 +52,13 @@ function PseudoStateIcon(props: {}) { ; } +function HistoryIcon(props: {kind: "shallow"|"deep"}) { + const w=20, h=20; + const text = props.kind === "shallow" ? "H" : "H*"; + return {text}; +} + + export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode, setMode}: TopPanelProps) { const [displayTime, setDisplayTime] = useState("0.000"); const [timescale, setTimescale] = useState(1); @@ -111,6 +118,8 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode ["and", "AND-states", ], ["or", "OR-states", ], ["pseudo", "pseudo-states", ], + ["shallow", "shallow history", ], + ["deep", "deep history", ], ["transition", "transitions", ], ["text", "text", <> T ], ] as [InsertMode, string, ReactElement][]).map(([m, hint, buttonTxt]) => diff --git a/src/VisualEditor/ArrowSVG.tsx b/src/VisualEditor/ArrowSVG.tsx index 3f4cd6b..98f2faa 100644 --- a/src/VisualEditor/ArrowSVG.tsx +++ b/src/VisualEditor/ArrowSVG.tsx @@ -31,7 +31,7 @@ export function ArrowSVG(props: { arrow: Arrow; selected: string[]; errors: stri data-parts="start end">{props.errors.join(' ')}} - - - - + {geomProps.map(([side, ps]) => <> + {(props.selected.includes(side) || props.highlight.includes(side)) && + } + + )} {textNode}; + }}> + {textNode} + {props.text.text} + ; } \ No newline at end of file diff --git a/src/VisualEditor/VisualEditor.css b/src/VisualEditor/VisualEditor.css index 27f6cf8..58a23e8 100644 --- a/src/VisualEditor/VisualEditor.css +++ b/src/VisualEditor/VisualEditor.css @@ -7,29 +7,16 @@ cursor: grabbing !important; } +/* do not render helpers while dragging something */ +.svgCanvas.dragging .helper:hover { + visibility: hidden !important; +} + .svgCanvas.active { background-color: rgb(255, 140, 0, 0.2); } -text, text.highlight { - user-select: none; - /* text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; */ - /* -webkit-text-stroke: 4px white; */ - paint-order: stroke; - stroke: white; - stroke-width: 4px; - stroke-linecap: butt; - stroke-linejoin: miter; - stroke-opacity: 1; - fill-opacity:1; - /* font-weight: 800; */ -} - -text.highlight { - fill: green; - font-weight: 600; -} - +/* rectangle drawn while a selection is being made */ .selecting { fill: blue; fill-opacity: 0.2; @@ -40,23 +27,12 @@ text.highlight { .rountangle { fill: white; - /* fill: none; */ stroke: black; stroke-width: 2px; } -.rountangle:hover { - /* stroke: blue; */ - /* stroke-opacity: 0.2; */ - /* fill: #eee; */ - /* stroke-width: 4px; */ - /* cursor: grab; */ -} - .rountangle.selected { fill: rgba(0, 0, 255, 0.2); - /* stroke: blue; - stroke-width: 4px; */ } .rountangle.error { stroke: rgb(230,0,0); @@ -72,32 +48,31 @@ text.highlight { cursor: grab; } -.lineHelper { +line.helper { stroke: rgba(0, 0, 0, 0); stroke-width: 16px; } -.lineHelper:hover:not(:active) { +line.helper:hover:not(:active) { stroke: blue; stroke-opacity: 0.2; cursor: grab; } -.pathHelper { +path.helper { fill: none; stroke: rgba(0, 0, 0, 0); stroke-width: 16px; } -.pathHelper:hover:not(:active) { +path.helper:hover:not(:active) { stroke: blue; stroke-opacity: 0.2; cursor: grab; } - -.circleHelper { +circle.helper { fill: rgba(0, 0, 0, 0); } -.circleHelper:hover:not(:active) { +circle.helper:hover:not(:active) { fill: blue; fill-opacity: 0.2; cursor: grab; @@ -118,12 +93,6 @@ text.highlight { stroke-width: 3px; } -/* .arrow.selected { - stroke: blue; - stroke-width: 4px; -} */ - - #arrowEnd { fill: context-stroke; } @@ -139,14 +108,43 @@ line.selected, circle.selected { stroke-width: 4px; } -text.selected, text.selected:hover { +.draggableText.selected, .draggableText.selected:hover { fill: blue; font-weight: 600; } -text:hover:not(:active) { +.draggableText:hover:not(:active) { fill: blue; cursor: grab; } +text.helper { + fill: rgba(0,0,0,0); + stroke: rgba(0,0,0,0); + stroke-width: 8px; +} +text.helper:hover { + stroke: blue; + stroke-opacity: 0.2; +} + +.draggableText, .draggableText.highlight { + user-select: none; + /* text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; */ + /* -webkit-text-stroke: 4px white; */ + paint-order: stroke; + stroke: white; + stroke-width: 4px; + stroke-linecap: butt; + stroke-linejoin: miter; + stroke-opacity: 1; + fill-opacity:1; + /* font-weight: 800; */ +} + +.draggableText.highlight { + fill: green; + font-weight: 600; +} + .highlight { stroke: green; @@ -156,7 +154,7 @@ text:hover:not(:active) { .arrow.error { stroke: rgb(230,0,0); } -text.error, tspan.error { +.draggableText.error, tspan.error { fill: rgb(230,0,0); font-weight: 600; } diff --git a/src/statecharts/concrete_syntax.ts b/src/statecharts/concrete_syntax.ts index 9f59385..1e35785 100644 --- a/src/statecharts/concrete_syntax.ts +++ b/src/statecharts/concrete_syntax.ts @@ -21,11 +21,18 @@ export type Arrow = { uid: string; } & Line2D; +export type History = { + uid: string; + kind: "shallow" | "deep"; + topLeft: Vec2D; +}; + export type VisualEditorState = { rountangles: Rountangle[]; texts: Text[]; arrows: Arrow[]; diamonds: Diamond[]; + history: History[]; nextID: number; }; @@ -34,19 +41,7 @@ export type RountanglePart = "left" | "top" | "right" | "bottom"; export type ArrowPart = "start" | "end"; export const emptyState: VisualEditorState = { - rountangles: [], texts: [], arrows: [], diamonds: [], nextID: 0, -}; - -export const onOffStateMachine = { - rountangles: [ - { uid: "0", topLeft: { x: 100, y: 100 }, size: { x: 100, y: 100 }, kind: "and" }, - { uid: "1", topLeft: { x: 100, y: 300 }, size: { x: 100, y: 100 }, kind: "and" }, - ], - texts: [], - arrows: [ - { uid: "2", start: { x: 150, y: 200 }, end: { x: 160, y: 300 } }, - ], - nextID: 3, + rountangles: [], texts: [], arrows: [], diamonds: [], history: [], nextID: 0, }; // used to find which rountangle an arrow connects to (src/tgt) diff --git a/src/statecharts/interpreter.ts b/src/statecharts/interpreter.ts index 556b0ef..2ef2e79 100644 --- a/src/statecharts/interpreter.ts +++ b/src/statecharts/interpreter.ts @@ -20,7 +20,7 @@ type EnteredScope = { enteredStates: Mode } & ActionScope; export function entryActions(simtime: number, state: ConcreteState, actionScope: ActionScope): ActionScope { // console.log('enter', stateDescription(state), '...'); let {environment, ...rest} = actionScope; - environment = environment.pushScope(); + // environment = environment.pushScope(); for (const action of state.entryActions) { ({environment, ...rest} = execAction(action, {environment, ...rest})); } @@ -52,7 +52,7 @@ export function exitActions(simtime: number, state: ConcreteState, actionScope: // remove all timers of 'state': return oldTimers.filter(([_, {state: s}]) => s !== state.uid); }, []); - environment = environment.popScope(); + // environment = environment.popScope(); return {...actionScope, environment}; } diff --git a/src/statecharts/runtime_types.ts b/src/statecharts/runtime_types.ts index c720953..8004cd0 100644 --- a/src/statecharts/runtime_types.ts +++ b/src/statecharts/runtime_types.ts @@ -17,8 +17,6 @@ export type TimerElapseEvent = { export type Mode = Set; // set of active states -// export type Environment = ReadonlyMap; // variable name -> value - export class Environment { scopes: ReadonlyMap[]; // array of nested scopes - scope at the back of the array is used first @@ -114,7 +112,6 @@ export type RaisedEvent = { param?: any, } - export type RaisedEvents = { internalEvents: RaisedEvent[]; outputEvents: RaisedEvent[];