@@ -61,6 +61,8 @@ const STRIPE_BYTES: usize = SCREEN_W * STRIPE_H * 2;
6161const BACKLIGHT_ACTIVE_HIGH : bool = true ;
6262const SAFE_X : i32 = 18 ;
6363const SAFE_W : u32 = SCREEN_W as u32 - ( SAFE_X as u32 * 2 ) ;
64+ const MEDIA_VISIBLE_CHARS : usize = 26 ;
65+ const MEDIA_GAP_CHARS : usize = 3 ;
6466const PANEL_RADIUS : u32 = 14 ;
6567const CHIP_RADIUS : u32 = 7 ;
6668const BAR_RADIUS : u32 = 5 ;
@@ -380,7 +382,9 @@ where
380382 } ) ,
381383 irq,
382384 } ,
383- renderer : QubeStatusRenderer { host_data } ,
385+ renderer : QubeStatusRenderer {
386+ host_data : host_data. clone ( ) ,
387+ } ,
384388 ctx : RenderContext :: default ( ) ,
385389 last_host_data : host_data,
386390 last_render : Instant :: from_ticks ( 0 ) ,
@@ -417,7 +421,7 @@ where
417421 fn sync_host_data ( & mut self ) {
418422 let host_data = rmk:: host_data:: snapshot ( ) ;
419423 if host_data != self . last_host_data {
420- self . last_host_data = host_data;
424+ self . last_host_data = host_data. clone ( ) ;
421425 self . renderer . host_data = host_data;
422426 self . request_redraw ( ) ;
423427 }
@@ -578,7 +582,7 @@ where
578582 central : & mut impl EventSubscriber < Event = CentralConnectedEvent > ,
579583 ) -> UiEv {
580584 match select (
581- Timer :: after ( Duration :: from_secs ( 1 ) ) ,
585+ Timer :: after ( Duration :: from_millis ( 250 ) ) ,
582586 Self :: next_any (
583587 layer, wpm, led, mods, key, sleep, bat, conn, peri_conn, peri_bat, central,
584588 ) ,
@@ -667,6 +671,9 @@ where
667671 UiEv :: Central ( e) => self . ctx . central_connected = e. connected ,
668672 UiEv :: HostDataTick => {
669673 self . sync_host_data ( ) ;
674+ if self . renderer . media_needs_marquee ( ) {
675+ self . request_redraw ( ) ;
676+ }
670677 need_redraw = false ;
671678 }
672679 }
@@ -708,16 +715,24 @@ pub struct QubeStatusRenderer {
708715 host_data : rmk:: host_data:: HostData ,
709716}
710717
718+ impl QubeStatusRenderer {
719+ fn media_needs_marquee ( & self ) -> bool {
720+ let mut media: heapless:: String < 72 > = heapless:: String :: new ( ) ;
721+ push_media_label ( & mut media, & self . host_data ) ;
722+ media. len ( ) > MEDIA_VISIBLE_CHARS
723+ }
724+ }
725+
711726impl DisplayRenderer < Rgb565 > for QubeStatusRenderer {
712727 fn render < D : DrawTarget < Color = Rgb565 > > ( & mut self , ctx : & RenderContext , display : & mut D ) {
713728 let _ = display. clear ( COL_BG ) ;
714729
715730 let layer_meta = MonoTextStyle :: new ( & FONT_6X10 , COL_LABEL ) ;
731+ let header_media = MonoTextStyle :: new ( & FONT_6X10 , COL_FG ) ;
732+ let header_fallback = MonoTextStyle :: new ( & FONT_8X13 , COL_ACCENT ) ;
716733 let body = MonoTextStyle :: new ( & FONT_8X13 , COL_FG ) ;
717- let body_accent = MonoTextStyle :: new ( & FONT_8X13 , COL_ACCENT ) ;
718734 let title_shadow = MonoTextStyle :: new ( & FONT_10X20 , COL_ACCENT_DIM ) ;
719735 let title = MonoTextStyle :: new ( & FONT_10X20 , COL_FG ) ;
720- let top = TextStyleBuilder :: new ( ) . baseline ( Baseline :: Top ) . build ( ) ;
721736 let tc = TextStyleBuilder :: new ( )
722737 . alignment ( Alignment :: Center )
723738 . baseline ( Baseline :: Top )
@@ -740,13 +755,13 @@ impl DisplayRenderer<Rgb565> for QubeStatusRenderer {
740755 draw_panel ( display, SAFE_X , 14 , SAFE_W , 28 , COL_PANEL , COL_BORDER_DIM ) ;
741756 draw_round_fill ( display, SAFE_X + 11 , 23 , 3 , 10 , 2 , COL_ACCENT ) ;
742757 let mut s: heapless:: String < 16 > = heapless:: String :: new ( ) ;
743- let _ = s . push_str ( host_layout_label ( self . host_data . layout ) ) ;
744- let _ =
745- Text :: with_text_style ( & s, Point :: new ( SAFE_X + 22 , 21 ) , body_accent , top ) . draw ( display ) ;
746- s . clear ( ) ;
747- push_host_time ( & mut s, self . host_data . hour , self . host_data . minute ) ;
748- let _ = Text :: with_text_style ( & s , Point :: new ( SAFE_X + SAFE_W as i32 - 14 , 21 ) , body , tr )
749- . draw ( display ) ;
758+ draw_media_or_fallback ( display , & self . host_data , header_media , header_fallback ) ;
759+ if host_time_available ( & self . host_data ) {
760+ push_host_time ( & mut s, self . host_data . hour , self . host_data . minute ) ;
761+ let _ =
762+ Text :: with_text_style ( & s, Point :: new ( SAFE_X + SAFE_W as i32 - 14 , 21 ) , body , tr )
763+ . draw ( display ) ;
764+ }
750765
751766 // Layer panel.
752767 draw_panel (
@@ -997,15 +1012,6 @@ fn layer_name(layer: u8) -> &'static str {
9971012 }
9981013}
9991014
1000- fn host_layout_label ( layout : Option < u8 > ) -> & ' static str {
1001- match layout {
1002- Some ( 0 ) => "EN" ,
1003- Some ( 1 ) => "RU" ,
1004- Some ( _) => "??" ,
1005- None => "--" ,
1006- }
1007- }
1008-
10091015fn push_host_time ( buffer : & mut heapless:: String < 16 > , hour : Option < u8 > , minute : Option < u8 > ) {
10101016 match ( hour, minute) {
10111017 ( Some ( hour) , Some ( minute) ) => {
@@ -1016,3 +1022,79 @@ fn push_host_time(buffer: &mut heapless::String<16>, hour: Option<u8>, minute: O
10161022 }
10171023 }
10181024}
1025+
1026+ fn draw_media_or_fallback < D : DrawTarget < Color = Rgb565 > > (
1027+ display : & mut D ,
1028+ host_data : & rmk:: host_data:: HostData ,
1029+ media_style : MonoTextStyle < ' _ , Rgb565 > ,
1030+ fallback_style : MonoTextStyle < ' _ , Rgb565 > ,
1031+ ) {
1032+ let top = TextStyleBuilder :: new ( ) . baseline ( Baseline :: Top ) . build ( ) ;
1033+ let mut media: heapless:: String < 72 > = heapless:: String :: new ( ) ;
1034+ push_media_label ( & mut media, host_data) ;
1035+
1036+ if media. is_empty ( ) {
1037+ if !host_time_available ( host_data) {
1038+ let _ = Text :: with_text_style ( "QUBE" , Point :: new ( SAFE_X + 22 , 21 ) , fallback_style, top)
1039+ . draw ( display) ;
1040+ }
1041+ return ;
1042+ }
1043+
1044+ let mut visible: heapless:: String < 32 > = heapless:: String :: new ( ) ;
1045+ if media. len ( ) <= MEDIA_VISIBLE_CHARS {
1046+ let _ = visible. push_str ( & media) ;
1047+ } else {
1048+ let elapsed = Instant :: now ( )
1049+ . duration_since ( Instant :: from_ticks ( 0 ) )
1050+ . as_millis ( ) as usize ;
1051+ let offset = ( elapsed / 300 ) % ( media. len ( ) + MEDIA_GAP_CHARS ) ;
1052+ push_marquee_slice ( & mut visible, & media, offset) ;
1053+ }
1054+
1055+ let _ = Text :: with_text_style ( & visible, Point :: new ( SAFE_X + 22 , 22 ) , media_style, top)
1056+ . draw ( display) ;
1057+ }
1058+
1059+ fn push_media_label ( buffer : & mut heapless:: String < 72 > , host_data : & rmk:: host_data:: HostData ) {
1060+ if !host_data. media_artist . is_empty ( ) {
1061+ push_ascii_text ( buffer, & host_data. media_artist ) ;
1062+ }
1063+ if !host_data. media_title . is_empty ( ) {
1064+ if !buffer. is_empty ( ) {
1065+ let _ = buffer. push_str ( " - " ) ;
1066+ }
1067+ push_ascii_text ( buffer, & host_data. media_title ) ;
1068+ }
1069+ }
1070+
1071+ fn push_ascii_text < const N : usize > ( buffer : & mut heapless:: String < N > , value : & str ) {
1072+ for ch in value. chars ( ) {
1073+ let ch = if ch. is_ascii_graphic ( ) || ch == ' ' {
1074+ ch
1075+ } else {
1076+ '?'
1077+ } ;
1078+ if buffer. push ( ch) . is_err ( ) {
1079+ break ;
1080+ }
1081+ }
1082+ }
1083+
1084+ fn push_marquee_slice ( buffer : & mut heapless:: String < 32 > , text : & str , offset : usize ) {
1085+ let bytes = text. as_bytes ( ) ;
1086+ let cycle_len = bytes. len ( ) + MEDIA_GAP_CHARS ;
1087+ for i in 0 ..MEDIA_VISIBLE_CHARS {
1088+ let idx = ( offset + i) % cycle_len;
1089+ let ch = if idx < bytes. len ( ) {
1090+ bytes[ idx] as char
1091+ } else {
1092+ ' '
1093+ } ;
1094+ let _ = buffer. push ( ch) ;
1095+ }
1096+ }
1097+
1098+ fn host_time_available ( host_data : & rmk:: host_data:: HostData ) -> bool {
1099+ host_data. hour . is_some ( ) && host_data. minute . is_some ( )
1100+ }
0 commit comments