diff --git a/apps/effectcraft/src/main.rs b/apps/effectcraft/src/main.rs index bc16a020..a7b24f3e 100644 --- a/apps/effectcraft/src/main.rs +++ b/apps/effectcraft/src/main.rs @@ -52,7 +52,7 @@ fn is_project_file(path: &str) -> bool { #[cfg(target_os = "linux")] fn wayland_drop_notice(wayland_display: Option<&std::ffi::OsStr>, backend: Option<&std::ffi::OsStr>) -> Option<&'static str> { (wayland_display.is_some_and(|v| !v.is_empty()) && backend != Some(std::ffi::OsStr::new("x11"))) - .then_some("Native Wayland file drops are unavailable. Use File > Import > File... or run via XWayland (see docs/footage.md).") + .then_some("Native Wayland file drops are unavailable. Use File ▸ Import ▸ File… or run via XWayland (see README, Linux).") } /// The command line. @@ -196,10 +196,8 @@ fn main() -> eframe::Result { app.offer_recovery(r); } #[cfg(target_os = "linux")] - if let Some(note) = wayland_drop_notice( - std::env::var_os("WAYLAND_DISPLAY").as_deref(), - std::env::var_os("WINIT_UNIX_BACKEND").as_deref(), - ) && app.ui.status.is_empty() + if let Some(note) = wayland_drop_notice(std::env::var_os("WAYLAND_DISPLAY").as_deref(), std::env::var_os("WINIT_UNIX_BACKEND").as_deref()) + && app.ui.status.is_empty() { app.ui.status = note.into(); } @@ -386,7 +384,7 @@ mod tests { fn wayland_drop_notice_recommends_import_or_xwayland() { use std::ffi::OsStr; let warning = super::wayland_drop_notice(Some(OsStr::new("wayland-0")), None).unwrap(); - assert!(warning.contains("File > Import") && warning.contains("XWayland")); + assert!(warning.contains("File ▸ Import") && warning.contains("XWayland")); assert!(super::wayland_drop_notice(None, None).is_none()); assert!(super::wayland_drop_notice(Some(OsStr::new("")), None).is_none()); assert!(super::wayland_drop_notice(Some(OsStr::new("wayland-0")), Some(OsStr::new("x11"))).is_none()); diff --git a/crates/ui-egui/src/prefs_live.rs b/crates/ui-egui/src/prefs_live.rs index 242f962c..85c26013 100644 --- a/crates/ui-egui/src/prefs_live.rs +++ b/crates/ui-egui/src/prefs_live.rs @@ -25,30 +25,13 @@ pub fn dropped_files(app: &mut EffectcraftApp, ui: &egui::Ui, bounds: Rect) { let ctx = ui.ctx().clone(); let hovered = ctx.input(|i| i.raw.hovered_files.len()); if hovered != 0 { - let label = if hovered == 1 { - "Drop file to import".to_string() - } else { - format!("Drop {hovered} files to import") - }; - let painter = ctx.layer_painter(egui::LayerId::new( - egui::Order::Foreground, - egui::Id::new("native-file-drop"), - )); - painter.rect_stroke( - bounds.shrink(8.0), - 6.0, - Stroke::new(2.0, app.tokens.accent), - StrokeKind::Inside, - ); + let label = + if hovered == 1 { crate::i18n::tr("Drop file to import").to_string() } else { crate::i18n::tr_args("Drop {} files to import", &[&hovered]) }; + let painter = ctx.layer_painter(egui::LayerId::new(egui::Order::Foreground, egui::Id::new("native-file-drop"))); + painter.rect_stroke(bounds.shrink(8.0), 6.0, Stroke::new(2.0, app.tokens.accent), StrokeKind::Inside); let hint = Rect::from_center_size(bounds.center(), vec2(300.0, 46.0)); painter.rect_filled(hint, 6.0, app.tokens.panel_bg); - painter.text( - hint.center(), - Align2::CENTER_CENTER, - &label, - crate::theme::Tokens::medium(14.0), - app.tokens.text, - ); + painter.text(hint.center(), Align2::CENTER_CENTER, &label, crate::theme::Tokens::medium(14.0), app.tokens.text); app.auto.add("file.dropTarget", hint, &label); } @@ -71,9 +54,7 @@ pub fn dropped_files(app: &mut EffectcraftApp, ui: &egui::Ui, bounds: Rect) { let mut errors = Vec::new(); if skipped != 0 { - errors.push(format!( - "Skipped {skipped} dropped file(s) without a local path. Use File > Import > File..." - )); + errors.push(crate::i18n::tr_args("Skipped {} dropped file(s) without a local path. Use File ▸ Import ▸ File…", &[&skipped])); } let (proj, rest): (Vec, Vec) = paths.into_iter().partition(|p| { @@ -82,30 +63,25 @@ pub fn dropped_files(app: &mut EffectcraftApp, ui: &egui::Ui, bounds: Rect) { }); if proj.len() > 1 { - errors.push(format!( - "Only one project can be opened per drop; skipped {} more", - proj.len() - 1 - )); + errors.push(crate::i18n::tr_args("Only one project can be opened per drop; skipped {} more", &[&proj.len().saturating_sub(1)])); } if let Some(p) = proj.first() && let Err(e) = crate::menus::invoke(app, &ctx, "file.open", json!({"path": p})) { - errors.push(format!("Cannot open dropped project {p}: {e}")); + errors.push(crate::i18n::tr_args("Cannot open dropped project {}: {}", &[&p, &e])); } if !rest.is_empty() { - let viewer = app.auto.elements.iter().find(|e| e.id == "viewer.area").map(|e| { - Rect::from_min_size( - egui::pos2(e.rect[0], e.rect[1]), - egui::vec2(e.rect[2], e.rect[3]), - ) - }); + let viewer = app + .auto + .elements + .iter() + .find(|e| e.id == "viewer.area") + .map(|e| Rect::from_min_size(egui::pos2(e.rect[0], e.rect[1]), egui::vec2(e.rect[2], e.rect[3]))); let at = if proj.is_empty() { - ctx.input(|i| i.pointer.hover_pos()) - .filter(|p| viewer.is_some_and(|v| v.contains(*p))) - .and_then(|p| crate::panels::viewer::screen_to_comp(&ctx, p)) + ctx.input(|i| i.pointer.hover_pos()).filter(|p| viewer.is_some_and(|v| v.contains(*p))).and_then(|p| crate::panels::viewer::screen_to_comp(&ctx, p)) } else { None }; @@ -120,7 +96,7 @@ pub fn dropped_files(app: &mut EffectcraftApp, ui: &egui::Ui, bounds: Rect) { }); if let Err(e) = crate::menus::invoke(app, &ctx, "file.import", params) { - errors.push(format!("Cannot import dropped files: {e}")); + errors.push(crate::i18n::tr_args("Cannot import dropped files: {}", &[&e])); } } @@ -191,21 +167,10 @@ fn video_preview(app: &mut EffectcraftApp, ctx: &egui::Context) { let opts = crate::panels::viewer::zoom_texture_options(app.session.prefs.viewer_zoom_smooth()); let t = match prev { Some((_, mut t)) => { - t.set( - crate::frames::fit_texture( - (*img).clone(), - crate::frames::max_texture_side(ctx), - ), - opts, - ); + t.set(crate::frames::fit_texture((*img).clone(), crate::frames::max_texture_side(ctx)), opts); t } - None => crate::frames::load_fitted( - ctx, - "video-preview", - (*img).clone(), - opts, - ), + None => crate::frames::load_fitted(ctx, "video-preview", (*img).clone(), opts), }; ctx.data_mut(|d| d.insert_temp(tex_id, (k, t.clone()))); Some(t) @@ -215,55 +180,30 @@ fn video_preview(app: &mut EffectcraftApp, ctx: &egui::Context) { (_, _, prev) => prev.map(|p| p.1), }; - let title = format!( - "Video Preview — {}", - app.session - .active_comp_id() - .and_then(|c| app.session.project.item(c)) - .map(|i| i.name.clone()) - .unwrap_or_default() - ); + let title = + format!("Video Preview — {}", app.session.active_comp_id().and_then(|c| app.session.project.item(c)).map(|i| i.name.clone()).unwrap_or_default()); - let mut builder = egui::ViewportBuilder::default() - .with_title(title) - .with_inner_size([960.0, 540.0]); + let mut builder = egui::ViewportBuilder::default().with_title(title).with_inner_size([960.0, 540.0]); if v.device == "fullscreen" { builder = builder.with_fullscreen(true); } let mut closed = false; - ctx.show_viewport_immediate( - egui::ViewportId::from_hash_of("effectcraft-video-preview"), - builder, - |vctx, _| { - egui::CentralPanel::default() - .frame(egui::Frame::NONE.fill(Color32::BLACK)) - .show(vctx, |ui| { - let area = ui.max_rect(); - if let Some(t) = &tex { - let [w, h] = t.size().map(|x| x.max(1) as f32); - let s = (area.width() / w).min(area.height() / h); - let r = Rect::from_center_size( - area.center(), - egui::vec2(w * s, h * s), - ); - ui.painter().image( - t.id(), - r, - Rect::from_min_max( - egui::pos2(0.0, 0.0), - egui::pos2(1.0, 1.0), - ), - Color32::WHITE, - ); - } - }); - if vctx.input(|i| i.viewport().close_requested()) { - closed = true; + ctx.show_viewport_immediate(egui::ViewportId::from_hash_of("effectcraft-video-preview"), builder, |vctx, _| { + egui::CentralPanel::default().frame(egui::Frame::NONE.fill(Color32::BLACK)).show(vctx, |ui| { + let area = ui.max_rect(); + if let Some(t) = &tex { + let [w, h] = t.size().map(|x| x.max(1) as f32); + let s = (area.width() / w).min(area.height() / h); + let r = Rect::from_center_size(area.center(), egui::vec2(w * s, h * s)); + ui.painter().image(t.id(), r, Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), Color32::WHITE); } - }, - ); + }); + if vctx.input(|i| i.viewport().close_requested()) { + closed = true; + } + }); if closed && let Err(e) = app.set_pref("video.enableOutput", Value::Bool(false)) { app.ui.status = e; diff --git a/crates/ui-egui/tests/ui_drag_drop.rs b/crates/ui-egui/tests/ui_drag_drop.rs index 6fe346d2..9738fef2 100644 --- a/crates/ui-egui/tests/ui_drag_drop.rs +++ b/crates/ui-egui/tests/ui_drag_drop.rs @@ -277,7 +277,7 @@ fn dropped_file_without_local_path_explains_the_fallback() { h.input_mut().dropped_files.push(Arc::new(Dropped("relative.mp4".into()))); h.run_steps(2); assert!(h.state().ui.status.contains("without a local path"), "{}", h.state().ui.status); - assert!(h.state().ui.status.contains("File > Import")); + assert!(h.state().ui.status.contains("File ▸ Import")); } #[test]