perf(project): keep the Size cell as After Effects shows it

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 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-16 16:42:34 +07:00
co-authored by Claude Opus 5.5
parent b63efeb0d3
commit 0e2f43b824
4 changed files with 2 additions and 134 deletions
-14
View File
@@ -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: {}"),
+2 -14
View File
@@ -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();
-102
View File
@@ -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(&center(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();
-4
View File
@@ -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