GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: TreeList scrollbar in the relief bevel style
ScrollBox::paint_scrollbar_relief draws the track as a carved groove
(recess) and the thumb as a raised rounded plate (bevel, configured
thumb color) — the DE highlight/shadow treatment, roll width scaled to
the bar so the default bevel width cannot swallow a 14px thumb. The
TreeList routes its scrollbar through it under control_relief, keeping
the behind-items / on-top-during-activity ordering; the flat-quad view
remains for the legacy path.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/widget/container/scroll_box.rs | 43 ++++++++++++++++++++++++++++++++++++++
src/widget/container/treelist.rs | 19 +++++++++++++----
2 files changed, 58 insertions(+), 4 deletions(-)
diff --git a/src/widget/container/scroll_box.rs b/src/widget/container/scroll_box.rs
index 410b0f9..91ea477 100644
--- a/src/widget/container/scroll_box.rs
+++ b/src/widget/container/scroll_box.rs
@@ -300,6 +300,49 @@ impl ScrollBox {
quads
}
+ /// The scrollbar in the DE's relief language: the track a carved groove
+ /// ([`PaintCtx::recess`]), the thumb a raised rounded plate riding in it
+ /// ([`PaintCtx::bevel`] with the configured thumb color) — the
+ /// highlight/shadow bevel treatment. The flat-quad view stays available
+ /// through [`extra_quads`](Self::extra_quads) for legacy paths.
+ pub fn paint_scrollbar_relief(&self, pc: &mut crate::scene::paint::PaintCtx) {
+ if self.content_h <= self.viewport_h {
+ return;
+ }
+ let sb_w = crate::layout::scrollbar_width();
+ let sb_x = self.base.x + self.base.w - sb_w - 4.0;
+ let sb_track_h = self.viewport_h - 8.0;
+ let sb_track_y = self.viewport_y + 4.0;
+
+ let visible_ratio = self.viewport_h / self.content_h;
+ let thumb_h = if sb_track_h <= 20.0 {
+ sb_track_h
+ } else {
+ (sb_track_h * visible_ratio).clamp(20.0, sb_track_h)
+ };
+ let max_scroll = (self.content_h - self.viewport_h).max(0.0);
+ let scroll_ratio = if max_scroll > 0.0 { self.scroll_y / max_scroll } else { 0.0 };
+ let thumb_y = sb_track_y + scroll_ratio * (sb_track_h - thumb_h);
+
+ // Pill radii; the roll width scales to the bar (the DE bevel width
+ // would swallow a 14px-wide thumb whole).
+ let r = sb_w * 0.5;
+ let depth = crate::layout::bevel_width().min(sb_w * 0.35);
+ let radii = (r, r, r, r);
+ use crate::scene::layout::Rect;
+ pc.recess(
+ Rect { x: sb_x, y: sb_track_y, width: sb_w, height: sb_track_h },
+ radii,
+ depth,
+ );
+ pc.bevel(
+ Rect { x: sb_x, y: thumb_y, width: sb_w, height: thumb_h },
+ radii,
+ crate::color::scrollbar_thumb_color(),
+ depth,
+ );
+ }
+
pub fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
// Focus never lands on the box itself (post-6av it is not an `WidgetHost`), and its id is
// never a tree ancestor of the focused widget — like the legacy address walk, this
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index 345c229..8603e90 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -789,10 +789,17 @@ impl Paint for TreeList {
};
let show_on_top = self.scrollbar_activity_timer > 0.0;
+ let relief_scrollbar = crate::layout::control_relief();
- // If NOT on top, draw scrollbar first (behind items)
+ // If NOT on top, draw scrollbar first (behind items). Relief style
+ // emits prims directly — they land before the quads flush below, so
+ // the ordering matches the flat path.
if !show_on_top {
- quads.extend(get_scrollbar_quads());
+ if relief_scrollbar {
+ self.scroll_box.paint_scrollbar_relief(pc);
+ } else {
+ quads.extend(get_scrollbar_quads());
+ }
}
// 2. Draw items (row backgrounds, separator lines, color previews)
@@ -915,8 +922,9 @@ impl Paint for TreeList {
}
}
- // If on top, draw scrollbar last
- if show_on_top {
+ // If on top (scroll activity), the scrollbar draws after the rows —
+ // the relief prims are emitted after the quads flush below.
+ if show_on_top && !relief_scrollbar {
quads.extend(get_scrollbar_quads());
}
for (qx, qy, qw, qh, qr, qc, qcorners) in quads {
@@ -926,6 +934,9 @@ impl Paint for TreeList {
pc.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
}
}
+ if show_on_top && relief_scrollbar {
+ self.scroll_box.paint_scrollbar_relief(pc);
+ }
// Recessed well like a text box or list: the tree floor sits below the
// pane surface, its wall carved over the bg and row quads above. Focus