better looking simulation

This commit is contained in:
Joeri Exelmans 2025-10-18 22:43:52 +02:00
parent f80086727c
commit b8bc977a8e
6 changed files with 44 additions and 39 deletions

View file

@ -1,11 +1,17 @@
details.active {
background-color: rgba(255, 140, 0, 0.2);
/* background-color: rgba(128, 72, 0, 0.855);
color: white; */
box-shadow: rgba(128, 72, 0, 0.856) 0 0 8;
}
details {
border: 1px black solid;
/* border-radius: 5px; */
background-color: white;
margin-bottom: 2px;
margin-bottom: 4px;
padding-right: 2px;
color: black;
width: fit-content;
border-radius: 10px;
}

View file

@ -58,13 +58,10 @@ export function App() {
if (refRightSideBar.current) {
const el = refRightSideBar.current;
// console.log('scrolling to', el.scrollHeight);
console.log('scrolling to:', el);
setTimeout(() => {
el.scrollIntoView({block: "end", behavior: "smooth"});
}, 100);
// el.scrollTo(0, el.scrollHeight+1000);
}
}
@ -106,6 +103,11 @@ export function App() {
};
}, []);
const highlightActive = (rtIdx !== undefined) && new Set([...rt[rtIdx].mode].filter(uid => {
const state = ast.uid2State.get(uid);
return state && state.parent?.kind !== "and";
})) || new Set();
return <Stack sx={{height:'100vh'}}>
{/* Top bar */}
<Box
@ -123,7 +125,7 @@ export function App() {
<Stack direction="row" sx={{height:'calc(100vh - 64px)'}}>
{/* main */}
<Box sx={{flexGrow:1, overflow:'auto'}}>
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors, mode}}/>
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors, mode, highlightActive}}/>
</Box>
{/* right sidebar */}
<Box
@ -133,10 +135,8 @@ export function App() {
flex: '0 0 content',
height: 'calc(100vh-32px)',
overflow: "auto",
// paddingRight: 1,
// paddingLeft: 1,
}}>
<ShowAST {...{...ast, rt: rt.at(rtIdx!)}}/>
<ShowAST {...{...ast, rt: rt.at(rtIdx!), highlightActive}}/>
<br/>
<div ref={refRightSideBar}>
<RTHistory {...{ast, rt, rtIdx, setTime, setRTIdx, refRightSideBar}}/>

View file

@ -1,4 +1,4 @@
import { Dispatch, SetStateAction } from "react";
import { Dispatch, Ref, SetStateAction } from "react";
import { Statechart, stateDescription } from "../statecharts/abstract_syntax";
import { BigStep, Environment, Mode, RaisedEvent } from "../statecharts/runtime_types";
import { formatTime } from "./util";
@ -10,23 +10,26 @@ type RTHistoryProps = {
ast: Statechart,
setRTIdx: Dispatch<SetStateAction<number|undefined>>,
setTime: Dispatch<SetStateAction<TimeMode>>,
refRightSideBar: Ref<HTMLDivElement>,
}
export function RTHistory({rt, rtIdx, ast, setRTIdx, setTime}: RTHistoryProps) {
export function RTHistory({rt, rtIdx, ast, setRTIdx, setTime, refRightSideBar}: RTHistoryProps) {
function gotoRt(idx: number, timestamp: number) {
setRTIdx(idx);
setTime({kind: "paused", simtime: timestamp});
}
return rt.map((rt, idx) => <>
<div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => gotoRt(idx, rt.simtime)}>
<div>({formatTime(rt.simtime)}, {rt.inputEvent || "<init>"})</div>
<ShowMode mode={rt.mode} statechart={ast}/>
<ShowEnvironment environment={rt.environment}/>
{rt.outputEvents.length>0 && <div>
{rt.outputEvents.map((e:RaisedEvent) => '^'+e.name).join(', ')}
</div>}
</div></>);
return <div>
{rt.map((r, idx) => <>
<div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => gotoRt(idx, r.simtime)}>
<div>({formatTime(r.simtime)}, {r.inputEvent || "<init>"})</div>
<ShowMode mode={r.mode} statechart={ast}/>
<ShowEnvironment environment={r.environment}/>
{r.outputEvents.length>0 && <div>
{r.outputEvents.map((e:RaisedEvent) => '^'+e.name).join(', ')}
</div>}
</div></>)}
</div>;
}
@ -45,13 +48,5 @@ function ShowMode(props: {mode: Mode, statechart: Statechart}) {
}
function getActiveLeafs(mode: Mode, sc: Statechart) {
const toDelete = [];
for (const stateA of mode) {
for (const stateB of mode) {
if (sc.uid2State.get(stateA)!.parent === sc.uid2State.get(stateB)) {
toDelete.push(stateB);
}
}
}
return mode.difference(new Set(toDelete));
return new Set([...mode].filter(uid => sc.uid2State.get(uid)?.children?.length === 0));
}

View file

@ -32,11 +32,11 @@ export function ShowAction(props: {action: Action}) {
}
}
export function ShowAST(props: {root: ConcreteState | PseudoState, transitions: Map<string, Transition[]>, rt: RT_Statechart | undefined}) {
export function ShowAST(props: {root: ConcreteState | PseudoState, transitions: Map<string, Transition[]>, rt: RT_Statechart | undefined, highlightActive: Set<string>}) {
const description = stateDescription(props.root);
const outgoing = props.transitions.get(props.root.uid) || [];
return <details open={true} className={props.rt?.mode.has(props.root.uid) ? "active" : ""}>
return <details open={true} className={props.highlightActive.has(props.root.uid) ? "active" : ""}>
<summary>{props.root.kind}: {description}</summary>
{props.root.kind !== "pseudo" && props.root.entryActions.length>0 &&
@ -51,7 +51,7 @@ export function ShowAST(props: {root: ConcreteState | PseudoState, transitions:
}
{props.root.kind !== "pseudo" && props.root.children.length>0 &&
props.root.children.map(child =>
<ShowAST root={child} transitions={props.transitions} rt={props.rt} />
<ShowAST root={child} transitions={props.transitions} rt={props.rt} highlightActive={props.highlightActive} />
)
}
{outgoing.length>0 &&

View file

@ -46,7 +46,7 @@
/* fill-opacity: 0.2; */
/* stroke: rgb(100, 149, 237); */
/* stroke: */
filter: drop-shadow( 0px 0px 6px rgba(0, 150, 255, 0.8));
filter: drop-shadow( 0px 0px 6px rgba(128, 72, 0, 0.856));
/* stroke-width: 3px; */
}

View file

@ -61,14 +61,16 @@ export const sides: [RountanglePart, (r:Rect2D)=>Line2D][] = [
export type InsertMode = "and"|"or"|"pseudo"|"shallow"|"deep"|"transition"|"text";
type VisualEditorProps = {
ast: Statechart,
setAST: Dispatch<SetStateAction<Statechart>>,
rt: BigStep|undefined,
errors: TraceableError[],
setErrors: Dispatch<SetStateAction<TraceableError[]>>,
mode: InsertMode,
highlightActive: Set<string>,
};
export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditorProps) {
export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highlightActive}: VisualEditorProps) {
const [historyState, setHistoryState] = useState<HistoryState>({current: emptyState, history: [], future: []});
const state = historyState.current;
@ -680,6 +682,8 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
const active = rt?.mode || new Set();
console.log(highlightActive);
const rootErrors = errors.filter(({shapeUid}) => shapeUid === "root").map(({message}) => message);
return <svg width="4000px" height="4000px"
@ -711,8 +715,8 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
{(rootErrors.length>0) && <text className="error" x={5} y={20}>{rootErrors.join(' ')}</text>}
{state.rountangles.map(rountangle =>
<RountangleSVG
{state.rountangles.map(rountangle => {
return <RountangleSVG
key={rountangle.uid}
rountangle={rountangle}
selected={selection.find(r => r.uid === rountangle.uid)?.parts || []}
@ -720,8 +724,8 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
errors={errors
.filter(({shapeUid}) => shapeUid === rountangle.uid)
.map(({message}) => message)}
active={active.has(rountangle.uid)}
/>)}
active={highlightActive.has(rountangle.uid)}
/>})}
{state.diamonds.map(diamond => <>
<DiamondSVG
@ -732,7 +736,7 @@ export function VisualEditor({setAST, rt, errors, setErrors, mode}: VisualEditor
errors={errors
.filter(({shapeUid}) => shapeUid === diamond.uid)
.map(({message}) => message)}
active={active.has(diamond.uid)}/>
active={false}/>
</>)}
{state.history.map(history => <>