Merge pull request #1100 from pravdomil/claude/compositing-buttons-positioning-b52693

UI: Composition Settings keeps Cancel and OK in place between tabs
This commit is contained in:
BFlat
2026-10-30 04:07:53 +07:00
committed by GitHub
2 changed files with 73 additions and 23 deletions
+53 -23
View File
@@ -246,36 +246,18 @@ fn matches(d: &CompDraft, w: u32, h: u32, par: f64, fps: f64) -> bool {
w == d.width && h == d.height && (par - d.pixel_aspect).abs() < 1e-3 && (fps - d.fps).abs() < 1e-3
}
/// Draw the dialog. Returns `Some(params)` when OK is pressed, `Some(Null)` on Cancel. `custom`
/// are the presets the user saved; saving or deleting one pushes its engine command to `cmds`.
pub fn show(
/// The controls of one tab.
fn tab_body(
ui: &mut egui::Ui,
tab: Tab,
d: &mut CompDraft,
t: &Tokens,
auto: &mut Registry,
custom: &[CompPreset],
cmds: &mut Vec<(&'static str, serde_json::Value)>,
) -> Option<serde_json::Value> {
let mut out = None;
ui.horizontal(|ui| {
ui.label(tr("Composition Name:"));
let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(300.0));
crate::widgets::focus_on_open(ui.ctx(), &r, &d.name);
auto.add("dialog.comp.name", r.rect, "Composition Name");
});
ui.add_space(8.0);
ui.horizontal(|ui| {
for (tab, label, key) in [(Tab::Basic, "Basic", "basic"), (Tab::Advanced, "Advanced", "advanced"), (Tab::Renderer, "3D Renderer", "renderer")] {
let r = ui.selectable_label(d.tab == tab, label);
auto.add(&format!("dialog.comp.tab.{key}"), r.rect, label);
if r.clicked() {
d.tab = tab;
}
}
});
ui.separator();
) {
let rate = FrameRate::from_f64(d.fps);
match d.tab {
match tab {
Tab::Basic => {
egui::Grid::new("comp-basic").num_columns(2).spacing([12.0, 9.0]).show(ui, |ui| {
ui.label(tr("Preset:"));
@@ -485,6 +467,54 @@ pub fn show(
});
}
}
}
/// The height `tab` would take at `top`, from laying it out unseen: it follows the font, the
/// language and the scale. Nothing it registers or changes is kept.
fn tab_height(ui: &mut egui::Ui, tab: Tab, d: &CompDraft, t: &Tokens, custom: &[CompPreset], top: f32) -> f32 {
let rect = egui::Rect::from_min_size(egui::pos2(ui.max_rect().left(), top), vec2(ui.max_rect().width(), 0.0));
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect).id_salt("comp-tab-measure").invisible());
tab_body(&mut child, tab, &mut d.clone(), t, &mut Registry::default(), custom, &mut Vec::new());
child.cursor().top() - top
}
/// Draw the dialog. Returns `Some(params)` when OK is pressed, `Some(Null)` on Cancel. `custom`
/// are the presets the user saved; saving or deleting one pushes its engine command to `cmds`.
pub fn show(
ui: &mut egui::Ui,
d: &mut CompDraft,
t: &Tokens,
auto: &mut Registry,
custom: &[CompPreset],
cmds: &mut Vec<(&'static str, serde_json::Value)>,
) -> Option<serde_json::Value> {
let mut out = None;
ui.horizontal(|ui| {
ui.label(tr("Composition Name:"));
let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(300.0));
crate::widgets::focus_on_open(ui.ctx(), &r, &d.name);
auto.add("dialog.comp.name", r.rect, "Composition Name");
});
ui.add_space(8.0);
ui.horizontal(|ui| {
for (tab, label, key) in [(Tab::Basic, "Basic", "basic"), (Tab::Advanced, "Advanced", "advanced"), (Tab::Renderer, "3D Renderer", "renderer")] {
let r = ui.selectable_label(d.tab == tab, label);
auto.add(&format!("dialog.comp.tab.{key}"), r.rect, label);
if r.clicked() {
d.tab = tab;
}
}
});
ui.separator();
// Every tab takes the height of the tallest one, so Cancel and OK stay put between tabs.
let body_top = ui.cursor().top();
tab_body(ui, d.tab, d, t, auto, custom, cmds);
let shown = ui.cursor().top() - body_top;
let tallest = [Tab::Basic, Tab::Advanced, Tab::Renderer]
.into_iter()
.filter(|tab| *tab != d.tab)
.fold(shown, |h, tab| h.max(tab_height(ui, tab, d, t, custom, body_top)));
ui.add_space((tallest - shown).max(0.0));
ui.add_space(14.0);
ui.horizontal(|ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
@@ -83,3 +83,23 @@ fn a_preset_name_cannot_be_a_built_in_one() {
assert!(h.state().auto.find("dialog.comp.presetName").is_none());
assert!(preset_names(&h).is_empty());
}
/// Cancel and OK stay where they are when switching between Basic, Advanced and 3D Renderer,
/// at any interface scale: the tabs' heights are measured, not assumed.
#[test]
fn composition_settings_buttons_stay_put_between_tabs() {
for zoom in [1.0, 1.3] {
let mut h = harness();
h.ctx.set_zoom_factor(zoom);
h.run_steps(3);
open_settings(&mut h);
let rects = |h: &Harness<'_, EffectcraftApp>| {
["dialog.comp.ok", "dialog.comp.cancel", "dialog.comp.name"].map(|id| h.state().auto.find(id).unwrap_or_else(|| panic!("no {id}")).rect)
};
let basic = rects(&h);
for tab in ["advanced", "renderer", "basic"] {
click(&mut h, &format!("dialog.comp.tab.{tab}"));
assert_eq!(rects(&h), basic, "{tab} at {zoom}x");
}
}
}