GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/display/status_dot.rs (4.2K)
1 //! Narrow-trait status dot (Phase 5c leaf sweep).
2 //!
3 //! **Deliberate behavior fix:** the legacy `WidgetHost` impl only set `color()` and never emitted
4 //! geometry on any render path (`all_quads` and `all_rounded_quads` were both empty for it, and
5 //! `render_widget` never reads `color()` directly), so the dot was **invisible** — a probe test
6 //! against the legacy widget confirmed zero rects emitted through `render_widget`. The narrow
7 //! [`Paint`] default emits the color quad, so the dot now actually shows. The probe test at the
8 //! bottom documents the fix.
9
10 use crate::scene::layout::Rect;
11 use crate::scene::paint::PaintCtx;
12 use crate::widget::{Adapted, Input, Layout, Paint};
13
14 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
15 pub enum DotStatus {
16 Active,
17 Inactive,
18 Warning,
19 Error,
20 }
21
22 #[derive(Debug, Clone)]
23 pub struct StatusDot {
24 pub status: DotStatus,
25 }
26
27 impl StatusDot {
28 /// The dot's default side.
29 pub const SIZE: f32 = 12.0;
30
31 pub fn new(status: DotStatus) -> Adapted<StatusDot> {
32 Adapted::new(StatusDot { status })
33 }
34
35 pub fn set_status(&mut self, status: DotStatus) {
36 self.status = status;
37 }
38 }
39
40 impl Layout for StatusDot {
41 /// A dot: a fixed small disc unless the host sizes it.
42 fn intrinsic_size(&self) -> Option<crate::scene::layout::Size> {
43 Some(crate::scene::layout::Size::new(StatusDot::SIZE, StatusDot::SIZE))
44 }
45 }
46
47 impl Paint for StatusDot {
48 fn color(&self) -> [f32; 4] {
49 match self.status {
50 DotStatus::Active => [0.20, 0.70, 0.35, 1.0],
51 DotStatus::Inactive => [0.50, 0.50, 0.55, 1.0],
52 DotStatus::Warning => [0.90, 0.60, 0.10, 1.0],
53 DotStatus::Error => [0.85, 0.25, 0.25, 1.0],
54 }
55 }
56
57 /// A disc: the colour as a rounded rect whose radius is half the short side. A rounded
58 /// rect rather than a circle prim so it survives every legacy bridge (`all_rounded_quads`,
59 /// `render_widget`), which carry rounded rects but not circles.
60 fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
61 let r = rect.width.min(rect.height) * 0.5;
62 ctx.rounded_rect(rect, r, (true, true, true, true), self.color());
63 }
64 }
65
66 impl Input for StatusDot {
67 fn blocks_root_plate_drag(&self) -> bool {
68 false
69 }
70 }
71
72 #[cfg(test)]
73 mod tests {
74 use super::*;
75 use crate::widget::{WidgetHost, UiContext};
76
77 #[test]
78 fn emits_its_disc_through_the_rounded_bridge() {
79 let mut dot = StatusDot::new(DotStatus::Warning);
80 WidgetHost::set_rect(&mut dot, 5.0, 6.0, 10.0, 10.0);
81 assert_eq!(
82 WidgetHost::all_rounded_quads(&dot, &UiContext::new()),
83 vec![(5.0, 6.0, 10.0, 10.0, 5.0, [0.90, 0.60, 0.10, 1.0], (true, true, true, true))],
84 "a disc: the rect at half-side radius",
85 );
86 assert!(WidgetHost::extra_quads(&dot).is_empty(), "nothing on the plain path (apps read both)");
87 // Drags pass through, as legacy declared.
88 assert!(!WidgetHost::blocks_root_plate_drag(&dot));
89 // State mutation through Deref, as call sites write it.
90 dot.set_status(DotStatus::Error);
91 assert_eq!(dot.status, DotStatus::Error);
92 }
93
94 /// Documents the behavior fix: the legacy `StatusDot` emitted **zero** rects through
95 /// `render_widget` (probe run against the pre-migration widget), i.e. the dot was invisible
96 /// wherever it was used. The migrated widget emits exactly one.
97 #[test]
98 fn render_widget_now_draws_the_dot() {
99 struct Probe {
100 rects: Vec<(f32, f32, f32, f32, [f32; 4])>,
101 }
102 impl crate::layout::RenderTarget for Probe {
103 fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
104 self.rects.push((x, y, w, h, color));
105 }
106 fn text(&mut self, _c: &str, _x: f32, _y: f32, _s: f32, _col: [f32; 4]) {}
107 }
108
109 let mut ctx = UiContext::new();
110 let mut probe = Probe { rects: Vec::new() };
111 let mut dot = StatusDot::new(DotStatus::Active);
112 crate::layout::render_widget(&mut probe, &mut dot, 10.0, 10.0, 10.0, 10.0, &mut ctx);
113 assert_eq!(probe.rects.len(), 1, "the dot is visible now (legacy emitted 0 here)");
114 assert_eq!(probe.rects[0].4, [0.20, 0.70, 0.35, 1.0], "active-status green");
115 }
116 }