@@ -17,7 +17,8 @@ use crate::{
17
17
MAX_EXERCISE_NAME_LEN ,
18
18
} ;
19
19
20
- const MAX_SCROLL_PADDING : usize = 5 ;
20
+ use super :: scroll_state:: ScrollState ;
21
+
21
22
// +1 for column padding.
22
23
const SPACE : & [ u8 ] = & [ b' ' ; MAX_EXERCISE_NAME_LEN + 1 ] ;
23
24
@@ -39,19 +40,14 @@ pub struct ListState<'a> {
39
40
/// Footer message to be displayed if not empty.
40
41
pub message : String ,
41
42
app_state : & ' a mut AppState ,
43
+ scroll_state : ScrollState ,
42
44
name_col_width : usize ,
43
45
filter : Filter ,
44
- n_rows_with_filter : usize ,
45
- /// Selected row out of the filtered ones.
46
- selected_row : Option < usize > ,
47
- row_offset : usize ,
48
46
term_width : u16 ,
49
47
term_height : u16 ,
50
48
separator_line : Vec < u8 > ,
51
49
narrow_term : bool ,
52
50
show_footer : bool ,
53
- max_n_rows_to_display : usize ,
54
- scroll_padding : usize ,
55
51
}
56
52
57
53
impl < ' a > ListState < ' a > {
@@ -70,50 +66,29 @@ impl<'a> ListState<'a> {
70
66
let n_rows_with_filter = app_state. exercises ( ) . len ( ) ;
71
67
let selected = app_state. current_exercise_ind ( ) ;
72
68
69
+ let ( width, height) = terminal:: size ( ) ?;
70
+ let scroll_state = ScrollState :: new ( n_rows_with_filter, Some ( selected) , 5 ) ;
71
+
73
72
let mut slf = Self {
74
73
message : String :: with_capacity ( 128 ) ,
75
74
app_state,
75
+ scroll_state,
76
76
name_col_width,
77
77
filter,
78
- n_rows_with_filter,
79
- selected_row : Some ( selected) ,
80
- row_offset : selected. saturating_sub ( MAX_SCROLL_PADDING ) ,
81
78
// Set by `set_term_size`
82
79
term_width : 0 ,
83
80
term_height : 0 ,
84
81
separator_line : Vec :: new ( ) ,
85
82
narrow_term : false ,
86
83
show_footer : true ,
87
- max_n_rows_to_display : 0 ,
88
- scroll_padding : 0 ,
89
84
} ;
90
85
91
- let ( width, height) = terminal:: size ( ) ?;
92
86
slf. set_term_size ( width, height) ;
93
87
slf. draw ( stdout) ?;
94
88
95
89
Ok ( slf)
96
90
}
97
91
98
- fn update_offset ( & mut self ) {
99
- let Some ( selected) = self . selected_row else {
100
- return ;
101
- } ;
102
-
103
- let min_offset = ( selected + self . scroll_padding )
104
- . saturating_sub ( self . max_n_rows_to_display . saturating_sub ( 1 ) ) ;
105
- let max_offset = selected. saturating_sub ( self . scroll_padding ) ;
106
- let global_max_offset = self
107
- . n_rows_with_filter
108
- . saturating_sub ( self . max_n_rows_to_display ) ;
109
-
110
- self . row_offset = self
111
- . row_offset
112
- . max ( min_offset)
113
- . min ( max_offset)
114
- . min ( global_max_offset) ;
115
- }
116
-
117
92
pub fn set_term_size ( & mut self , width : u16 , height : u16 ) {
118
93
self . term_width = width;
119
94
self . term_height = height;
@@ -124,7 +99,7 @@ impl<'a> ListState<'a> {
124
99
125
100
let wide_help_footer_width = 95 ;
126
101
// The help footer is shorter when nothing is selected.
127
- self . narrow_term = width < wide_help_footer_width && self . selected_row . is_some ( ) ;
102
+ self . narrow_term = width < wide_help_footer_width && self . scroll_state . selected ( ) . is_some ( ) ;
128
103
129
104
let header_height = 1 ;
130
105
// 2 separator, 1 progress bar, 1-2 footer message.
@@ -135,13 +110,10 @@ impl<'a> ListState<'a> {
135
110
self . separator_line = "─" . as_bytes ( ) . repeat ( width as usize ) ;
136
111
}
137
112
138
- self . max_n_rows_to_display = height
139
- . saturating_sub ( header_height + u16:: from ( self . show_footer ) * footer_height)
140
- as usize ;
141
-
142
- self . scroll_padding = ( self . max_n_rows_to_display / 4 ) . min ( MAX_SCROLL_PADDING ) ;
143
-
144
- self . update_offset ( ) ;
113
+ self . scroll_state . set_max_n_rows_to_display (
114
+ height. saturating_sub ( header_height + u16:: from ( self . show_footer ) * footer_height)
115
+ as usize ,
116
+ ) ;
145
117
}
146
118
147
119
fn draw_rows (
@@ -150,15 +122,16 @@ impl<'a> ListState<'a> {
150
122
filtered_exercises : impl Iterator < Item = ( usize , & ' a Exercise ) > ,
151
123
) -> io:: Result < usize > {
152
124
let current_exercise_ind = self . app_state . current_exercise_ind ( ) ;
125
+ let row_offset = self . scroll_state . offset ( ) ;
153
126
let mut n_displayed_rows = 0 ;
154
127
155
128
for ( exercise_ind, exercise) in filtered_exercises
156
- . skip ( self . row_offset )
157
- . take ( self . max_n_rows_to_display )
129
+ . skip ( row_offset)
130
+ . take ( self . scroll_state . max_n_rows_to_display ( ) )
158
131
{
159
132
let mut writer = MaxLenWriter :: new ( stdout, self . term_width as usize ) ;
160
133
161
- if self . selected_row == Some ( self . row_offset + n_displayed_rows) {
134
+ if self . scroll_state . selected ( ) == Some ( row_offset + n_displayed_rows) {
162
135
writer. stdout . queue ( SetBackgroundColor ( Color :: Rgb {
163
136
r : 40 ,
164
137
g : 40 ,
@@ -225,7 +198,7 @@ impl<'a> ListState<'a> {
225
198
Filter :: None => self . draw_rows ( stdout, iter) ?,
226
199
} ;
227
200
228
- for _ in 0 ..self . max_n_rows_to_display - n_displayed_rows {
201
+ for _ in 0 ..self . scroll_state . max_n_rows_to_display ( ) - n_displayed_rows {
229
202
next_ln ( stdout) ?;
230
203
}
231
204
@@ -247,7 +220,7 @@ impl<'a> ListState<'a> {
247
220
let mut writer = MaxLenWriter :: new ( stdout, self . term_width as usize ) ;
248
221
if self . message . is_empty ( ) {
249
222
// Help footer message
250
- if self . selected_row . is_some ( ) {
223
+ if self . scroll_state . selected ( ) . is_some ( ) {
251
224
writer. write_str ( "↓/j ↑/k home/g end/G | <c>ontinue at | <r>eset exercise" ) ?;
252
225
if self . narrow_term {
253
226
next_ln ( stdout) ?;
@@ -298,13 +271,8 @@ impl<'a> ListState<'a> {
298
271
stdout. queue ( EndSynchronizedUpdate ) ?. flush ( )
299
272
}
300
273
301
- fn set_selected ( & mut self , selected : usize ) {
302
- self . selected_row = Some ( selected) ;
303
- self . update_offset ( ) ;
304
- }
305
-
306
274
fn update_rows ( & mut self ) {
307
- self . n_rows_with_filter = match self . filter {
275
+ let n_rows = match self . filter {
308
276
Filter :: Done => self
309
277
. app_state
310
278
. exercises ( )
@@ -320,15 +288,7 @@ impl<'a> ListState<'a> {
320
288
Filter :: None => self . app_state . exercises ( ) . len ( ) ,
321
289
} ;
322
290
323
- if self . n_rows_with_filter == 0 {
324
- self . selected_row = None ;
325
- return ;
326
- }
327
-
328
- self . set_selected (
329
- self . selected_row
330
- . map_or ( 0 , |selected| selected. min ( self . n_rows_with_filter - 1 ) ) ,
331
- ) ;
291
+ self . scroll_state . set_n_rows ( n_rows) ;
332
292
}
333
293
334
294
#[ inline]
@@ -341,28 +301,24 @@ impl<'a> ListState<'a> {
341
301
self . update_rows ( ) ;
342
302
}
343
303
304
+ #[ inline]
344
305
pub fn select_next ( & mut self ) {
345
- if let Some ( selected) = self . selected_row {
346
- self . set_selected ( ( selected + 1 ) . min ( self . n_rows_with_filter - 1 ) ) ;
347
- }
306
+ self . scroll_state . select_next ( ) ;
348
307
}
349
308
309
+ #[ inline]
350
310
pub fn select_previous ( & mut self ) {
351
- if let Some ( selected) = self . selected_row {
352
- self . set_selected ( selected. saturating_sub ( 1 ) ) ;
353
- }
311
+ self . scroll_state . select_previous ( ) ;
354
312
}
355
313
314
+ #[ inline]
356
315
pub fn select_first ( & mut self ) {
357
- if self . n_rows_with_filter > 0 {
358
- self . set_selected ( 0 ) ;
359
- }
316
+ self . scroll_state . select_first ( ) ;
360
317
}
361
318
319
+ #[ inline]
362
320
pub fn select_last ( & mut self ) {
363
- if self . n_rows_with_filter > 0 {
364
- self . set_selected ( self . n_rows_with_filter - 1 ) ;
365
- }
321
+ self . scroll_state . select_last ( ) ;
366
322
}
367
323
368
324
fn selected_to_exercise_ind ( & self , selected : usize ) -> Result < usize > {
@@ -390,7 +346,7 @@ impl<'a> ListState<'a> {
390
346
}
391
347
392
348
pub fn reset_selected ( & mut self ) -> Result < ( ) > {
393
- let Some ( selected) = self . selected_row else {
349
+ let Some ( selected) = self . scroll_state . selected ( ) else {
394
350
self . message . push_str ( "Nothing selected to reset!" ) ;
395
351
return Ok ( ( ) ) ;
396
352
} ;
@@ -408,7 +364,7 @@ impl<'a> ListState<'a> {
408
364
409
365
// Return `true` if there was something to select.
410
366
pub fn selected_to_current_exercise ( & mut self ) -> Result < bool > {
411
- let Some ( selected) = self . selected_row else {
367
+ let Some ( selected) = self . scroll_state . selected ( ) else {
412
368
self . message . push_str ( "Nothing selected to continue at!" ) ;
413
369
return Ok ( false ) ;
414
370
} ;
0 commit comments