git.lucas.co / cce-ui
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 }