Skip to main content

concinnity_world/schema/
panel.rs

1//! UI panel container schema.
2
3/// A titled background container for grouping UI overlay elements.
4///
5/// `Panel` is a build-time shorthand: it expands into a filled, optionally
6/// rounded background [Sprite](#sprite) and, when `title` is set, a
7/// [TextLabel](#textlabel) heading inset from the top-left corner. Place other
8/// overlay elements over it to frame a group (a settings card, a dialog body).
9///
10/// Like the other build-time UI shorthands, generated names are prefixed with
11/// this asset's `name` (`<name>_bg`, `<name>_title`), so a panel named with a
12/// screen prefix (`pause_card`) puts its children in that [Screen](#screen)
13/// (`pause`) via the `<screen>_*` rule and they never clash with hand-authored
14/// assets.
15///
16/// ```rust
17/// # use concinnity_world::registry::build_only::Panel;
18/// Panel {
19///     title: "Paused".into(),
20///     x: 440.0,
21///     y: 220.0,
22///     width: 400.0,
23///     height: 280.0,
24///     ..Default::default()
25/// };
26/// ```
27#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
28#[serde(default)]
29pub struct Panel {
30    /// Left edge of the panel in window pixels.
31    pub x: f32,
32    /// Top edge of the panel in window pixels.
33    pub y: f32,
34    /// Panel width in window pixels.
35    pub width: f32,
36    /// Panel height in window pixels.
37    pub height: f32,
38    /// RGBA fill of the background box, each channel in [0, 1].
39    pub color: [f32; 4],
40    /// Corner rounding radius of the background box, in panel pixels. 0 keeps
41    /// sharp corners.
42    pub corner_radius: f32,
43    /// Heading text drawn at the top-left. Empty draws no heading.
44    pub title: String,
45    /// [Font](#font) for the title. Empty uses the built-in font.
46    pub title_font: String,
47    /// Linear-space RGB colour of the title text.
48    pub title_color: [f32; 3],
49    /// Scale applied to the title text.
50    pub title_scale: f32,
51    /// Inset of the title from the panel's top-left corner, in pixels.
52    pub padding: f32,
53}
54
55impl Default for Panel {
56    fn default() -> Self {
57        Self {
58            x: 0.0,
59            y: 0.0,
60            width: 400.0,
61            height: 300.0,
62            color: [0.08, 0.09, 0.12, 0.96],
63            corner_radius: 8.0,
64            title: String::new(),
65            title_font: String::new(),
66            title_color: [0.95, 0.95, 0.97],
67            title_scale: 1.0,
68            padding: 16.0,
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn a_blank_panel_is_a_rounded_near_opaque_dark_container() {
79        let p = Panel::default();
80        assert_eq!((p.width, p.height), (400.0, 300.0));
81        assert_eq!(p.corner_radius, 8.0);
82        assert_eq!(p.padding, 16.0);
83        assert_eq!(p.title_scale, 1.0);
84        assert!(p.title.is_empty());
85        assert!(p.title_font.is_empty());
86        // Near-opaque rather than fully so, so the world reads faintly behind it.
87        assert_eq!(p.color[3], 0.96);
88    }
89
90    #[test]
91    fn an_authored_panel_parses_and_round_trips_through_postcard() {
92        let p: Panel = serde_json::from_str(
93            r#"{"x":20,"y":30,"width":520,"height":360,"color":[0,0,0,1],
94                "corner_radius":0,"title":"Outliner","title_font":"body",
95                "title_color":[1,1,1],"title_scale":1.2,"padding":8}"#,
96        )
97        .unwrap();
98        assert_eq!(p.title, "Outliner");
99        assert_eq!(p.corner_radius, 0.0);
100
101        let bytes = postcard::to_allocvec(&p).unwrap();
102        let back: Panel = postcard::from_bytes(&bytes).unwrap();
103        assert_eq!((back.x, back.y), (20.0, 30.0));
104        assert_eq!((back.width, back.height), (520.0, 360.0));
105        assert_eq!(back.color, [0.0, 0.0, 0.0, 1.0]);
106        assert_eq!(back.title_font, "body");
107        assert_eq!(back.title_color, [1.0, 1.0, 1.0]);
108        assert_eq!(back.title_scale, 1.2);
109        assert_eq!(back.padding, 8.0);
110    }
111}