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+ } ;
1124use 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+
2237fn 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 ! ( "\n 1. 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 ! ( "\n 2. 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 ! ( "\n 3. 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 ! ( "\n 4. 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 ! ( "\n 5. 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 ! ( "\n Wearable 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