forked from 1b-agent/opencadstudio-upstream
feat(ui): shared dropdown popup width calculation with wide-character support
- Add dropdown_popup_width helper in common.rs replacing magic formula heuristics - Implement is_wide range table to count East Asian Wide/Fullwidth (CJK) glyphs double for localized strings - Calibrate unit scale factor to 0.68 * font_size preserving existing width behavior at font size 11 - Apply shared calculation across viewport controls and ribbon dropdowns - Update unit tests to verify width calculation on standard and CJK labels
This commit is contained in:
1 parent
a7e3cf3a6d
commit
b53cd580f6
3 files changed
+69
-9
No files matched your search
@@ -131,12 +131,13 @@ pub(super) fn viewport_controls<'a>(
|
||||
);
|
||||
|
||||
let preview_mode = render_mode_preview.unwrap_or(render_mode);
|
||||
let max_label_len = render_modes
|
||||
.iter()
|
||||
.map(|c| c.to_string().chars().count())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let dropdown_w = (max_label_len as f32 * 7.5 + 46.0).max(180.0);
|
||||
let mode_names: Vec<String> = render_modes.iter().map(|c| c.to_string()).collect();
|
||||
let dropdown_w = crate::ui::style::common::dropdown_popup_width(
|
||||
mode_names.iter().map(|s| s.as_str()),
|
||||
11.0,
|
||||
46.0,
|
||||
180.0,
|
||||
);
|
||||
|
||||
let mut choices = column![].spacing(2).width(Length::Fill);
|
||||
for choice in render_modes {
|
||||
@@ -445,13 +446,30 @@ mod tests {
|
||||
.map(|c| c.label.chars().count())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let dropdown_w = (max_label_len as f32 * 7.5 + 46.0).max(180.0);
|
||||
let dropdown_w = crate::ui::style::common::dropdown_popup_width(
|
||||
crate::modules::view::visual_style::VISUAL_STYLES
|
||||
.iter()
|
||||
.map(|c| c.label),
|
||||
11.0,
|
||||
46.0,
|
||||
180.0,
|
||||
);
|
||||
|
||||
// Longest default label is "Gouraud Shaded + Edges" (22 chars)
|
||||
assert_eq!(max_label_len, 22);
|
||||
// Computed width should easily fit the 22-char label + checkmark + padding
|
||||
assert!(dropdown_w >= 210.0);
|
||||
|
||||
// CJK labels count wide characters double
|
||||
let cjk_labels = ["二维线框", "着色+边"];
|
||||
let cjk_w = crate::ui::style::common::dropdown_popup_width(
|
||||
cjk_labels.iter().copied(),
|
||||
11.0,
|
||||
46.0,
|
||||
180.0,
|
||||
);
|
||||
assert_eq!(cjk_w, 180.0);
|
||||
|
||||
for style in crate::modules::view::visual_style::VISUAL_STYLES {
|
||||
assert!(!style.label.contains('\n'), "Label should be a single line");
|
||||
}
|
||||
|
||||
+14
-2
@@ -853,6 +853,7 @@ impl Ribbon {
|
||||
let label_el =
|
||||
text(t!(*label))
|
||||
.size(11)
|
||||
.wrapping(iced::advanced::text::Wrapping::None)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_current).then_some(
|
||||
theme
|
||||
@@ -880,11 +881,22 @@ impl Ribbon {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let labels: Vec<String> = items
|
||||
.iter()
|
||||
.map(|(_, label, _)| t!(*label).to_string())
|
||||
.collect();
|
||||
let panel_w = crate::ui::style::common::dropdown_popup_width(
|
||||
labels.iter().map(|s| s.as_str()),
|
||||
11.0,
|
||||
68.0,
|
||||
190.0,
|
||||
);
|
||||
|
||||
let panel = container(column(rows))
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(190.0));
|
||||
.width(Length::Fixed(panel_w));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, 190.0, win.0);
|
||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, panel_w, win.0);
|
||||
Some(dropdown_backdrop(position_ribbon_dropdown(
|
||||
panel.into(),
|
||||
align_right,
|
||||
|
||||
@@ -73,3 +73,33 @@ pub(crate) fn accessible_accent(accent: Color, bg: Color, fallback: Color) -> Co
|
||||
pub(crate) fn canvas_is_light(bg: [f32; 4]) -> bool {
|
||||
wcag_luminance(Color::from_rgb(bg[0], bg[1], bg[2])) > 0.5
|
||||
}
|
||||
|
||||
/// True if a character is East Asian Wide or Fullwidth (CJK ideographs, kana, hangul, fullwidth forms).
|
||||
pub(crate) fn is_wide(c: char) -> bool {
|
||||
let u = c as u32;
|
||||
matches!(
|
||||
u,
|
||||
0x1100..=0x115F // Hangul Jamo
|
||||
| 0x2E80..=0xA4CF // CJK Radicals, Kangxi, Hiragana, Katakana, Bopomofo, Hangul Compatibility, CJK Unified Ideographs, Yi
|
||||
| 0xAC00..=0xD7A3 // Hangul Syllables
|
||||
| 0xF900..=0xFAFF // CJK Compatibility Ideographs
|
||||
| 0xFE30..=0xFE4F // CJK Compatibility Forms
|
||||
| 0xFF01..=0xFF60 // Fullwidth ASCII variants
|
||||
| 0xFFE0..=0xFFE6 // Fullwidth symbol variants
|
||||
| 0x20000..=0x2FA1F // CJK Unified Ideographs Extension B-F, Compatibility Supplement
|
||||
)
|
||||
}
|
||||
|
||||
/// Estimated popup width for a vertical list of labels. Wide (CJK) glyphs
|
||||
/// count double so localized labels via t!() don't truncate.
|
||||
pub fn dropdown_popup_width<'a>(
|
||||
labels: impl Iterator<Item = &'a str>,
|
||||
font_size: f32,
|
||||
chrome: f32,
|
||||
min: f32,
|
||||
) -> f32 {
|
||||
let max_units = labels
|
||||
.map(|l| l.chars().map(|c| if is_wide(c) { 2.0 } else { 1.0 }).sum::<f32>())
|
||||
.fold(0.0f32, f32::max);
|
||||
(max_units * font_size * 0.68 + chrome).max(min)
|
||||
}
|
||||
Reference in new issue
Block a user