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 { 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 { details {
border: 1px black solid; border: 1px black solid;
/* border-radius: 5px; */ /* border-radius: 5px; */
background-color: white; background-color: white;
margin-bottom: 2px; margin-bottom: 4px;
padding-right: 2px; padding-right: 2px;
color: black;
width: fit-content;
border-radius: 10px;
} }

View file

@ -58,13 +58,10 @@ export function App() {
if (refRightSideBar.current) { if (refRightSideBar.current) {
const el = refRightSideBar.current; const el = refRightSideBar.current;
// console.log('scrolling to', el.scrollHeight);
console.log('scrolling to:', el); console.log('scrolling to:', el);
setTimeout(() => { setTimeout(() => {
el.scrollIntoView({block: "end", behavior: "smooth"}); el.scrollIntoView({block: "end", behavior: "smooth"});
}, 100); }, 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'}}> return <Stack sx={{height:'100vh'}}>
{/* Top bar */} {/* Top bar */}
<Box <Box
@ -123,7 +125,7 @@ export function App() {
<Stack direction="row" sx={{height:'calc(100vh - 64px)'}}> <Stack direction="row" sx={{height:'calc(100vh - 64px)'}}>
{/* main */} {/* main */}
<Box sx={{flexGrow:1, overflow:'auto'}}> <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> </Box>
{/* right sidebar */} {/* right sidebar */}
<Box <Box
@ -133,10 +135,8 @@ export function App() {
flex: '0 0 content', flex: '0 0 content',
height: 'calc(100vh-32px)', height: 'calc(100vh-32px)',
overflow: "auto", overflow: "auto",
// paddingRight: 1,
// paddingLeft: 1,
}}> }}>
<ShowAST {...{...ast, rt: rt.at(rtIdx!)}}/> <ShowAST {...{...ast, rt: rt.at(rtIdx!), highlightActive}}/>
<br/> <br/>
<div ref={refRightSideBar}> <div ref={refRightSideBar}>
<RTHistory {...{ast, rt, rtIdx, setTime, setRTIdx, 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 { Statechart, stateDescription } from "../statecharts/abstract_syntax";
import { BigStep, Environment, Mode, RaisedEvent } from "../statecharts/runtime_types"; import { BigStep, Environment, Mode, RaisedEvent } from "../statecharts/runtime_types";
import { formatTime } from "./util"; import { formatTime } from "./util";
@ -10,23 +10,26 @@ type RTHistoryProps = {
ast: Statechart, ast: Statechart,
setRTIdx: Dispatch<SetStateAction<number|undefined>>, setRTIdx: Dispatch<SetStateAction<number|undefined>>,
setTime: Dispatch<SetStateAction<TimeMode>>, 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) { function gotoRt(idx: number, timestamp: number) {
setRTIdx(idx); setRTIdx(idx);
setTime({kind: "paused", simtime: timestamp}); setTime({kind: "paused", simtime: timestamp});
} }
return rt.map((rt, idx) => <> return <div>
<div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => gotoRt(idx, rt.simtime)}> {rt.map((r, idx) => <>
<div>({formatTime(rt.simtime)}, {rt.inputEvent || "<init>"})</div> <div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => gotoRt(idx, r.simtime)}>
<ShowMode mode={rt.mode} statechart={ast}/> <div>({formatTime(r.simtime)}, {r.inputEvent || "<init>"})</div>
<ShowEnvironment environment={rt.environment}/> <ShowMode mode={r.mode} statechart={ast}/>
{rt.outputEvents.length>0 && <div> <ShowEnvironment environment={r.environment}/>
{rt.outputEvents.map((e:RaisedEvent) => '^'+e.name).join(', ')} {r.outputEvents.length>0 && <div>
</div>} {r.outputEvents.map((e:RaisedEvent) => '^'+e.name).join(', ')}
</div></>); </div>}
</div></>)}
</div>;
} }
@ -45,13 +48,5 @@ function ShowMode(props: {mode: Mode, statechart: Statechart}) {
} }
function getActiveLeafs(mode: Mode, sc: Statechart) { function getActiveLeafs(mode: Mode, sc: Statechart) {
const toDelete = []; return new Set([...mode].filter(uid => sc.uid2State.get(uid)?.children?.length === 0));
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));
} }

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 description = stateDescription(props.root);
const outgoing = props.transitions.get(props.root.uid) || []; 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> <summary>{props.root.kind}: {description}</summary>
{props.root.kind !== "pseudo" && props.root.entryActions.length>0 && {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.kind !== "pseudo" && props.root.children.length>0 &&
props.root.children.map(child => 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 && {outgoing.length>0 &&

View file

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