system settings
git clone https://git.lucas.co/cce-system-interface.git
page layer: replay control carves in the widgets' own order
The page layer had the popover layer's inversion the other way round:
every page rect, then every carve, so each control's carve — a TextBox's
well walls, a Button's face plate — landed over everything its widget drew
after it. The button icon pass was a hand fix for one symptom of that.
The page collector's per-carve marks now travel through the rect cull
(culled rects collapse onto the next survivor), a button's face plate
takes its mark right after the button's own quad, and display_list emits
rects and carves interleaved on the marks, under the page clip, before the
section wells. The popover and page layers share one carve emitter.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AxEbg3N3GndoetpY7LThEA
src/main.rs | 126 ++++++++++++++++++++++++++++++--------------------------
src/renderer.rs | 62 +++++++++++++++-------------
2 files changed, 100 insertions(+), 88 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 3f57922..2e293d0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -110,6 +110,9 @@ struct SystemInterface {
// Section wells: body box + title tab (page coords, pre-scroll) — carved by
// display_list.
page_reliefs: Vec<((f32, f32, f32, f32), Option<(f32, f32, f32, f32)>)>,
+ /// Per page carve, the index into `widgets` it precedes — see
+ /// `popover_control_relief_marks`; the page layer interleaves the same way.
+ page_control_relief_marks: Vec<usize>,
/// Control troughs from `PageContent::control_reliefs` (page coords,
/// pre-scroll) — each carved as a flush inset plate after the section wells.
page_control_reliefs: Vec<ControlCarve>,
@@ -133,6 +136,20 @@ struct SystemInterface {
}
+/// One collected control carve as the real relief prim it stands for.
+fn emit_control_carve(pc: &mut cce_ui::scene::paint::PaintCtx, carve: ControlCarve) {
+ use cce_ui::scene::layout::Rect;
+ match carve {
+ ControlCarve::Plate { x, y, w, h, radius, depth, color } => pc.inset_plate(
+ Rect { x, y, width: w, height: h },
+ (radius, radius, radius, radius),
+ color,
+ depth,
+ ),
+ ControlCarve::Step(c) => pc.carve(&c),
+ }
+}
+
impl cce_ui::engine::Application for SystemInterface {
type Message = AppAction;
@@ -216,6 +233,7 @@ impl cce_ui::engine::Application for SystemInterface {
content_h: 0.0,
page_reliefs: Vec::new(),
page_control_reliefs: Vec::new(),
+ page_control_relief_marks: Vec::new(),
page_button_images: Vec::new(),
sans_serif_family: sans_family,
serif_family,
@@ -344,26 +362,45 @@ impl cce_ui::engine::Application for SystemInterface {
);
}
- for w in &self.widgets {
- let color = if w.hovering { w.hover_color } else { w.color };
- let rect = Rect { x: w.x, y: w.y, width: w.w, height: w.h };
- if w.radius > 0.1 {
- pc.rounded_rect(rect, w.radius, w.corners, color);
- } else {
- pc.quad(rect, color);
+ // The page's rects and the control carves the flat bridge offered
+ // (a Dropdown's inset plate, a TextBox's well, a Toggle's steps, the
+ // buttons' faces), replayed in the widgets' OWN order — each carve
+ // just before the rect it was claimed ahead of, so a TextBox's
+ // selection highlight and caret land on its well instead of under
+ // its walls. Carves clip to the page viewport like the wells; the
+ // rects were already clamped to it when collected.
+ {
+ let view = self.page_view(width, height);
+ let scroll_y = self.scroll_y;
+ let mut carves = self
+ .page_control_reliefs
+ .iter()
+ .copied()
+ .zip(self.page_control_relief_marks.iter().copied())
+ .peekable();
+ let mut emit_pending = |pc: &mut cce_ui::scene::paint::PaintCtx, upto: usize| {
+ while carves.peek().map_or(false, |&(_, mark)| mark <= upto) {
+ let (carve, _) = carves.next().unwrap();
+ pc.clip(view, |pc| emit_control_carve(pc, carve.shifted_y(-scroll_y)));
+ }
+ };
+ for (i, w) in self.widgets.iter().enumerate() {
+ emit_pending(&mut pc, i);
+ let color = if w.hovering { w.hover_color } else { w.color };
+ let rect = Rect { x: w.x, y: w.y, width: w.w, height: w.h };
+ if w.radius > 0.1 {
+ pc.rounded_rect(rect, w.radius, w.corners, color);
+ } else {
+ pc.quad(rect, color);
+ }
}
+ emit_pending(&mut pc, usize::MAX);
}
// The section wells, carved after the page's flat quads so the walls shade
// the fills they cross (the designer relief order), clipped to the page
// viewport so a scrolled-off well can't shade the status bar or search row.
if !self.page_reliefs.is_empty() {
- let view = Rect {
- x: self.sidebar_width,
- y: self.header_height,
- width: width - self.sidebar_width,
- height: (height - self.header_height - self.status_height
- - if self.search_open { 42.0 } else { 0.0 }).max(0.0),
- };
+ let view = self.page_view(width, height);
let r = 20.0f32;
let scroll_y = self.scroll_y;
pc.clip(view, |pc| {
@@ -452,44 +489,11 @@ impl cce_ui::engine::Application for SystemInterface {
});
}
- // Control troughs (Dropdown flush inset chrome, offered by the flat
- // bridge): carved after the section wells so they shade the fills
- // beneath, clipped to the page viewport like the wells.
- if !self.page_control_reliefs.is_empty() {
- let view = Rect {
- x: self.sidebar_width,
- y: self.header_height,
- width: width - self.sidebar_width,
- height: (height - self.header_height - self.status_height
- - if self.search_open { 42.0 } else { 0.0 }).max(0.0),
- };
- let scroll_y = self.scroll_y;
- pc.clip(view, |pc| {
- for &carve in &self.page_control_reliefs {
- match carve.shifted_y(-scroll_y) {
- ControlCarve::Plate { x, y, w, h, radius, depth, color } => pc.inset_plate(
- Rect { x, y, width: w, height: h },
- (radius, radius, radius, radius),
- color,
- depth,
- ),
- ControlCarve::Step(c) => pc.carve(&c),
- }
- }
- });
- }
-
// Button icon faces, over the page's quads and its carves — clipped to
// the page viewport, which is what cuts a half-scrolled list row's icon
// at the list edge (an image has no geometry to trim, only a clip).
if !self.page_button_images.is_empty() {
- let view = Rect {
- x: self.sidebar_width,
- y: self.header_height,
- width: width - self.sidebar_width,
- height: (height - self.header_height - self.status_height
- - if self.search_open { 42.0 } else { 0.0 }).max(0.0),
- };
+ let view = self.page_view(width, height);
pc.clip(view, |pc| {
for &(image, x, y, w, h, alpha) in &self.page_button_images {
pc.image(image, Rect { x, y, width: w, height: h }, alpha);
@@ -515,15 +519,6 @@ impl cce_ui::engine::Application for SystemInterface {
// so the hovered-row highlight a Dropdown draws after its plate lands
// on top of the frosted face instead of underneath it.
{
- let emit_carve = |pc: &mut cce_ui::scene::paint::PaintCtx, carve: ControlCarve| match carve {
- ControlCarve::Plate { x, y, w, h, radius, depth, color } => pc.inset_plate(
- Rect { x, y, width: w, height: h },
- (radius, radius, radius, radius),
- color,
- depth,
- ),
- ControlCarve::Step(c) => pc.carve(&c),
- };
let mut carves = self
.popover_control_reliefs
.iter()
@@ -533,7 +528,7 @@ impl cce_ui::engine::Application for SystemInterface {
for (i, w) in self.popover_widgets.iter().enumerate() {
while carves.peek().map_or(false, |&(_, mark)| mark <= i) {
let (carve, _) = carves.next().unwrap();
- emit_carve(&mut pc, carve);
+ emit_control_carve(&mut pc, carve);
}
let rect = Rect { x: w.x, y: w.y, width: w.w, height: w.h };
if w.radius > 0.1 {
@@ -543,7 +538,7 @@ impl cce_ui::engine::Application for SystemInterface {
}
}
for (carve, _) in carves {
- emit_carve(&mut pc, carve);
+ emit_control_carve(&mut pc, carve);
}
}
for (text, font_size, x, y, col, font, bounds) in self.texts.iter().chain(self.popover_texts.iter()) {
@@ -612,6 +607,19 @@ impl cce_ui::engine::Application for SystemInterface {
}
impl SystemInterface {
+ /// The page viewport in window coords: everything under the header and
+ /// above the status bar (and the search row while it is open).
+ fn page_view(&self, width: f32, height: f32) -> cce_ui::scene::layout::Rect {
+ use cce_ui::scene::layout::Rect;
+ Rect {
+ x: self.sidebar_width,
+ y: self.header_height,
+ width: width - self.sidebar_width,
+ height: (height - self.header_height - self.status_height
+ - if self.search_open { 42.0 } else { 0.0 }).max(0.0),
+ }
+ }
+
fn tick_internal(&mut self, dt: f32) -> bool {
let mut needs_redraw = false;
diff --git a/src/renderer.rs b/src/renderer.rs
index 45d8064..4d6c9eb 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -273,35 +273,7 @@ impl SystemInterface {
let pc = self.render_page_content(lcx, lcy, lcw, lch);
self.page_reliefs = pc.reliefs.clone();
self.page_control_reliefs = pc.control_reliefs.clone();
- // Buttons never reach the toolkit's flat-path bridge here: this app
- // collects them into `pc.buttons` and draws them itself, so
- // `render_widget` — where a Dropdown's and a TextBox's carves are
- // offered — never sees one. Ask each button for the inset face its own
- // `paint` draws (`Button::inset_face`, the same source the drawn one
- // reads) and carve it with the rest. Page coords, pre-scroll, like
- // everything else in this list.
- if cce_ui::layout::control_relief() {
- for (btn, _, _) in &pc.buttons {
- let base = btn.base();
- let rect = cce_ui::scene::layout::Rect {
- x: base.x,
- y: base.y,
- width: base.w,
- height: base.h,
- };
- if let Some((face, radius, depth, color)) = btn.inset_face(rect) {
- self.page_control_reliefs.push(ControlCarve::Plate {
- x: face.x,
- y: face.y,
- w: face.width,
- h: face.height,
- radius,
- depth,
- color,
- });
- }
- }
- }
+ self.page_control_relief_marks.clear();
@@ -361,7 +333,12 @@ impl SystemInterface {
let scroll_offset_y = self.scroll_y;
+ // Where each page rect landed in `widgets` (culled rects collapse onto
+ // the next survivor), so a carve's mark — "the rect I was claimed
+ // before" — survives the cull. One extra entry for "after the last".
+ let mut rect_widget_index: Vec<usize> = Vec::with_capacity(pc.rects.len() + 1);
for (c, x, y, w, h, r, corners) in pc.rects.iter() {
+ rect_widget_index.push(widgets.len());
let wx = *x * s;
let mut wy = (*y - scroll_offset_y) * s;
let ww = *w * s;
@@ -388,6 +365,10 @@ impl SystemInterface {
corners: *corners,
});
}
+ rect_widget_index.push(widgets.len());
+ for &mark in &pc.control_relief_marks {
+ self.page_control_relief_marks.push(rect_widget_index[mark.min(rect_widget_index.len() - 1)]);
+ }
for (t, size, x, y, tc, font_opt, bounds) in pc.texts.iter() {
let shifted_bounds = bounds.map(|[bl, bt, br, bb]| {
[bl, bt - scroll_offset_y, br, bb - scroll_offset_y]
@@ -513,6 +494,29 @@ impl SystemInterface {
radius: cce_ui::layout::button_corner_radius() * s,
corners: (true, true, true, true),
});
+ // Buttons never reach the toolkit's flat-path bridge here: this
+ // app collects them into `pc.buttons` and draws them itself, so
+ // `render_widget` — where a widget's carves are offered — never
+ // sees one. Ask each button for the inset face its own `paint`
+ // draws (`Button::inset_face`, the same source the drawn one
+ // reads) and carve it right after the button's quad, where its
+ // paint puts it — the mark is "before the next widget". Page
+ // coords, pre-scroll, like everything else in this list.
+ if cce_ui::layout::control_relief() {
+ let rect = cce_ui::scene::layout::Rect { x: base.x, y: base.y, width: base.w, height: base.h };
+ if let Some((face, radius, depth, color)) = btn.inset_face(rect) {
+ self.page_control_reliefs.push(ControlCarve::Plate {
+ x: face.x,
+ y: face.y,
+ w: face.width,
+ h: face.height,
+ radius,
+ depth,
+ color,
+ });
+ self.page_control_relief_marks.push(widgets.len());
+ }
+ }
// An icon face replaces the label entirely (as it does in
// `Button::paint`). The rect comes from the button's own
// `icon_rect` so the glyph lands where the paint path would put