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
+172 -102
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 { pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let panel = gtk4::Box::new(gtk4::Orientation::Vertical, 10); let panel = gtk4::Box::new(gtk4::Orientation::Vertical, 10);
panel.add_css_class("dashboard-stack"); 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); interaction_row.append(&interaction_controls);
cpu_body.append(&interaction_row); 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( let (setup_group, setup_body) = preview_group(
"Preview setups", "Preview setups",
"Apply a built-in workload shape quickly, or reset everything back to the Studio defaults.", "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 preview_controls = preview_controls.clone();
let studio_defaults = studio_defaults.clone(); let studio_defaults = studio_defaults.clone();
let control_sync = control_sync.clone(); let control_sync = control_sync.clone();
let scene_dropdown = scene_dropdown.clone(); let control_widgets = control_widgets.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();
button.connect_clicked(move |_| { button.connect_clicked(move |_| {
let studio = profile.studio(); let studio = profile.studio();
studio_defaults.replace(studio.clone()); studio_defaults.replace(studio.clone());
persist_studio_options(&studio); persist_studio_options(&studio);
control_sync.set(true); apply_preview_control_state(&control_sync, &control_widgets, &studio, None);
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);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio); maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio);
}); });
profile_rows.attach(&button, (index % 2) as i32, (index / 2) as i32, 1, 1); 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 preview_controls_reset = preview_controls.clone();
let studio_defaults_reset = studio_defaults.clone(); let studio_defaults_reset = studio_defaults.clone();
let control_sync_reset = control_sync.clone(); let control_sync_reset = control_sync.clone();
let scene_dropdown_reset = scene_dropdown.clone(); let control_widgets_reset = control_widgets.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();
reset_preview_button.connect_clicked(move |_| { reset_preview_button.connect_clicked(move |_| {
let studio = PreviewStudioOptions::default(); let studio = PreviewStudioOptions::default();
let (default_width, default_height) = default_preview_window_size(); 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_studio_options(&studio);
persist_preview_window_width(default_width); persist_preview_window_width(default_width);
persist_preview_window_height(default_height); persist_preview_window_height(default_height);
control_sync_reset.set(true); apply_preview_control_state(
if let Some(index) = StudioScene::all() &control_sync_reset,
.iter() &control_widgets_reset,
.position(|scene| *scene == studio.scene) &studio,
{ Some((default_width, default_height)),
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);
maybe_restart_active_preview(&ctx_reset, &preview_controls_reset, studio); maybe_restart_active_preview(&ctx_reset, &preview_controls_reset, studio);
}); });
setup_body.append(&profile_rows); setup_body.append(&profile_rows);
@@ -753,19 +764,22 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
scene_dropdown.connect_selected_notify(move |dropdown| { scene_dropdown.connect_selected_notify(move |dropdown| {
if control_sync.get() {
return;
}
let Some(scene) = StudioScene::all() let Some(scene) = StudioScene::all()
.get(dropdown.selected() as usize) .get(dropdown.selected() as usize)
.copied() .copied()
else { else {
return; return;
}; };
let mut studio = studio_defaults.borrow_mut(); update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.scene = scene; studio.scene = scene;
persist_preview_studio_scene(scene); persist_preview_studio_scene(scene);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); },
);
}); });
} }
@@ -775,14 +789,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
fps_spin.connect_value_changed(move |spin| { 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 value = spin.value().round().clamp(0.0, 1000.0) as i32;
let mut studio = studio_defaults.borrow_mut(); 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) }; studio.fps_cap = if value <= 0 { None } else { Some(value as u32) };
persist_studio_fps_cap(value); persist_studio_fps_cap(value);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); },
);
}); });
} }
@@ -792,14 +809,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
particle_count_spin.connect_value_changed(move |spin| { 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 count = spin.value().round().clamp(100.0, 500_000.0) as i32;
let mut studio = studio_defaults.borrow_mut(); update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.particle_count = count as u32; studio.particle_count = count as u32;
persist_studio_particle_count(count); persist_studio_particle_count(count);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); },
);
}); });
} }
@@ -809,14 +829,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
particle_size_spin.connect_value_changed(move |spin| { particle_size_spin.connect_value_changed(move |spin| {
if control_sync.get() {
return;
}
let size = spin.value().clamp(0.01, 5.0); let size = spin.value().clamp(0.01, 5.0);
let mut studio = studio_defaults.borrow_mut(); update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.particle_size = size as f32; studio.particle_size = size as f32;
persist_studio_particle_size(size); persist_studio_particle_size(size);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); },
);
}); });
} }
@@ -826,14 +849,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
gpu_passes_spin.connect_value_changed(move |spin| { 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 passes = spin.value().round().clamp(1.0, 64.0) as i32;
let mut studio = studio_defaults.borrow_mut(); update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.gpu_passes = passes as u32; studio.gpu_passes = passes as u32;
persist_studio_gpu_passes(passes); persist_studio_gpu_passes(passes);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); },
);
}); });
} }
@@ -843,14 +869,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
interaction_spin.connect_value_changed(move |spin| { 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 steps = spin.value().round().clamp(0.0, 256.0) as i32;
let mut studio = studio_defaults.borrow_mut(); update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.interaction_steps = steps as u32; studio.interaction_steps = steps as u32;
persist_studio_interaction_steps(steps); persist_studio_interaction_steps(steps);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); },
);
}); });
} }
@@ -860,14 +889,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
vram_spin.connect_value_changed(move |spin| { 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 mb = spin.value().round().clamp(0.0, 4096.0) as i32;
let mut studio = studio_defaults.borrow_mut(); update_studio_runtime_setting(
&control_sync,
&ctx,
&preview_controls,
&studio_defaults,
move |studio| {
studio.vram_pressure_mb = mb as u32; studio.vram_pressure_mb = mb as u32;
persist_studio_vram_pressure(mb); persist_studio_vram_pressure(mb);
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); },
);
}); });
} }
@@ -877,13 +909,17 @@ pub(crate) fn build_preview_panel(ctx: &PageBuildContext) -> gtk4::Box {
let ctx = ctx.clone(); let ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
vsync_switch.connect_active_notify(move |switch| { vsync_switch.connect_active_notify(move |switch| {
if control_sync.get() { let active = switch.is_active();
return; update_studio_runtime_setting(
} &control_sync,
let mut studio = studio_defaults.borrow_mut(); &ctx,
studio.vsync = switch.is_active(); &preview_controls,
persist_preview_vsync(switch.is_active()); &studio_defaults,
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); 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 ctx = ctx.clone();
let preview_controls = preview_controls.clone(); let preview_controls = preview_controls.clone();
pause_switch.connect_active_notify(move |switch| { pause_switch.connect_active_notify(move |switch| {
if control_sync.get() { let active = switch.is_active();
return; update_studio_runtime_setting(
} &control_sync,
let mut studio = studio_defaults.borrow_mut(); &ctx,
studio.paused = switch.is_active(); &preview_controls,
maybe_apply_studio_preview_runtime(&ctx, &preview_controls, studio.clone()); &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( pub(super) fn preview_window_settings(
_scene: PreviewScene, _scene: PreviewScene,
config: &AnnotatedConfig, config: &AnnotatedConfig,