diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs index e95a667ec..6f735e9c1 100644 --- a/crates/ui-egui/src/panels/dialogs.rs +++ b/crates/ui-egui/src/panels/dialogs.rs @@ -192,6 +192,22 @@ pub(crate) fn modal_fit(ctx: &egui::Context, title: &str, size: egui::Vec2, t: & }); } +/// The buttons of a [`modal_fit`] body of `height`, kept in view: `footer` lays them out first, +/// at the bottom (bottom up: its first row is the lowest), and the rest of the body gets the room +/// above them. Returns that room (a left-to-right row, clipped to it, so a long or wide body +/// scrolls or clips under the buttons instead of pushing them out of the dialog) and its height. +pub(crate) fn above_footer(ui: &mut egui::Ui, height: f32, footer: impl FnOnce(&mut egui::Ui)) -> (egui::Ui, f32) { + let area = ui.allocate_space(vec2(ui.available_width(), height)).1; + let mut foot = ui.new_child(egui::UiBuilder::new().max_rect(area).layout(egui::Layout::bottom_up(egui::Align::Min))); + footer(&mut foot); + foot.add_space(8.0); + let body_h = (foot.min_rect().top() - area.top()).max(0.0); + let room = Rect::from_min_size(area.min, vec2(area.width(), body_h)); + let mut body = ui.new_child(egui::UiBuilder::new().max_rect(room).layout(egui::Layout::left_to_right(egui::Align::Min))); + body.set_clip_rect(room.intersect(body.clip_rect())); + (body, body_h) +} + pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context) { let Some(d) = app.dialog else { return }; let t = app.tokens; diff --git a/crates/ui-egui/src/panels/settings.rs b/crates/ui-egui/src/panels/settings.rs index f7a45febb..5f115b842 100644 --- a/crates/ui-egui/src/panels/settings.rs +++ b/crates/ui-egui/src/panels/settings.rs @@ -10,7 +10,7 @@ use crate::i18n::{tr, tr_args}; use effectcraft_engine::prefs::{APPEARANCE_MODES, DARK_THEMES, Item, Kind, LIGHT_THEMES, Page, pages}; use effectcraft_engine::segment::Task; -use egui::{Color32, Rect, RichText, vec2}; +use egui::{Color32, RichText, vec2}; use serde_json::{Value, json}; use crate::theme::{ThemeKind, Tokens}; @@ -633,37 +633,31 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { let mut acts: Vec = vec![]; let (mut ok, mut cancel) = (false, false); super::dialogs::modal_fit(ctx, "Settings", vec2(820.0, 600.0), t, |ui, height| { - // The buttons go first, from the bottom up, and the pages get the height they leave: at - // a high UI Scale the pages scroll and the buttons stay in view. - let area = ui.allocate_space(vec2(ui.available_width(), height)).1; - let mut foot = ui.new_child(egui::UiBuilder::new().max_rect(area).layout(egui::Layout::bottom_up(egui::Align::Min))); - foot.horizontal(|ui| { - let r = ui.add_enabled(idx > 0, egui::Button::new(tr("Previous"))); - app.auto.add("settings.previous", r.rect, "Previous"); - if r.clicked() { - page = all[idx - 1].id.to_string(); - } - let r = ui.add_enabled(idx + 1 < all.len(), egui::Button::new(tr("Next"))); - app.auto.add("settings.next", r.rect, "Next"); - if r.clicked() { - page = all[idx + 1].id.to_string(); - } - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let r = ui.add(egui::Button::new(RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)); - app.auto.add("settings.ok", r.rect, "OK"); - ok |= r.clicked(); - let r = ui.button(tr("Cancel")); - app.auto.add("settings.cancel", r.rect, "Cancel"); - cancel |= r.clicked(); + // The buttons go first and the pages get the height they leave: at a high UI Scale the + // pages scroll and the buttons stay in view. + let (mut body, body_h) = super::dialogs::above_footer(ui, height, |foot| { + foot.horizontal(|ui| { + let r = ui.add_enabled(idx > 0, egui::Button::new(tr("Previous"))); + app.auto.add("settings.previous", r.rect, "Previous"); + if r.clicked() { + page = all[idx - 1].id.to_string(); + } + let r = ui.add_enabled(idx + 1 < all.len(), egui::Button::new(tr("Next"))); + app.auto.add("settings.next", r.rect, "Next"); + if r.clicked() { + page = all[idx + 1].id.to_string(); + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let r = ui.add(egui::Button::new(RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)); + app.auto.add("settings.ok", r.rect, "OK"); + ok |= r.clicked(); + let r = ui.button(tr("Cancel")); + app.auto.add("settings.cancel", r.rect, "Cancel"); + cancel |= r.clicked(); + }); }); + foot.separator(); }); - foot.separator(); - foot.add_space(8.0); - let body_h = (foot.min_rect().top() - area.top()).max(0.0); - // (A child, clipped to its share, so a wide page can't widen the dialog under the buttons.) - let pages = Rect::from_min_size(area.min, vec2(area.width(), body_h)); - let mut body = ui.new_child(egui::UiBuilder::new().max_rect(pages).layout(egui::Layout::left_to_right(egui::Align::Min))); - body.set_clip_rect(pages.intersect(body.clip_rect())); { let ui = &mut body; // Page list (left, like After Effects).