git.lucas.co / cce-mail
mail client (IMAP/SMTP)
git clone https://git.lucas.co/cce-mail.git

commit78edf48794a692ae189eec2c48b79e6738eb4ed8
parent1a445a7e2d
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-05 22:13
fix: the unread dot and an attachment chip's remove mark are cce-icons glyphs

A list row's unread indicator was a 6x6 square quad, and a compose
attachment chip read "{name} ×", the × a character in a fallback face.
The dot is the circle glyph in the same blue, a 6px disc where the quad
stood; the chip draws its name and the x glyph in the room it already
kept past it. The whole chip is still the remove target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/main.rs | 41 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 1b85488..185c645 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -767,6 +767,8 @@ fn pane_fill() -> [f32; 4] {
 fn pane_inner_pad() -> f32 {
     cce_ui::layout::plate_padding()
 }
+/// The unread mark on a list row (the `circle` glyph), linear like a prim.
+const UNREAD_DOT: [f32; 4] = [0.20, 0.45, 0.85, 1.0];
 const LIST_W_DEFAULT: f32 = 300.0;
 const LIST_W_MIN: f32 = 180.0;
 /// The detail pane never gets squeezed below this by a drag or a narrow window.
@@ -902,7 +904,8 @@ fn compose_chip_rects(attachments: &[String], modal_x: f32, modal_y: f32) -> Vec
         let shown = ellipsize(name, 22);
         // Estimated glyph advance at font_size 10 — the chip is a painted
         // quad, not a widget, so an estimate only has to be consistent
-        // between paint and hit-test (it is: both use this fn).
+        // between paint and hit-test (it is: both use this fn). The 10 past
+        // the name is the room the remove mark (the `x` glyph) is drawn in.
         let w = shown.chars().count() as f32 * 6.0 + 2.0 * cce_ui::layout::CONTROL_TEXT_INSET + 10.0;
         rects.push((x, y, w, COMPOSE_CHIP_H));
         x += w + l.gap;
@@ -4178,7 +4181,8 @@ impl ClearEmailApp {
                 labels.push(TextLabel { text: name.to_string(), x: l.label_x, y: row_y + 4.0, font_size: 11.0, color: [0x83, 0x83, 0x8a] });
             }
 
-            // Attachment chips: name + "×", clickable to remove (hit-test in
+            // Attachment chips: the name (its `x` glyph is drawn with the
+            // chip's plate), clickable to remove (hit-test in
             // handle_mouse_input via the same compose_chip_rects).
             for (path, (cx, cy, _cw, _ch)) in self
                 .compose_attachments
@@ -4190,7 +4194,7 @@ impl ClearEmailApp {
                     .and_then(|n| n.to_str())
                     .unwrap_or("attachment");
                 labels.push(TextLabel {
-                    text: format!("{} \u{00d7}", ellipsize(name, 22)),
+                    text: ellipsize(name, 22),
                     x: cx + cce_ui::layout::CONTROL_TEXT_INSET,
                     y: cy + 6.0,
                     font_size: 10.0,
@@ -5607,10 +5611,20 @@ impl Application for ClearEmailApp {
             if self.email_list.get_item_draw_y(idx, 0.0).is_some() {
                 cce_ui::scene::painter::paint_root_into(&self.ui_context, &self.email_buttons[idx], &mut *quads.pc);
 
-                // Blue dot/unread indicator for this row
+                // Unread: the `circle` glyph in blue, a 6px dot (the glyph's
+                // disc is three quarters of its box) centred where the
+                // square quad it replaced stood.
                 if !filtered[idx].read {
                     if let Some(draw_y) = self.email_list.get_item_draw_y(idx, 0.0) {
-                        quads.push((list_x + (pane_inner_pad() - 6.0) / 2.0, draw_y + 12.0, 6.0, 6.0, [0.20, 0.45, 0.85, 1.0]));
+                        let side = 8.0;
+                        let dot = cce_ui::scene::layout::Rect {
+                            x: list_x + (pane_inner_pad() - side) / 2.0,
+                            y: draw_y + 15.0 - side / 2.0,
+                            width: side,
+                            height: side,
+                        };
+                        // Prims take linear colour; a glyph is tinted in sRGB.
+                        quads.pc.icon("circle", dot, cce_ui::colors::to_srgb(UNREAD_DOT));
                     }
                 }
             }
@@ -5787,8 +5801,10 @@ impl Application for ClearEmailApp {
                 );
             }
 
-            // Attachment chips: control plates here, labels in the labels
-            // pass — both laid out by compose_chip_rects.
+            // Attachment chips: control plates and their `x` glyphs here,
+            // names in the labels pass — all laid out by compose_chip_rects.
+            // The glyph sits in the room the chip keeps past the name; the
+            // whole chip is still the remove target (handle_mouse_input).
             for (cx, cy, cw, ch) in compose_chip_rects(&self.compose_attachments, modal_x, modal_y) {
                 let r = cce_ui::layout::control_corner_radius();
                 quads.pc.bevel(
@@ -5797,6 +5813,17 @@ impl Application for ClearEmailApp {
                     &cce_ui::scene::Material::control(),
                     cce_ui::layout::bevel_width().min(ch * 0.2),
                 );
+                let side = 8.0;
+                quads.pc.icon(
+                    "x",
+                    cce_ui::scene::layout::Rect {
+                        x: cx + cw - cce_ui::layout::CONTROL_TEXT_INSET - side,
+                        y: cy + (ch - side) / 2.0,
+                        width: side,
+                        height: side,
+                    },
+                    [0xc8 as f32 / 255.0, 0xc8 as f32 / 255.0, 0xd2 as f32 / 255.0, 1.0],
+                );
             }
 
             self.compose_to.prepare_text(&mut self.font_system);