Add toggle button and slider to change step rate

This commit is contained in:
Mikolaj Wielgus 2026-06-04 21:41:02 +02:00
parent 0cd4b005fd
commit c7430f9fc9
5 changed files with 127 additions and 6 deletions

View File

@ -2,19 +2,24 @@
//
// SPDX-License-Identifier: MIT
use egui::{Context, Ui};
use crate::{
action::{Action, Trigger},
action::{Action, Switch, Trigger},
menu_bar::MenuBar,
translator::Translator,
};
pub struct Actions {
pub file: FileActions,
pub debug: DebugActions,
}
impl Actions {
pub fn new(tr: &Translator) -> Self {
Self {
file: FileActions::new(tr),
debug: DebugActions::new(tr),
}
}
}
@ -68,3 +73,20 @@ impl FileActions {
}
}
}
pub struct DebugActions {
pub fix_step_rate: Switch,
}
impl DebugActions {
pub fn new(tr: &Translator) -> Self {
Self {
fix_step_rate: Action::new_keyless(tr.text("tr-menu-debug-fix-step-rate"))
.into_switch(),
}
}
pub fn render_menu(&mut self, _ctx: &Context, ui: &mut Ui, menu_bar: &mut MenuBar) {
self.fix_step_rate.checkbox(ui, &mut menu_bar.fix_step_rate);
}
}

View File

@ -134,7 +134,12 @@ impl eframe::App for App {
workspace.update_appearance_panel(ctx);
}
self.viewport.update(ctx, self.workspace.as_mut());
self.viewport.update(
&self.translator,
ctx,
&self.menu_bar,
self.workspace.as_mut(),
);
self.update_locale();
self.update_title(ctx);

View File

@ -4,6 +4,10 @@
use std::sync::mpsc::Sender;
use egui::{
PopupCloseBehavior,
containers::menu::{MenuButton, MenuConfig},
};
use serde::{Deserialize, Serialize};
use specctra::{
error::{ParseError, ParseErrorContext},
@ -19,12 +23,16 @@ use crate::{
#[derive(Deserialize, Serialize)]
pub struct MenuBar {
step_rate: f32,
pub fix_step_rate: bool,
pub step_rate: f64,
}
impl MenuBar {
pub fn new() -> Self {
Self { step_rate: 1.0 }
Self {
fix_step_rate: false,
step_rate: 1.0,
}
}
pub fn update(
@ -42,6 +50,29 @@ impl MenuBar {
});
ui.separator();
MenuButton::new(tr.text("tr-menu-debug"))
.config(
MenuConfig::default()
.close_behavior(PopupCloseBehavior::CloseOnClickOutside),
)
.ui(ui, |ui| {
actions.debug.render_menu(ctx, ui, self);
ui.add_enabled_ui(self.fix_step_rate, |ui| {
ui.label(tr.text("tr-menu-debug-step-rate"));
ui.add(
egui::widgets::Slider::new(&mut self.step_rate, 0.1..=1000.0)
.suffix(format!(
" {}",
tr.text("tr-menu-debug-step-rate-unit")
)),
);
});
});
ui.separator();
egui::widgets::global_theme_preference_switch(ui);
});

View File

@ -5,7 +5,7 @@
use egui::Pos2;
use topola::{MasterInteractor, Vector2, Workspace};
use crate::{display::Display, workspace::GuiWorkspace};
use crate::{display::Display, menu_bar::MenuBar, translator::Translator, workspace::GuiWorkspace};
pub struct Viewport {
pub scene_rect: egui::Rect,
@ -24,7 +24,13 @@ impl Viewport {
}
}
pub fn update(&mut self, ctx: &egui::Context, workspace: Option<&mut GuiWorkspace>) {
pub fn update(
&mut self,
tr: &Translator,
ctx: &egui::Context,
menu_bar: &MenuBar,
workspace: Option<&mut GuiWorkspace>,
) {
egui::CentralPanel::default().show(ctx, |ui| {
egui::Frame::canvas(ui.style()).show(ui, |ui| {
ui.ctx().request_repaint();
@ -51,6 +57,20 @@ impl Viewport {
Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into());
if let Some(workspace) = workspace {
workspace.advance_state_by_dt(
tr,
menu_bar.fix_step_rate.then_some(menu_bar.step_rate),
ctx.input(|i| {
if i.stable_dt <= i.predicted_dt {
i.stable_dt
} else {
// Clamp dt to egui's predicted dt to
// additionally safeguard against stuttering.
i.predicted_dt
}
}) as f64,
);
let escape_pressed = ctx.input(|i| i.key_pressed(egui::Key::Escape));
if escape_pressed {
if let Some(interactor) = self.master_interactor.as_mut() {
@ -70,6 +90,7 @@ impl Viewport {
ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary));
let primary_released =
ctx.input(|i| i.pointer.button_released(egui::PointerButton::Primary));
let delete_pressed = ctx.input(|i| i.key_pressed(egui::Key::Delete));
let mut maybe_pointer_on_scene: Option<Vector2<i64>> = None;

View File

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::{ops::ControlFlow, time::Instant};
use topola::{Board, Workspace};
use crate::{layers_panel::LayersPanel, translator::Translator};
@ -9,6 +11,7 @@ use crate::{layers_panel::LayersPanel, translator::Translator};
pub struct GuiWorkspace {
pub workspace: Workspace,
pub appearance_panel: LayersPanel,
pub dt_accum: f64,
}
impl GuiWorkspace {
@ -18,14 +21,53 @@ impl GuiWorkspace {
Self {
workspace: Workspace::new_board(board),
appearance_panel,
dt_accum: 0.0,
}
}
pub fn advance_state_by_dt(
&mut self,
tr: &Translator,
step_rate: Option<f64>,
dt: f64,
) -> bool {
let instant = Instant::now();
if step_rate.is_some() {
self.dt_accum += dt;
}
while step_rate.is_none_or(|step_rate| self.dt_accum >= 1.0 / step_rate) {
if let Some(step_rate) = step_rate {
self.dt_accum -= 1.0 / step_rate;
}
if let ControlFlow::Break(()) = self.step(tr) {
return true;
}
// Hard limit: never spend more time on advancing state than the
// duration of last frame to prevent stuttering.
// Of course, this does not safeguard against infinite loops.
if instant.elapsed().as_secs_f64() >= dt {
return false;
}
}
true
}
pub fn step(&mut self, tr: &Translator) -> ControlFlow<()> {
ControlFlow::Continue(())
}
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
let Self {
workspace,
appearance_panel,
dt_accum,
} = self;
appearance_panel.update(ctx, workspace.board());
}
}