Scroll a plugin window's parameters when they don't fit

A plugin window listed every parameter in one grid. VST3 and CLAP
plugins expose all of theirs, so a plugin with more parameters than fit
on the screen ran off the bottom edge, and the ones below it couldn't be
reached (#6). The parameters now scroll inside the window, below the
header and the EQ curve, and the window stays on the screen.
This commit is contained in:
grovebit 2026-10-10 13:46:39 +01:00
parent 7414719f37
commit 16b7c38b6b
2 changed files with 57 additions and 36 deletions

View File

@ -407,44 +407,46 @@ fn plugin_windows(app: &mut SoundApp, ctx: &egui::Context) {
if info.id == "eq_7band" || info.id == "eq_1band" {
eq_curve(ui, info.id, &ins);
}
egui::Grid::new(("pg", tid.0, slot)).num_columns(3).show(ui, |ui| {
for p in info.params {
let v = ins.params.get(p.id).copied().unwrap_or(p.default);
ui.label(p.name);
let mut x = v;
let resp = if !p.choices.is_empty() {
let mut idx = x.round().max(0.0) as usize;
let r = egui::ComboBox::from_id_salt(("pc", tid.0, slot, p.id))
.selected_text(p.choices.get(idx).copied().unwrap_or("?"))
.show_ui(ui, |ui| {
for (i, c) in p.choices.iter().enumerate() {
ui.selectable_value(&mut idx, i, *c);
}
});
x = idx as f32;
r.response
} else {
let mut sl = egui::Slider::new(&mut x, p.min..=p.max).show_value(false);
if p.taper == soundcraft_dsp::Taper::Log && p.min > 0.0 {
sl = sl.logarithmic(true);
}
ui.add(sl)
};
if (x - v).abs() > f32::EPSILON {
let _ = app.engine.execute_merged(
"mix.insert_param",
&json!({"track": tid.0, "slot": slot, "param": p.id, "value": x}),
&format!("param:{}:{slot}:{}", tid.0, p.id),
);
if let Some(pl) = &app.player {
// Keep the plugin's own editor in step.
pl.editor_set_param(tid, slot, p.id, x);
egui::ScrollArea::vertical().show(ui, |ui| {
egui::Grid::new(("pg", tid.0, slot)).num_columns(3).show(ui, |ui| {
for p in info.params {
let v = ins.params.get(p.id).copied().unwrap_or(p.default);
ui.label(p.name);
let mut x = v;
let resp = if !p.choices.is_empty() {
let mut idx = x.round().max(0.0) as usize;
let r = egui::ComboBox::from_id_salt(("pc", tid.0, slot, p.id))
.selected_text(p.choices.get(idx).copied().unwrap_or("?"))
.show_ui(ui, |ui| {
for (i, c) in p.choices.iter().enumerate() {
ui.selectable_value(&mut idx, i, *c);
}
});
x = idx as f32;
r.response
} else {
let mut sl = egui::Slider::new(&mut x, p.min..=p.max).show_value(false);
if p.taper == soundcraft_dsp::Taper::Log && p.min > 0.0 {
sl = sl.logarithmic(true);
}
ui.add(sl)
};
if (x - v).abs() > f32::EPSILON {
let _ = app.engine.execute_merged(
"mix.insert_param",
&json!({"track": tid.0, "slot": slot, "param": p.id, "value": x}),
&format!("param:{}:{slot}:{}", tid.0, p.id),
);
if let Some(pl) = &app.player {
// Keep the plugin's own editor in step.
pl.editor_set_param(tid, slot, p.id, x);
}
}
let _ = resp;
ui.label(egui::RichText::new(p.format(x)).font(mono(11.0)));
ui.end_row();
}
let _ = resp;
ui.label(egui::RichText::new(p.format(x)).font(mono(11.0)));
ui.end_row();
}
});
});
});
if open {

View File

@ -0,0 +1,19 @@
use egui_kittest::Harness;
use soundcraft_ui_egui::{Services, SoundApp};
#[test]
fn a_long_plugin_window_stays_on_a_short_screen() {
let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default());
let kick = app.engine.session().track_by_name("Kick").unwrap().id;
app.ui.plugin_windows = vec![(kick, 0)];
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 500.0)).build_ui_state(
|ui, app: &mut SoundApp| {
app.logic(&ui.ctx().clone());
app.ui(ui);
},
app,
);
h.run_steps(4);
let window = h.ctx.memory(|m| m.area_rect(egui::Id::new(("plugin", kick.0, 0usize)))).unwrap();
assert!(window.bottom() <= 500.0, "{window:?}");
}