refactor: share preview studio control update helpers

Deduplicate the preview panel's repeated control-sync and runtime-update
paths so built-in preview setups and individual runtime controls all go
through the same widget/state wiring.
This commit is contained in:
2026-03-31 19:02:44 -04:00
parent 21ed77c74c
commit 203457b31b
+186 -116
View File
@@ -224,6 +224,48 @@ impl PreviewSessionWidgets {
}
}
#[derive(Clone)]
struct PreviewStudioControlWidgets {
scene_dropdown: gtk4::DropDown,
fps_spin: gtk4::SpinButton,
particle_count_spin: gtk4::SpinButton,
particle_size_spin: gtk4::SpinButton,
gpu_passes_spin: gtk4::SpinButton,
interaction_spin: gtk4::SpinButton,
vram_spin: gtk4::SpinButton,
vsync_switch: gtk4::Switch,
pause_switch: gtk4::Switch,
width_spin: gtk4::SpinButton,
height_spin: gtk4::SpinButton,
}
impl PreviewStudioControlWidgets {
fn apply_studio_runtime(&self, studio: &PreviewStudioOptions) {
if let Some(index) = StudioScene::all()
.iter()
.position(|scene| *scene == studio.scene)
{
self.scene_dropdown.set_selected(index as u32);
}
self.fps_spin.set_value(studio.fps_cap.unwrap_or(0) as f64);
self.particle_count_spin
.set_value(studio.particle_count as f64);
self.particle_size_spin
.set_value(studio.particle_size as f64);
self.gpu_passes_spin.set_value(studio.gpu_passes as f64);
self.interaction_spin
.set_value(studio.interaction_steps as f64);
self.vram_spin.set_value(studio.vram_pressure_mb as f64);
self.vsync_switch.set_active(studio.vsync);
self.pause_switch.set_active(studio.paused);
}
fn apply_window_size(&self, width: i32, height: i32) {
self.width_spin.set_value(width as f64);
self.height_spin.set_value(height as f64);
}
}
pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let panel = gtk4::Box::new(gtk4::Orientation::Vertical, 10);
panel.add_css_class("dashboard-stack");
@@ -640,6 +682,20 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
interaction_row.append(&interaction_controls);
cpu_body.append(&interaction_row);
let control_widgets = PreviewStudioControlWidgets {
scene_dropdown: scene_dropdown.clone(),
fps_spin: fps_spin.clone(),
particle_count_spin: particle_count_spin.clone(),
particle_size_spin: particle_size_spin.clone(),
gpu_passes_spin: gpu_passes_spin.clone(),
interaction_spin: interaction_spin.clone(),
vram_spin: vram_spin.clone(),
vsync_switch: vsync_switch.clone(),
pause_switch: pause_switch.clone(),
width_spin: width_spin.clone(),
height_spin: height_spin.clone(),
};
let (setup_group, setup_body) = preview_group(
"Preview setups",
"Apply a built-in workload shape quickly, or reset everything back to the Studio defaults.",
@@ -658,35 +714,12 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let preview_controls = preview_controls.clone();
let studio_defaults = studio_defaults.clone();
let control_sync = control_sync.clone();
let scene_dropdown = scene_dropdown.clone();
let fps_spin = fps_spin.clone();
let particle_count_spin = particle_count_spin.clone();
let particle_size_spin = particle_size_spin.clone();
let gpu_passes_spin = gpu_passes_spin.clone();
let interaction_spin = interaction_spin.clone();
let vram_spin = vram_spin.clone();
let vsync_switch = vsync_switch.clone();
let pause_switch = pause_switch.clone();
let control_widgets = control_widgets.clone();
button.connect_clicked(move |_| {
let studio = profile.studio();
studio_defaults.replace(studio.clone());
persist_studio_options(&studio);
control_sync.set(true);
if let Some(index) = StudioScene::all()
.iter()
.position(|scene| *scene == studio.scene)
{
scene_dropdown.set_selected(index as u32);
}
fps_spin.set_value(studio.fps_cap.unwrap_or(0) as f64);
particle_count_spin.set_value(studio.particle_count as f64);
particle_size_spin.set_value(studio.particle_size as f64);
gpu_passes_spin.set_value(studio.gpu_passes as f64);
interaction_spin.set_value(studio.interaction_steps as f64);
vram_spin.set_value(studio.vram_pressure_mb as f64);
vsync_switch.set_active(studio.vsync);
pause_switch.set_active(studio.paused);
control_sync.set(false);
apply_preview_control_state(&control_sync, &control_widgets, &studio, None);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio);
});
profile_rows.attach(&button, (index % 2) as i32, (index / 2) as i32, 1, 1);
@@ -697,17 +730,7 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let preview_controls_reset = preview_controls.clone();
let studio_defaults_reset = studio_defaults.clone();
let control_sync_reset = control_sync.clone();
let scene_dropdown_reset = scene_dropdown.clone();
let fps_spin_reset = fps_spin.clone();
let particle_count_spin_reset = particle_count_spin.clone();
let particle_size_spin_reset = particle_size_spin.clone();
let gpu_passes_spin_reset = gpu_passes_spin.clone();
let interaction_spin_reset = interaction_spin.clone();
let vram_spin_reset = vram_spin.clone();
let vsync_switch_reset = vsync_switch.clone();
let pause_switch_reset = pause_switch.clone();
let width_spin_reset = width_spin.clone();
let height_spin_reset = height_spin.clone();
let control_widgets_reset = control_widgets.clone();
reset_preview_button.connect_clicked(move |_| {
let studio = PreviewStudioOptions::default();
let (default_width, default_height) = default_preview_window_size();
@@ -715,24 +738,12 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
persist_studio_options(&studio);
persist_preview_window_width(default_width);
persist_preview_window_height(default_height);
control_sync_reset.set(true);
if let Some(index) = StudioScene::all()
.iter()
.position(|scene| *scene == studio.scene)
{
scene_dropdown_reset.set_selected(index as u32);
}
fps_spin_reset.set_value(studio.fps_cap.unwrap_or(0) as f64);
particle_count_spin_reset.set_value(studio.particle_count as f64);
particle_size_spin_reset.set_value(studio.particle_size as f64);
gpu_passes_spin_reset.set_value(studio.gpu_passes as f64);
interaction_spin_reset.set_value(studio.interaction_steps as f64);
vram_spin_reset.set_value(studio.vram_pressure_mb as f64);
vsync_switch_reset.set_active(studio.vsync);
pause_switch_reset.set_active(studio.paused);
width_spin_reset.set_value(default_width as f64);
height_spin_reset.set_value(default_height as f64);
control_sync_reset.set(false);
apply_preview_control_state(
&control_sync_reset,
&control_widgets_reset,
&studio,
Some((default_width, default_height)),
);
maybe_restart_active_preview(&ctx_reset, &preview_controls_reset, studio);
});
setup_body.append(&profile_rows);
@@ -753,19 +764,22 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
scene_dropdown.connect_selected_notify(move |dropdown| {
if control_sync.get() {
return;
}
let Some(scene) = StudioScene::all()
.get(dropdown.selected() as usize)
.copied()
else {
return;
};
let mut studio = studio_defaults.borrow_mut();
studio.scene = scene;
persist_preview_studio_scene(scene);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.scene = scene;
persist_preview_studio_scene(scene);
},
);
});
}
@@ -775,14 +789,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
fps_spin.connect_value_changed(move |spin| {
if control_sync.get() {
return;
}
let value = spin.value().round().clamp(0.0, 1000.0) as i32;
let mut studio = studio_defaults.borrow_mut();
studio.fps_cap = if value <= 0 { None } else { Some(value as u32) };
persist_studio_fps_cap(value);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.fps_cap = if value <= 0 { None } else { Some(value as u32) };
persist_studio_fps_cap(value);
},
);
});
}
@@ -792,14 +809,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
particle_count_spin.connect_value_changed(move |spin| {
if control_sync.get() {
return;
}
let count = spin.value().round().clamp(100.0, 500_000.0) as i32;
let mut studio = studio_defaults.borrow_mut();
studio.particle_count = count as u32;
persist_studio_particle_count(count);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.particle_count = count as u32;
persist_studio_particle_count(count);
},
);
});
}
@@ -809,14 +829,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
particle_size_spin.connect_value_changed(move |spin| {
if control_sync.get() {
return;
}
let size = spin.value().clamp(0.01, 5.0);
let mut studio = studio_defaults.borrow_mut();
studio.particle_size = size as f32;
persist_studio_particle_size(size);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.particle_size = size as f32;
persist_studio_particle_size(size);
},
);
});
}
@@ -826,14 +849,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
gpu_passes_spin.connect_value_changed(move |spin| {
if control_sync.get() {
return;
}
let passes = spin.value().round().clamp(1.0, 64.0) as i32;
let mut studio = studio_defaults.borrow_mut();
studio.gpu_passes = passes as u32;
persist_studio_gpu_passes(passes);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.gpu_passes = passes as u32;
persist_studio_gpu_passes(passes);
},
);
});
}
@@ -843,14 +869,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
interaction_spin.connect_value_changed(move |spin| {
if control_sync.get() {
return;
}
let steps = spin.value().round().clamp(0.0, 256.0) as i32;
let mut studio = studio_defaults.borrow_mut();
studio.interaction_steps = steps as u32;
persist_studio_interaction_steps(steps);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.interaction_steps = steps as u32;
persist_studio_interaction_steps(steps);
},
);
});
}
@@ -860,14 +889,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
vram_spin.connect_value_changed(move |spin| {
if control_sync.get() {
return;
}
let mb = spin.value().round().clamp(0.0, 4096.0) as i32;
let mut studio = studio_defaults.borrow_mut();
studio.vram_pressure_mb = mb as u32;
persist_studio_vram_pressure(mb);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.vram_pressure_mb = mb as u32;
persist_studio_vram_pressure(mb);
},
);
});
}
@@ -877,13 +909,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
vsync_switch.connect_active_notify(move |switch| {
if control_sync.get() {
return;
}
let mut studio = studio_defaults.borrow_mut();
studio.vsync = switch.is_active();
persist_preview_vsync(switch.is_active());
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
let active = switch.is_active();
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.vsync = active;
persist_preview_vsync(active);
},
);
});
}
@@ -893,12 +929,16 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone();
let preview_controls = preview_controls.clone();
pause_switch.connect_active_notify(move |switch| {
if control_sync.get() {
return;
}
let mut studio = studio_defaults.borrow_mut();
studio.paused = switch.is_active();
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone());
let active = switch.is_active();
update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.paused = active;
},
);
});
}
@@ -1163,6 +1203,36 @@ fn persist_preview_vsync(enabled: bool) {
}
}
fn apply_preview_control_state(
control_sync: &Cell<bool>,
controls: &PreviewStudioControlWidgets,
studio: &PreviewStudioOptions,
window_size: Option<(i32, i32)>,
) {
control_sync.set(true);
controls.apply_studio_runtime(studio);
if let Some((width, height)) = window_size {
controls.apply_window_size(width, height);
}
control_sync.set(false);
}
fn update_studio_runtime_setting(
control_sync: &Cell<bool>,
ctx: &PageBuildContext,
preview_controls: &PreviewSessionWidgets,
studio_defaults: &Rc<RefCell<PreviewStudioOptions>>,
update: impl FnOnce(&mut PreviewStudioOptions),
) {
if control_sync.get() {
return;
}
let mut studio = studio_defaults.borrow_mut();
update(&mut studio);
maybe_apply_studio_preview_runtime(ctx, preview_controls, studio.clone());
}
pub(super) fn preview_window_settings(
_scene: PreviewScene,
config: &AnnotatedConfig,