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