mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 04:57:54 -06:00
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:
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user