33use crate :: {
44 events:: { AppEvent , AppState , EventHandler } ,
55 layout:: AppLayout ,
6- settings:: { Settings , SettingsAction , SettingsManager } ,
6+ settings:: { Settings , SettingsAction , SettingsManager , SettingsModalState } ,
77 theme:: { Element , Theme } ,
88} ;
99use crossterm:: {
@@ -17,8 +17,7 @@ use ratatui::{
1717 layout:: Alignment ,
1818 widgets:: { Block , Borders , Paragraph , Wrap } ,
1919} ;
20- use std:: { io, time:: Duration } ;
21- use tokio:: time;
20+ use std:: io;
2221
2322/// Main application state and manager
2423pub struct App {
@@ -34,6 +33,8 @@ pub struct App {
3433 event_handler : EventHandler ,
3534 /// Settings manager for configuration
3635 settings : SettingsManager ,
36+ /// Settings modal state for navigation
37+ modal_state : Option < SettingsModalState > ,
3738 /// Last known terminal size for resize detection
3839 last_size : Option < ( u16 , u16 ) > ,
3940}
@@ -48,6 +49,7 @@ impl App {
4849 layout : AppLayout :: new ( ) . expect ( "Failed to create layout" ) ,
4950 event_handler : EventHandler :: default ( ) ,
5051 settings : SettingsManager :: new ( ) ,
52+ modal_state : None ,
5153 last_size : None ,
5254 }
5355 }
@@ -69,48 +71,53 @@ impl App {
6971 self . previous_state = self . state . clone ( ) ;
7072 }
7173 self . state = AppState :: Settings ;
74+
75+ // Initialize modal state with current theme
76+ self . modal_state = Some ( SettingsModalState :: new ( self . settings . get ( ) . theme_variant ) ) ;
7277 }
7378
7479 /// Exit settings modal and return to previous state
7580 pub fn exit_settings ( & mut self ) {
7681 self . state = self . previous_state . clone ( ) ;
82+ self . modal_state = None ;
7783 }
7884
7985 /// Main application run loop with proper async event handling
8086 pub async fn run < B : Backend > (
8187 & mut self ,
8288 terminal : & mut Terminal < B > ,
8389 ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
84- let mut interval = time:: interval ( Duration :: from_millis ( 16 ) ) ; // ~60 FPS
90+ // Initial render
91+ terminal. draw ( |f| self . draw ( f) ) ?;
8592
8693 loop {
87- // Handle the render/update cycle
88- tokio:: select! {
89- _ = interval. tick( ) => {
90- // Render the UI
91- terminal. draw( |f| self . draw( f) ) ?;
92-
93- // Check if we should quit
94- if self . should_quit( ) {
95- break ;
96- }
97- }
98-
99- // Handle input events
100- event_result = {
101- let event_handler = self . event_handler. clone( ) ;
102- tokio:: task:: spawn_blocking( move || event_handler. next_event( ) )
103- } => {
104- match event_result? {
105- Ok ( event) => {
106- self . handle_event( event) ;
107- }
108- Err ( e) => {
109- self . state = AppState :: Error ( format!( "Input error: {}" , e) ) ;
94+ // Handle input events - this will block until an event occurs
95+ let event_result = {
96+ let event_handler = self . event_handler . clone ( ) ;
97+ tokio:: task:: spawn_blocking ( move || event_handler. next_event ( ) )
98+ } . await ;
99+
100+ match event_result? {
101+ Ok ( event) => {
102+ // Only handle events that aren't None
103+ if event != AppEvent :: None {
104+ self . handle_event ( event) ;
105+
106+ // Only redraw after handling a real event
107+ terminal. draw ( |f| self . draw ( f) ) ?;
108+
109+ // Check if we should quit after handling the event
110+ if self . should_quit ( ) {
110111 break ;
111112 }
112113 }
113114 }
115+ Err ( e) => {
116+ self . state = AppState :: Error ( format ! ( "Input error: {}" , e) ) ;
117+ // Redraw to show error state
118+ terminal. draw ( |f| self . draw ( f) ) ?;
119+ break ;
120+ }
114121 }
115122 }
116123
@@ -135,6 +142,42 @@ impl App {
135142 self . state = AppState :: Quitting ;
136143 }
137144 }
145+ AppEvent :: NavigateUp => {
146+ // Only handle navigation in settings modal
147+ if matches ! ( self . state, AppState :: Settings ) {
148+ if let Some ( ref mut modal_state) = self . modal_state {
149+ modal_state. navigate_up ( ) ;
150+ // Apply live theme preview
151+ let selected_theme = modal_state. selected_theme ( ) ;
152+ self . theme . set_variant ( selected_theme) ;
153+ }
154+ }
155+ }
156+ AppEvent :: NavigateDown => {
157+ // Only handle navigation in settings modal
158+ if matches ! ( self . state, AppState :: Settings ) {
159+ if let Some ( ref mut modal_state) = self . modal_state {
160+ modal_state. navigate_down ( ) ;
161+ // Apply live theme preview
162+ let selected_theme = modal_state. selected_theme ( ) ;
163+ self . theme . set_variant ( selected_theme) ;
164+ }
165+ }
166+ }
167+ AppEvent :: Select => {
168+ // Only handle selection in settings modal
169+ if matches ! ( self . state, AppState :: Settings ) {
170+ if let Some ( ref modal_state) = self . modal_state {
171+ let selected_theme = modal_state. selected_theme ( ) ;
172+ let action = SettingsAction :: ChangeTheme ( selected_theme) ;
173+ if let Err ( e) = self . handle_settings_action ( action) {
174+ self . state = AppState :: Error ( format ! ( "Settings error: {}" , e) ) ;
175+ }
176+ // Close modal after selection
177+ self . exit_settings ( ) ;
178+ }
179+ }
180+ }
138181 AppEvent :: SettingsAction ( action) => {
139182 // Handle settings actions and apply theme changes immediately
140183 if let Err ( e) = self . handle_settings_action ( action) {
@@ -180,6 +223,13 @@ impl App {
180223 self . render_header ( frame, layout_rects. header ) ;
181224 self . render_main_content ( frame, layout_rects. body ) ;
182225 self . render_footer ( frame, layout_rects. footer ) ;
226+
227+ // Render modal overlay if in settings state
228+ if matches ! ( self . state, AppState :: Settings ) {
229+ if let Some ( ref modal_state) = self . modal_state {
230+ crate :: settings:: render_settings_modal ( frame, size, modal_state, & self . theme ) ;
231+ }
232+ }
183233 }
184234
185235 /// Render the header section
@@ -320,7 +370,7 @@ impl App {
320370
321371 let footer_text = match self . state {
322372 AppState :: Main => format ! (
323- "ESC/q: Quit | T: Toggle Theme | , : Settings | Current: [{}] | Production v0.1.0" ,
373+ "ESC/q: Quit | T: Toggle Theme | S : Settings | Current: [{}] | Production v0.1.0" ,
324374 current_theme
325375 ) ,
326376 AppState :: Settings => format ! (
0 commit comments