file manager
git clone https://git.lucas.co/cce-files.git
feat: the split divider is a raised bead, not a painted hairline
Gives Prim::Ridge its first production caller in the workspace — the raised
half of the Ridge/Trough pair was previously only ever exercised by test
patches — and the shape is what a ridge is actually for. The two panes are
separate plates at the SAME level with a strip of window plate between
them, so the boundary wants a crest rising out of that strip and falling
back to it: not a step (nothing here is higher or lower) and not a painted
line (everything else in the DE is lit geometry). It is also the handle you
grab to resize, so it earns relief rather than decoration.
ONE wall enabled, not a full ring. A ridge's crest rides the whole outline,
so a closed ring on a thin tall rect gives two parallel rails and a pair of
caps instead of a bead; the rect stops at the gap's centreline with only its
right wall live, which puts a single crest mid-gap.
Depth is a QUARTER of the gap, not half. Half looks like the obvious fit
(the crest straddles its wall by ±depth/2) but the two panes' own lit rims
already spend most of that gap: at gap 12 the genuinely flat strip measures
~4 logical px. Measured against a no-bead frame, a half-gap bead spanned 9
device px and ran its bright lobe (+74) straight into the left pane's rim,
reading as one thick band; the quarter-gap bead spans 6 and sits inside the
flat strip with air either side. Size it to the CLEARANCE, not the nominal
gap.
Profile checks out as a single evaluation: +85/-17 across 6px with ONE sign
change, against the ridge control patch's +81/-19 across 8px.
The resting hairline is gone — divider_quad now returns None at rest and the
bead IS the divider. The hover/drag accent stays a quad, and since a
PageContent emits all its rects before all its reliefs, the accent tints the
strip and the bead's shading then lights it: an active divider reads as a
coloured bead rather than a line laid over one. Verified live by injecting
the pointer: rest neutral, hover bluish across all 960 rows, drag green
(rgb 201,215,204) with the divider tracking the cursor 43px.
PageContent::reliefs grows an `edges` member for this; the three box reliefs
pass all-true.
src/main.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++---------
src/pages/mod.rs | 38 ++++++++++++++++++++++++++-----
2 files changed, 89 insertions(+), 17 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index f58ff5e..548ff2d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -133,6 +133,9 @@ enum WidgetFx {
/// A flush inset control (buttons): groove ring carved down around the
/// rect, beveled lip back up inside, face level with the surface.
Inset(f32),
+ /// A raised crest riding the rect's boundary, with `edges` selecting which
+ /// walls exist — one wall, so one bead. The split divider.
+ Ridge { depth: f32, edges: (bool, bool, bool, bool) },
/// A GPU-textured quad; the id comes from `cce_ui::vk::upload_rgba`
/// (the preview pane's image). `color` is unused.
Image { id: u32, alpha: f32 },
@@ -256,17 +259,48 @@ impl SplitPane {
std::mem::take(&mut self.dragging)
}
- /// The divider quad (`SplitBox::extra_quads`): accent while dragging, tint on hover,
- /// hairline otherwise.
- fn divider_quad(&self) -> (f32, f32, f32, f32, [f32; 4]) {
+ /// The divider's active accent (`SplitBox::extra_quads`): accent while
+ /// dragging, tint on hover, and NOTHING at rest — the resting divider is
+ /// [`Self::divider_bead`], a lit crest rather than a painted hairline.
+ fn divider_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> {
let (sx, sy, sw, sh) = self.divider_rect();
- if self.dragging {
- (sx + sw / 2.0 - 1.0, sy, 2.0, sh, [0.36, 0.56, 0.38, 0.8])
+ let color = if self.dragging {
+ [0.36, 0.56, 0.38, 0.8]
} else if self.hovered {
- (sx + sw / 2.0 - 1.0, sy, 2.0, sh, [0.25, 0.25, 0.32, 0.6])
+ [0.25, 0.25, 0.32, 0.6]
} else {
- (sx + sw / 2.0 - 0.5, sy, 1.0, sh, [0.15, 0.15, 0.18, 0.4])
- }
+ return None;
+ };
+ Some((sx + sw / 2.0 - 1.0, sy, 2.0, sh, color))
+ }
+
+ /// The divider as relief: a raised bead running the gap's centreline.
+ ///
+ /// This is the DE's one production `Prim::Ridge` — and the shape is exactly
+ /// what a ridge is for. The two panes are separate plates at the SAME level
+ /// with a strip of window plate between them, so the boundary wants a crest
+ /// that rises out of that strip and falls back to it, not a step (nothing is
+ /// higher or lower here) and not a painted line (everything else in the DE
+ /// is lit geometry). It is also the handle you grab to resize, so it earns
+ /// relief rather than decoration.
+ ///
+ /// Returned as (rect, depth, edges) with ONE wall enabled: a ridge's crest
+ /// rides the whole outline, so a full ring on a thin tall rect would give
+ /// two parallel rails and a pair of caps, not a bead. The enabled wall is
+ /// the right one, and the rect stops at the centreline, so the crest lands
+ /// mid-gap.
+ ///
+ /// Depth is a QUARTER of the gap, not half of it. The crest straddles its
+ /// wall by ±depth/2, so half a gap looks like the obvious fit — but the two
+ /// panes are plates whose own lit rims already spend most of that gap: at
+ /// gap 12 the genuinely flat strip between them measures ~4 logical px, not
+ /// 12. A half-gap bead runs its bright lobe straight into the left pane's
+ /// rim and the two read as one thick band instead of a bead with air around
+ /// it. Size it to the CLEARANCE, not the nominal gap.
+ fn divider_bead(&self) -> ((f32, f32, f32, f32), f32, (bool, bool, bool, bool)) {
+ let (sx, sy, sw, sh) = self.divider_rect();
+ let depth = cce_ui::layout::bevel_width().min(sw * 0.25);
+ ((sx, sy, sw * 0.5, sh), depth, (false, true, false, false))
}
}
@@ -513,8 +547,18 @@ impl FilesystemApp {
Page::Network => &self.network_split,
Page::Space => &self.space_split,
};
- let (dx, dy, dw, dh, dc) = split.divider_quad();
- plain_pc.rects.push((dc, dx, dy, dw, dh, 0.0, (true, true, true, true)));
+ // The bead is the divider's physical form and is always
+ // there; the quad only marks hover/drag. Call order here is
+ // irrelevant — display_list emits ALL of a PageContent's
+ // rects before ALL of its reliefs — and that ordering is the
+ // one we want: the accent tints the strip, then the bead's
+ // shading lights it, so an active divider reads as a
+ // coloured bead rather than a line laid over one.
+ let ((bx, by, bw, bh), bd, bedges) = split.divider_bead();
+ plain_pc.relief_ridge(bx, by, bw, bh, bd, bedges);
+ if let Some((dx, dy, dw, dh, dc)) = split.divider_quad() {
+ plain_pc.rects.push((dc, dx, dy, dw, dh, 0.0, (true, true, true, true)));
+ }
let (px_r, py_r, pw_r, ph_r) = split.right_rect();
// The pane clamps its own text bounds to its rect inside
// push_prims (the old SplitBox clamp, absorbed).
@@ -750,7 +794,7 @@ impl FilesystemApp {
fx: WidgetFx::Image { id: *id, alpha: *alpha },
});
}
- for (rx, ry, rw, rh, rr, rd, kind) in &pc_part.reliefs {
+ for (rx, ry, rw, rh, rr, rd, kind, redges) in &pc_part.reliefs {
let (mut wy, mut wh) = (*ry, *rh);
if is_page_content {
match clip_to_viewport(wy, wh, content_y, content_y + content_h) {
@@ -769,6 +813,7 @@ impl FilesystemApp {
fx: match *kind {
pages::RELIEF_RAISED => WidgetFx::Boss(*rd),
pages::RELIEF_INSET => WidgetFx::Inset(*rd),
+ pages::RELIEF_RIDGE => WidgetFx::Ridge { depth: *rd, edges: *redges },
_ => WidgetFx::Recess(*rd),
},
});
@@ -1375,6 +1420,7 @@ impl Application for FilesystemApp {
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),
+ WidgetFx::Ridge { depth, edges } => pc.ridge_edges(rect, radii, depth, edges),
WidgetFx::Image { id, alpha } => pc.image(id, rect, alpha),
WidgetFx::Groove { ax, ay, bx, by, width, depth } => {
pc.groove((ax, ay), (bx, by), width, depth, rect)
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index a1ff07a..2f6a27a 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -125,16 +125,22 @@ pub fn dropdown_relief(pc: &mut PageContent, rect: cce_ui::scene::layout::Rect)
pub const RELIEF_RECESSED: u8 = 0;
pub const RELIEF_RAISED: u8 = 1;
pub const RELIEF_INSET: u8 = 2;
+/// A raised crest riding the rect's boundary (`Prim::Ridge`) — used with a
+/// single enabled wall, which is what makes it ONE bead rather than a closed
+/// loop of them. See [`PageContent::relief_ridge`].
+pub const RELIEF_RIDGE: u8 = 3;
pub struct PageContent {
pub rects: Vec<([f32; 4], f32, f32, f32, f32, f32, (bool, bool, bool, bool))>,
pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>)>,
pub buttons: Vec<(cce_ui::widget::Adapted<cce_ui::widget::Button>, crate::Message)>,
/// Relief steps for the control_relief styling — (x, y, w, h, radius, depth,
- /// kind: [`RELIEF_RECESSED`]/[`RELIEF_RAISED`]/[`RELIEF_INSET`]). The flat rects
- /// own the faces; these are the edges-only walls emitted over them (the
+ /// kind, edges). `kind` is [`RELIEF_RECESSED`]/[`RELIEF_RAISED`]/[`RELIEF_INSET`]/
+ /// [`RELIEF_RIDGE`]; `edges` is (top, right, bottom, left), which walls of the
+ /// shape actually exist — all-true for everything but a ridge bead. The flat
+ /// rects own the faces; these are the edges-only walls emitted over them (the
/// ParametersBg::reliefs idiom for flat-view hosts).
- pub reliefs: Vec<(f32, f32, f32, f32, f32, f32, u8)>,
+ pub reliefs: Vec<(f32, f32, f32, f32, f32, f32, u8, (bool, bool, bool, bool))>,
/// Engraved lines over the flat rects — (ax, ay, bx, by, width, depth) plus
/// the (x, y, w, h) of the surface being engraved, which the shading fades
/// out against. Unlike [`reliefs`] these are not axis-aligned: this is the
@@ -191,7 +197,7 @@ impl PageContent {
pub fn relief_recessed(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
if cce_ui::layout::control_relief() {
let depth = cce_ui::layout::bevel_width().min(h * 0.2);
- self.reliefs.push((x, y, w, h, radius, depth, RELIEF_RECESSED));
+ self.reliefs.push((x, y, w, h, radius, depth, RELIEF_RECESSED, (true, true, true, true)));
}
}
@@ -200,7 +206,27 @@ impl PageContent {
pub fn relief_raised(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
if cce_ui::layout::control_relief() {
let depth = cce_ui::layout::bevel_width().min(h * 0.2);
- self.reliefs.push((x, y, w, h, radius, depth, RELIEF_RAISED));
+ self.reliefs.push((x, y, w, h, radius, depth, RELIEF_RAISED, (true, true, true, true)));
+ }
+ }
+
+ /// A raised bead riding ONE wall of (x, y, w, h) — `Prim::Ridge` with the
+ /// other three walls suppressed, so the crest is a single line rather than a
+ /// closed loop. `edges` is (top, right, bottom, left); enable exactly one.
+ ///
+ /// Depth is the caller's, not `h * 0.2` like the box reliefs: those derive it
+ /// from the control's height because they wrap a control, and a bead running
+ /// the height of a pane would take a 9.3px wall from that rule regardless of
+ /// how much room it actually has beside it. A bead is sized by its
+ /// clearance — no-op when control_relief is off.
+ pub fn relief_ridge(
+ &mut self,
+ x: f32, y: f32, w: f32, h: f32,
+ depth: f32,
+ edges: (bool, bool, bool, bool),
+ ) {
+ if cce_ui::layout::control_relief() {
+ self.reliefs.push((x, y, w, h, 0.0, depth, RELIEF_RIDGE, edges));
}
}
@@ -219,7 +245,7 @@ impl PageContent {
pub fn relief_inset(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
if cce_ui::layout::control_relief() {
let depth = cce_ui::layout::bevel_width().min(h * 0.2);
- self.reliefs.push((x, y, w, h, radius, depth, RELIEF_INSET));
+ self.reliefs.push((x, y, w, h, radius, depth, RELIEF_INSET, (true, true, true, true)));
}
}