@@ -53,15 +53,10 @@ impl App {
5353 pub fn draw_settings ( & mut self , frame : & mut Frame ) {
5454
5555 // Padding
56- let padding = ratatui:: widgets:: Padding {
57- left : 1 ,
58- right : 1 ,
59- top : 0 ,
60- bottom : 0 ,
61- } ;
56+ let padding = ratatui:: widgets:: Padding { left : 1 , right : 1 , top : 0 , bottom : 0 } ;
6257
6358 // Calculate maximum available width for text
64- let available_width = self . layout . graph . width as usize - 1 ;
59+ let available_width = self . layout . graph . width . saturating_sub ( 1 ) as usize ;
6560 let max_text_width = available_width. saturating_sub ( 2 ) ;
6661
6762 // Credentials
@@ -78,36 +73,41 @@ impl App {
7873 lines. push ( Line :: from ( Span :: styled ( "commit heatmap (last year)" , Style :: default ( ) . fg ( self . theme . COLOR_TEXT ) ) ) . centered ( ) ) ;
7974 lines. push ( Line :: default ( ) ) ;
8075
81- let heatmap_width = self . heatmap [ 0 ] . len ( ) ;
76+ // Each heat cell is "X " - two columns
77+ let cell_width = 2 ;
78+
79+ // Borders
80+ let border_width = 8 ;
81+
82+ // Available width
83+ let usable_width = available_width. saturating_sub ( border_width) ;
84+
85+ // How many weeks fit horizontally
86+ let max_weeks_fit = ( usable_width / cell_width) . max ( 1 ) ;
87+
88+ let total_weeks = self . heatmap [ 0 ] . len ( ) ;
89+ let visible_weeks = max_weeks_fit. min ( total_weeks) ;
90+
91+ // Right align and keep most recent weeks
92+ let week_start = total_weeks. saturating_sub ( visible_weeks) ;
93+
94+ // Width used by the heatmap body excluding borders
95+ let heatmap_width = visible_weeks * cell_width;
8296
8397 // Top border
84- lines. push ( Line :: from (
85- Span :: styled (
86- format ! ( "╭{}╮" , "─" . repeat( heatmap_width + 2 ) ) ,
87- Style :: default ( ) . fg ( self . theme . COLOR_GREY_800 ) ,
88- )
89- ) . centered ( ) ) ;
98+ lines. push ( Line :: from ( Span :: styled ( format ! ( "╭{}╮" , "─" . repeat( heatmap_width + 2 ) ) , Style :: default ( ) . fg ( self . theme . COLOR_GREY_800 ) ) ) . centered ( ) ) ;
9099
91100 // Body
92101 for day in 0 ..7 {
93102 let mut spans = Vec :: new ( ) ;
94103 spans. push ( Span :: styled ( "│ " , Style :: default ( ) . fg ( self . theme . COLOR_GREY_800 ) ) ) ;
95- spans. extend (
96- self . heatmap [ day]
97- . iter ( )
98- . map ( |& count| heat_cell ( count, & self . theme ) )
99- ) ;
104+ spans. extend ( self . heatmap [ day] [ week_start..] . iter ( ) . map ( |& count| heat_cell ( count, & self . theme ) ) ) ;
100105 spans. push ( Span :: styled ( " │" , Style :: default ( ) . fg ( self . theme . COLOR_GREY_800 ) ) ) ;
101106 lines. push ( Line :: from ( spans) . centered ( ) ) ;
102107 }
103108
104109 // Bottom border
105- lines. push ( Line :: from (
106- Span :: styled (
107- format ! ( "╰{}╯" , "─" . repeat( heatmap_width + 2 ) ) ,
108- Style :: default ( ) . fg ( self . theme . COLOR_GREY_800 ) ,
109- )
110- ) . centered ( ) ) ;
110+ lines. push ( Line :: from ( Span :: styled ( format ! ( "╰{}╯" , "─" . repeat( heatmap_width + 2 ) ) , Style :: default ( ) . fg ( self . theme . COLOR_GREY_800 ) ) ) . centered ( ) ) ;
111111 lines. push ( Line :: default ( ) ) ;
112112 lines. push ( Line :: default ( ) ) ;
113113
@@ -171,31 +171,23 @@ impl App {
171171
172172 // Snap to nearest selectable line if needed
173173 if !self . settings_selections . contains ( & self . settings_selected ) {
174-
174+
175175 // Find nearest selectable line above or below
176176 let mut nearest = None ;
177177
178178 // Moving down
179179 if self . last_input_direction == Some ( Direction :: Down ) {
180- nearest = self . settings_selections . iter ( )
181- . copied ( )
182- . find ( |& i| i > self . settings_selected ) ;
180+ nearest = self . settings_selections . iter ( ) . copied ( ) . find ( |& i| i > self . settings_selected ) ;
183181 }
184182
185183 // Moving up
186184 if nearest. is_none ( ) && self . last_input_direction == Some ( Direction :: Up ) {
187- nearest = self . settings_selections . iter ( )
188- . rev ( )
189- . copied ( )
190- . find ( |& i| i < self . settings_selected ) ;
185+ nearest = self . settings_selections . iter ( ) . rev ( ) . copied ( ) . find ( |& i| i < self . settings_selected ) ;
191186 }
192187
193188 // Fallback to nearest by distance if neither direction flag is set
194189 if nearest. is_none ( ) {
195- nearest = self . settings_selections
196- . iter ( )
197- . min_by_key ( |& & i| i. abs_diff ( self . settings_selected ) )
198- . copied ( ) ;
190+ nearest = self . settings_selections . iter ( ) . min_by_key ( |& & i| i. abs_diff ( self . settings_selected ) ) . copied ( ) ;
199191 }
200192
201193 if let Some ( target) = nearest {
@@ -214,24 +206,28 @@ impl App {
214206 . map ( |( i, line) | {
215207 let absolute_idx = start + i;
216208 let mut item = line. clone ( ) ;
209+
210+ // Ensure there is at least one span
211+ if item. spans . is_empty ( ) {
212+ item. spans . push ( Span :: raw ( " " ) ) ;
213+ }
214+
215+ // Highlight the selected line if focused
217216 if absolute_idx == self . settings_selected && self . focus == Focus :: Viewport {
218- let spans: Vec < Span > = item. clone ( ) . spans . iter ( ) . map ( |span| {
217+ let spans: Vec < Span > = item. spans . iter ( ) . map ( |span| {
219218 let mut style = span. style ;
220219 style = style. bg ( self . theme . COLOR_GREY_800 ) ;
221220 Span :: styled ( span. content . clone ( ) , style)
222221 } ) . collect ( ) ;
223222 item = Line :: from ( spans) . centered ( ) ;
224223 }
224+
225225 ListItem :: from ( item)
226226 } )
227227 . collect ( ) ;
228228
229229 // Setup the list
230- let list = List :: new ( list_items)
231- . block (
232- Block :: default ( )
233- . padding ( padding)
234- ) ;
230+ let list = List :: new ( list_items) . block ( Block :: default ( ) . padding ( padding) ) ;
235231
236232 // Render the list
237233 frame. render_widget ( list, self . layout . graph ) ;
0 commit comments