git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commit06b174aac9bef3627579af1bf28c5f69ad46cfa7
parentad52975ca1
authorLucas Galante <[email protected]>
date2026-07-09 11:14
feat: whole frame through display_list — text as prims, TextItem assembly deleted (Phase 6g)

Flip Application::display_list_text and emit rebuild_layout's text tuples
as text_with prims; the rebuild check moves into display_list and the
view/view_rounded_quads overrides, the CCE_LEGACY_PAINT fallback, and the
TextItem assembly are deleted. The app-side FontSystem stays for button-
label width measurement (centering) and the search-match highlight rect.
The scroll fast-path mutates the tuples' y/bounds in place, same as it
did TextItems. Popovers and the context menu are untouched: they render
on the engine's xdg-popup surface via render_popovers, and main-surface
text under them now gets the engine's dl-text popover clamp.

Verified live: full-window A/B pixel-identical (AE=0); page-dropdown
popup, page switch, service-list scroll, and live process refresh all
exercised.

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

 src/input_handler.rs |  6 ++--
 src/main.rs          | 70 ++++++++++++++++++----------------------------
 src/renderer.rs      | 78 +++++++++++-----------------------------------------
 3 files changed, 45 insertions(+), 109 deletions(-)

diff --git a/src/input_handler.rs b/src/input_handler.rs
index f8d3d8f..f6cf58b 100644
--- a/src/input_handler.rs
+++ b/src/input_handler.rs
@@ -262,9 +262,9 @@ impl SystemInterface {
                 for w in &mut self.widgets[self.scrollable_widgets_start_idx..] {
                     w.y -= actual_dy;
                 }
-                for ti in &mut self.text_items[self.scrollable_text_items_start_idx..] {
-                    ti.y -= actual_dy;
-                    if let Some(ref mut b) = ti.bounds {
+                for (_, _, _, ty, _, _, bounds) in &mut self.texts[self.scrollable_text_items_start_idx..] {
+                    *ty -= actual_dy;
+                    if let Some(ref mut b) = bounds {
                         b[1] -= actual_dy;
                         b[3] -= actual_dy;
                     }
diff --git a/src/main.rs b/src/main.rs
index 408191f..b81d825 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,7 +34,10 @@ struct SystemInterface {
     app: AppState,
     font_system: FontSystem,
     widgets: Vec<AppWidget>,
-    text_items: Vec<cce_ui::widget::TextItem>,
+    // (content, font_size, x, y, color, font, bounds) — the frame's text, emitted as
+    // display-list Text prims (scroll shift, search dim/highlight, and viewport clamps
+    // already applied by rebuild_layout).
+    texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>)>,
     page_buttons: Vec<(cce_ui::widget::Adapted<cce_ui::widget::Button>, AppAction)>,
 
     sidebar_width: f32,
@@ -144,7 +147,7 @@ impl cce_ui::engine::Application for SystemInterface {
             app: app_state,
             font_system,
             widgets: Vec::new(),
-            text_items: Vec::new(),
+            texts: Vec::new(),
             page_buttons: Vec::new(),
             sidebar_width,
             header_height: 0.0,
@@ -262,7 +265,10 @@ impl cce_ui::engine::Application for SystemInterface {
         }
     }
 
-    fn view(&mut self, _quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, size: cce_ui::engine::LogicalSize, scale: f64) {
+    fn display_list(&mut self, size: cce_ui::engine::LogicalSize, scale: f64) -> Option<cce_ui::scene::paint::DisplayList> {
+        // Phase 6 single paint path: the whole frame — geometry and text — is this one list.
+        // rebuild_layout flattens the UI into self.widgets/self.texts (scroll shift, search
+        // dim/highlight, and viewport clamps already applied).
         let (width, height) = (size.width, size.height);
         if self.needs_rebuild || self.ui_context.is_dirty() || self.width != width as u32 || self.height != height as u32 || self.scale_factor != scale {
             self.width = width as u32;
@@ -271,45 +277,6 @@ impl cce_ui::engine::Application for SystemInterface {
             cce_ui::scale::set_scale_factor(scale as f32);
             self.rebuild_layout(width, height);
         }
-    }
-
-    fn view_rounded_quads(&mut self, quads: &mut Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))>, size: cce_ui::engine::LogicalSize, scale: f64) {
-        let (width, height) = (size.width, size.height);
-        if self.needs_rebuild || self.ui_context.is_dirty() || self.width != width as u32 || self.height != height as u32 || self.scale_factor != scale {
-            self.width = width as u32;
-            self.height = height as u32;
-            self.scale_factor = scale;
-            cce_ui::scale::set_scale_factor(scale as f32);
-            self.rebuild_layout(width, height);
-        }
-        for w in &self.widgets {
-            let color = if w.hovering { w.hover_color } else { w.color };
-            quads.push((w.x, w.y, w.w, w.h, w.radius, color, w.corners));
-        }
-
-        // Draw global hover highlight if active
-        let s = 1.0f32;
-        cce_ui::widget::hover_animation::post_render_check();
-        if let Some((qx, qy, qw, qh, qc)) = cce_ui::widget::hover_animation::get_quad() {
-            quads.push((
-                qx * s,
-                (qy - self.scroll_y) * s,
-                qw * s,
-                qh * s,
-                0.0,
-                qc,
-                (true, true, true, true),
-            ));
-        }
-    }
-
-    fn display_list(&mut self, _size: cce_ui::engine::LogicalSize, _scale: f64) -> Option<cce_ui::scene::paint::DisplayList> {
-        // Phase 3 single paint path. This app flattens its UI into a `widgets` quad list (rebuilt
-        // by view_rounded_quads, which runs before this), so build the DisplayList directly from
-        // that list — the flat-list bridge. CCE_LEGACY_PAINT falls back.
-        if std::env::var("CCE_LEGACY_PAINT").is_ok() {
-            return None;
-        }
         use cce_ui::scene::layout::Rect;
         let mut pc = cce_ui::scene::paint::PaintCtx::new();
         for w in &self.widgets {
@@ -325,11 +292,26 @@ impl cce_ui::engine::Application for SystemInterface {
         if let Some((qx, qy, qw, qh, qc)) = cce_ui::widget::hover_animation::get_quad() {
             pc.quad(Rect { x: qx, y: qy - self.scroll_y, width: qw, height: qh }, qc);
         }
+        for (text, font_size, x, y, col, font, bounds) in &self.texts {
+            pc.text_with(
+                text.clone(),
+                *x,
+                *y,
+                *font_size,
+                [
+                    (col[0] * 255.0) as u8,
+                    (col[1] * 255.0) as u8,
+                    (col[2] * 255.0) as u8,
+                ],
+                font.clone(),
+                *bounds,
+            );
+        }
         Some(pc.finish())
     }
 
-    fn text_items(&self) -> &[cce_ui::widget::TextItem] {
-        &self.text_items
+    fn display_list_text(&self) -> bool {
+        true
     }
 
     fn ui_context(&self) -> Option<&cce_ui::context::UiContext> {
diff --git a/src/renderer.rs b/src/renderer.rs
index 81c8922..e9767c2 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -1,7 +1,7 @@
 use crate::{SystemInterface, AppWidget, make_text_buffer_with_font};
 use cce_settings::app::PageContent;
 use cce_settings::pages::Page;
-use cce_ui::widget::{Element, TextItem};
+use cce_ui::widget::Element;
 
 impl SystemInterface {
 
@@ -40,7 +40,7 @@ impl SystemInterface {
         self.sidebar_width = 0.0;
         self.header_height = 0.0; // No CSD Titlebar
         let mut widgets = Vec::new();
-        let mut text_items = Vec::new();
+        let mut texts = Vec::new();
         let mut page_buttons = Vec::new();
 
         cce_ui::widget::hover_animation::reset_frame_registration();
@@ -168,27 +168,12 @@ impl SystemInterface {
             }
             for (idx, (t, size, x, y, tc, font_opt, bounds)) in pc_part.texts.iter().enumerate() {
                 eprintln!("WINDOW_PC_TEXT idx={}: text='{}', size={}, x={}, y={}, bounds={:?}", idx, t, size, x, y, bounds);
-                text_items.push(TextItem {
-                    buffer: make_text_buffer_with_font(
-                        &mut self.font_system,
-                        t,
-                        *size,
-                        font_opt.as_deref(),
-                        &self.sans_serif_family,
-                        &self.serif_family,
-                        &self.monospace_family,
-                    ),
-                    x: *x, y: *y,
-                    color: glyphon::Color::rgb(
-                        (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
-                    ),
-                    bounds: *bounds,
-                });
+                texts.push((t.clone(), *size, *x, *y, *tc, font_opt.clone(), *bounds));
             }
         }
 
         self.scrollable_widgets_start_idx = widgets.len();
-        self.scrollable_text_items_start_idx = text_items.len();
+        self.scrollable_text_items_start_idx = texts.len();
         self.scrollable_buttons_start_idx = page_buttons.len();
 
         // Page content in LOGICAL coordinates, then scale to physical
@@ -368,22 +353,7 @@ impl SystemInterface {
                 *tc
             };
 
-            text_items.push(TextItem {
-                buffer: make_text_buffer_with_font(
-                    &mut self.font_system,
-                    t,
-                    *size,
-                    font_opt.as_deref(),
-                    &self.sans_serif_family,
-                    &self.serif_family,
-                    &self.monospace_family,
-                ),
-                x: *x, y: *y - scroll_offset_y,
-                color: glyphon::Color::rgb(
-                    (text_color[0] * 255.0) as u8, (text_color[1] * 255.0) as u8, (text_color[2] * 255.0) as u8,
-                ),
-                bounds: final_bounds,
-            });
+            texts.push((t.clone(), *size, *x, *y - scroll_offset_y, text_color, font_opt.clone(), final_bounds));
         }
         for (btn, action) in &pc.buttons {
             let base = btn.base().unwrap();
@@ -463,16 +433,15 @@ impl SystemInterface {
                 logical_sw,
                 viewport_bottom,
             ]);
-            text_items.push(TextItem {
-                buffer: buf,
-                x: text_x, y: (base.y - scroll_offset_y) + (base.h - logical_lh) / 2.0,
-                color: glyphon::Color::rgb(
-                    (label_color[0] * 255.0) as u8,
-                    (label_color[1] * 255.0) as u8,
-                    (label_color[2] * 255.0) as u8,
-                ),
-                bounds: button_bounds,
-            });
+            texts.push((
+                label.to_string(),
+                label_size,
+                text_x,
+                (base.y - scroll_offset_y) + (base.h - logical_lh) / 2.0,
+                label_color,
+                btn.widget_font(),
+                button_bounds,
+            ));
             let mut btn_clone = btn.clone();
             if let Some(base_mut) = btn_clone.base_mut() {
                 base_mut.x *= s;
@@ -504,26 +473,11 @@ impl SystemInterface {
             });
         }
         for (t, size, x, y, tc, font_opt, bounds) in &search_pc.texts {
-            text_items.push(TextItem {
-                buffer: make_text_buffer_with_font(
-                    &mut self.font_system,
-                    t,
-                    *size,
-                    font_opt.as_deref(),
-                    &self.sans_serif_family,
-                    &self.serif_family,
-                    &self.monospace_family,
-                ),
-                x: *x, y: *y,
-                color: glyphon::Color::rgb(
-                    (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
-                ),
-                bounds: *bounds,
-            });
+            texts.push((t.clone(), *size, *x, *y, *tc, font_opt.clone(), *bounds));
         }
 
         self.widgets = widgets;
-        self.text_items = text_items;
+        self.texts = texts;
         self.page_buttons = page_buttons;
         self.needs_rebuild = false;
         self.last_scroll_y = self.scroll_y;