git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commita7f30731c1bccb853b23394cb0fdfa5b0dc127c7
parent999d3389ca
authorLucas Galante <[email protected]>
date2026-07-10 15:07
refactor(engine): delete the legacy view*/text_items/text_areas path — display_list is the only frame

Phase 6 endgame, part 1. Now that all six remaining apps are across (6af–6ak),
the legacy geometry/text trait surface is dead code and comes out:

- Application trait: `view`, `view_rounded_quads`, `view_vectors`, `text_items`,
  and the default `text_areas` mapping are removed. `overlay_quads` (status-bar
  tray hover) and `custom_vertices` (display-manager card glow, test-interface
  gallery) stay — they were always separate from the single paint path.
- render(): the `quads`/`rounded_quads`/`vectors` collection + the tuple-wrapping
  `None =>` branch that rebuilt a DisplayList from them are gone; `dl` is now just
  `display_list().unwrap_or_else(|| PaintCtx::new().finish())`. The `text_areas()`
  call is replaced by an empty `areas` vec that the display-list-text loop fills.

Pure dead-path removal: every Application implementor already returns Some(dl)
with an empty text_items, so the deleted arms were never taken. cce-designer and
cce-cloud drive WgpuAdapter directly and never touch Application, so they're
unaffected. Doc comments on display_list / display_list_text updated to stop
referring to a legacy path that no longer exists.

All 16 client apps compile; 180 cce-ui tests pass; settings live-verified
(sidebar tabs, section headers, swatches, rounded frames all render through the
display list). Next: the per-widget text getters, then Element + Adapted.

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

 src/backend/window_runner.rs | 133 ++++++++-----------------------------------
 1 file changed, 23 insertions(+), 110 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 73b067b..bbfd25d 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1569,16 +1569,9 @@ pub trait Application: Sized + 'static {
     }
     fn update(&mut self, msg: Self::Message, needs_rebuild: &mut bool, exit: &mut bool);
     fn tick(&mut self, dt: f32, needs_rebuild: &mut bool);
-    /// Legacy geometry sink. Default no-op since Phase 6: an app whose whole frame comes from
-    /// [`display_list`](Application::display_list) (+ [`display_list_text`]) implements neither
-    /// this nor [`text_items`](Application::text_items).
-    fn view(&mut self, _quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, _size: LogicalSize, _scale: f64) {}
-    fn view_rounded_quads(&mut self, _quads: &mut Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))>, _size: LogicalSize, _scale: f64) {}
-    fn view_vectors(&mut self, _vectors: &mut Vec<(f32, f32, f32, f32, f32, [f32; 4], LineCap)>, _size: LogicalSize, _scale: f64) {}
+    /// On-top overlay quads drawn after the display list and its text (e.g. the status bar's
+    /// tray-hover highlights). Deliberately separate from the single paint path.
     fn overlay_quads(&mut self, _quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>, _size: LogicalSize, _scale: f64) {}
-    fn text_items(&self) -> &[TextItem] {
-        &[]
-    }
     fn input_regions(&self) -> Option<Vec<(i32, i32, i32, i32)>> {
         None
     }
@@ -1603,46 +1596,6 @@ pub trait Application: Sized + 'static {
         }
     }
     
-    fn text_areas(&self, scale_f32: f32, bounds: TextBounds) -> Vec<TextArea<'_>> {
-        let mut overlay_rects: Vec<(f32, f32, f32, f32)> = Vec::new();
-        if let Some(ctx) = self.ui_context() {
-            for popover_ptr in &ctx.active_popovers {
-                unsafe {
-                    if let Some(popover) = popover_ptr.as_ref() {
-                        if let Some((x, y, w, h)) = popover.popover_rect() {
-                            overlay_rects.push((x, y, w, h));
-                        }
-                    }
-                }
-            }
-        }
-
-        self.text_items().iter().map(|ti| {
-            let mut item_bounds = if let Some([l, t, r, b]) = ti.bounds {
-                TextBounds {
-                    left: ((l * scale_f32).round() as i32).clamp(0, bounds.right),
-                    top: ((t * scale_f32).round() as i32).clamp(0, bounds.bottom),
-                    right: ((r * scale_f32).round() as i32).clamp(0, bounds.right),
-                    bottom: ((b * scale_f32).round() as i32).clamp(0, bounds.bottom),
-                }
-            } else {
-                bounds
-            };
-
-            popover_occlusion_clamp(&overlay_rects, ti, scale_f32, &mut item_bounds);
-
-            TextArea {
-                buffer: &ti.buffer,
-                left: (ti.x * scale_f32).round(),
-                top: (ti.y * scale_f32).round(),
-                scale: 1.0,
-                bounds: item_bounds,
-                default_color: ti.color,
-                custom_glyphs: &[],
-            }
-        }).collect()
-    }
-    
     fn clear_color(&self) -> [f32; 4] {
         [0.0, 0.0, 0.0, 0.0]
     }
@@ -1660,24 +1613,22 @@ pub trait Application: Sized + 'static {
 
     fn custom_vertices(&mut self, _verts: &mut Vec<Vertex>, _size: LogicalSize, _scale: f64) {}
 
-    /// Opt into the single paint path (Phase 3): return a display list for this frame and `render()`
-    /// draws its geometry via one batched, GPU-scissor-clipped pass instead of the legacy
-    /// `view*` geometry. Default `None` keeps the legacy path. Overlays and `custom_vertices`
-    /// still go through their existing paths; text renders from the list when
-    /// [`display_list_text`](Application::display_list_text) opts in. Receives the frame's
-    /// logical size and HiDPI scale, like `view`. Typically implemented as
+    /// The frame's geometry, drawn via one batched, GPU-scissor-clipped pass (the single
+    /// paint path). Every rendering app implements this — the legacy `view*` sinks are gone;
+    /// `None` yields an empty frame. Overlays ([`overlay_quads`](Application::overlay_quads))
+    /// and [`custom_vertices`](Application::custom_vertices) still go through their own paths;
+    /// text renders from the list when [`display_list_text`](Application::display_list_text)
+    /// opts in. Receives the frame's logical size and HiDPI scale. Typically implemented as
     /// `Some(cce_ui::scene::painter::paint_tree(&self.ui_context, root_ptr))`.
     fn display_list(&mut self, _size: LogicalSize, _scale: f64) -> Option<crate::scene::paint::DisplayList> {
         None
     }
 
-    /// Phase 6 opt-in: render the display list's `Prim::Text` items through the glyphon pass
-    /// (shaped via the shared buffer cache, clipped to the item clip ∩ the prim bounds). A
-    /// fully migrated app's ENTIRE frame — geometry and text — is then one
-    /// [`display_list`](Application::display_list); its [`text_items`](Application::text_items)
-    /// is typically empty. Default `false`: the seven Phase 3 adopters' lists already carry
-    /// Text prims that those apps ALSO push as `TextItem`s — rendering both would double-draw,
-    /// so each app flips this only when it stops pushing its own.
+    /// Opt in to render the display list's `Prim::Text` items through the glyphon pass
+    /// (shaped via the shared buffer cache, clipped to the item clip ∩ the prim bounds). An
+    /// app's ENTIRE frame — geometry and text — is then one
+    /// [`display_list`](Application::display_list). Default `false` draws no text (an app that
+    /// only draws geometry, or none at all).
     ///
     /// Display-list text gets the same popover-occlusion clamp as the legacy `text_areas`
     /// mapping (`popover_occlusion_clamp`, driven by `ui_context().active_popovers`), so an
@@ -1870,9 +1821,6 @@ impl<A: Application> EngineState<A> {
         let logical_w = self.logical_width;
         let logical_h = self.logical_height;
         let scale_factor = self.scale_factor;
-        
-        let mut quads = Vec::new();
-        self.inner.as_mut().unwrap().view(&mut quads, LogicalSize::new(logical_w, logical_h), scale_factor);
 
         if let Some(ref surface) = self.surface {
             if let Some(regions) = self.inner.as_ref().unwrap().input_regions() {
@@ -1886,48 +1834,12 @@ impl<A: Application> EngineState<A> {
             }
         }
         
-        let mut rounded_quads = Vec::new();
-        self.inner.as_mut().unwrap().view_rounded_quads(&mut rounded_quads, LogicalSize::new(logical_w, logical_h), scale_factor);
-        
-        let mut vectors = Vec::new();
-        self.inner.as_mut().unwrap().view_vectors(&mut vectors, LogicalSize::new(logical_w, logical_h), scale_factor);
-
-        // Phase 3 single paint path: when the app provides a display list, its geometry replaces the
-        // legacy view* geometry and is drawn as batched, GPU-scissor-clipped runs. Default `None`
-        // keeps the legacy path byte-for-byte.
-        let display_list = self.inner.as_mut().unwrap().display_list(LogicalSize::new(logical_w, logical_h), scale_factor);
-
-        // 1. Build the frame's DisplayList — from the app's display_list() when provided, otherwise
-        // by wrapping its legacy view*/view_vectors geometry — then tessellate it as one path. This
-        // is the Phase 3 single paint path: every app, migrated or not, renders through here.
-        let dl = match display_list {
-            Some(dl) => dl,
-            None => {
-                use crate::scene::layout::Rect;
-                use crate::scene::paint::{Cap, PaintCtx};
-                let mut pc = PaintCtx::new();
-                for &(qx, qy, qw, qh, qr, qc, qcorners) in &rounded_quads {
-                    let rect = Rect { x: qx, y: qy, width: qw, height: qh };
-                    if qr > 0.1 {
-                        pc.rounded_rect(rect, qr, qcorners, qc);
-                    } else {
-                        pc.quad(rect, qc);
-                    }
-                }
-                for &(qx, qy, qw, qh, qc) in &quads {
-                    pc.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
-                }
-                for &(vx1, vy1, vx2, vy2, vthickness, vcolor, vcap) in &vectors {
-                    let cap = match vcap {
-                        LineCap::Flat => Cap::Flat,
-                        LineCap::Round => Cap::Round,
-                        LineCap::Arrow => Cap::Arrow,
-                    };
-                    pc.vector(vx1, vy1, vx2, vy2, vthickness, vcolor, cap);
-                }
-                pc.finish()
-            }
-        };
+        // 1. The frame's geometry IS the app's display list — the single paint path. Tessellated
+        // below as one batched, GPU-scissor-clipped pass. An app that draws nothing returns
+        // `None`, giving an empty frame (the legacy view*/tuple-wrapping path is gone).
+        let dl = self.inner.as_mut().unwrap()
+            .display_list(LogicalSize::new(logical_w, logical_h), scale_factor)
+            .unwrap_or_else(|| crate::scene::paint::PaintCtx::new().finish());
 
         // 1a. Phase 6 display-list text: shape the list's Text prims through the shared buffer
         // cache and hold them for the glyphon pass (the TextAreas built below borrow these).
@@ -2019,9 +1931,10 @@ impl<A: Application> EngineState<A> {
         adapter.text_viewport.update(&adapter.queue, Resolution { width: pw, height: ph });
         
         let bounds = TextBounds { left: 0, top: 0, right: pw as i32, bottom: ph as i32 };
-        let mut areas = self.inner.as_ref().unwrap().text_areas(scale_f32, bounds);
-        // Phase 6 display-list text — the default `text_areas` mapping (scale + surface clamp),
-        // including the popover-occlusion clamp against the app's registered popovers.
+        // All text is display-list text now (the legacy text_items/text_areas path is gone):
+        // map each dl Text prim with the default mapping (scale + surface clamp) plus the
+        // popover-occlusion clamp against the app's registered popovers.
+        let mut areas: Vec<TextArea<'_>> = Vec::new();
         let mut dl_overlay_rects: Vec<(f32, f32, f32, f32)> = Vec::new();
         if let Some(ctx) = self.inner.as_ref().unwrap().ui_context() {
             for popover_ptr in &ctx.active_popovers {