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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//! Elevation and z-index layer system.
//!
//! Provides a 5-to-25 elevation scale as named constants, and documented
//! z-index layer constants for consistent visual stacking.
//!
//! # Elevation Scale
//!
//! Elevation levels map to shadow depth. Higher levels cast deeper shadows:
//!
//! | Level | Name | Use Case |
//! |-------|------|----------|
//! | 0 | FLAT | No shadow, baseline elements |
//! | 1 | RESTING | Cards at rest, static content |
//! | 2 | HOVER | Cards on hover, subtle lift |
//! | 4 | FLOATING | Floating action buttons, sticky headers |
//! | 8 | OVERLAY | Dropdowns, popovers, tooltips |
//! | 16 | MODAL | Dialogs, sheets, overlays |
//! | 25 | TOAST | Notifications, highest priority |
//!
//! # Z-Index Layers
//!
//! Named z-index constants ensure consistent stacking across the UI:
//!
//! - `BASE` (0): Background content
//! - `DROPDOWN` (1000): Dropdown menus, select popups
//! - `STICKY` (2000): Sticky headers, persistent bars
//! - `MODAL` (3000): Modal dialogs, full-screen overlays
//! - `TOAST` (4000): Toast notifications, highest non-debug layer
//! - `TOOLTIP` (5000): Tooltips (always on top)
//! - `DEBUG` (99999): Debug overlays, development tools
/// Elevation level constants (0-25 scale).
#[allow(clippy::module_inception)]
pub mod elevation {
/// No elevation — flat baseline elements with no shadow.
pub const FLAT: u8 = 0;
/// Resting elevation — cards, panels at their default state.
pub const RESTING: u8 = 1;
/// Hover elevation — subtle lift for interactive elements on hover.
pub const HOVER: u8 = 2;
/// Floating elevation — FABs, sticky headers, persistent UI.
pub const FLOATING: u8 = 4;
/// Overlay elevation — dropdowns, popovers, context menus.
pub const OVERLAY: u8 = 8;
/// Modal elevation — dialogs, sheets, full-screen overlays.
pub const MODAL: u8 = 16;
/// Toast elevation — notifications, highest standard UI layer.
pub const TOAST: u8 = 25;
/// Maximum elevation level.
pub const MAX: u8 = 25;
/// Convert an elevation level to a shadow blur radius.
pub fn to_blur_radius(level: u8) -> f32 {
match level {
0 => 0.0,
1 => 2.0,
2 => 4.0,
4 => 8.0,
8 => 12.0,
16 => 24.0,
25 => 40.0,
_ => (level as f32) * 1.6,
}
}
/// Convert an elevation level to a shadow offset (Y).
pub fn to_offset_y(level: u8) -> f32 {
match level {
0 => 0.0,
1 => 1.0,
2 => 2.0,
4 => 4.0,
8 => 6.0,
16 => 12.0,
25 => 20.0,
_ => (level as f32) * 0.8,
}
}
/// Convert an elevation level to shadow opacity.
pub fn to_opacity(level: u8) -> f32 {
match level {
0 => 0.0,
1 => 0.1,
2 => 0.15,
4 => 0.2,
8 => 0.25,
16 => 0.3,
25 => 0.35,
_ => (level as f32) / 100.0,
}
}
}
/// Named z-index layer constants for consistent visual stacking.
pub mod z_index {
/// Base layer — background content, canvas.
pub const BASE: i32 = 0;
/// Content layer — main page content.
pub const CONTENT: i32 = 100;
/// Dropdown layer — select popups, dropdown menus.
pub const DROPDOWN: i32 = 1000;
/// Sticky layer — sticky headers, persistent bars.
pub const STICKY: i32 = 2000;
/// Modal layer — dialogs, full-screen overlays.
pub const MODAL: i32 = 3000;
/// Toast layer — notification toasts.
pub const TOAST: i32 = 4000;
/// Tooltip layer — always-on-top tooltips.
pub const TOOLTIP: i32 = 5000;
/// Debug layer — development tools, highest priority.
pub const DEBUG: i32 = 99999;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Never, Rect, Renderer, View, testing::MockRenderer};
struct SolidView {
color: [f32; 4],
}
impl View for SolidView {
type Body = Never;
fn body(self) -> Self::Body {
unreachable!()
}
fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
renderer.fill_rect(rect, self.color);
}
}
#[test]
fn elevation_determines_draw_order() {
// Two views at different elevations: higher elevation should render
// (push content) after lower elevation in draw order
let mut renderer = MockRenderer::new();
let rect = Rect::new(0.0, 0.0, 100.0, 50.0);
// Draw flat view first
let flat_view = SolidView {
color: [1.0, 0.0, 0.0, 1.0], // red
};
flat_view.render(&mut renderer, rect);
// Draw modal view second (higher elevation)
let modal_view = SolidView {
color: [0.0, 0.0, 1.0, 1.0], // blue
}
.elevation(elevation::MODAL as f32);
modal_view.render(&mut renderer, rect);
// Should have 2 draw calls (one from flat, one from modal)
renderer.assert_draw_call_count(2);
// The second draw call should be blue (the modal view's content)
renderer.assert_color_at(50.0, 25.0, [0.0, 0.0, 1.0, 1.0]);
}
#[test]
fn z_index_values_are_positive() {
// All named z-index layers should be non-negative
assert!(z_index::BASE >= 0);
assert!(z_index::CONTENT > 0);
assert!(z_index::DROPDOWN > z_index::CONTENT);
assert!(z_index::TOOLTIP < z_index::DEBUG);
}
#[test]
fn elevation_scale_ordering() {
// Higher levels should have larger blur radii
assert!(
elevation::to_blur_radius(elevation::FLAT)
< elevation::to_blur_radius(elevation::RESTING)
);
assert!(
elevation::to_blur_radius(elevation::RESTING)
< elevation::to_blur_radius(elevation::HOVER)
);
assert!(
elevation::to_blur_radius(elevation::HOVER)
< elevation::to_blur_radius(elevation::FLOATING)
);
assert!(
elevation::to_blur_radius(elevation::FLOATING)
< elevation::to_blur_radius(elevation::OVERLAY)
);
assert!(
elevation::to_blur_radius(elevation::OVERLAY)
< elevation::to_blur_radius(elevation::MODAL)
);
assert!(
elevation::to_blur_radius(elevation::MODAL)
< elevation::to_blur_radius(elevation::TOAST)
);
}
#[test]
fn elevation_offsets_increase() {
// Higher levels should have larger Y offsets
assert!(
elevation::to_offset_y(elevation::FLAT) < elevation::to_offset_y(elevation::RESTING)
);
assert!(
elevation::to_offset_y(elevation::RESTING) < elevation::to_offset_y(elevation::HOVER)
);
assert!(
elevation::to_offset_y(elevation::HOVER) < elevation::to_offset_y(elevation::FLOATING)
);
}
#[test]
fn elevation_opacities_increase() {
assert!(elevation::to_opacity(elevation::FLAT) < elevation::to_opacity(elevation::RESTING));
assert!(
elevation::to_opacity(elevation::RESTING) < elevation::to_opacity(elevation::MODAL)
);
}
#[test]
fn flat_has_no_shadow() {
assert_eq!(elevation::to_blur_radius(elevation::FLAT), 0.0);
assert_eq!(elevation::to_offset_y(elevation::FLAT), 0.0);
assert_eq!(elevation::to_opacity(elevation::FLAT), 0.0);
}
#[test]
fn z_index_layers_stack_correctly() {
// Lower layers should have lower z-index values
assert!(z_index::BASE < z_index::CONTENT);
assert!(z_index::CONTENT < z_index::DROPDOWN);
assert!(z_index::DROPDOWN < z_index::STICKY);
assert!(z_index::STICKY < z_index::MODAL);
assert!(z_index::MODAL < z_index::TOAST);
assert!(z_index::TOAST < z_index::TOOLTIP);
assert!(z_index::TOOLTIP < z_index::DEBUG);
}
#[test]
fn z_index_modal_above_dropdown() {
// A modal dialog should render above a dropdown
assert!(z_index::MODAL > z_index::DROPDOWN);
}
#[test]
fn z_index_toast_above_modal() {
// A toast notification should render above a modal
assert!(z_index::TOAST > z_index::MODAL);
}
#[test]
fn z_index_tooltip_above_toast() {
// Tooltips should render above toasts
assert!(z_index::TOOLTIP > z_index::TOAST);
}
#[test]
fn elevation_max_is_25() {
assert_eq!(elevation::MAX, 25);
assert_eq!(elevation::TOAST, 25);
}
#[test]
fn custom_elevation_interpolates() {
// Custom levels (not in the named constants) should interpolate
let blur_3 = elevation::to_blur_radius(3);
let blur_2 = elevation::to_blur_radius(2);
let blur_4 = elevation::to_blur_radius(4);
assert!(blur_2 < blur_3);
assert!(blur_3 < blur_4);
}
}