11use crate :: {
2- bluetooth:: display_properties ,
3- route:: Route ,
2+ bluetooth:: ConnectedCharacteristic ,
3+ route:: { CharacteristicValue , Route } ,
44 tui:: {
5- ui:: { block, BlendrBlock } ,
6- AppRoute ,
5+ ui:: {
6+ block:: { self , Title } ,
7+ BlendrBlock ,
8+ } ,
9+ AppRoute , HandleKeydownResult ,
710 } ,
811 Ctx ,
912} ;
@@ -52,6 +55,54 @@ fn try_parse_numeric_value<T: ByteOrder>(
5255 } )
5356}
5457
58+ fn render_title_with_navigation_controls (
59+ area : & tui:: layout:: Rect ,
60+ char : & ConnectedCharacteristic ,
61+ historical_view_index : Option < usize > ,
62+ history : & [ CharacteristicValue ] ,
63+ ) -> Title < ' static > {
64+ const PREVIOUS_BUTTON : & str = " [<- Previous] " ;
65+ const PREVIOUS_BUTTON_DENSE : & str = " [<-] " ;
66+ const NEXT_BUTTON : & str = " [Next ->] " ;
67+ const NEXT_BUTTON_DENSE : & str = " [->] " ;
68+
69+ let mut spans = vec ! [ ] ;
70+ let available_width = area. width - 2 ; // 2 chars for borders on the left and right
71+ let base_title = format ! (
72+ "Characteristic {} / Service {}" ,
73+ char . char_name( ) ,
74+ char . service_name( )
75+ ) ;
76+
77+ let previous_button_style = if history. len ( ) < 2 || historical_view_index == Some ( 0 ) {
78+ Style :: default ( ) . fg ( Color :: DarkGray )
79+ } else {
80+ Style :: default ( )
81+ } ;
82+
83+ let next_button_style = if history. len ( ) < 2 || historical_view_index. is_none ( ) {
84+ Style :: default ( ) . fg ( Color :: DarkGray )
85+ } else {
86+ Style :: default ( )
87+ } ;
88+
89+ let not_enough_space = ( available_width as i32 ) . saturating_sub (
90+ PREVIOUS_BUTTON . len ( ) as i32 + NEXT_BUTTON . len ( ) as i32 + base_title. len ( ) as i32 ,
91+ ) < 0 ;
92+
93+ if not_enough_space {
94+ spans. push ( Span :: styled ( PREVIOUS_BUTTON_DENSE , previous_button_style) ) ;
95+ spans. push ( Span :: raw ( format ! ( "Char. {}" , char . char_name( ) ) ) ) ;
96+ spans. push ( Span :: styled ( NEXT_BUTTON_DENSE , next_button_style) ) ;
97+ } else {
98+ spans. push ( Span :: styled ( PREVIOUS_BUTTON , previous_button_style) ) ;
99+ spans. push ( Span :: raw ( base_title) ) ;
100+ spans. push ( Span :: styled ( NEXT_BUTTON , next_button_style) ) ;
101+ }
102+
103+ Title :: new ( spans)
104+ }
105+
55106impl AppRoute for ConnectionView {
56107 fn new ( ctx : std:: sync:: Arc < crate :: Ctx > ) -> Self
57108 where
@@ -67,21 +118,63 @@ impl AppRoute for ConnectionView {
67118 }
68119 }
69120
70- fn handle_input ( & mut self , key : & crossterm:: event:: KeyEvent ) {
121+ fn handle_input ( & mut self , key : & crossterm:: event:: KeyEvent ) -> HandleKeydownResult {
71122 match key. code {
72123 KeyCode :: Char ( 'f' ) => {
73124 self . float_numbers = !self . float_numbers ;
74- return ;
125+ return HandleKeydownResult :: Handled ;
75126 }
76127 KeyCode :: Char ( 'u' ) => {
77128 self . unsigned_numbers = !self . unsigned_numbers ;
78- return ;
129+ return HandleKeydownResult :: Handled ;
79130 }
80131 _ => ( ) ,
81132 }
82133
83134 let active_route = self . ctx . get_active_route ( ) ;
84135
136+ if let Route :: CharacteristicView {
137+ historical_view_index,
138+ history,
139+ ..
140+ } = active_route. deref ( )
141+ {
142+ let update_index = |new_index| {
143+ historical_view_index. write ( new_index) ;
144+ } ;
145+
146+ match (
147+ key. code ,
148+ history. read ( ) . ok ( ) . as_ref ( ) ,
149+ historical_view_index. deref ( ) . read ( ) ,
150+ ) {
151+ ( KeyCode :: Left , _, Some ( current_historical_index) ) => {
152+ if current_historical_index >= 1 {
153+ update_index ( current_historical_index - 1 ) ;
154+ }
155+ }
156+ ( KeyCode :: Left , Some ( history) , None ) => {
157+ update_index ( history. len ( ) - 1 ) ;
158+ }
159+ ( KeyCode :: Right , Some ( history) , Some ( current_historical_index) )
160+ if current_historical_index == history. len ( ) - 2 =>
161+ {
162+ historical_view_index. annulate ( ) ;
163+ }
164+ ( KeyCode :: Right , Some ( history) , Some ( current_historical_index) ) => {
165+ if history. len ( ) > current_historical_index {
166+ update_index ( current_historical_index + 1 ) ;
167+ }
168+ }
169+ _ => ( ) ,
170+ }
171+
172+ if matches ! ( key. code, KeyCode :: Left | KeyCode :: Right ) {
173+ // on this view we always handing arrows as history navigation and preventing other view's actions
174+ return HandleKeydownResult :: Handled ;
175+ }
176+ }
177+
85178 match ( active_route. deref ( ) , self . clipboard . as_mut ( ) ) {
86179 ( Route :: CharacteristicView { characteristic, .. } , Some ( clipboard) ) => match key. code {
87180 KeyCode :: Char ( 'c' ) => {
@@ -96,6 +189,8 @@ impl AppRoute for ConnectionView {
96189 } ,
97190 _ => ( ) ,
98191 }
192+
193+ HandleKeydownResult :: Continue
99194 }
100195
101196 fn render (
@@ -105,35 +200,47 @@ impl AppRoute for ConnectionView {
105200 f : & mut tui:: Frame < super :: TerminalBackend > ,
106201 ) -> crate :: error:: Result < ( ) > {
107202 let active_route = self . ctx . active_route . read ( ) ?;
108- let ( _, characteristic, value) = if let Route :: CharacteristicView {
109- peripheral,
110- characteristic,
111- value,
112- } = active_route. deref ( )
113- {
114- ( peripheral, characteristic, value)
115- } else {
116- tracing:: error!(
117- "ConnectionView::render called when active route is not CharacteristicView"
118- ) ;
203+ let ( _, characteristic, history, historical_view_index) =
204+ if let Route :: CharacteristicView {
205+ peripheral,
206+ characteristic,
207+ history,
208+ historical_view_index,
209+ } = active_route. deref ( )
210+ {
211+ ( peripheral, characteristic, history, historical_view_index)
212+ } else {
213+ tracing:: error!(
214+ "ConnectionView::render called when active route is not CharacteristicView"
215+ ) ;
119216
120- return Ok ( ( ) ) ;
121- } ;
217+ return Ok ( ( ) ) ;
218+ } ;
122219
123- let mut text = vec ! [ ] ;
124- text . push ( Line :: from ( "" ) ) ;
220+ let history = history . read ( ) ? ;
221+ let historical_index = historical_view_index . deref ( ) . read ( ) ;
125222
126- text . push ( Line :: from ( format ! (
127- "Properties: {}" ,
128- display_properties ( characteristic . ble_characteristic . properties )
129- ) ) ) ;
223+ let active_value = match historical_index {
224+ Some ( index ) => history . get ( index ) ,
225+ None => history . last ( ) ,
226+ } ;
130227
131- if let Some ( value) = value. read ( ) . unwrap ( ) . as_ref ( ) {
228+ let mut text = vec ! [ ] ;
229+ if let Some ( value) = active_value. as_ref ( ) {
132230 text. push ( Line :: from ( "" ) ) ;
133231
134232 text. push ( Line :: from ( format ! (
135- "Last updated: {}" ,
136- value. time. format( "%Y-%m-%d %H:%M:%S" )
233+ "{label}: {}" ,
234+ value. time. format( "%Y-%m-%d %H:%M:%S" ) ,
235+ label = if let Some ( index) = historical_index {
236+ format!(
237+ "Historical data ({} of {}) viewing data of\n " ,
238+ index + 1 ,
239+ history. len( )
240+ )
241+ } else {
242+ "Latest value received" . to_owned( )
243+ } ,
137244 ) ) ) ;
138245
139246 text. push ( Line :: from ( "" ) ) ;
@@ -210,10 +317,12 @@ impl AppRoute for ConnectionView {
210317 . block ( tui:: widgets:: Block :: from ( BlendrBlock {
211318 route_active,
212319 focused : route_active,
213- title : format ! (
214- "Characteristic {} / Service {}" ,
215- characteristic. char_name( ) ,
216- characteristic. service_name( )
320+ title_alignment : tui:: layout:: Alignment :: Center ,
321+ title : render_title_with_navigation_controls (
322+ & area,
323+ characteristic,
324+ historical_index,
325+ & history,
217326 ) ,
218327 ..Default :: default ( )
219328 } ) ) ;
@@ -222,6 +331,8 @@ impl AppRoute for ConnectionView {
222331 if chunks[ 1 ] . height > 0 {
223332 f. render_widget (
224333 block:: render_help ( [
334+ Some ( ( "<-" , "Previous value" , false ) ) ,
335+ Some ( ( "->" , "Next value" , false ) ) ,
225336 Some ( ( "d" , "Disconnect from device" , false ) ) ,
226337 Some ( ( "u" , "Parse numeric as unsigned" , self . unsigned_numbers ) ) ,
227338 Some ( ( "f" , "Parse numeric as floats" , self . float_numbers ) ) ,
0 commit comments