diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index f2d2e8822..b565166d9 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -329,16 +329,24 @@ fn cancel_label(dlg: &Dialog, sam: &SamState) -> &'static str { /// The label of a dialog's confirm button (empty: it has none). fn ok_label(app: &LightcraftApp, dlg: &Dialog, informational: bool, sam: &SamState) -> String { - let label = match dlg { - Dialog::Import { opts } => { - let n = opts.selected_paths().len(); - let verb = if opts.copy && opts.move_files { "Move" } else { "Import" }; - return crate::i18n::tr_format!("{verb} {n} Photo{}", if n == 1 { "" } else { "s" }, verb = crate::i18n::tr(verb), n = n); - } - Dialog::Export { .. } => { - let n = app.session.selection.ids.len().max(1); - return crate::i18n::tr_format!("{verb} {n} Photo{}", if n == 1 { "" } else { "s" }, verb = crate::i18n::tr("Export"), n = n); - } + match dlg { + Dialog::Import { opts } => photos_ok_label(if opts.copy && opts.move_files { "Move" } else { "Import" }, opts.selected_paths().len()), + Dialog::Export { .. } => photos_ok_label("Export", app.session.selection.ids.len().max(1)), + Dialog::SamModel { .. } => sam_ok_label(dlg, sam).to_string(), + _ => fixed_ok_label(app, dlg, informational).to_string(), + } +} + +/// "Import 3 Photos", "Export 1 Photo": a verb and how many photos it acts on. +fn photos_ok_label(verb: &str, n: usize) -> String { + crate::i18n::tr_format!("{verb} {n} Photo{}", crate::i18n::plural_s(n), verb = crate::i18n::tr(verb), n = n) +} + +/// The confirm labels that are one fixed phrase. +fn fixed_ok_label(app: &LightcraftApp, dlg: &Dialog, informational: bool) -> &'static str { + match dlg { + // worded with a count or by the SAM state: `ok_label` builds them + Dialog::Import { .. } | Dialog::Export { .. } | Dialog::SamModel { .. } => "", Dialog::LightroomResult { first: Some(_), .. } => "Show in Library", Dialog::ContactSheet { .. } => "Choose PDF…", Dialog::SlideshowExport { .. } => "Export…", @@ -376,7 +384,6 @@ fn ok_label(app: &LightcraftApp, dlg: &Dialog, informational: bool, sam: &SamSta d @ Dialog::LightroomDestination { .. } => crate::lightroom_destination::ok_label(d), Dialog::SynchronizeFolder { .. } if informational => "Close", Dialog::SynchronizeFolder { .. } => "Synchronize", - Dialog::SamModel { .. } => return sam_ok_label(dlg, sam).to_string(), Dialog::RenamePerson { from, to, .. } if person_rename_plan(&app.session.catalog, from, to).needs_merge => "Merge", Dialog::RenamePerson { .. } => "Rename", // the dialogs that only show something, or a model file that can't be used @@ -391,8 +398,7 @@ fn ok_label(app: &LightcraftApp, dlg: &Dialog, informational: bool, sam: &SamSta | Dialog::LightroomResult { .. } | Dialog::DenoiseModel { .. } | Dialog::FaceModel { .. } => "Close", - }; - label.to_string() + } } /// The label of the SAM 3 dialog's confirm button. @@ -1105,13 +1111,7 @@ fn contact_sheet_body(app: &mut LightcraftApp, ui: &mut egui::Ui, dlg: &mut Dial options.rows = options.rows.clamp(1, 10); let n = app.session.selected().len(); let pages = n.div_ceil(options.columns * options.rows); - ui.label(crate::i18n::tr_format!( - "{n} selected photo{} · {pages} page{}", - if n == 1 { "" } else { "s" }, - if pages == 1 { "" } else { "s" }, - n = n, - pages = pages - )); + ui.label(contact_sheet_summary(n, pages)); field(ui, "Paper", |ui| { let c = crate::widgets::combo_box("contactSheetPaper").selected_text(&options.paper).show_ui(ui, |ui| { ui.selectable_value(&mut options.paper, "a4".into(), "A4"); @@ -1131,6 +1131,11 @@ fn contact_sheet_body(app: &mut LightcraftApp, ui: &mut egui::Ui, dlg: &mut Dial ui.label(crate::i18n::tr("Choose a PDF destination after confirming. Print the PDF from your viewer.")); } +/// "N selected photos · M pages". +fn contact_sheet_summary(n: usize, pages: usize) -> String { + crate::i18n::tr_format!("{n} selected photo{} · {pages} page{}", crate::i18n::plural_s(n), crate::i18n::plural_s(pages), n = n, pages = pages) +} + /// The body of the Export dialog. fn export_body(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens, dlg: &mut Dialog) { let Dialog::Export { opts, full_size, resize, preset_name, limit_kb, dir } = dlg else { return }; diff --git a/crates/ui-egui/src/dialogs/slideshow_export.rs b/crates/ui-egui/src/dialogs/slideshow_export.rs index 39d48c372..fccd7ce72 100644 --- a/crates/ui-egui/src/dialogs/slideshow_export.rs +++ b/crates/ui-egui/src/dialogs/slideshow_export.rs @@ -79,37 +79,13 @@ pub fn poll(app: &mut LightcraftApp, ctx: &egui::Context) { pub(super) fn slideshow_export_body(app: &mut LightcraftApp, ui: &mut egui::Ui, settings: &mut lightcraft_engine::slideshow_export::ExportSettings) { use lightcraft_engine::slideshow_export::{ExportSettings, Format}; let t = Tokens::get(ui.ctx()); - let photos = app.session.visible_cloned().len(); - let slide = app.session.slideshow.slide_duration; - let (intro, ending) = app.session.slide_titles.durations(slide); - let titles = usize::from(intro.is_some()) + usize::from(ending.is_some()); - let seconds = photos as f64 * slide + intro.unwrap_or(0.0) + ending.unwrap_or(0.0); - let summary = crate::i18n::tr_format!( - "{n} photo{} in view · {slides} slide{} · {time}", - if photos == 1 { "" } else { "s" }, - if photos + titles == 1 { "" } else { "s" }, - n = photos, - slides = photos + titles, - time = format!("{}:{:02}", (seconds / 60.0).floor() as u64, (seconds % 60.0).round() as u64) - ); - ui.label(egui::RichText::new(summary).color(t.text_dim)); + ui.label(egui::RichText::new(slideshow_summary(app)).color(t.text_dim)); let before = settings.format; choices(ui, "Format", "slideshowExportFormat", &[(Format::Pdf, "PDF"), (Format::Jpeg, "JPEG"), (Format::Video, "Video")], &mut settings.format); if settings.format != before { *settings = ExportSettings { full_screen: settings.full_screen, ..ExportSettings::for_format(settings.format) }; } - match settings.format { - Format::Video => { - choices(ui, "Size", "slideshowExportSize", &[(720, "720p"), (1080, "1080p")], &mut settings.size); - choices(ui, "Frame rate", "slideshowExportFps", &[(24, "24 fps"), (30, "30 fps")], &mut settings.fps); - let mut webm = settings.container == "webm"; - choices(ui, "Container", "slideshowExportContainer", &[(false, "MP4"), (true, "WebM")], &mut webm); - settings.container = if webm { "webm".into() } else { "mp4".into() }; - } - _ => { - choices(ui, "Size", "slideshowExportSize", &[(1024, "1024 px"), (1920, "1920 px"), (3840, "3840 px")], &mut settings.size); - } - } + slideshow_size_rows(ui, settings); field(ui, "Quality", |ui| { let r = ui.add(egui::Slider::new(&mut settings.quality, 1..=100)); crate::widgets::register(ui, "slider:slideshowExportQuality", r.rect); @@ -130,6 +106,46 @@ pub(super) fn slideshow_export_body(app: &mut LightcraftApp, ui: &mut egui::Ui, ); } +/// How many photos are in view, how many slides they make with the intro and ending screens, and how long it plays. +fn slideshow_counts(app: &mut LightcraftApp) -> (usize, usize, f64) { + let photos = app.session.visible_cloned().len(); + let slide = app.session.slideshow.slide_duration; + let (intro, ending) = app.session.slide_titles.durations(slide); + let titles = usize::from(intro.is_some()) + usize::from(ending.is_some()); + (photos, photos + titles, photos as f64 * slide + intro.unwrap_or(0.0) + ending.unwrap_or(0.0)) +} + +/// "N photos in view · M slides · m:ss". +fn slideshow_summary(app: &mut LightcraftApp) -> String { + let (photos, slides, seconds) = slideshow_counts(app); + let time = format!("{}:{:02}", (seconds / 60.0).floor() as u64, (seconds % 60.0).round() as u64); + crate::i18n::tr_format!( + "{n} photo{} in view · {slides} slide{} · {time}", + crate::i18n::plural_s(photos), + crate::i18n::plural_s(slides), + n = photos, + slides = slides, + time = time + ) +} + +/// The size choices of the chosen format, and a video's frame rate and container. +fn slideshow_size_rows(ui: &mut egui::Ui, settings: &mut lightcraft_engine::slideshow_export::ExportSettings) { + use lightcraft_engine::slideshow_export::Format; + match settings.format { + Format::Video => { + choices(ui, "Size", "slideshowExportSize", &[(720, "720p"), (1080, "1080p")], &mut settings.size); + choices(ui, "Frame rate", "slideshowExportFps", &[(24, "24 fps"), (30, "30 fps")], &mut settings.fps); + let mut webm = settings.container == "webm"; + choices(ui, "Container", "slideshowExportContainer", &[(false, "MP4"), (true, "WebM")], &mut webm); + settings.container = if webm { "webm".into() } else { "mp4".into() }; + } + _ => { + choices(ui, "Size", "slideshowExportSize", &[(1024, "1024 px"), (1920, "1920 px"), (3840, "3840 px")], &mut settings.size); + } + } +} + #[cfg(test)] mod tests { use super::*;