From 50d52a08489727df86324512bb3ef46be4b5135e Mon Sep 17 00:00:00 2001 From: Alex Gorevski Date: Wed, 7 Oct 2026 04:09:51 -0700 Subject: [PATCH] Clarify integer and float bit-depth labels (#324) (#428) * Clarify integer and float bit-depth labels Add type-specific labels to New Document and Image Mode with translations and focused UI/catalog tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f77709e9-6475-4953-ab57-b78347b4499d * Fix bit-depth labels and tooltips Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e5e4c3e6-e406-4010-b05d-5042f9f4e4fc * Keep the reference menu names for the depth commands and preference The Image > Mode items keep their menu-catalogue labels, so the engine labels (command palette, MCP, Keyboard Shortcuts) and the preference choice stay "8/16/32 Bits/Channel" to match; the integer / floating point hint comes from the new tooltips. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Brandon Thomas Co-authored-by: Claude Opus 5.5 Copilot-Session: f77709e9-6475-4953-ab57-b78347b4499d Copilot-Session: e5e4c3e6-e406-4010-b05d-5042f9f4e4fc --- crates/ui-egui/src/doc_props_ui.rs | 18 ++++++++++++++++-- crates/ui-egui/src/i18n/cs.tsv | 5 +++++ crates/ui-egui/src/i18n/es.tsv | 5 +++++ crates/ui-egui/src/i18n/ja.tsv | 5 +++++ crates/ui-egui/src/i18n/ru.tsv | 5 +++++ crates/ui-egui/src/i18n/zh-hans.tsv | 5 +++++ crates/ui-egui/src/i18n/zh-hant.tsv | 5 +++++ crates/ui-egui/src/menus.rs | 5 +++++ crates/ui-egui/src/new_doc_ui.rs | 15 ++++++++++++++- crates/ui-egui/src/widgets.rs | 17 +++++++++++++++++ 10 files changed, 82 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/doc_props_ui.rs b/crates/ui-egui/src/doc_props_ui.rs index 8a28a2dac0..544ce7cf36 100644 --- a/crates/ui-egui/src/doc_props_ui.rs +++ b/crates/ui-egui/src/doc_props_ui.rs @@ -190,8 +190,9 @@ pub fn properties(app: &mut PhotocraftApp, ui: &mut egui::Ui) { ui.horizontal(|ui| { ui.add_space(label_w + 8.0); let mut d = depth; - let opts: Vec<(SampleType, &str)> = DEPTHS.iter().map(|(d, l, _)| (*d, *l)).collect(); - if widgets::dropdown(ui, "doc-props-depth", &mut d, &opts, 150.0) + let opts: Vec<(SampleType, &str, &str)> = + DEPTHS.iter().map(|(d, l, _)| (*d, *l, if *d == SampleType::F32 { "Floating point" } else { "Integer" })).collect(); + if widgets::dropdown_with_tooltips(ui, "doc-props-depth", &mut d, &opts, 150.0) && let Some((_, _, id)) = DEPTHS.iter().find(|(x, _, _)| *x == d) { run.push(((*id).to_string(), Value::Null)); @@ -315,4 +316,17 @@ mod tests { assert!(photocraft_engine::commands::find(id).is_some(), "{id} is not an engine command"); } } + + #[test] + fn image_mode_depth_labels_match_sample_types_and_are_translated() { + assert_eq!( + DEPTHS.iter().map(|(sample, label, _)| (*sample, *label)).collect::>(), + [(SampleType::U8, "8 Bits/Channel"), (SampleType::U16, "16 Bits/Channel"), (SampleType::F32, "32 Bits/Channel"),] + ); + for lang in crate::i18n::Lang::all().filter(|lang| lang.code() != "en") { + for (_, label, id) in DEPTHS { + assert_ne!(crate::i18n::tr_id(lang, id, label), *label, "{}: {label}", lang.code()); + } + } + } } diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 7f3fe278c9..f2307f69ed 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -181,8 +181,13 @@ Lab Color Barvy Lab Multichannel Vícekanálový 8 Bits/Channel 8 bitů/kanál + 8 bit 8 bitů + Integer Celé číslo 16 Bits/Channel 16 bitů/kanál + 16 bit 16 bitů 32 Bits/Channel 32 bitů/kanál + 32 bit (float) 32 bitů (float) + Floating point Číslo s plovoucí desetinnou čárkou Color Table… Tabulka barev… Bitmap… Bitmapa… Duotone… Dvoutón… diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 8e399e4b35..9a08d45a8f 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -173,8 +173,13 @@ Lab Color Color Lab Multichannel Multicanal 8 Bits/Channel 8 bits/canal + 8 bit 8 bits + Integer Entero 16 Bits/Channel 16 bits/canal + 16 bit 16 bits 32 Bits/Channel 32 bits/canal + 32 bit (float) 32 bits (float) + Floating point Punto flotante Color Table… Tabla de colores… Bitmap… Mapa de bits… Duotone… Duotono… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 6ee1b67d01..7bfa5f8d02 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -177,8 +177,13 @@ Lab Color Lab カラー Multichannel マルチチャンネル 8 Bits/Channel 8 ビット/チャンネル + 8 bit 8 ビット + Integer 整数 16 Bits/Channel 16 ビット/チャンネル + 16 bit 16 ビット 32 Bits/Channel 32 ビット/チャンネル + 32 bit (float) 32 ビット(float) + Floating point 浮動小数点 Color Table… カラーテーブル… Bitmap… ビットマップ… Duotone… ダブルトーン… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index f8beb72477..9755a5e0fb 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -106,6 +106,11 @@ 16 Bits/Channel 16 бит/канал 32 Bits/Channel 32 бита/канал 8 Bits/Channel 8 бит/канал + 8 bit 8 бит + Integer Целое число + 16 bit 16 бит + 32 bit (float) 32 бит (float) + Floating point С плавающей точкой Mode Режим Grayscale Градации серого RGB Color RGB diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 181f71d6ee..021d78e69b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -175,8 +175,13 @@ Lab Color Lab 颜色 Multichannel 多通道 8 Bits/Channel 8 位/通道 + 8 bit 8 位 + Integer 整数 16 Bits/Channel 16 位/通道 + 16 bit 16 位 32 Bits/Channel 32 位/通道 + 32 bit (float) 32 位(浮点) + Floating point 浮点数 Color Table… 颜色表… Bitmap… 位图… Duotone… 双色调… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index a6198321e6..caa7fa654e 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -181,8 +181,13 @@ Lab Color Lab 色彩 Multichannel 多重色版 8 Bits/Channel 8 位元/色版 + 8 bit 8 位元 + Integer 整數 16 Bits/Channel 16 位元/色版 + 16 bit 16 位元 32 Bits/Channel 32 位元/色版 + 32 bit (float) 32 位元(浮點) + Floating point 浮點數 Color Table… 色彩表… Bitmap… 點陣圖… Duotone… 雙色調… diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index d4dd34e023..d8381d75fb 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -844,6 +844,11 @@ fn render_level_rows(ui: &mut egui::Ui, items: &[&MenuItem], depth: usize, click } let hit = nav.row(ui, depth - 1, it.enabled, Some(&it.id), |ui, _| { let r = ui.add_enabled(it.enabled, b); + let r = match it.id.as_str() { + "image.mode.bits8" | "image.mode.bits16" => r.on_hover_text(crate::i18n::tr(lang, "Integer")), + "image.mode.bits32" => r.on_hover_text(crate::i18n::tr(lang, "Floating point")), + _ => r, + }; let hit = r.clicked(); (r, hit) }); diff --git a/crates/ui-egui/src/new_doc_ui.rs b/crates/ui-egui/src/new_doc_ui.rs index a2cf049786..d30cb5ea1b 100644 --- a/crates/ui-egui/src/new_doc_ui.rs +++ b/crates/ui-egui/src/new_doc_ui.rs @@ -73,6 +73,8 @@ pub const CATEGORIES: &[(&str, &[Preset])] = &[ ), ]; +const DEPTH_OPTIONS: &[(u64, &str, &str)] = &[(8, "8 bit", "Integer"), (16, "16 bit", "Integer"), (32, "32 bit (float)", "Floating point")]; + /// Width/Height units: (key, label, units per inch; 0 = pixels). pub const UNITS: &[(&str, &str, f32)] = &[("px", "Pixels", 0.0), ("in", "Inches", 1.0), ("cm", "Centimeters", 2.54), ("mm", "Millimeters", 25.4), ("pt", "Points", 72.0), ("pica", "Picas", 6.0)]; @@ -280,7 +282,8 @@ pub fn body(ui: &mut egui::Ui, f: &mut Map) { f.insert("mode".into(), json!(mode)); } let mut depth = f.get("depth").and_then(Value::as_u64).unwrap_or(8); - if widgets::dropdown(ui, "nd-depth", &mut depth, &[(8u64, "8 bit"), (16, "16 bit"), (32, "32 bit")], 120.0) { + let depth_options: Vec<(u64, &str, &str)> = DEPTH_OPTIONS.iter().map(|(bits, label, tooltip)| (*bits, *label, *tooltip)).collect(); + if widgets::dropdown_with_tooltips(ui, "nd-depth", &mut depth, &depth_options, 120.0) { f.insert("depth".into(), json!(depth)); } }); @@ -312,6 +315,16 @@ mod tests { assert_eq!(to_unit(640.0, "px", 72.0), 640.0); } + #[test] + fn new_document_depth_labels_and_tooltips_are_translated() { + for lang in crate::i18n::Lang::all().filter(|lang| lang.code() != "en") { + for (_, label, tooltip) in DEPTH_OPTIONS { + assert_ne!(crate::i18n::tr(lang, label), *label, "{}: {label}", lang.code()); + assert_ne!(crate::i18n::tr(lang, tooltip), *tooltip, "{}: {tooltip}", lang.code()); + } + } + } + #[test] fn preset_sets_size_resolution_and_create_params_drop_ui_keys() { let mut f = crate::state::UiState::new_document_fields(); diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 8644833d1e..b8fba1daeb 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -417,6 +417,23 @@ pub fn swatch_popup(swatch: &Response) -> egui::Popup<'static> { .align_alternatives(&[egui::RectAlign::TOP_START]) } +pub fn dropdown_with_tooltips(ui: &mut Ui, id: &str, current: &mut T, options: &[(T, &str, &str)], width: f32) -> bool { + let label = options.iter().find(|(v, _, _)| v == current).map(|(_, l, _)| tl!(l)).unwrap_or("—"); + let mut changed = false; + let response = egui::ComboBox::from_id_salt(id).selected_text(label).width(width).height(420.0).icon(chevron_icon).show_ui(ui, |ui| { + for (v, l, tip) in options { + if ui.selectable_label(v == current, tl!(l)).on_hover_text(tl!(tip)).clicked() { + *current = v.clone(); + changed = true; + } + } + }); + if let Some((_, _, tip)) = options.iter().find(|(v, _, _)| v == current) { + let _ = response.response.on_hover_text(tl!(tip)); + } + changed +} + /// Paint a small checkerboard (transparency) in `rect`. pub fn checker(painter: &egui::Painter, rect: Rect, cell: f32) { painter.rect_filled(rect, 0.0, Color32::from_gray(250));