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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use egui::Color32;
/// Visual parameters of a glass surface. All lengths are in logical points.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct GlassStyle {
/// Corner radius. Use [`f32::INFINITY`] for a capsule.
pub corner_radius: f32,
/// Continuous-corner smoothing, 0 = circular arc, 1 = fully smoothed.
/// 0.6 matches the look of iOS continuous corners. Same model as Figma's
/// corner smoothing: the transition spans `(1 + smoothing) * radius`.
pub corner_smoothing: f32,
/// Backdrop blur radius.
pub blur: f32,
/// Maximum displacement of the backdrop at the edge (lens effect).
pub refraction: f32,
/// Width of the lens zone measured inwards from the edge.
pub edge_width: f32,
/// Chromatic aberration inside the lens zone, 0..=1.
pub chromatic: f32,
/// Tint mixed over the backdrop; alpha is the mix amount.
pub tint: Color32,
/// Backdrop brightness multiplier.
pub brightness: f32,
/// Backdrop saturation multiplier.
pub saturation: f32,
/// Specular rim/sheen strength, 0..=1.
pub specular: f32,
/// Inner border strength, 0..=1.
pub border: f32,
/// Drop shadow strength, 0..=1.
pub shadow: f32,
/// Drop shadow blur radius.
pub shadow_radius: f32,
}
impl Default for GlassStyle {
fn default() -> Self {
Self::regular()
}
}
impl GlassStyle {
/// Apple "regular" glass: legible, slightly frosted.
pub const fn regular() -> Self {
Self {
corner_radius: 22.0,
corner_smoothing: 0.6,
blur: 12.0,
refraction: 14.0,
edge_width: 26.0,
chromatic: 0.2,
tint: Color32::from_rgba_unmultiplied_const(255, 255, 255, 60),
brightness: 1.05,
saturation: 1.2,
specular: 0.7,
border: 0.8,
shadow: 0.16,
shadow_radius: 22.0,
}
}
/// Apple "clear" glass: almost no frost, strong lensing.
pub const fn clear() -> Self {
Self {
blur: 2.0,
refraction: 22.0,
edge_width: 34.0,
chromatic: 0.35,
tint: Color32::from_rgba_unmultiplied_const(255, 255, 255, 18),
..Self::regular()
}
}
/// Flat frosted panel for large surfaces (sidebars, sheets): heavy blur,
/// almost no lensing, soft light. Apple keeps big glass calm.
pub const fn panel() -> Self {
Self {
blur: 36.0,
refraction: 3.0,
edge_width: 10.0,
chromatic: 0.0,
tint: Color32::from_rgba_unmultiplied_const(255, 255, 255, 120),
brightness: 1.0,
saturation: 1.0,
specular: 0.2,
border: 0.45,
shadow: 0.12,
shadow_radius: 28.0,
..Self::regular()
}
}
/// [`Self::panel`] for dark themes.
pub const fn panel_dark() -> Self {
Self {
tint: Color32::from_rgba_unmultiplied_const(0, 0, 0, 110),
specular: 0.15,
border: 0.3,
shadow: 0.35,
..Self::panel()
}
}
/// Dark tinted glass for light content.
pub const fn dark() -> Self {
Self {
tint: Color32::from_rgba_unmultiplied_const(0, 0, 0, 90),
brightness: 0.9,
specular: 0.5,
border: 0.5,
..Self::regular()
}
}
pub const fn with_corner_radius(mut self, radius: f32) -> Self {
self.corner_radius = radius;
self
}
pub const fn capsule(self) -> Self {
self.with_corner_radius(f32::INFINITY)
}
pub const fn with_tint(mut self, tint: Color32) -> Self {
self.tint = tint;
self
}
/// True when the tint makes the surface dark enough to need light text.
pub fn is_dark(&self) -> bool {
let [r, g, b, a] = self.tint.to_array();
a >= 60 && (0.2126 * r as f32 + 0.7152 * g as f32 + 0.0722 * b as f32) < 0.5 * a as f32
}
/// Variant used while the pointer is over an interactive glass widget.
pub fn hovered(mut self) -> Self {
self.brightness *= 1.08;
self.specular = (self.specular + 0.15).min(1.0);
self
}
/// Variant used while an interactive glass widget is pressed.
pub fn pressed(mut self) -> Self {
self.brightness *= 0.9;
self.tint = self.tint.gamma_multiply(1.3);
self
}
}
#[cfg(all(test, feature = "serde"))]
mod tests {
use super::GlassStyle;
#[test]
fn json_round_trip_and_missing_fields_default() {
let style = GlassStyle::clear().with_corner_radius(7.5);
let json = serde_json::to_string(&style).unwrap();
assert_eq!(serde_json::from_str::<GlassStyle>(&json).unwrap(), style);
let partial: GlassStyle = serde_json::from_str(r#"{"blur": 3.0}"#).unwrap();
assert_eq!(partial, GlassStyle { blur: 3.0, ..GlassStyle::regular() });
}
}