file manager
git clone https://git.lucas.co/cce-files.git
style: the context menu is a lit plate, not a drawn frame
The menu painted three marks for one edge: a 1px border rect, a flat
fill inset inside it, and a Boss laid over the top. Boss is edges-only —
a rim with no face of its own — so at radius 0 the result read as a hard
light-on-two-sides, dark-on-two-sides frame drawn AROUND a flat chip,
next to panes whose edges are rolled and lit. It is one Prim::Plate now,
at plate_corner_radius over bevel_width: rounded face and rolled
perimeter in a single primitive and a single lighting evaluation, the
same material pc.plate already gives the preview stub.
PageContent could only emit faces (rects) and rims (reliefs) separately,
never a plate owning both, so it gains a `plates` list carrying its own
color plus a WidgetFx::Plate to paint it. Two consequences that are not
optional:
- Plates are emitted BEFORE the part's rects. A plate is the floor of
its part; behind them it painted over the hover row, which vanished.
- occlude_against sweeps plates as well as rects. The menu's opaque
face IS a plate now, and a face missing from that sweep lets the file
rows' text draw straight through the menu covering them.
The hover row gains rect_rounded so it can follow the plate's corners:
the last row is the only one meeting a rounded corner (row 0 is the
header and never highlights), so it carries the plate's radius on its
bottom two, and the fill insets by half the roll depth to sit on the
face rather than climbing the lit edge.
The breadcrumb's menu is untouched — cce-ui's shared context_menu
renders through a flat extra_quads() list with no notion of a plate, so
matching it there restyles every cce app's menu at once.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/main.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++---------
src/pages/mod.rs | 38 ++++++++++++++++++++++++++-
2 files changed, 104 insertions(+), 14 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 45c35ad..fc85540 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -85,11 +85,19 @@ fn occlude_against(
overlays: &[&pages::PageContent],
) -> Option<[f32; 4]> {
for overlay_pc in overlays {
- for (_, ox, oy, ow, oh, _, _) in &overlay_pc.rects {
- let o_min_x = *ox;
- let o_max_x = *ox + *ow;
- let o_min_y = *oy;
- let o_max_y = *oy + *oh;
+ // Plates occlude exactly as rects do — an overlay's opaque face is its
+ // plate now, not a flat fill, and a face missing from this sweep lets the
+ // page's text bleed through the surface covering it.
+ let faces = overlay_pc
+ .rects
+ .iter()
+ .map(|(_, x, y, w, h, _, _)| (*x, *y, *w, *h))
+ .chain(overlay_pc.plates.iter().map(|(_, x, y, w, h, _, _)| (*x, *y, *w, *h)));
+ for (ox, oy, ow, oh) in faces {
+ let o_min_x = ox;
+ let o_max_x = ox + ow;
+ let o_min_y = oy;
+ let o_max_y = oy + oh;
if t_max_x > o_min_x && t_min_x < o_max_x && t_max_y > o_min_y && t_min_y < o_max_y {
if t_min_x >= o_min_x && t_max_x <= o_max_x && t_min_y >= o_min_y && t_max_y <= o_max_y {
@@ -128,6 +136,11 @@ enum WidgetFx {
Bevel(f32),
/// Edges-only raised plateau over whatever is painted below.
Boss(f32),
+ /// A lit plate: a rounded face in the quad's own color plus the rolled, lit
+ /// perimeter, at full size. What the pane plates and the preview stub wear.
+ /// Distinct from [`WidgetFx::Bevel`], which insets its fill by the depth, and
+ /// from [`WidgetFx::Boss`], which is a rim with no face of its own.
+ Plate(f32),
/// Edges-only carve into whatever is painted below (recessed wells).
Recess(f32),
/// A flush inset control (buttons): groove ring carved down around the
@@ -700,17 +713,32 @@ impl FilesystemApp {
let cw = self.context_menu.w;
let ch = self.context_menu.h;
- // Border
- context_menu_pc.rect([0.22, 0.22, 0.28, 1.0], cx, cy, cw, ch);
- // Background
- context_menu_pc.rect(cce_ui::color::popover_bg_color(), cx + 1.0, cy + 1.0, cw - 2.0, ch - 2.0);
- // Hover highlight
+ // The menu is a lit plate, the same material as the panes it floats
+ // over: a rounded face plus the rolled perimeter, one primitive. It
+ // used to be a flat fill inside a 1px border rect with a Boss rim
+ // laid over the top — three marks for one edge, and the rim's hard
+ // light/dark frame at radius 0 read as a drawn box rather than a
+ // surface with a lit edge.
+ let menu_r = cce_ui::layout::plate_corner_radius();
+ context_menu_pc.plate(cce_ui::color::popover_bg_color(), cx, cy, cw, ch, menu_r);
+ // Hover highlight, inset off the roll so it sits on the face rather
+ // than climbing the lit edge. The last row is the only one that meets
+ // a rounded corner (row 0 is the header and never highlights), so it
+ // carries the plate's radius on the bottom two.
if let Some(h_idx) = self.context_menu.hovered {
let iy = cy + h_idx as f32 * ROW_H;
- context_menu_pc.rect([0.20, 0.40, 0.65, 0.6], cx + 2.0, iy + 2.0, cw - 4.0, 20.0);
+ let inset = (cce_ui::layout::bevel_width().min(ch * 0.2) * 0.5).max(2.0);
+ let last = h_idx + 1 == self.context_menu.options.len();
+ context_menu_pc.rect_rounded(
+ [0.20, 0.40, 0.65, 0.6],
+ cx + inset,
+ iy + 2.0,
+ cw - 2.0 * inset,
+ ROW_H - 4.0,
+ (menu_r - inset).max(0.0),
+ (false, false, last, last),
+ );
}
- // Raised menu plate (control_relief styling).
- context_menu_pc.relief_raised(cx, cy, cw, ch, 0.0);
// Text options
for (idx, (opt, _)) in self.context_menu.options.iter().enumerate() {
let iy = cy + idx as f32 * ROW_H + (ROW_H - 12.0) / 2.0;
@@ -766,6 +794,31 @@ impl FilesystemApp {
for (part_idx, pc_part) in [&window_pc, &pc, &popover_pc, &context_menu_pc, &dialog_pc].into_iter().enumerate() {
let is_page_content = part_idx == 1;
+ // Plates first: a plate owns a FACE, so it is the floor of its part —
+ // everything else the part emits (a hover fill, an engraved seam, the
+ // labels) is meant to land on top of it. Emitted after the rects it
+ // would paint straight over them, which is where the context menu's
+ // hover row went the first time this ran.
+ for (c, px, py, pw, ph, pr, pd) in &pc_part.plates {
+ let (mut wy, mut wh) = (*py, *ph);
+ if is_page_content {
+ match clip_to_viewport(wy, wh, content_y, content_y + content_h) {
+ Some((cy, ch)) => { wy = cy; wh = ch; }
+ None => continue,
+ }
+ }
+ widgets.push(AppWidget {
+ x: *px,
+ y: wy,
+ w: *pw,
+ h: wh,
+ color: *c,
+ radius: *pr,
+ corners: (true, true, true, true),
+ fx: WidgetFx::Plate(*pd),
+ });
+ }
+
for (c, x, y, w, h, r, corners) in &pc_part.rects {
let wx = *x;
let mut wy = *y;
@@ -1457,6 +1510,7 @@ impl Application for FilesystemApp {
let radii = (w.radius, w.radius, w.radius, w.radius);
match w.fx {
WidgetFx::Bevel(depth) => pc.bevel(rect, radii, w.color, depth),
+ WidgetFx::Plate(depth) => pc.plate(rect, radii, w.color, depth),
WidgetFx::Boss(depth) => pc.boss(rect, radii, depth),
WidgetFx::Recess(depth) => pc.recess(rect, radii, depth),
WidgetFx::Inset(depth) => pc.inset_plate(rect, radii, w.color, depth),
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 0e487ea..1cdb318 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -166,6 +166,15 @@ pub struct PageContent {
/// alpha). Drawn after the part's rects, so a fill emitted earlier is the floor
/// beneath the image and overlay parts still cover it.
pub images: Vec<(u32, f32, f32, f32, f32, f32)>,
+ /// Lit plates — (color, x, y, w, h, radius, depth). Unlike [`reliefs`], a plate
+ /// owns its FILL as well as its edge: one primitive carrying a rounded face and
+ /// the rolled, lit perimeter, shaded in a single lighting evaluation. That is
+ /// what the pane plates and the preview stub wear, so a floating surface
+ /// (the context menu) reads as the same material rather than as a flat chip
+ /// inside a drawn frame.
+ ///
+ /// [`reliefs`]: PageContent::reliefs
+ pub plates: Vec<([f32; 4], f32, f32, f32, f32, f32, f32)>,
}
impl PageContent {
@@ -177,6 +186,7 @@ impl PageContent {
reliefs: Vec::new(),
grooves: Vec::new(),
images: Vec::new(),
+ plates: Vec::new(),
}
}
@@ -188,13 +198,14 @@ impl PageContent {
/// exactly that reason. The destructuring below turns a new field into a
/// compile error instead of a missing mark on screen.
pub fn absorb(&mut self, other: PageContent) {
- let PageContent { rects, texts, buttons, reliefs, grooves, images } = other;
+ let PageContent { rects, texts, buttons, reliefs, grooves, images, plates } = other;
self.rects.extend(rects);
self.texts.extend(texts);
self.buttons.extend(buttons);
self.reliefs.extend(reliefs);
self.grooves.extend(grooves);
self.images.extend(images);
+ self.plates.extend(plates);
}
/// A GPU-textured quad (id from `cce_ui::vk::upload_rgba`).
@@ -206,6 +217,31 @@ impl PageContent {
self.rects.push((color, x, y, w, h, 0.0, (true, true, true, true)));
}
+ /// [`PageContent::rect`] with a corner radius, applied only to `corners`
+ /// (top-left, top-right, bottom-right, bottom-left) — for a fill that has to
+ /// follow the rounded corner of the plate it sits inside.
+ pub fn rect_rounded(
+ &mut self,
+ color: [f32; 4],
+ x: f32,
+ y: f32,
+ w: f32,
+ h: f32,
+ radius: f32,
+ corners: (bool, bool, bool, bool),
+ ) {
+ self.rects.push((color, x, y, w, h, radius, corners));
+ }
+
+ /// A lit plate at (x, y, w, h): a rounded face in `color` plus the rolled,
+ /// lit perimeter — the treatment the pane plates wear. Unlike the relief
+ /// helpers this is NOT gated on `control_relief`: a plate owns the fill, so
+ /// skipping it would leave the surface unpainted rather than merely flat.
+ pub fn plate(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32) {
+ let depth = cce_ui::layout::bevel_width().min(h * 0.2);
+ self.plates.push((color, x, y, w, h, radius, depth));
+ }
+
/// A recessed well carved over the control at (x, y, w, h) — no-op when the
/// DE's control_relief styling is off.
pub fn relief_recessed(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {