From 0e2f43b824c1e4d942ead290b7832e4d72514ba6 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Fri, 16 Oct 2026 16:42:34 +0700 Subject: [PATCH] perf(project): keep the Size cell as After Effects shows it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the byte-count tooltip, the "—" marker and their strings and UI test, and the bench wrapper script: they are outside this PR's scope and After Effects leaves the Size cell blank when the size is unknown. The size sort and its tests are unchanged. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n/ui.rs | 14 ---- crates/ui-egui/src/panels/project.rs | 16 +--- crates/ui-egui/tests/ui_project_rows.rs | 102 ------------------------ scripts/bench-project-size-sort.sh | 4 - 4 files changed, 2 insertions(+), 134 deletions(-) delete mode 100755 scripts/bench-project-size-sort.sh diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs index d61eff44d..3a4dd4aff 100644 --- a/crates/ui-egui/src/i18n/ui.rs +++ b/crates/ui-egui/src/i18n/ui.rs @@ -252,8 +252,6 @@ const JAPANESE: &[(&str, &str)] = &[ ("Focus Distance", "フォーカス距離"), ("Follow Feature Rotation and Scale", "特徴の回転とスケールに追従"), ("Footage: {}", "フッテージ:{}"), - ("File size: {} bytes", "ファイルサイズ: {} バイト"), - ("File size unavailable", "ファイルサイズを取得できません"), ("Format: {}", "形式:{}"), ("Frame Aspect Ratio: {}", "フレームアスペクト比:{}"), ("Frame Blending: {}", "フレームブレンド:{}"), @@ -798,8 +796,6 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[ ("Focus Distance", "焦距"), ("Follow Feature Rotation and Scale", "跟随特征的旋转和缩放"), ("Footage: {}", "素材:{}"), - ("File size: {} bytes", "文件大小:{} 字节"), - ("File size unavailable", "无法获取文件大小"), ("Format: {}", "格式:{}"), ("Frame Aspect Ratio: {}", "帧长宽比:{}"), ("Frame Blending: {}", "帧混合:{}"), @@ -1532,8 +1528,6 @@ const TRADITIONAL_CHINESE: &[(&str, &str)] = &[ ("Focus Distance", "焦距"), ("Follow Feature Rotation and Scale", "跟隨特徵的旋轉和縮放"), ("Footage: {}", "素材:{}"), - ("File size: {} bytes", "檔案大小:{} 位元組"), - ("File size unavailable", "無法取得檔案大小"), ("Format: {}", "格式:{}"), ("Frame Aspect Ratio: {}", "影格長寬比:{}"), ("Frame Blending: {}", "影格混合:{}"), @@ -2269,8 +2263,6 @@ const UKRAINIAN: &[(&str, &str)] = &[ ("Focus Distance", "Відстань фокусування"), ("Follow Feature Rotation and Scale", "Враховувати обертання та масштаб ознаки"), ("Footage: {}", "Матеріал: {}"), - ("File size: {} bytes", "Розмір файлу: {} байт"), - ("File size unavailable", "Розмір файлу недоступний"), ("Format: {}", "Формат: {}"), ("Frame Aspect Ratio: {}", "Пропорції кадру: {}"), ("Frame Blending: {}", "Змішування кадрів: {}"), @@ -2823,8 +2815,6 @@ const PORTUGUESE: &[(&str, &str)] = &[ ("Focus Distance", "Distância de foco"), ("Follow Feature Rotation and Scale", "Acompanhar rotação e escala do recurso"), ("Footage: {}", "Mídia: {}"), - ("File size: {} bytes", "Tamanho do arquivo: {} bytes"), - ("File size unavailable", "Tamanho do arquivo indisponível"), ("Format: {}", "Formato: {}"), ("Frame Aspect Ratio: {}", "Proporção do quadro: {}"), ("Frame Blending: {}", "Mesclagem de quadros: {}"), @@ -3275,8 +3265,6 @@ const CZECH: &[(&str, &str)] = &[ ("Focus Distance", "Vzdálenost zaostření"), ("Follow Feature Rotation and Scale", "Sledovat otočení a měřítko prvku"), ("Footage: {}", "Záznam: {}"), - ("File size: {} bytes", "Velikost souboru: {} bajtů"), - ("File size unavailable", "Velikost souboru není k dispozici"), ("Format: {}", "Formát: {}"), ("Frame Aspect Ratio: {}", "Poměr stran snímku: {}"), ("Frame Blending: {}", "Prolnutí snímků: {}"), @@ -3832,8 +3820,6 @@ const SPANISH: &[(&str, &str)] = &[ ("Focus Distance", "Distancia de enfoque"), ("Follow Feature Rotation and Scale", "Seguir la rotación y la escala de la característica"), ("Footage: {}", "Material de archivo: {}"), - ("File size: {} bytes", "Tamaño del archivo: {} bytes"), - ("File size unavailable", "Tamaño del archivo no disponible"), ("Format: {}", "Formato: {}"), ("Frame Aspect Ratio: {}", "Relación de aspecto del fotograma: {}"), ("Frame Blending: {}", "Fusión de fotogramas: {}"), diff --git a/crates/ui-egui/src/panels/project.rs b/crates/ui-egui/src/panels/project.rs index 2cb6b8c73..76309a5d0 100644 --- a/crates/ui-egui/src/panels/project.rs +++ b/crates/ui-egui/src/panels/project.rs @@ -766,19 +766,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let cp = lp.with_clip_rect(cell); let txt = match *key { "type" => it.type_name().to_string(), - "size" => { - let size = file_size(it); - let text = size.map(fmt_size).unwrap_or_else(|| if matches!(it.kind, ItemKind::Footage(_)) { "—".into() } else { String::new() }); - if matches!(it.kind, ItemKind::Footage(_)) { - let detail = match size { - Some(bytes) => crate::i18n::tr_args("File size: {} bytes", &[&bytes]), - None => crate::i18n::tr("File size unavailable").to_string(), - }; - let resp = ui.interact(cell, egui::Id::new(("psize", id.0)), Sense::hover()).on_hover_text(detail.as_str()); - app.auto.add_response(&format!("project.item.{}.size", id.0), &resp, &detail); - } - text - } + "size" => file_size(it).map(fmt_size).unwrap_or_default(), "duration" => it.duration().map(|d| fmt_dur(d.seconds(), it.frame_rate().map(|r| r.as_f64()).unwrap_or(30.0))).unwrap_or_default(), "fps" => it.frame_rate().map(|f| format!("{:.2}", f.as_f64())).unwrap_or_default(), "path" => file_path(it).to_string(), @@ -1224,7 +1212,7 @@ mod tests { } #[test] - #[ignore = "filesystem benchmark; run scripts/bench-project-size-sort.sh"] + #[ignore = "filesystem benchmark; run with --ignored --nocapture"] fn size_sort_benchmark() { let files = SizeFiles::new(); let mut project = Project::default(); diff --git a/crates/ui-egui/tests/ui_project_rows.rs b/crates/ui-egui/tests/ui_project_rows.rs index 949310f5c..27ce5e693 100644 --- a/crates/ui-egui/tests/ui_project_rows.rs +++ b/crates/ui-egui/tests/ui_project_rows.rs @@ -84,108 +84,6 @@ fn command_clicking_a_folder_twirl_opens_the_folders_inside_it() { assert!(h.state().ui.project_open_folders.is_empty(), "and close again together"); } -#[test] -fn size_column_sorts_and_explains_sizes_without_swallowing_row_clicks() { - use effectcraft_ui_egui::theme::ThemeKind; - for (name, size, theme) in [ - ("wide-dark", egui::vec2(1600.0, 1000.0), ThemeKind::Dark), - ("compact-dark", egui::vec2(1024.0, 768.0), ThemeKind::Dark), - ("compact-light", egui::vec2(1024.0, 768.0), ThemeKind::Light), - ] { - check_size_column(name, size, theme); - } -} - -fn check_size_column(name: &str, size: egui::Vec2, theme: effectcraft_ui_egui::theme::ThemeKind) { - struct Files(std::path::PathBuf); - impl Drop for Files { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.0); - } - } - let files = Files(std::env::temp_dir().join(format!( - "effectcraft-size-ui-{}-{}", - std::process::id(), - std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() - ))); - std::fs::create_dir(&files.0).unwrap(); - let mut s = Session::default(); - s.prefs.general.language = "en".into(); - let project = std::sync::Arc::make_mut(&mut s.project); - let folder = project.add_item("Folder", Label::Aqua, None, ItemKind::Folder); - let mut ids = Vec::new(); - for (name, bytes) in [("Small", Some(10)), ("Large", Some(20)), ("Unavailable", None), ("Empty", Some(0))] { - let path = files.0.join(name); - if let Some(bytes) = bytes { - std::fs::File::create(&path).unwrap().set_len(bytes).unwrap(); - } - ids.push(project.add_item(name, Label::Aqua, None, ItemKind::Footage(Footage { path: path.to_string_lossy().into_owned(), ..Default::default() }))); - } - let [small, large, unavailable, empty] = ids.as_slice() else { panic!("fixture items") }; - let (small, large, unavailable, empty) = (*small, *large, *unavailable, *empty); - let mut app = EffectcraftApp::new(s); - app.ui.start_screen = false; - app.ui.project_columns = vec!["size".into()]; - let mut h = Harness::builder().with_size(size).build_eframe(|_| app); - let ctx = h.ctx.clone(); - h.state_mut().set_theme(&ctx, theme); - h.run_steps(3); - let size_id = |id: ItemId| format!("project.item.{}.size", id.0); - let assert_order = |h: &Harness<'_, EffectcraftApp>, expected: &[ItemId]| { - let mut visible = expected.to_vec(); - visible.sort_by(|a, b| center(h, &format!("project.item.{}", a.0)).y.total_cmp(¢er(h, &format!("project.item.{}", b.0)).y)); - assert_eq!(visible, expected); - }; - let undo = h.state().session.history.undo.len(); - click(&mut h, "project.sort.size"); - assert_order(&h, &[folder, unavailable, empty, small, large]); - click(&mut h, "project.sort.size"); - assert_order(&h, &[large, small, empty, unavailable, folder]); - assert_eq!(h.state().auto.find(&size_id(empty)).unwrap().label, "File size: 0 bytes"); - assert!(h.state().auto.find(&size_id(folder)).is_none()); - let at = center(&h, &size_id(small)); - h.input_mut().events.push(Event::PointerMoved(at)); - h.run_steps(5); - assert!(h.query_by_label("File size: 10 bytes").is_some(), "hover exposes exact bytes when both files display 1 KB"); - if let Ok(dir) = std::env::var("PROJECT_SIZE_SNAPSHOTS") { - h.render().unwrap().save(format!("{dir}/project-size-{name}-hover.png")).unwrap(); - } - click_at(&mut h, at); - assert_eq!(h.state().session.state.project_selection, [small]); - let at = center(&h, &size_id(unavailable)); - h.input_mut().events.push(Event::PointerMoved(at)); - h.run_steps(5); - assert!(h.query_by_label("File size unavailable").is_some()); - let has_dash = |shape: &egui::Shape| matches!(shape, egui::Shape::Text(text) if text.galley.text() == "—"); - assert!(h.output().shapes.iter().any(|shape| has_dash(&shape.shape)), "unavailable footage has a visible marker"); - if let Ok(dir) = std::env::var("PROJECT_SIZE_SNAPSHOTS") { - h.render().unwrap().save(format!("{dir}/project-size-{name}-unavailable.png")).unwrap(); - } - std::fs::OpenOptions::new().write(true).open(files.0.join("Small")).unwrap().set_len(30).unwrap(); - h.run_steps(3); - assert_order(&h, &[small, large, empty, unavailable, folder]); - assert_eq!(h.state().auto.find(&size_id(small)).unwrap().label, "File size: 30 bytes"); - assert_eq!(h.state().session.history.undo.len(), undo, "sorting and size details don't edit the project"); - let from = center(&h, &size_id(small)); - let to = center(&h, &format!("project.item.{}.name", folder.0)); - h.input_mut().events.push(Event::PointerMoved(from)); - h.step(); - button(&mut h, from, true); - h.step(); - for k in 1..=6 { - h.input_mut().events.push(Event::PointerMoved(from + (to - from) * (k as f32 / 6.0))); - h.step(); - } - button(&mut h, to, false); - h.run_steps(3); - assert_eq!(h.state().session.project.item(small).unwrap().parent, Some(folder), "dragging from Size still moves the item"); - assert_eq!(h.state().session.history.undo.len(), undo + 1); - h.state_mut().session.execute("edit.undo", json!({})).unwrap(); - h.run_steps(3); - assert_eq!(h.state().session.project.item(small).unwrap().parent, None); - assert_order(&h, &[small, large, empty, unavailable, folder]); -} - #[test] fn folder_twirl_opens_and_closes_the_folder_on_one_click() { let (mut h, folder, comp, _) = harness(); diff --git a/scripts/bench-project-size-sort.sh b/scripts/bench-project-size-sort.sh deleted file mode 100755 index b85e495d2..000000000 --- a/scripts/bench-project-size-sort.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")/.." -CARGO_PROFILE_RELEASE_LTO=false cargo test -p effectcraft-ui-egui --release --lib panels::project::tests::size_sort_benchmark -- --ignored --nocapture