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 ;
+}
+
+
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[];