mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-05 18:18:49 +00:00
UI: Composition Settings widens to fit a longer translation (#2841)
The dialog was a fixed 600 points wide inside a scroll area, so a row wider than that in another language (French "Conserver la fréquence d'images en cas d'imbrication ou dans la file d'attente de rendu", the French and Spanish Lock Aspect Ratio row) pushed OK past the right edge, reachable only by scrolling sideways. The dialog now measures the widest row of its three tabs (as it already measured their heights) and takes that width, or 600 points if that is more, capped by the window as before. English keeps 600 points.
This commit is contained in:
1 parent
9d7baea5cc
commit
cab353eb5b
3 files changed
+60
-11
No files matched your search
@@ -469,13 +469,24 @@ fn tab_body(
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// The width and height `tab` would take at `top`, from laying it out unseen: they follow the
|
||||
/// font, the language and the scale. Nothing it registers or changes is kept.
|
||||
fn tab_size(ui: &mut egui::Ui, tab: Tab, d: &CompDraft, t: &Tokens, custom: &[CompPreset], top: f32) -> egui::Vec2 {
|
||||
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
|
||||
vec2(child.min_rect().width(), child.cursor().top() - top)
|
||||
}
|
||||
|
||||
fn content_width_id() -> egui::Id {
|
||||
egui::Id::unique("comp-settings-content-width")
|
||||
}
|
||||
|
||||
/// The width the dialog's content took when it was last shown: its widest row on any tab. A
|
||||
/// translation longer than the English (French, #2841) is wider than the 600-point dialog, so the
|
||||
/// dialog grows to it rather than hiding OK past its right edge.
|
||||
pub fn content_width(ctx: &egui::Context) -> f32 {
|
||||
ctx.data(|d| d.get_temp::<f32>(content_width_id())).unwrap_or(0.0)
|
||||
}
|
||||
|
||||
/// Draw the dialog. Returns `Some(params)` when OK is pressed, `Some(Null)` on Cancel. `custom`
|
||||
@@ -490,14 +501,16 @@ pub fn show(
|
||||
preview: Option<&mut bool>,
|
||||
) -> Option<serde_json::Value> {
|
||||
let mut out = None;
|
||||
ui.horizontal(|ui| {
|
||||
// The dialog's visible area, which Cancel and OK stay inside.
|
||||
auto.add("dialog.comp.view", ui.clip_rect().intersect(ui.max_rect()), "Composition Settings");
|
||||
let name_row = 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| {
|
||||
let tab_row = 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, tr(label));
|
||||
auto.add(&format!("dialog.comp.tab.{key}"), r.rect, label);
|
||||
@@ -511,11 +524,12 @@ pub fn show(
|
||||
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)));
|
||||
let sizes: Vec<egui::Vec2> = [Tab::Basic, Tab::Advanced, Tab::Renderer].into_iter().map(|tab| tab_size(ui, tab, d, t, custom, body_top)).collect();
|
||||
let tallest = sizes.iter().fold(shown, |h, s| h.max(s.y));
|
||||
ui.add_space((tallest - shown).max(0.0));
|
||||
// Every tab takes the width of the widest one too, so the dialog doesn't change size with them.
|
||||
let widest = sizes.iter().fold(name_row.response.rect.width().max(tab_row.response.rect.width()), |w, s| w.max(s.x));
|
||||
ui.ctx().data_mut(|m| m.insert_temp(content_width_id(), widest.ceil()));
|
||||
ui.add_space(14.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
|
||||
@@ -446,7 +446,9 @@ fn comp_settings(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens, exis
|
||||
let live = existing && super::live_preview::active(app);
|
||||
let place = super::live_preview::place(app);
|
||||
let mut preview = app.dialog_state.live.on;
|
||||
modal_placed(ctx, "Composition Settings", vec2(600.0, 470.0), t, place, |ui| {
|
||||
// 600 points wide, or as wide as the content took last time (with the 18-point margins).
|
||||
let width = (super::comp_settings::content_width(ctx) + 36.0).max(600.0);
|
||||
modal_placed(ctx, "Composition Settings", vec2(width, 470.0), t, place, |ui| {
|
||||
result = super::comp_settings::show(ui, &mut d, t, &mut app.auto, &custom, &mut cmds, live.then_some(&mut preview));
|
||||
});
|
||||
app.dialog_state.live.on = preview;
|
||||
|
||||
@@ -103,3 +103,36 @@ fn composition_settings_buttons_stay_put_between_tabs() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Composition Settings keeps Cancel and OK in view in every interface language: a translation
|
||||
/// wider than the English (French, #2841) widens the dialog instead of pushing OK past its right
|
||||
/// edge, where it could be reached only by scrolling sideways.
|
||||
#[test]
|
||||
fn composition_settings_buttons_fit_every_language() {
|
||||
// English, then French (#2841) and the other catalogs whose rows overflowed the 600 points.
|
||||
for language in ["en", "fr", "es", "ka", "cs", "pt-br"] {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Edit", "width": 2560, "height": 1440, "frameRate": 60, "duration": 4})).unwrap();
|
||||
s.prefs.general.language = language.into();
|
||||
let mut h =
|
||||
Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).with_step_dt(1.0 / 60.0).allow_missing_glyphs().build_eframe(|_| EffectcraftApp::new(s));
|
||||
h.run_steps(3);
|
||||
open_settings(&mut h);
|
||||
let mut oks = vec![];
|
||||
for tab in ["basic", "advanced", "renderer"] {
|
||||
click(&mut h, &format!("dialog.comp.tab.{tab}"));
|
||||
let rect = |id: &str| h.state().auto.find(id).unwrap_or_else(|| panic!("no {id}")).rect;
|
||||
let (view, ok, cancel) = (rect("dialog.comp.view"), rect("dialog.comp.ok"), rect("dialog.comp.cancel"));
|
||||
for (name, r) in [("OK", ok), ("Cancel", cancel)] {
|
||||
assert!(r[0] >= view[0] && r[0] + r[2] <= view[0] + view[2] + 0.5, "{language} {tab}: {name} {r:?} is outside the dialog {view:?}");
|
||||
}
|
||||
if language == "en" {
|
||||
// English still fits the 600-point dialog (18-point margins).
|
||||
assert_eq!(view[2], 600.0 - 36.0, "{tab}");
|
||||
}
|
||||
oks.push(ok);
|
||||
}
|
||||
// The dialog takes the width of its widest tab, so OK stays put between tabs.
|
||||
assert!(oks.windows(2).all(|w| w[0] == w[1]), "{language}: OK moves between tabs: {oks:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user