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

commit73772662ba44418e0bcc1e10affecc60704d5b0d
parent1146ce6159
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-08 17:07
refactor: the page dropdown and search box paint themselves

The window chrome was collected into a flat target (collect_window_child,
the chevron icon list) and replayed. The page dropdown, and the search box
while search is open, are now walked live with paint_root_into from
display_list, so the dropdown draws its face plate; the search band is
unchanged. The search box's render_widget call only lays it out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/main.rs     |   9 ++--
 src/renderer.rs | 161 ++++----------------------------------------------------
 2 files changed, 15 insertions(+), 155 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index e737c85..8250ea0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -170,7 +170,6 @@ struct SystemInterface {
     popover_icon_images: Vec<PlacedIcon>,
     /// Glyphs of the window chrome (the page dropdown's arrow), window
     /// coordinates, drawn over the chrome outside the page clip.
-    window_icon_images: Vec<PlacedIcon>,
     // root plate container + StatusBar DISSOLVED (Phase 6s): the window plate and the status
     // bar are emitted as tuples in rebuild_layout.
     sans_serif_family: String,
@@ -312,7 +311,6 @@ impl cce_ui::engine::Application for SystemInterface {
             page_control_relief_marks: Vec::new(),
             page_icon_images: Vec::new(),
             popover_icon_images: Vec::new(),
-            window_icon_images: Vec::new(),
             sans_serif_family: sans_family,
             serif_family,
             monospace_family,
@@ -602,8 +600,11 @@ impl cce_ui::engine::Application for SystemInterface {
             });
         }
 
-        for icon in &self.window_icon_images {
-            draw_placed_icon(&mut pc, icon);
+        // The window chrome, as it paints itself: the page dropdown in the status bar
+        // and, while open, the search box in its band.
+        cce_ui::scene::painter::paint_root_into(&self.ui_context, &*self.page_dropdown, &mut pc);
+        if self.search_open {
+            cce_ui::scene::painter::paint_root_into(&self.ui_context, &*self.search_box, &mut pc);
         }
 
         // The page scrollbar's fore copy: over the page content at the
diff --git a/src/renderer.rs b/src/renderer.rs
index 76ee119..63807ea 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -4,90 +4,6 @@ use cce_settings::app::{ControlCarve, PageContent};
 use cce_settings::pages::Page;
 use cce_ui::widget::WidgetHost;
 
-type RectTuple = ([f32; 4], f32, f32, f32, f32, f32, (bool, bool, bool, bool));
-type TextTuple = (String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>);
-
-/// One child's contribution to the dissolved root's window assembly, replicating the
-/// legacy `render_widget(root plate container)` aggregate exactly: plain quads are skipped
-/// when they are a rounded child's own bg (the rounded pass carries it), clipped to
-/// the window, and corner-resolved against the root's rounded rect (a quad flush with
-/// a window corner picks up the plate radius there); rounded quads are clipped;
-/// text bounds are clamped to the window (unbounded labels become window-bounded).
-fn collect_window_child(
-    w: &dyn WidgetHost,
-    ctx: &cce_ui::context::UiContext,
-    win_w: f32,
-    win_h: f32,
-    plate_radius: f32,
-    plain: &mut Vec<RectTuple>,
-    rounded: &mut Vec<RectTuple>,
-    texts: &mut Vec<TextTuple>,
-) {
-    let (cx, cy, cw, ch) = w.rect();
-    let child_rounded = w.corner_style().1 != (false, false, false, false);
-    for (qx, qy, qw, qh, qc) in w.all_quads(ctx) {
-        if child_rounded && (qx - cx).abs() < 0.1 && (qy - cy).abs() < 0.1 && (qw - cw).abs() < 0.1 && (qh - ch).abs() < 0.1 {
-            continue;
-        }
-        let x0 = qx.max(0.0);
-        let y0 = qy.max(0.0);
-        let x1 = (qx + qw).min(win_w);
-        let y1 = (qy + qh).min(win_h);
-        if x1 <= x0 || y1 <= y0 {
-            continue;
-        }
-        let corners = (
-            x0 <= 1.5 && y0 <= 1.5,
-            x1 >= win_w - 1.5 && y0 <= 1.5,
-            x1 >= win_w - 1.5 && y1 >= win_h - 1.5,
-            x0 <= 1.5 && y1 >= win_h - 1.5,
-        );
-        if corners == (false, false, false, false) {
-            plain.push((qc, x0, y0, x1 - x0, y1 - y0, 0.0, (false, false, false, false)));
-        } else {
-            plain.push((qc, x0, y0, x1 - x0, y1 - y0, plate_radius, corners));
-        }
-    }
-    for (qx, qy, qw, qh, qr, qc, qcorners) in w.all_rounded_quads(ctx) {
-        let x0 = qx.max(0.0);
-        let y0 = qy.max(0.0);
-        let x1 = (qx + qw).min(win_w);
-        let y1 = (qy + qh).min(win_h);
-        if x1 <= x0 || y1 <= y0 {
-            continue;
-        }
-        rounded.push((qc, x0, y0, x1 - x0, y1 - y0, qr, qcorners));
-    }
-    // Text via the paint walk (not the legacy text_labels* getters): same labels, with the
-    // widget's content font and any container clip composed into the prim bounds; clamped
-    // to the window exactly as before.
-    let mut scratch = cce_ui::scene::paint::PaintCtx::new();
-    cce_ui::scene::painter::append_widget_text(ctx, w, &mut scratch);
-    for item in scratch.finish().items {
-        if let cce_ui::scene::paint::Prim::Text { text, x, y, font_size, color, font, bounds, .. } = item.prim {
-            let cb = match bounds {
-                Some(b) => {
-                    let bx0 = b[0].max(0.0);
-                    let by0 = b[1].max(0.0);
-                    let bx1 = b[2].min(win_w);
-                    let by1 = b[3].min(win_h);
-                    if bx1 <= bx0 || by1 <= by0 {
-                        continue;
-                    }
-                    Some([bx0, by0, bx1, by1])
-                }
-                None => Some([0.0, 0.0, win_w, win_h]),
-            };
-            let colorf = [
-                color[0] as f32 / 255.0,
-                color[1] as f32 / 255.0,
-                color[2] as f32 / 255.0,
-                1.0,
-            ];
-            texts.push((text, font_size, x, y, colorf, font, cb));
-        }
-    }
-}
 
 impl SystemInterface {
 
@@ -171,15 +87,8 @@ impl SystemInterface {
         // root plate container dissolved, hand it the plate frame for its concentric-corner cut.
         self.page_dropdown.set_corner_frame(Some(((0.0, 0.0, logical_sw, logical_sh), 12.0, (true, true, true, true))));
         cce_ui::layout::render_widget(&mut dummy_pc, &mut self.page_dropdown, dropdown_x, dropdown_y, dropdown_w, dropdown_h, &mut self.ui_context);
-        // The chrome is collected below through `all_quads` and the text walk,
-        // which carry no images — so the closed dropdown's `chevron-down`
-        // comes from this pass, which `render_widget` hands to
-        // `RenderTarget::icon`. Window coordinates: no scroll applies.
-        let window_icon_images: Vec<crate::PlacedIcon> = dummy_pc
-            .icons
-            .iter()
-            .map(|ic| crate::PlacedIcon { image: ic.image, x: ic.x, y: ic.y, w: ic.w, h: ic.h, alpha: ic.alpha, clip: ic.clip })
-            .collect();
+        // The dropdown is laid out by this pass and painted live in display_list
+        // (`paint_root_into`), chevron and all.
         let switcher_h = if self.search_open {
             logical_sh - self.header_height - 42.0 - self.status_height
         } else {
@@ -204,43 +113,11 @@ impl SystemInterface {
         }
         self.page_scroll_bar.update(self.scroll_y, self.content_h, switcher_h);
 
-        // Assemble the window exactly as the legacy `render_widget(root plate container)`
-        // aggregate did: every child plain quad (clipped to the window, with the root's
-        // corner resolution against its rounded rect), then the translucent window
-        // plate, then every child rounded quad, then the root-clamped text — in the old
-        // child order [switcher, statusbar, dropdown, search box]. The StatusBar widget
-        // is dissolved outright: its theming was root plate container-parent-coupled (statusbar
-        // theme color falling back to STATUS_BG, bottom corners rounded at the root's
-        // radius, statusbar text color/font), replicated here as tuples.
-        let plate_radius = 12.0f32;
-        let mut window_pc = PageContent::new();
-        {
-            let mut plain: Vec<RectTuple> = Vec::new();
-            let mut rounded: Vec<RectTuple> = Vec::new();
-            let mut wtexts: Vec<TextTuple> = Vec::new();
-
-            // The page scrollbar is NOT collected here: baked into the rebuilt
-            // layout, its thumb froze for every wheel tick the scroll fast path
-            // absorbed (the fast path shifts cached geometry without a rebuild,
-            // so the bar only moved once scrolling stopped and something else
-            // rebuilt). display_list emits it fresh each frame instead — under
-            // the window plate while sunk, over the page content while raised.
-
-            // The status bar has no background of its own anymore: the beveled window
-            // plate shows through and display_list carves its recess (data-editor's
-            // with_recess idiom).
-
-            collect_window_child(&self.page_dropdown, &self.ui_context, logical_sw, logical_sh, plate_radius, &mut plain, &mut rounded, &mut wtexts);
-            if self.search_open {
-                collect_window_child(&self.search_box, &self.ui_context, logical_sw, logical_sh, plate_radius, &mut plain, &mut rounded, &mut wtexts);
-            }
-
-            // The window plate itself is emitted by display_list as a beveled
-            // pc.plate() prim, under everything collected here.
-            window_pc.rects.extend(plain);
-            window_pc.rects.extend(rounded);
-            window_pc.texts.extend(wtexts);
-        }
+        // The window chrome — the page dropdown and, while open, the search box — is
+        // painted live in display_list, each as it paints itself (`paint_root_into`).
+        // Until 2026-10-08 it was flattened here through the legacy tuple views (plain
+        // quads, rounded quads, then the text walk) into the cached frame, which drew
+        // the dropdown without its relief and the search box twice.
 
         let mut search_pc = PageContent::new();
         if self.search_open {
@@ -266,8 +143,10 @@ impl SystemInterface {
             // The toolkit's textbox height, centred in the 42px band.
             let inset = cce_ui::layout::root_plate_inset();
             let box_h = cce_ui::layout::textbox_height();
+            // Laid out here; painted live in display_list.
+            let mut layout_only = PageContent::new();
             cce_ui::layout::render_widget(
-                &mut search_pc,
+                &mut layout_only,
                 &mut self.search_box,
                 self.sidebar_width + inset,
                 sh - 42.0 + (42.0 - box_h) / 2.0,
@@ -279,25 +158,6 @@ impl SystemInterface {
 
         // CSD Titlebar removed
 
-        for pc_part in &[window_pc] {
-            for (c, x, y, w, h, r, corners) in pc_part.rects.iter() {
-                let wx = *x * s;
-                let wy = *y * s;
-                let ww = *w * s;
-                let wh = *h * s;
-                widgets.push(AppWidget {
-                    x: wx, y: wy, w: ww, h: wh,
-                    color: *c, hover_color: *c,
-                    hovering: check_hover(wx, wy, ww, wh),
-                    radius: *r * s,
-                    corners: *corners,
-                });
-            }
-            for (t, size, x, y, tc, font_opt, bounds) in pc_part.texts.iter() {
-                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 = texts.len();
         self.scrollable_buttons_start_idx = page_buttons.len();
@@ -788,7 +648,6 @@ impl SystemInterface {
         self.texts = texts;
         self.page_buttons = page_buttons;
         self.page_icon_images = page_icon_images;
-        self.window_icon_images = window_icon_images;
 
         // The id-rooted router (`propagate_event(event, WidgetId)`) resolves roots
         // through the registry, and `clear_hierarchy` above wiped it. The view pass