Skip to content

Commit 43caec3

Browse files
committed
feat(examples): upgrade wearable_os_subsystems_showcase with interactive SDL2 simulator window
1 parent c73ecc7 commit 43caec3

1 file changed

Lines changed: 147 additions & 102 deletions

File tree

Lines changed: 147 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1-
//! Showcase: Wearable OS Subsystems & UI Patterns
1+
//! Showcase: Interactive Wearable OS Subsystems Simulator
22
//!
3-
//! Demonstrates:
3+
//! Visualizes all 5 Wearable OS UI patterns on a 240x240 display:
44
//! 1. **Timeline Relationship Bars (`TimelineNodeWidget`)**: Chronological connective bars linking past, active, and future event pins.
5-
//! 2. **Reactive Peek Banners (`PeekBannerWidget`)**: Heads-up reminder ribbon modifying unobstructed screen canvas.
5+
//! 2. **Reactive Canvas Peek Banners (`PeekBannerWidget`)**: Heads-up reminder ribbon modifying unobstructed screen canvas.
66
//! 3. **Modal Notification Sheets (`NotificationSheetWidget`)**: Priority alert popups with action choices and auto-dismiss countdowns.
77
//! 4. **Cascading Action Menus (`ActionMenuWidget`)**: Hierarchical nested action sheets with highlight cursor.
88
//! 5. **Rich Multi-Span Text Nodes (`RichTextNodeWidget`)**: Formatted inline text tags and badge spans.
9-
10-
use embedded_graphics_core::pixelcolor::{Rgb565, WebColors};
9+
//!
10+
//! ### Controls:
11+
//! - **Up / Down Arrow**: Navigate Action Menu items
12+
//! - **Left / Right Arrow**: Switch Notification action button
13+
//! - **Space**: Toggle Peek Banner expansion
14+
//! - **Esc / Q**: Exit simulator
15+
16+
use embedded_graphics_core::{
17+
draw_target::DrawTarget,
18+
geometry::Size,
19+
pixelcolor::{Rgb565, WebColors},
20+
};
21+
use embedded_graphics_simulator::{
22+
OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, sdl2::Keycode,
23+
};
1124
use embedded_gui::{
12-
framebuffer::Framebuffer,
1325
geometry::Rect,
1426
render::RenderCtx,
1527
round::UnobstructedArea,
@@ -19,101 +31,134 @@ use embedded_gui::{
1931
},
2032
};
2133

34+
const W: u32 = 240;
35+
const H: u32 = 240;
36+
2237
fn main() {
23-
println!("=== embedded-gui: Wearable OS Subsystems Showcase ===");
24-
25-
let screen = Rect::new(0, 0, 240, 240);
26-
let mut fb = Framebuffer::<{ 240 * 240 }>::new(240, 240);
27-
let mut ctx = RenderCtx::new(&mut fb, screen);
28-
29-
// 1. Timeline Connective RelBar Demonstration
30-
println!("\n1. Timeline Relationship Connector Bars:");
31-
let past_node = TimelineNodeWidget::new(TimelineNodeState::Past);
32-
let mut active_node = TimelineNodeWidget::new(TimelineNodeState::ActiveNow);
33-
active_node.active_color = Rgb565::CSS_ORANGE;
34-
let future_node = TimelineNodeWidget::new(TimelineNodeState::Future);
35-
36-
let past_slot = Rect::new(12, 10, 16, 40);
37-
let active_slot = Rect::new(12, 50, 16, 40);
38-
let future_slot = Rect::new(12, 90, 16, 40);
39-
40-
past_node.render(&mut ctx, past_slot).unwrap();
41-
active_node.render(&mut ctx, active_slot).unwrap();
42-
future_node.render(&mut ctx, future_slot).unwrap();
43-
44-
println!(" Past pin connector: {:?}", past_slot);
45-
println!(" Active NOW pin node: {:?}", active_slot);
46-
println!(" Upcoming pin node: {:?}", future_slot);
47-
48-
// 2. Reactive Peek Banner adapting UnobstructedArea
49-
println!("\n2. Reactive Canvas Peek Banner:");
50-
let mut unobstructed = UnobstructedArea::new(screen);
51-
let peek = PeekBannerWidget::new("TEAM SYNC (10m)");
52-
peek.apply_to_unobstructed_area(&mut unobstructed);
53-
54-
let banner_rect = Rect::new(screen.x, screen.y, screen.w, peek.height as u32);
55-
peek.render(&mut ctx, banner_rect).unwrap();
56-
println!(" Peek banner rendered at: {:?}", banner_rect);
57-
println!(
58-
" Canvas area adjusted to: {:?}",
59-
unobstructed.visible_rect()
60-
);
61-
62-
// 3. Multi-Span Rich Text Flow Node
63-
println!("\n3. Multi-Span Rich Text Node with Badges:");
64-
let mut text_node = RichTextNodeWidget::<4>::new();
65-
text_node
66-
.push_span(TextSpan::badge(
67-
"CRITICAL",
68-
Rgb565::CSS_WHITE,
69-
Rgb565::new(28, 4, 4),
70-
))
71-
.unwrap();
72-
text_node
73-
.push_span(TextSpan::plain("CPU core temp 48C", Rgb565::CSS_WHITE))
74-
.unwrap();
75-
76-
let text_rect = Rect::new(40, 60, 190, 24);
77-
text_node.render(&mut ctx, text_rect).unwrap();
78-
println!(
79-
" Rendered {} styled text spans in {:?}",
80-
text_node.spans.len(),
81-
text_rect
82-
);
83-
84-
// 4. Modal Notification Sheet with Action Buttons & Progress Bar
85-
println!("\n4. Modal Notification Sheet:");
86-
let mut notif = NotificationSheetWidget::<3>::new(
87-
"CALENDAR REMINDER",
88-
"Architecture Design Review @ 10:00",
89-
NotificationPriority::Important,
90-
);
91-
notif.add_action("DISMISS", 101).unwrap();
92-
notif.add_action("SNOOZE", 102).unwrap();
93-
notif.auto_dismiss_progress = 0.75; // 75% timer remaining
94-
95-
let notif_rect = Rect::new(20, 140, 200, 85);
96-
notif.render(&mut ctx, notif_rect).unwrap();
97-
println!(
98-
" Rendered modal notification card with {} actions & 75% countdown timer.",
99-
notif.actions.len()
100-
);
101-
102-
// 5. Cascading Hierarchical Action Menu
103-
println!("\n5. Cascading Action Menu:");
104-
let mut menu = ActionMenuWidget::<4>::new(Some("SYSTEM ACTIONS"));
105-
menu.add_item("Wireless Sync", 1, true).unwrap();
106-
menu.add_item("Do Not Disturb", 2, false).unwrap();
107-
menu.add_item("Power Options", 3, true).unwrap();
108-
menu.selected_index = 0;
109-
110-
let menu_rect = Rect::new(30, 40, 180, 75);
111-
menu.render(&mut ctx, menu_rect).unwrap();
112-
println!(
113-
" Rendered action menu with {} items, highlighted index {}.",
114-
menu.items.len(),
115-
menu.selected_index
116-
);
117-
118-
println!("\nWearable OS subsystems showcase executed and rendered successfully!");
38+
println!("=== embedded-gui: Interactive Wearable OS Subsystems Showcase ===");
39+
println!("Controls:");
40+
println!(" [Up / Down] - Navigate Action Menu");
41+
println!(" [Left / Right] - Switch Notification Action");
42+
println!(" [Space] - Toggle Peek Banner expansion");
43+
println!(" [Esc / Q] - Exit");
44+
45+
let mut display = SimulatorDisplay::<Rgb565>::new(Size::new(W, H));
46+
let settings = OutputSettingsBuilder::new().scale(3).build();
47+
let mut window = Window::new("Wearable OS Subsystems Showcase (240x240)", &settings);
48+
49+
// Subsystem States
50+
let mut peek_expanded = false;
51+
let mut selected_menu_idx = 0usize;
52+
let mut selected_notif_action = 0usize;
53+
let mut notif_progress = 1.0f32;
54+
55+
'running: loop {
56+
for event in window.events() {
57+
match event {
58+
SimulatorEvent::Quit => break 'running,
59+
SimulatorEvent::KeyDown { keycode, .. } => match keycode {
60+
Keycode::Escape | Keycode::Q => break 'running,
61+
Keycode::Up => {
62+
selected_menu_idx = selected_menu_idx.saturating_sub(1);
63+
}
64+
Keycode::Down => {
65+
selected_menu_idx = (selected_menu_idx + 1).min(2);
66+
}
67+
Keycode::Left => {
68+
selected_notif_action = selected_notif_action.saturating_sub(1);
69+
}
70+
Keycode::Right => {
71+
selected_notif_action = (selected_notif_action + 1).min(1);
72+
}
73+
Keycode::Space => {
74+
peek_expanded = !peek_expanded;
75+
}
76+
_ => {}
77+
},
78+
_ => {}
79+
}
80+
}
81+
82+
// Animate notification auto-dismiss countdown
83+
notif_progress -= 0.003;
84+
if notif_progress <= 0.0 {
85+
notif_progress = 1.0;
86+
}
87+
88+
// Clear display with dark slate background
89+
display.clear(Rgb565::new(1, 2, 3)).unwrap();
90+
91+
let screen = Rect::new(0, 0, W, H);
92+
let mut ctx = RenderCtx::new(&mut display, screen);
93+
94+
// 1. Reactive Peek Banner at top
95+
let mut peek = PeekBannerWidget::new("TEAM SYNC (10m)");
96+
peek.subtitle = Some("Room 4B • Audio Bridge active");
97+
peek.is_expanded = peek_expanded;
98+
peek.height = if peek_expanded { 38 } else { 22 };
99+
100+
let mut unobstructed = UnobstructedArea::new(screen);
101+
peek.apply_to_unobstructed_area(&mut unobstructed);
102+
103+
let banner_rect = Rect::new(screen.x, screen.y, screen.w, peek.height as u32);
104+
peek.render(&mut ctx, banner_rect).unwrap();
105+
106+
// 2. Timeline Relationship Connectors (RelBar) on left column
107+
let past_node = TimelineNodeWidget::new(TimelineNodeState::Past);
108+
let mut active_node = TimelineNodeWidget::new(TimelineNodeState::ActiveNow);
109+
active_node.active_color = Rgb565::CSS_ORANGE;
110+
let future_node = TimelineNodeWidget::new(TimelineNodeState::Upcoming);
111+
112+
let base_y = banner_rect.bottom() + 6;
113+
let past_slot = Rect::new(8, base_y, 14, 28);
114+
let active_slot = Rect::new(8, past_slot.bottom(), 14, 32);
115+
let future_slot = Rect::new(8, active_slot.bottom(), 14, 28);
116+
117+
past_node.render(&mut ctx, past_slot).unwrap();
118+
active_node.render(&mut ctx, active_slot).unwrap();
119+
future_node.render(&mut ctx, future_slot).unwrap();
120+
121+
// 3. Multi-Span Rich Text Badges next to timeline
122+
let mut text_node = RichTextNodeWidget::<4>::new();
123+
text_node
124+
.push_span(TextSpan::badge(
125+
"CRITICAL",
126+
Rgb565::CSS_WHITE,
127+
Rgb565::new(28, 4, 4),
128+
))
129+
.unwrap();
130+
text_node
131+
.push_span(TextSpan::plain("Core 48C", Rgb565::CSS_WHITE))
132+
.unwrap();
133+
134+
let text_rect = Rect::new(26, base_y + 4, 96, 16);
135+
text_node.render(&mut ctx, text_rect).unwrap();
136+
137+
// 4. Cascading Action Menu on upper-right
138+
let mut menu = ActionMenuWidget::<4>::new(Some("ACTIONS"));
139+
menu.add_item("Wireless Sync", 1, true).unwrap();
140+
menu.add_item("Do Not Disturb", 2, false).unwrap();
141+
menu.add_item("Power Save", 3, true).unwrap();
142+
menu.selected_index = selected_menu_idx;
143+
144+
let menu_rect = Rect::new(126, base_y, 106, 68);
145+
menu.render(&mut ctx, menu_rect).unwrap();
146+
147+
// 5. Modal Notification Sheet at bottom
148+
let mut notif = NotificationSheetWidget::<2>::new(
149+
"CALENDAR ALERT",
150+
"Design Review in 10 mins",
151+
NotificationPriority::Important,
152+
);
153+
notif.add_action("DISMISS", 101).unwrap();
154+
notif.add_action("SNOOZE", 102).unwrap();
155+
notif.selected_action = selected_notif_action;
156+
notif.auto_dismiss_progress = notif_progress;
157+
158+
let notif_rect = Rect::new(8, 150, 224, 82);
159+
notif.render(&mut ctx, notif_rect).unwrap();
160+
161+
window.update(&display);
162+
std::thread::sleep(std::time::Duration::from_millis(16));
163+
}
119164
}

0 commit comments

Comments
 (0)