git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitda6841413a9f6231243f2784744d4ba40e3143a2
parentad53ee78c6
authorLucas Galante <[email protected]>
date2026-07-05 13:55
fix: implement mathematical cell-width metrics for mono-spaced text fading

 src/widget/input/dropdown.rs | 73 ++++++++++++++++++++++++++++++++------------
 1 file changed, 53 insertions(+), 20 deletions(-)

diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 712af69..509d2d3 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -545,23 +545,46 @@ impl Element for Dropdown {
         let chars: Vec<char> = selected_text.chars().collect();
         let n = chars.len();
 
-        let mut char_offsets = Vec::with_capacity(n);
-        if n > 0 {
-            char_offsets.push(0.0f32);
-        }
+        let is_monospace = {
+            let w_i10 = crate::widget::display::measure_text_width("iiiiiiiiii", &font_family, 12.0);
+            let w_m10 = crate::widget::display::measure_text_width("mmmmmmmmmm", &font_family, 12.0);
+            (w_i10 - w_m10).abs() < 5.0
+        };
 
-        let mut prefix = String::new();
-        for i in 1..n {
-            prefix.push(chars[i - 1]);
-            let measure_str = format!("{}M", prefix);
-            let w_prefix_dummy = crate::widget::display::measure_text_width(&measure_str, &font_family, 12.0);
-            let offset = (w_prefix_dummy - w_dummy).max(0.0);
-            char_offsets.push(offset);
+        let cell_width = if is_monospace {
+            let w_m10 = crate::widget::display::measure_text_width("mmmmmmmmmm", &font_family, 12.0);
+            let w_m20 = crate::widget::display::measure_text_width("mmmmmmmmmmmmmmmmmmmm", &font_family, 12.0);
+            ((w_m20 - w_m10) / 10.0).max(1.0)
+        } else {
+            0.0
+        };
+
+        let mut char_offsets = Vec::with_capacity(n);
+        if is_monospace {
+            for i in 0..n {
+                char_offsets.push(i as f32 * cell_width);
+            }
+        } else {
+            if n > 0 {
+                char_offsets.push(0.0f32);
+            }
+            let mut prefix = String::new();
+            for i in 1..n {
+                prefix.push(chars[i - 1]);
+                let measure_str = format!("{}M", prefix);
+                let w_prefix_dummy = crate::widget::display::measure_text_width(&measure_str, &font_family, 12.0);
+                let offset = (w_prefix_dummy - w_dummy).max(0.0);
+                char_offsets.push(offset);
+            }
         }
 
         let total_advance = if n > 0 {
-            let measure_str = format!("{}M", selected_text);
-            (crate::widget::display::measure_text_width(&measure_str, &font_family, 12.0) - w_dummy).max(0.0)
+            if is_monospace {
+                n as f32 * cell_width
+            } else {
+                let measure_str = format!("{}M", selected_text);
+                (crate::widget::display::measure_text_width(&measure_str, &font_family, 12.0) - w_dummy).max(0.0)
+            }
         } else {
             0.0
         };
@@ -580,7 +603,11 @@ impl Element for Dropdown {
         let mut prefix_w = 0.0;
         if split_idx > 0 {
             let prefix_str: String = chars[0..split_idx].iter().collect();
-            prefix_w = crate::widget::display::measure_text_width(&prefix_str, &font_family, 12.0);
+            prefix_w = if is_monospace {
+                split_idx as f32 * cell_width
+            } else {
+                crate::widget::display::measure_text_width(&prefix_str, &font_family, 12.0)
+            };
             labels.push(TextLabel {
                 text: prefix_str,
                 x: start_x,
@@ -594,13 +621,15 @@ impl Element for Dropdown {
         let mut prev_char_end = prefix_w;
         for i in split_idx..n {
             let mut offset = char_offsets[i];
-            if i == split_idx && split_idx > 0 {
-                offset = offset.max(prefix_w + 1.0);
-            } else if i > split_idx {
-                offset = offset.max(prev_char_end + 1.0);
+            if !is_monospace {
+                if i == split_idx && split_idx > 0 {
+                    offset = offset.max(prefix_w + 1.0);
+                } else if i > split_idx {
+                    offset = offset.max(prev_char_end + 1.0);
+                }
             }
             let next_offset = if i < n - 1 { char_offsets[i + 1] } else { total_advance };
-            let c_w = next_offset - offset;
+            let c_w = if is_monospace { cell_width } else { next_offset - offset };
             let cur_x = start_x + offset;
 
             if cur_x >= right_limit {
@@ -633,7 +662,11 @@ impl Element for Dropdown {
                     font_size: 12.0,
                     color,
                 });
-                let c_w_ink = crate::widget::display::measure_text_width(&chars[i].to_string(), &font_family, 12.0);
+                let c_w_ink = if is_monospace {
+                    cell_width
+                } else {
+                    crate::widget::display::measure_text_width(&chars[i].to_string(), &font_family, 12.0)
+                };
                 prev_char_end = offset + c_w_ink;
             }
         }