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

commitf1a07d4b829d5f793b7af187db71191303f4e4de
parente12bde6189
authorLucas Galante <[email protected]>
date2026-07-10 13:16
feat: whole frame via display_list (Phase 6ai) — off legacy path; FIXES invisible list/detail text

cce-email (4 of 6). The view() body (all plain quads) moves into display_list()
via a small __EmailQuadSink shim that maps the ported quads.push/extend calls to
PaintCtx::quad; rebuild_text_items becomes emit_text_prims(&mut pc), each
text_items.push(TextItem) rewritten to pc.text_with. The text_items field/getter
and the shape-into-buffer machinery are deleted.

FIX, not a match: the app used create_font_system_with_system_fonts(), so the
email-list rows, the detail-pane metadata/body, and the placeholder were shaped
with fontdb face IDs the engine bundled FS could not rasterize — invisible at
runtime (only the paginator sidebar tabs showed). Switched the app FontSystem to
bundled create_font_system() (kept for the TextBoxes' prepare_text measurement,
now matching the engine render FS) and moved rendering to prims; all text now
renders.

Live-verified: inbox list (3 emails), detail placeholder, and on click the full
detail view (subject header, From/To/Date, body, Reply/Delete/Mark-Unread
toolbar). Event routing untouched.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/main.rs | 167 ++++++++++++++++--------------------------------------------
 1 file changed, 45 insertions(+), 122 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 012555d..f56b356 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,8 @@
 use wayland_client::QueueHandle;
-use glyphon::{FontSystem, Buffer, Metrics, Attrs};
+use glyphon::FontSystem;
 use cce_ui::engine::{Application, EngineState, LogicalPosition, LogicalSize, WindowSettings};
 use cce_ui::widget::{
-    MouseButton, ElementState, MouseScrollDelta, KeyEvent, TextItem, Element,
+    MouseButton, ElementState, MouseScrollDelta, KeyEvent, Element,
     TextBox, Button, TextLabel, Key, List, Paginator, PageSelector, MenuController
 };
 use cce_ui::context::UiContext;
@@ -128,7 +128,6 @@ struct ClearEmailApp {
     width: u32,
     height: u32,
     scale_factor: f64,
-    text_items: Vec<TextItem>,
     font_system: FontSystem,
     needs_rebuild: bool,
     ui_context: UiContext,
@@ -765,8 +764,7 @@ impl ClearEmailApp {
         }
     }
 
-    fn rebuild_text_items(&mut self) {
-        self.text_items.clear();
+    fn emit_text_prims(&mut self, pc: &mut cce_ui::scene::paint::PaintCtx) {
         let mut labels = Vec::new();
         let font_system = &mut self.font_system;
         let ctx = &self.ui_context;
@@ -805,17 +803,7 @@ impl ClearEmailApp {
         } else {
             self.search_box.prepare_text(font_system);
             for (label, bounds) in self.search_box.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
         }
 
@@ -963,17 +951,7 @@ impl ClearEmailApp {
                 // Body rendering
                 self.detail_body.prepare_text(font_system);
                 for (label, bounds) in self.detail_body.text_labels_with_bounds(ctx) {
-                    let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                    let mut buf = Buffer::new(font_system, metrics);
-                    buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                    buf.shape_until_scroll(font_system, true);
-                    self.text_items.push(TextItem {
-                        buffer: buf,
-                        x: label.x,
-                        y: label.y,
-                        color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                        bounds,
-                    });
+                    pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
                 }
             }
         } else {
@@ -1022,47 +1000,17 @@ impl ClearEmailApp {
             // Compose inputs text labels
             self.compose_to.prepare_text(font_system);
             for (label, bounds) in self.compose_to.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
 
             self.compose_subject.prepare_text(font_system);
             for (label, bounds) in self.compose_subject.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
 
             self.compose_body.prepare_text(font_system);
             for (label, bounds) in self.compose_body.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
         }
 
@@ -1107,78 +1055,45 @@ impl ClearEmailApp {
             // 7a. Add Account Inputs text labels
             self.add_acc_email.prepare_text(font_system);
             for (label, bounds) in self.add_acc_email.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
 
             self.add_acc_password.prepare_text(font_system);
             for (label, bounds) in self.add_acc_password.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
 
             self.add_acc_imap.prepare_text(font_system);
             for (label, bounds) in self.add_acc_imap.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
 
             self.add_acc_smtp.prepare_text(font_system);
             for (label, bounds) in self.add_acc_smtp.text_labels_with_bounds(ctx) {
-                let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-                let mut buf = Buffer::new(font_system, metrics);
-                buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-                buf.shape_until_scroll(font_system, true);
-                self.text_items.push(TextItem {
-                    buffer: buf,
-                    x: label.x,
-                    y: label.y,
-                    color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                    bounds,
-                });
+                pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, bounds);
             }
         }
 
-        // Shape and append static text items
+        // Emit accumulated static labels as text prims.
+        let _ = font_system;
         for label in labels {
-            let metrics = Metrics::new(label.font_size, label.font_size * 1.4);
-            let mut buf = Buffer::new(font_system, metrics);
-            buf.set_text(font_system, &label.text, Attrs::new(), glyphon::Shaping::Advanced);
-            buf.shape_until_scroll(font_system, true);
-            self.text_items.push(TextItem {
-                buffer: buf,
-                x: label.x,
-                y: label.y,
-                color: glyphon::Color::rgb(label.color[0], label.color[1], label.color[2]),
-                bounds: None,
-            });
+            pc.text_with(label.text.clone(), label.x, label.y, label.font_size, label.color, None, None);
+        }
+    }
+}
+
+/// Adapts the legacy `quads.push((x,y,w,h,color))` / `quads.extend(extra_quads())` calls in
+/// the ported view() body to the single paint path: each tuple becomes a `PaintCtx::quad`.
+struct __EmailQuadSink<'a> {
+    pc: &'a mut cce_ui::scene::paint::PaintCtx,
+}
+impl<'a> __EmailQuadSink<'a> {
+    fn push(&mut self, q: (f32, f32, f32, f32, [f32; 4])) {
+        self.pc.quad(cce_ui::scene::layout::Rect { x: q.0, y: q.1, width: q.2, height: q.3 }, q.4);
+    }
+    fn extend<I: IntoIterator<Item = (f32, f32, f32, f32, [f32; 4])>>(&mut self, it: I) {
+        for q in it {
+            self.push(q);
         }
     }
 }
@@ -1294,8 +1209,7 @@ impl Application for ClearEmailApp {
             width: 1000,
             height: 600,
             scale_factor: 1.0,
-            text_items: Vec::new(),
-            font_system: cce_ui::create_font_system_with_system_fonts(),
+            font_system: cce_ui::create_font_system(),
             needs_rebuild: true,
             ui_context: UiContext::new(),
         }
@@ -1658,7 +1572,14 @@ impl Application for ClearEmailApp {
         }
     }
 
-    fn view(&mut self, quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, size: LogicalSize, scale: f64) {
+    fn display_list(&mut self, size: LogicalSize, scale: f64) -> Option<cce_ui::scene::paint::DisplayList> {
+        // Phase 6ai single paint path: the view() geometry (all plain quads) and the text
+        // (the old rebuild_text_items assembly, now prims via emit_text_prims) are this one
+        // list. The app FontSystem stays for the TextBoxes' prepare_text measurement, but is
+        // now bundled create_font_system() (was _with_system_fonts) — the render path is the
+        // engine's bundled cache, so measurement matches and no face-ID mismatch remains.
+        let mut __pc = cce_ui::scene::paint::PaintCtx::new();
+        let quads = &mut __EmailQuadSink { pc: &mut __pc };
         let size_changed = self.width != size.width as u32 || self.height != size.height as u32 || self.scale_factor != scale;
         if self.needs_rebuild || size_changed {
             self.width = size.width as u32;
@@ -1841,7 +1762,6 @@ impl Application for ClearEmailApp {
                 self.btn_add_acc_icloud.set_rect(-9999.0, -9999.0, 0.0, 0.0);
             }
 
-            self.rebuild_text_items();
             self.needs_rebuild = false;
         }
 
@@ -1987,10 +1907,13 @@ impl Application for ClearEmailApp {
             quads.extend(self.btn_add_acc_oauth.extra_quads());
             quads.extend(self.btn_add_acc_icloud.extra_quads());
         }
+
+        self.emit_text_prims(&mut __pc);
+        Some(__pc.finish())
     }
 
-    fn text_items(&self) -> &[TextItem] {
-        &self.text_items
+    fn display_list_text(&self) -> bool {
+        true
     }
 
     fn handle_pointer_move(&mut self, pos: LogicalPosition, needs_rebuild: &mut bool) {