GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/display/separator.rs (5.2K)
1 //! Narrow-trait separator line (Phase 5c leaf sweep). The legacy struct carried its own public
2 //! x/y/w/h fields instead of a `Widget` base; the rect now lives on the [`Adapted`] base, so
3 //! callers position it via `set_rect`/`rect` (cce-status-interface's rotation loop was updated
4 //! accordingly).
5 //!
6 //! **Relief.** Under `control_relief` the rule is a groove carved into the plate it sits
7 //! on — the seam vocabulary (`CLAUDE.md`): the rect is the groove's floor, the walls
8 //! rise either side of it into the plate, and the cut dies out at the rect's two ends.
9 //! Flat, it is the coloured hairline it always was.
10
11 use crate::scene::layout::Rect;
12 use crate::scene::paint::PaintCtx;
13 use crate::widget::{Adapted, WidgetHost, Input, Layout, Paint};
14
15 #[derive(Debug, Clone)]
16 pub struct Separator {
17 pub color: [f32; 4],
18 }
19
20 impl Separator {
21 pub fn new(x: f32, y: f32, w: f32, h: f32, color: [f32; 4]) -> Adapted<Separator> {
22 let mut sep = Adapted::new(Separator { color });
23 WidgetHost::set_rect(&mut sep, x, y, w, h);
24 sep
25 }
26 }
27
28 impl Separator {
29 /// The groove's wall run: the seam depth a control-height plate gets
30 /// (`bevel_width` capped at a fifth of the height — Breadcrumb's, ColorSelector's).
31 pub fn groove_depth() -> f32 {
32 crate::layout::bevel_width().min(crate::layout::button_height() * 0.2)
33 }
34
35 /// The relief cut for `rect`: the groove's two ends, floor width, depth and host.
36 /// The line runs along the rect's long axis through its centre; the floor is the
37 /// rect's thickness (a hairline at least); the host is the rect stretched across
38 /// the line by three depths, so the walls stand clear of the host's own roll while
39 /// the cut still fades out at the ends.
40 pub fn groove(&self, rect: Rect) -> ((f32, f32), (f32, f32), f32, f32, Rect) {
41 let depth = Self::groove_depth();
42 let horizontal = rect.width >= rect.height;
43 let floor = rect.width.min(rect.height).max(crate::widget::Breadcrumb::SEAM_WIDTH);
44 let reach = 3.0 * depth;
45 if horizontal {
46 let cy = rect.y + rect.height * 0.5;
47 let host = Rect { x: rect.x, y: cy - reach, width: rect.width, height: 2.0 * reach };
48 ((rect.x, cy), (rect.x + rect.width, cy), floor, depth, host)
49 } else {
50 let cx = rect.x + rect.width * 0.5;
51 let host = Rect { x: cx - reach, y: rect.y, width: 2.0 * reach, height: rect.height };
52 ((cx, rect.y), (cx, rect.y + rect.height), floor, depth, host)
53 }
54 }
55 }
56
57 impl Layout for Separator {}
58
59 impl Paint for Separator {
60 /// Flat: the rule's colour. Relief: none — the groove is shading on the plate.
61 fn color(&self) -> [f32; 4] {
62 if crate::layout::control_relief() { [0.0, 0.0, 0.0, 0.0] } else { self.color }
63 }
64
65 fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
66 if crate::layout::control_relief() {
67 let (a, b, width, depth, host) = self.groove(rect);
68 ctx.groove(a, b, width, depth, host);
69 } else if self.color[3].abs() > 0.001 {
70 ctx.quad(rect, self.color);
71 }
72 }
73 }
74
75 impl Input for Separator {
76 fn blocks_root_plate_drag(&self) -> bool {
77 false
78 }
79 }
80
81 #[cfg(test)]
82 mod tests {
83 use super::*;
84
85 #[test]
86 fn constructor_places_the_rect_and_bridge_emits_it() {
87 let sep = Separator::new(100.0, 0.0, 1.0, 24.0, [0.3, 0.3, 0.3, 1.0]);
88 assert_eq!(WidgetHost::rect(&sep), (100.0, 0.0, 1.0, 24.0));
89 assert!(!WidgetHost::blocks_root_plate_drag(&sep));
90 if !crate::layout::control_relief() {
91 assert_eq!(WidgetHost::color(&sep), [0.3, 0.3, 0.3, 1.0]);
92 assert_eq!(WidgetHost::extra_quads(&sep), vec![(100.0, 0.0, 1.0, 24.0, [0.3, 0.3, 0.3, 1.0])]);
93 }
94 }
95
96 /// The relief cut runs along the long axis through the centre, the floor is the
97 /// rect's thickness, and the host spans the line without outrunning its ends.
98 #[test]
99 fn the_groove_follows_the_long_axis() {
100 let h = Separator::new(20.0, 100.0, 200.0, 1.0, [0.5; 4]);
101 let (a, b, floor, depth, host) = h.inner().groove(Rect { x: 20.0, y: 100.0, width: 200.0, height: 1.0 });
102 assert_eq!((a, b), ((20.0, 100.5), (220.0, 100.5)));
103 assert_eq!(floor, 1.0);
104 assert_eq!(depth, Separator::groove_depth());
105 assert_eq!((host.x, host.width), (20.0, 200.0), "the cut dies at the rect's ends");
106 assert!(host.y < 100.5 - depth && host.y + host.height > 100.5 + depth, "the walls stand inside the host");
107 let v = Separator::new(100.0, 0.0, 1.0, 24.0, [0.5; 4]);
108 let (a, b, _, _, host) = v.inner().groove(Rect { x: 100.0, y: 0.0, width: 1.0, height: 24.0 });
109 assert_eq!((a, b), ((100.5, 0.0), (100.5, 24.0)));
110 assert_eq!((host.y, host.height), (0.0, 24.0));
111 }
112
113 /// The status bar's vertical-rotation pattern, post-migration: transpose via rect/set_rect.
114 #[test]
115 fn rotation_via_set_rect() {
116 let mut sep = Separator::new(100.0, 0.0, 1.0, 24.0, [0.3, 0.3, 0.3, 1.0]);
117 let (x, y, w, h) = WidgetHost::rect(&sep);
118 WidgetHost::set_rect(&mut sep, y, x, h, w);
119 assert_eq!(WidgetHost::rect(&sep), (0.0, 100.0, 24.0, 1.0));
120 }
121 }