git.lucas.co / cce-notifier
notification daemon
git clone https://git.lucas.co/cce-notifier.git

commit68484d1e1ddf4e0b7226048189fa6fd006fe5db9
parente356846ef5
authorLucas Galante <[email protected]>
date2026-07-09 10:12
feat: whole frame as one display list (cce-ui Phase 6b)

The notification frame — green accent quad and the three text lines — is
now a single Application::display_list() with display_list_text(); the
engine shapes the text through its shared buffer cache. Deleted: the
app-side FontSystem, the TextItem cache, rebuild_layout, and the
scale/rebuild bookkeeping. Live A/B on a private D-Bus session:
text/accent pixel-identical (residual = compositor translucency noise in
the alpha-0.9 background).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016MjP3pGQEDLkJbV5WEmYBe

 src/main.rs | 102 ++++++++++++++++++------------------------------------------
 1 file changed, 31 insertions(+), 71 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 8ef4979..adfa7f0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,14 +3,13 @@ use std::collections::HashMap;
 use zbus::zvariant::Value;
 use zbus::{connection, interface};
 
-use glyphon::FontSystem;
 use wayland_client::QueueHandle;
 
 use cce_ui::engine::{
     Application, EngineState, LayerAnchor, LayerKeyboardInteractivity, LayerKind, LayerSettings,
-    LogicalPosition, LogicalSize, WindowSettings,
+    LogicalPosition, WindowSettings,
 };
-use cce_ui::widget::{ElementState, KeyEvent, MouseButton, MouseScrollDelta, TextItem};
+use cce_ui::widget::{ElementState, KeyEvent, MouseButton, MouseScrollDelta};
 
 const NOTIF_WIDTH: u32 = 360;
 const NOTIF_HEIGHT: u32 = 100;
@@ -65,19 +64,22 @@ fn play_bell_if_configured() {
     }
 }
 
-fn glyphon_color(linear: [f32; 4]) -> glyphon::Color {
+fn srgb_u8(linear: [f32; 4]) -> [u8; 3] {
     let srgb = cce_ui::colors::to_srgb(linear);
-    glyphon::Color::rgb(
+    [
         (srgb[0] * 255.0) as u8,
         (srgb[1] * 255.0) as u8,
         (srgb[2] * 255.0) as u8,
-    )
+    ]
 }
 
 // ── Application ───────────────────────────────────────────────────────────
+//
+// Phase 6 shape: the whole frame — accent quad and text — is one display list
+// (`display_list` + `display_list_text`); the engine shapes the text through the shared
+// buffer cache. No app-side FontSystem, TextItem cache, or rebuild bookkeeping.
 
 struct NotifierApp {
-    font_system: FontSystem,
     app_name: String,
     summary: String,
     body: String,
@@ -85,63 +87,14 @@ struct NotifierApp {
     dismiss_timer: f32,
     opacity: f32,
     bg_color: [f32; 4],
-    scale_factor: f64,
-    needs_rebuild: bool,
-    text_items: Vec<TextItem>,
     sender: calloop::channel::Sender<UserEvent>,
 }
 
-impl NotifierApp {
-    fn rebuild_layout(&mut self) {
-        self.text_items.clear();
-        if self.visible {
-            // Use a configured (bundled) font family so glyph font-ids resolve
-            // in the engine's render FontSystem too — a bare default can pick a
-            // system font absent from the engine's bundled-only database.
-            let family = cce_ui::layout::statusbar_font_parsed().0;
-            let font = Some(family.as_str());
-            // app name, summary, body (logical px; the engine applies HiDPI scale)
-            self.text_items.push(TextItem::new(
-                &mut self.font_system,
-                &self.app_name,
-                10.0,
-                18.0,
-                12.0,
-                glyphon_color(cce_ui::colors::TEXT_DIM),
-                font,
-                None,
-            ));
-            self.text_items.push(TextItem::new(
-                &mut self.font_system,
-                &self.summary,
-                13.0,
-                18.0,
-                28.0,
-                glyphon_color(cce_ui::colors::TEXT_HEADER),
-                font,
-                None,
-            ));
-            self.text_items.push(TextItem::new(
-                &mut self.font_system,
-                &self.body,
-                11.0,
-                18.0,
-                48.0,
-                glyphon_color(cce_ui::colors::TEXT_FG),
-                font,
-                None,
-            ));
-        }
-        self.needs_rebuild = false;
-    }
-}
-
 impl Application for NotifierApp {
     type Message = UserEvent;
 
     fn new(_qh: &QueueHandle<EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
         Self {
-            font_system: cce_ui::create_font_system_with_system_fonts(),
             app_name: String::new(),
             summary: String::new(),
             body: String::new(),
@@ -149,9 +102,6 @@ impl Application for NotifierApp {
             dismiss_timer: 0.0,
             opacity: read_opacity(),
             bg_color: read_bg_color(),
-            scale_factor: 1.0,
-            needs_rebuild: false,
-            text_items: Vec::new(),
             sender,
         }
     }
@@ -189,12 +139,10 @@ impl Application for NotifierApp {
                 self.bg_color = read_bg_color();
                 self.visible = true;
                 self.dismiss_timer = read_duration();
-                self.needs_rebuild = true;
                 *needs_rebuild = true;
             }
             UserEvent::CloseNotification => {
                 self.visible = false;
-                self.needs_rebuild = true;
                 *needs_rebuild = true;
             }
         }
@@ -205,25 +153,37 @@ impl Application for NotifierApp {
             self.dismiss_timer -= dt;
             if self.dismiss_timer <= 0.0 {
                 self.visible = false;
-                self.needs_rebuild = true;
                 *needs_rebuild = true;
             }
         }
     }
 
-    fn view(&mut self, quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, size: LogicalSize, scale: f64) {
-        if self.needs_rebuild || (self.scale_factor - scale).abs() > f64::EPSILON {
-            self.scale_factor = scale;
-            self.rebuild_layout();
-        }
+    /// The whole frame as one display list (Phase 6): the green accent border plus the three
+    /// text lines. Coordinates are logical px; the engine applies HiDPI scale and shapes the
+    /// text through its shared buffer cache.
+    fn display_list(&mut self) -> Option<cce_ui::scene::paint::DisplayList> {
+        use cce_ui::scene::layout::Rect;
+        use cce_ui::scene::paint::PaintCtx;
+        let mut pc = PaintCtx::new();
         if self.visible {
-            // Bright green left accent border.
-            quads.push((0.0, 0.0, 6.0, size.height, cce_ui::colors::TOGGLE_ON));
+            pc.quad(
+                Rect { x: 0.0, y: 0.0, width: 6.0, height: NOTIF_HEIGHT as f32 },
+                cce_ui::colors::TOGGLE_ON,
+            );
+            // Use a configured (bundled) font family so glyph font-ids resolve in the
+            // engine's render FontSystem — a bare default can pick a system font absent
+            // from the engine's bundled-only database.
+            let family = cce_ui::layout::statusbar_font_parsed().0;
+            let font = Some(family);
+            pc.text_with(&self.app_name, 18.0, 12.0, 10.0, srgb_u8(cce_ui::colors::TEXT_DIM), font.clone(), None);
+            pc.text_with(&self.summary, 18.0, 28.0, 13.0, srgb_u8(cce_ui::colors::TEXT_HEADER), font.clone(), None);
+            pc.text_with(&self.body, 18.0, 48.0, 11.0, srgb_u8(cce_ui::colors::TEXT_FG), font, None);
         }
+        Some(pc.finish())
     }
 
-    fn text_items(&self) -> &[TextItem] {
-        &self.text_items
+    fn display_list_text(&self) -> bool {
+        true
     }
 
     fn clear_color(&self) -> [f32; 4] {