progress bar shows how long until next timer elapse

This commit is contained in:
Joeri Exelmans 2025-11-13 23:56:52 +01:00
parent bdc2a66b1c
commit 632cf9b542
3 changed files with 28 additions and 9 deletions

View file

@ -17,7 +17,6 @@ import { useUrlHashState } from "../hooks/useUrlHashState";
import { plants } from "./plants"; import { plants } from "./plants";
import { emptyState } from "@/statecharts/concrete_syntax"; import { emptyState } from "@/statecharts/concrete_syntax";
import { ModalOverlay } from "./Modals/ModalOverlay"; import { ModalOverlay } from "./Modals/ModalOverlay";
import { usePersistentState } from "@/hooks/usePersistentState";
export type EditHistory = { export type EditHistory = {
current: VisualEditorState, current: VisualEditorState,

View file

@ -2,6 +2,8 @@ import { Dispatch, memo, SetStateAction, useCallback, useEffect } from "react";
import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo"; import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
import { setRealtime, TimeMode } from "@/statecharts/time"; import { setRealtime, TimeMode } from "@/statecharts/time";
import SpeedIcon from '@mui/icons-material/Speed';
export const SpeedControl = memo(function SpeedControl({showKeys, timescale, setTimescale, setTime}: {showKeys: boolean, timescale: number, setTimescale: Dispatch<SetStateAction<number>>, setTime: Dispatch<SetStateAction<TimeMode>>}) { export const SpeedControl = memo(function SpeedControl({showKeys, timescale, setTimescale, setTime}: {showKeys: boolean, timescale: number, setTimescale: Dispatch<SetStateAction<number>>, setTime: Dispatch<SetStateAction<TimeMode>>}) {
const onTimeScaleChange = useCallback((newValue: string, wallclktime: number) => { const onTimeScaleChange = useCallback((newValue: string, wallclktime: number) => {
@ -51,7 +53,7 @@ export const SpeedControl = memo(function SpeedControl({showKeys, timescale, set
const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden; const KeyInfo = showKeys ? KeyInfoVisible : KeyInfoHidden;
return <> return <>
<label htmlFor="number-timescale">speed</label>&nbsp; <label htmlFor="number-timescale"><SpeedIcon fontSize="small"/></label>&nbsp;
<KeyInfo keyInfo={<kbd>S</kbd>}> <KeyInfo keyInfo={<kbd>S</kbd>}>
<button title="slower" onClick={onSlower}>÷2</button> <button title="slower" onClick={onSlower}>÷2</button>
</KeyInfo> </KeyInfo>

View file

@ -1,4 +1,4 @@
import { Dispatch, memo, ReactElement, SetStateAction, useCallback, useEffect, useState } from "react"; import { Dispatch, memo, ReactElement, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { TimerElapseEvent, Timers } from "../../statecharts/runtime_types"; import { TimerElapseEvent, Timers } from "../../statecharts/runtime_types";
import { getSimTime, setPaused, setRealtime, TimeMode } from "../../statecharts/time"; import { getSimTime, setPaused, setRealtime, TimeMode } from "../../statecharts/time";
import { InsertMode } from "./InsertModes"; import { InsertMode } from "./InsertModes";
@ -13,6 +13,9 @@ import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode'; import LightModeIcon from '@mui/icons-material/LightMode';
import BrightnessAutoIcon from '@mui/icons-material/BrightnessAuto'; import BrightnessAutoIcon from '@mui/icons-material/BrightnessAuto';
import SpeedIcon from '@mui/icons-material/Speed';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import AccessAlarmIcon from '@mui/icons-material/AccessAlarm'; import AccessAlarmIcon from '@mui/icons-material/AccessAlarm';
import CachedIcon from '@mui/icons-material/Cached'; import CachedIcon from '@mui/icons-material/Cached';
import InfoOutlineIcon from '@mui/icons-material/InfoOutline'; import InfoOutlineIcon from '@mui/icons-material/InfoOutline';
@ -52,7 +55,7 @@ export type TopPanelProps = {
const ShortCutShowKeys = <kbd>~</kbd>; const ShortCutShowKeys = <kbd>~</kbd>;
export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onRotate, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory}: TopPanelProps) { export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onRotate, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory}: TopPanelProps) {
const [displayTime, setDisplayTime] = useState("0.000"); const [displayTime, setDisplayTime] = useState(0);
const [timescale, setTimescale] = usePersistentState("timescale", 1); const [timescale, setTimescale] = usePersistentState("timescale", 1);
const config = trace && trace.trace[trace.idx]; const config = trace && trace.trace[trace.idx];
@ -62,9 +65,16 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
const updateDisplayedTime = useCallback(() => { const updateDisplayedTime = useCallback(() => {
const now = Math.round(performance.now()); const now = Math.round(performance.now());
const timeMs = getSimTime(time, now); const timeMs = getSimTime(time, now);
setDisplayTime(formatTime(timeMs)); setDisplayTime((timeMs));
}, [time, setDisplayTime]); }, [time, setDisplayTime]);
const formattedDisplayTime = useMemo(() => formatTime(displayTime), [displayTime]);
// const lastSimTime = useMemo(() => time.kind === "realtime" ? time.since.simtime : time.simtime, [time]);
const lastSimTime = config?.simtime || 0;
useEffect(() => { useEffect(() => {
// This has no effect on statechart execution. In between events, the statechart is doing nothing. However, by updating the displayed time, we give the illusion of continuous progress. // This has no effect on statechart execution. In between events, the statechart is doing nothing. However, by updating the displayed time, we give the illusion of continuous progress.
const interval = setInterval(() => { const interval = setInterval(() => {
@ -105,6 +115,9 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
} }
}, [nextTimedTransition, setTime]); }, [nextTimedTransition, setTime]);
console.log({lastSimTime, displayTime, nxt: nextTimedTransition?.[0]});
useEffect(() => { useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {
// don't capture keyboard events when focused on an input element: // don't capture keyboard events when focused on an input element:
@ -228,15 +241,20 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
{/* time, next */} {/* time, next */}
<div className="toolbarGroup"> <div className="toolbarGroup">
<div className="toolbarGroup"> <div className="toolbarGroup">
<label htmlFor="time">time (s)</label>&nbsp; <label htmlFor="time"><AccessTimeIcon fontSize="small"/></label>&nbsp;
<input title="the current simulated time" id="time" disabled={!config} value={displayTime} readOnly={true} className="readonlyTextBox" /> <progress style={{position:'absolute', width: 60, marginTop: 23, height: 2, background: 'rgba(0,0,0,0)', border: 0, accentColor: 'var(--accent-border-color)', appearance: 'none'}} max={1} value={(displayTime-lastSimTime)/((nextTimedTransition?.[0]||Infinity)-lastSimTime)}/>
<input title="the current simulated time" id="time" disabled={!config} value={formattedDisplayTime} readOnly={true} className="readonlyTextBox" />
</div> </div>
&emsp; &emsp;
<div className="toolbarGroup"> <div className="toolbarGroup">
<label htmlFor="next-timeout">next (s)</label>&nbsp; <label htmlFor="next-timeout"><AccessAlarmIcon fontSize="small"/></label>&nbsp;
<input title="next point in simulated time where a timed transition may fire" id="next-timeout" disabled={!config} value={nextTimedTransition ? formatTime(nextTimedTransition[0]) : '+inf'} readOnly={true} className="readonlyTextBox"/> <input title="next point in simulated time where a timed transition may fire" id="next-timeout" disabled={!config} value={nextTimedTransition ? formatTime(nextTimedTransition[0]) : '+inf'} readOnly={true} className="readonlyTextBox"/>
<KeyInfo keyInfo={<kbd>Tab</kbd>}> <KeyInfo keyInfo={<kbd>Tab</kbd>}>
<button title="advance time just enough for the next timer to elapse" disabled={nextTimedTransition===undefined} onClick={onSkip}><SkipNextIcon fontSize="small"/><AccessAlarmIcon fontSize="small"/></button> <button title="advance time just enough for the next timer to elapse" disabled={nextTimedTransition===undefined} onClick={onSkip}>
<SkipNextIcon fontSize="small"/>
</button>
</KeyInfo> </KeyInfo>
&emsp; &emsp;
</div> </div>