system settings
git clone https://git.lucas.co/cce-system-interface.git
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;