git.lucas.co / cce-authenticator
login authentication (PAM + fingerprint)
git clone https://git.lucas.co/cce-authenticator.git

commit8059a1e9d00d7e0d25a628b47f3332f959db020e
parent4ba8ee8fe5
authorLucas Galante <[email protected]>
date2026-07-10 12:55
feat: whole frame via display_list (Phase 6ag) — off legacy path; FIXES invisible text

cce-authenticator (2 of 6). The single view() body — card, columns, widget
geometry, and text — moves into display_list(); the text_items assembly becomes
Prim::Text emitted fresh per frame, and the app FontSystem, make_text_buffer,
the text_items field/getter are deleted.

This migration is a FIX, not a match: the app's FontSystem was
create_font_system_with_system_fonts(), and every buffer it shaped carried
fontdb face IDs the engine's render FontSystem didn't have — so ALL of this
dialog's text was silently invisible at runtime (the 6e class; the log spewed
"did not find font ID" once per glyph). Shaped as display-list prims through
the engine cache, every label now renders — verified against the baseline
where the card showed geometry only, no text.

Live-verified (--standalone + CCE_AUTH_SIMULATE): full dialog text renders,
zero font-ID warnings (was hundreds), fingerprint-scan click activates the
animated green glow + scan line and updates the hint. Event routing untouched.

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

 src/main.rs | 169 ++++++++++++++++++++++++++----------------------------------
 1 file changed, 73 insertions(+), 96 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 68fe516..1d0a7e9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,9 +2,8 @@ use wayland_client::QueueHandle;
 use cce_ui::engine::{Application, EngineState, LogicalPosition, LogicalSize, WindowSettings};
 use cce_ui::widget::{
     Button, ContentBg, Element, ElementState, MouseButton, Key, NamedKey, KeyEvent, TextBox,
-    TextItem, MouseScrollDelta
+    MouseScrollDelta
 };
-use glyphon::{Attrs, Buffer, FontSystem, Metrics};
 use futures::StreamExt;
 use std::sync::{Arc, Mutex};
 use std::io::Write;
@@ -14,15 +13,6 @@ use std::ops::Deref;
 const ACCENT: [f32; 4] = [0.30, 0.50, 0.32, 1.0];
 const TOGGLE_OFF: [f32; 4] = [0.16, 0.16, 0.24, 1.0];
 
-fn make_text_buffer(fs: &mut FontSystem, text: &str, size: f32) -> Buffer {
-    let scale = cce_ui::scale::scale_factor();
-    let physical_size = size * scale;
-    let metrics = Metrics::new(physical_size, physical_size * 1.4);
-    let mut buf = Buffer::new(fs, metrics);
-    buf.set_text(fs, text, Attrs::new(), glyphon::Shaping::Advanced);
-    buf.shape_until_scroll(fs, true);
-    buf
-}
 
 #[derive(Clone, Debug)]
 enum AuthResult {
@@ -54,7 +44,6 @@ static ACTIVE_SENDER: Mutex<Option<calloop::channel::Sender<AppMessage>>> = Mute
 static ACTIVE_COOKIE: Mutex<Option<String>> = Mutex::new(None);
 
 struct AuthenticatorApp {
-    font_system: FontSystem,
     bg: cce_ui::widget::Adapted<ContentBg>,
     password_box: cce_ui::widget::Adapted<TextBox>,
     verify_btn: cce_ui::widget::Adapted<cce_ui::widget::Button>,
@@ -72,7 +61,6 @@ struct AuthenticatorApp {
     rx_auth: std::sync::mpsc::Receiver<AuthResult>,
     tx_auth: std::sync::mpsc::Sender<AuthResult>,
     
-    text_items: Vec<TextItem>,
     width: f32,
     height: f32,
     
@@ -94,7 +82,6 @@ impl Application for AuthenticatorApp {
     }
 
     fn new(_qh: &QueueHandle<EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
-        let font_system = cce_ui::create_font_system_with_system_fonts();
         let bg = ContentBg::new();
         
         let password_box = TextBox::new(String::new())
@@ -191,7 +178,6 @@ impl Application for AuthenticatorApp {
         *ACTIVE_SENDER.lock().unwrap() = Some(sender.clone());
         
         let mut app = Self {
-            font_system,
             bg,
             password_box,
             verify_btn,
@@ -209,7 +195,6 @@ impl Application for AuthenticatorApp {
             rx_auth,
             tx_auth,
             
-            text_items: Vec::new(),
             width: 800.0,
             height: 600.0,
             
@@ -414,38 +399,49 @@ impl Application for AuthenticatorApp {
         }
     }
 
-    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 6ag single paint path: the whole frame — card, columns, widgets, and all
+        // text — is this one list. NOTE this migration is a FIX, not a match: the app's old
+        // FontSystem shaped buffers whose fontdb face IDs did not resolve in the engine's
+        // render FontSystem, so ALL of this dialog's text was silently invisible (the 6e
+        // class). Shaped as display-list Text prims through the engine cache, it renders.
+        use cce_ui::scene::layout::Rect;
         cce_ui::scale::set_scale_factor(scale as f32);
         let sw = size.width as f32;
         let sh = size.height as f32;
         self.width = sw;
         self.height = sh;
-        
-        quads.push((0.0, 0.0, sw, sh, [0.03, 0.03, 0.05, 0.8]));
-        
+
+        let mut pc = cce_ui::scene::paint::PaintCtx::new();
+        let quad = |pc: &mut cce_ui::scene::paint::PaintCtx, x: f32, y: f32, w: f32, h: f32, c: [f32; 4]| {
+            pc.quad(Rect { x, y, width: w, height: h }, c);
+        };
+
+        quad(&mut pc, 0.0, 0.0, sw, sh, [0.03, 0.03, 0.05, 0.8]);
+
         let card_w = 540.0f32;
         let card_h = 320.0f32;
         let card_x = (sw - card_w) / 2.0;
         let card_y = (sh - card_h) / 2.0;
-        
-        quads.push((card_x, card_y, card_w, card_h, [0.07, 0.07, 0.10, 0.95]));
-        
+
+        quad(&mut pc, card_x, card_y, card_w, card_h, [0.07, 0.07, 0.10, 0.95]);
+
         let border_color = [0.20, 0.40, 0.65, 0.6];
-        quads.push((card_x, card_y, card_w, 1.5, border_color)); 
-        quads.push((card_x, card_y + card_h - 1.5, card_w, 1.5, border_color)); 
-        quads.push((card_x, card_y, 1.5, card_h, border_color)); 
-        quads.push((card_x + card_w - 1.5, card_y, 1.5, card_h, border_color)); 
-        
+        quad(&mut pc, card_x, card_y, card_w, 1.5, border_color);
+        quad(&mut pc, card_x, card_y + card_h - 1.5, card_w, 1.5, border_color);
+        quad(&mut pc, card_x, card_y, 1.5, card_h, border_color);
+        quad(&mut pc, card_x + card_w - 1.5, card_y, 1.5, card_h, border_color);
+
         let fp_col_x = card_x + 30.0;
         let fp_col_y = card_y + 80.0;
         let fp_col_w = 220.0;
-        
+
         let fp_btn_w = 120.0f32;
         let fp_btn_h = 120.0f32;
         let fp_btn_x = fp_col_x + (fp_col_w - fp_btn_w) / 2.0;
         let fp_btn_y = fp_col_y + 10.0;
         self.fingerprint_btn.set_rect(fp_btn_x, fp_btn_y, fp_btn_w, fp_btn_h);
-        
+
         let fp_bg = if self.fingerprint_success {
             ACCENT
         } else if self.fingerprint_active {
@@ -454,99 +450,80 @@ impl Application for AuthenticatorApp {
         } else {
             TOGGLE_OFF
         };
-        quads.push((fp_btn_x, fp_btn_y, fp_btn_w, fp_btn_h, fp_bg));
-        
+        quad(&mut pc, fp_btn_x, fp_btn_y, fp_btn_w, fp_btn_h, fp_bg);
+
         if self.fingerprint_active {
             let scan_y = fp_btn_y + 10.0 + (50.0 + 50.0 * self.glow_timer.sin()).clamp(0.0, fp_btn_h - 20.0);
-            quads.push((fp_btn_x + 10.0, scan_y, fp_btn_w - 20.0, 2.0, [0.30, 0.90, 0.32, 0.8]));
+            quad(&mut pc, fp_btn_x + 10.0, scan_y, fp_btn_w - 20.0, 2.0, [0.30, 0.90, 0.32, 0.8]);
         }
-        
+
         let pw_col_x = card_x + 290.0;
         let pw_col_y = card_y + 80.0;
         let pw_col_w = 220.0;
-        
+
         self.password_box.set_rect(pw_col_x, pw_col_y + 20.0, pw_col_w, 36.0);
-        
+
         let btn_w = 100.0f32;
         let btn_h = 32.0f32;
         let verify_x = pw_col_x;
         let cancel_x = pw_col_x + pw_col_w - btn_w;
-        
+
         self.verify_btn.set_rect(verify_x, pw_col_y + 80.0, btn_w, btn_h);
         self.cancel_btn.set_rect(cancel_x, pw_col_y + 80.0, btn_w, btn_h);
-        
+
         for w in &self.widgets_iter() {
-            quads.push((w.rect().0, w.rect().1, w.rect().2, w.rect().3, w.color()));
-            quads.extend(w.extra_quads());
+            let (wx, wy, ww, wh) = w.rect();
+            quad(&mut pc, wx, wy, ww, wh, w.color());
+            for (qx, qy, qw, qh, qc) in w.extra_quads() {
+                quad(&mut pc, qx, qy, qw, qh, qc);
+            }
         }
-        
-        self.text_items.clear();
-        
-        self.text_items.push(TextItem {
-            buffer: make_text_buffer(&mut self.font_system, "CCE AUTHENTICATOR", 15.0),
-            x: card_x + 30.0,
-            y: card_y + 30.0,
-            color: glyphon::Color::rgb(0xee, 0xee, 0xf5),
-            bounds: None,
-        });
-        
-        self.text_items.push(TextItem {
-            buffer: make_text_buffer(&mut self.font_system, "FINGERPRINT AUTHENTICATION", 10.0),
-            x: fp_col_x,
-            y: fp_col_y - 15.0,
-            color: glyphon::Color::rgb(0x83, 0x83, 0x8a),
-            bounds: None,
-        });
-        
-        self.text_items.push(TextItem {
-            buffer: make_text_buffer(&mut self.font_system, &self.fingerprint_msg, 9.0),
-            x: fp_col_x,
-            y: fp_btn_y + fp_btn_h + 12.0,
-            color: if self.fingerprint_success { glyphon::Color::rgb(0xa0, 0xee, 0xa0) } else { glyphon::Color::rgb(0xbb, 0xbb, 0xbf) },
-            bounds: Some([fp_col_x, fp_btn_y + fp_btn_h + 12.0, fp_col_x + fp_col_w, fp_btn_y + fp_btn_h + 50.0]),
-        });
-        
-        self.text_items.push(TextItem {
-            buffer: make_text_buffer(&mut self.font_system, "PASSWORD AUTHENTICATION", 10.0),
-            x: pw_col_x,
-            y: pw_col_y - 15.0,
-            color: glyphon::Color::rgb(0x83, 0x83, 0x8a),
-            bounds: None,
-        });
-        
+
+        // ── Text (the old text_items assembly, now prims shaped by the engine) ──
+        pc.text_with("CCE AUTHENTICATOR".to_string(), card_x + 30.0, card_y + 30.0, 15.0, [0xee, 0xee, 0xf5], None, None);
+        pc.text_with("FINGERPRINT AUTHENTICATION".to_string(), fp_col_x, fp_col_y - 15.0, 10.0, [0x83, 0x83, 0x8a], None, None);
+        let fp_msg_color = if self.fingerprint_success { [0xa0, 0xee, 0xa0] } else { [0xbb, 0xbb, 0xbf] };
+        pc.text_with(
+            self.fingerprint_msg.clone(),
+            fp_col_x,
+            fp_btn_y + fp_btn_h + 12.0,
+            9.0,
+            fp_msg_color,
+            None,
+            Some([fp_col_x, fp_btn_y + fp_btn_h + 12.0, fp_col_x + fp_col_w, fp_btn_y + fp_btn_h + 50.0]),
+        );
+        pc.text_with("PASSWORD AUTHENTICATION".to_string(), pw_col_x, pw_col_y - 15.0, 10.0, [0x83, 0x83, 0x8a], None, None);
+
         let mut labels = Vec::new();
         for w in &self.widgets_iter() {
             labels.extend(w.text_labels());
         }
         for label in labels {
-            self.text_items.push(TextItem {
-                buffer: make_text_buffer(&mut self.font_system, &label.text, label.font_size),
-                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, label.x, label.y, label.font_size, label.color, None, None);
         }
-        
+
         let status_color = if self.status_is_success {
-            glyphon::Color::rgb(0xa0, 0xee, 0xa0)
+            [0xa0, 0xee, 0xa0]
         } else if self.status_is_error {
-            glyphon::Color::rgb(0xee, 0x5c, 0x5c)
+            [0xee, 0x5c, 0x5c]
         } else {
-            glyphon::Color::rgb(0xbb, 0xbb, 0xbf)
+            [0xbb, 0xbb, 0xbf]
         };
-        
-        self.text_items.push(TextItem {
-            buffer: make_text_buffer(&mut self.font_system, &self.status_msg, 10.0),
-            x: card_x + 30.0,
-            y: card_y + card_h - 40.0,
-            color: status_color,
-            bounds: Some([card_x + 30.0, card_y + card_h - 45.0, card_x + card_w - 30.0, card_y + card_h - 5.0]),
-        });
+        pc.text_with(
+            self.status_msg.clone(),
+            card_x + 30.0,
+            card_y + card_h - 40.0,
+            10.0,
+            status_color,
+            None,
+            Some([card_x + 30.0, card_y + card_h - 45.0, card_x + card_w - 30.0, card_y + card_h - 5.0]),
+        );
+
+        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) {