more keyboard shortcuts

This commit is contained in:
Joeri Exelmans 2025-10-19 14:15:17 +02:00
parent 9ce55e0264
commit 59d5e9913a
3 changed files with 61 additions and 19 deletions

View file

@ -32,6 +32,7 @@ export function App() {
setRT([{inputEvent: null, simtime: 0, ...config}]); setRT([{inputEvent: null, simtime: 0, ...config}]);
setRTIdx(0); setRTIdx(0);
setTime({kind: "paused", simtime: 0}); setTime({kind: "paused", simtime: 0});
scrollDownSidebar();
} }
function onClear() { function onClear() {
@ -51,12 +52,17 @@ export function App() {
function appendNewConfig(inputEvent: string, simtime: number, config: BigStepOutput) { function appendNewConfig(inputEvent: string, simtime: number, config: BigStepOutput) {
setRT([...rt.slice(0, rtIdx!+1), {inputEvent, simtime, ...config}]); setRT([...rt.slice(0, rtIdx!+1), {inputEvent, simtime, ...config}]);
setRTIdx(rtIdx!+1); setRTIdx(rtIdx!+1);
console.log('new config:', config); // console.log('new config:', config);
scrollDownSidebar();
}
function scrollDownSidebar() {
if (refRightSideBar.current) { if (refRightSideBar.current) {
const el = refRightSideBar.current; const el = refRightSideBar.current;
// hack: we want to scroll to the new element, but we have to wait until it is rendered...
setTimeout(() => { setTimeout(() => {
el.scrollIntoView({block: "end", behavior: "smooth"}); el.scrollIntoView({block: "end", behavior: "smooth"});
}, 100); }, 50);
} }
} }

View file

@ -72,18 +72,39 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {
if (e.key === " ") { if (e.key === " ") {
e.preventDefault(); e.preventDefault();
if (rt)
onChangePaused(time.kind !== "paused", performance.now()); onChangePaused(time.kind !== "paused", performance.now());
}; };
if (e.key === "i") { if (e.key === "i") {
e.preventDefault(); e.preventDefault();
onInit(); onInit();
} }
if (e.key === "c") {
e.preventDefault();
onClear();
}
if (e.key === "Tab") {
e.preventDefault();
onSkip();
}
if (e.key === "s") {
e.preventDefault();
onSlower();
}
if (e.key === "f") {
e.preventDefault();
onFaster();
}
if (e.key === "`") {
e.preventDefault();
setShowKeys(show => !show);
}
}; };
window.addEventListener("keydown", onKeyDown); window.addEventListener("keydown", onKeyDown);
return () => { return () => {
window.removeEventListener("keydown", onKeyDown); window.removeEventListener("keydown", onKeyDown);
}; };
}, [time, onInit]); }, [time, onInit, timescale]);
useEffect(() => { useEffect(() => {
setTimeout(() => localStorage.setItem("showKeys", showKeys?"1":"0"), 100); setTimeout(() => localStorage.setItem("showKeys", showKeys?"1":"0"), 100);
@ -143,6 +164,25 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
const timers: Timers = (rt?.environment.get("_timers") || []); const timers: Timers = (rt?.environment.get("_timers") || []);
const nextTimedTransition: [number, TimerElapseEvent] | undefined = timers[0]; const nextTimedTransition: [number, TimerElapseEvent] | undefined = timers[0];
function onSkip() {
const now = performance.now();
setTime(time => {
if (time.kind === "paused") {
return {kind: "paused", simtime: nextTimedTransition[0]};
}
else {
return {kind: "realtime", scale: time.scale, since: {simtime: nextTimedTransition[0], wallclktime: now}};
}
});
}
function onSlower() {
onTimeScaleChange((timescale/2).toString(), performance.now());
}
function onFaster() {
onTimeScaleChange((timescale*2).toString(), performance.now());
}
return <> return <>
<div className="toolbar"> <div className="toolbar">
@ -175,7 +215,9 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
<KeyInfo keyInfo={<kbd>I</kbd>}> <KeyInfo keyInfo={<kbd>I</kbd>}>
<button title="(re)initialize simulation" onClick={onInit} ><PlayArrowIcon fontSize="small"/><CachedIcon fontSize="small"/></button> <button title="(re)initialize simulation" onClick={onInit} ><PlayArrowIcon fontSize="small"/><CachedIcon fontSize="small"/></button>
</KeyInfo> </KeyInfo>
<KeyInfo keyInfo={<kbd>C</kbd>}>
<button title="clear the simulation" onClick={onClear} disabled={!rt}><StopIcon fontSize="small"/></button> <button title="clear the simulation" onClick={onClear} disabled={!rt}><StopIcon fontSize="small"/></button>
</KeyInfo>
&emsp; &emsp;
@ -186,21 +228,19 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
&emsp; &emsp;
<label htmlFor="number-timescale">timescale</label>&nbsp; <label htmlFor="number-timescale">speed</label>&nbsp;
<KeyInfo keyInfo={<kbd>S</kbd>}> <KeyInfo keyInfo={<kbd>S</kbd>}>
<button title="slower" onClick={() => onTimeScaleChange((timescale/2).toString(), performance.now())}>÷2</button> <button title="slower" onClick={onSlower}>÷2</button>
</KeyInfo> </KeyInfo>
<input title="controls how fast the simulation should run in real time mode - larger than 1 means: faster than wall-clock time" id="number-timescale" value={timescale.toFixed(3)} style={{width:40}} readOnly onChange={e => onTimeScaleChange(e.target.value, performance.now())}/> <input title="controls how fast the simulation should run in real time mode - larger than 1 means: faster than wall-clock time" id="number-timescale" value={timescale.toFixed(3)} style={{width:40}} readOnly onChange={e => onTimeScaleChange(e.target.value, performance.now())}/>
<KeyInfo keyInfo={<kbd>F</kbd>}> <KeyInfo keyInfo={<kbd>F</kbd>}>
<button title="faster" onClick={() => onTimeScaleChange((timescale*2).toString(), performance.now())}>×2</button> <button title="faster" onClick={onFaster}>×2</button>
</KeyInfo> </KeyInfo>
&emsp; &emsp;
<KeyInfo>
<label htmlFor="time">time (s)</label>&nbsp; <label htmlFor="time">time (s)</label>&nbsp;
<input title="the current simulated time" id="time" disabled={!rt} value={displayTime} readOnly={true} className="readonlyTextBox" /> <input title="the current simulated time" id="time" disabled={!rt} value={displayTime} readOnly={true} className="readonlyTextBox" />
</KeyInfo>
&emsp; &emsp;
@ -210,17 +250,7 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
<input title="next point in simulated time where a timed transition may fire" id="next-timeout" disabled={!rt} 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={!rt} value={nextTimedTransition ? formatTime(nextTimedTransition[0]) : '+inf'} readOnly={true} className="readonlyTextBox"/>
</KeyInfo> </KeyInfo>
<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={() => { <button title="advance time just enough for the next timer to elapse" disabled={nextTimedTransition===undefined} onClick={onSkip}><SkipNextIcon fontSize="small"/><AccessAlarmIcon fontSize="small"/></button>
const now = performance.now();
setTime(time => {
if (time.kind === "paused") {
return {kind: "paused", simtime: nextTimedTransition[0]};
}
else {
return {kind: "realtime", scale: time.scale, since: {simtime: nextTimedTransition[0], wallclktime: now}};
}
});
}}><SkipNextIcon fontSize="small"/><AccessAlarmIcon fontSize="small"/></button>
</KeyInfo> </KeyInfo>
</div> </div>
@ -259,8 +289,10 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
&emsp; &emsp;
<div style={{display:"inline-block"}}> <div style={{display:"inline-block"}}>
<KeyInfo keyInfo={<kbd>~</kbd>}>
<input id="checkbox-keys" type="checkbox" checked={showKeys} onChange={e => setShowKeys(e.target.checked)}></input> <input id="checkbox-keys" type="checkbox" checked={showKeys} onChange={e => setShowKeys(e.target.checked)}></input>
<label for="checkbox-keys">shortcuts</label> <label for="checkbox-keys">shortcuts</label>
</KeyInfo>
</div> </div>
</div> </div>

View file

@ -35,3 +35,7 @@ input {
::selection { ::selection {
background-color: rgba(0,0,255,0.2); background-color: rgba(0,0,255,0.2);
} }
label {
user-select: none;
}