diff --git a/src/main.rs b/src/main.rs index c20de04..58526be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -295,10 +295,28 @@ fn draw(f: &mut Frame, app: &App) { draw_title(f, main_chunks[0], app); draw_content(f, main_chunks[1], app); draw_tile_bar(f, main_chunks[2], app); - draw_bottom_bar(f, main_chunks[3], app, is_cmd_mode); + + if is_cmd_mode { + draw_command_bar(f, main_chunks[3], app); + } else { + draw_status(f, main_chunks[3], app); + } // Overlays (rendered last so they appear on top) - draw_overlays(f, main_chunks[1], app); + if matches!(app.mode, AppMode::Help) { + f.render_widget(HelpWidget, size); + } + if matches!(app.mode, AppMode::ImportWizard) { + if let Some(wizard) = &app.wizard { + f.render_widget(ImportWizardWidget::new(wizard), size); + } + } + if matches!(app.mode, AppMode::ExportPrompt { .. }) { + draw_export_prompt(f, size, app); + } + if app.is_empty_model() && matches!(app.mode, AppMode::Normal | AppMode::CommandMode { .. }) { + draw_welcome(f, main_chunks[1], app); + } } fn draw_title(f: &mut Frame, area: Rect, app: &App) { @@ -312,8 +330,17 @@ fn draw_title(f: &mut Frame, area: Rect, app: &App) { .unwrap_or_default(); let title = format!(" improvise · {}{}{} ", app.model.name, file, dirty); let right = " ?:help :q quit "; - let line = fill_line(title, right, area.width); - f.render_widget(Paragraph::new(line).style(title_bar_style()), area); + let pad = " ".repeat((area.width as usize).saturating_sub(title.len() + right.len())); + let line = format!("{title}{pad}{right}"); + f.render_widget( + Paragraph::new(line).style( + Style::default() + .fg(Color::Black) + .bg(Color::Blue) + .add_modifier(Modifier::BOLD), + ), + area, + ); } fn draw_content(f: &mut Frame, area: Rect, app: &App) { @@ -379,22 +406,51 @@ fn draw_tile_bar(f: &mut Frame, area: Rect, app: &App) { } fn draw_status(f: &mut Frame, area: Rect, app: &App) { - let mode_badge = mode_badge_text(&app.mode); - let search_part = search_indicator(&app.search_query, app.search_mode); + let mode_badge = match &app.mode { + AppMode::Normal => "NORMAL", + AppMode::Editing { .. } => "INSERT", + AppMode::FormulaEdit { .. } => "FORMULA", + AppMode::FormulaPanel => "FORMULAS", + AppMode::CategoryPanel => "CATEGORIES", + AppMode::CategoryAdd { .. } => "NEW CATEGORY", + AppMode::ItemAdd { .. } => "ADD ITEMS", + AppMode::ViewPanel => "VIEWS", + AppMode::TileSelect { .. } => "TILES", + AppMode::ImportWizard => "IMPORT", + AppMode::ExportPrompt { .. } => "EXPORT", + AppMode::CommandMode { .. } => "COMMAND", + AppMode::Help => "HELP", + AppMode::Quit => "QUIT", + }; + + let search_part = if app.search_mode { + format!(" /{}▌", app.search_query) + } else { + String::new() + }; + let msg = if !app.status_msg.is_empty() { app.status_msg.as_str() } else { app.hint_text() }; + let yank_indicator = if app.yanked.is_some() { " [yank]" } else { "" }; let view_badge = format!(" {}{} ", app.model.active_view, yank_indicator); let left = format!(" {mode_badge}{search_part} {msg}"); - let line = fill_line(left, &view_badge, area.width); - f.render_widget( - Paragraph::new(line).style(status_bar_style(&app.mode)), - area, - ); + let right = view_badge; + let pad = " ".repeat((area.width as usize).saturating_sub(left.len() + right.len())); + let line = format!("{left}{pad}{right}"); + + let badge_style = match &app.mode { + AppMode::Editing { .. } => Style::default().fg(Color::Black).bg(Color::Green), + AppMode::CommandMode { .. } => Style::default().fg(Color::Black).bg(Color::Yellow), + AppMode::TileSelect { .. } => Style::default().fg(Color::Black).bg(Color::Magenta), + _ => Style::default().fg(Color::Black).bg(Color::DarkGray), + }; + + f.render_widget(Paragraph::new(line).style(badge_style), area); } fn draw_command_bar(f: &mut Frame, area: Rect, app: &App) { @@ -404,7 +460,10 @@ fn draw_command_bar(f: &mut Frame, area: Rect, app: &App) { "" }; let line = format!(":{buf}▌"); - f.render_widget(Paragraph::new(line).style(command_bar_style()), area); + f.render_widget( + Paragraph::new(line).style(Style::default().fg(Color::White).bg(Color::Black)), + area, + ); } fn draw_export_prompt(f: &mut Frame, area: Rect, app: &App) { @@ -413,15 +472,18 @@ fn draw_export_prompt(f: &mut Frame, area: Rect, app: &App) { } else { "" }; - let popup = centered_popup(area, 64, 3); - f.render_widget(Clear, popup); + let popup_w = 64u16.min(area.width); + let x = area.x + area.width.saturating_sub(popup_w) / 2; + let y = area.y + area.height / 2; + let popup_area = Rect::new(x, y, popup_w, 3); + f.render_widget(Clear, popup_area); let block = Block::default() .borders(Borders::ALL) .border_style(Style::default().fg(Color::Yellow)) .title(" Export CSV — path (Esc cancel) "); - let inner = block.inner(popup); - f.render_widget(block, popup); + let inner = block.inner(popup_area); + f.render_widget(block, popup_area); f.render_widget( Paragraph::new(format!("{buf}▌")).style(Style::default().fg(Color::Green)), inner, @@ -429,7 +491,12 @@ fn draw_export_prompt(f: &mut Frame, area: Rect, app: &App) { } fn draw_welcome(f: &mut Frame, area: Rect, _app: &App) { - let popup = centered_popup(area, 58, 20); + let w = 58u16.min(area.width.saturating_sub(4)); + let h = 20u16.min(area.height.saturating_sub(2)); + let x = area.x + area.width.saturating_sub(w) / 2; + let y = area.y + area.height.saturating_sub(h) / 2; + let popup = Rect::new(x, y, w, h); + f.render_widget(Clear, popup); let block = Block::default() @@ -439,35 +506,7 @@ fn draw_welcome(f: &mut Frame, area: Rect, _app: &App) { let inner = block.inner(popup); f.render_widget(block, popup); - let lines = welcome_lines(); - for (i, (text, style)) in lines.iter().enumerate() { - if i >= inner.height as usize { - break; - } - f.render_widget( - Paragraph::new(*text).style(*style), - Rect::new( - inner.x + 1, - inner.y + i as u16, - inner.width.saturating_sub(2), - 1, - ), - ); - } -} - -// ── Drawing Helpers ────────────────────────────────────────────────────────── - -fn title_bar_style() -> Style { - Style::default().fg(Color::Black).bg(Color::Blue).add_modifier(Modifier::BOLD) -} - -fn command_bar_style() -> Style { - Style::default().fg(Color::White).bg(Color::Black) -} - -fn welcome_lines() -> Vec<(&'static str, Style)> { - vec![ + let lines: &[(&str, Style)] = &[ ( "Multi-dimensional data modeling — in your terminal.", Style::default().fg(Color::White), @@ -475,7 +514,9 @@ fn welcome_lines() -> Vec<(&'static str, Style)> { ("", Style::default()), ( "Getting started", - Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD), + Style::default() + .fg(Color::Blue) + .add_modifier(Modifier::BOLD), ), ("", Style::default()), ( @@ -505,7 +546,9 @@ fn welcome_lines() -> Vec<(&'static str, Style)> { ("", Style::default()), ( "Navigation", - Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD), + Style::default() + .fg(Color::Blue) + .add_modifier(Modifier::BOLD), ), ("", Style::default()), ( @@ -526,80 +569,20 @@ fn welcome_lines() -> Vec<(&'static str, Style)> { Style::default(), ), (":q Quit", Style::default()), - ] -} + ]; -fn fill_line(left: String, right: &str, width: u16) -> String { - let pad = " ".repeat((width as usize).saturating_sub(left.len() + right.len())); - format!("{left}{pad}{right}") -} - -fn status_bar_style(mode: &AppMode) -> Style { - match mode { - AppMode::Editing { .. } => Style::default().fg(Color::Black).bg(Color::Green), - AppMode::CommandMode { .. } => Style::default().fg(Color::Black).bg(Color::Yellow), - AppMode::TileSelect { .. } => Style::default().fg(Color::Black).bg(Color::Magenta), - _ => Style::default().fg(Color::Black).bg(Color::DarkGray), - } -} - -fn mode_badge_text(mode: &AppMode) -> &'static str { - match mode { - AppMode::Normal => "NORMAL", - AppMode::Editing { .. } => "INSERT", - AppMode::FormulaEdit { .. } => "FORMULA", - AppMode::FormulaPanel => "FORMULAS", - AppMode::CategoryPanel => "CATEGORIES", - AppMode::CategoryAdd { .. } => "NEW CATEGORY", - AppMode::ItemAdd { .. } => "ADD ITEMS", - AppMode::ViewPanel => "VIEWS", - AppMode::TileSelect { .. } => "TILES", - AppMode::ImportWizard => "IMPORT", - AppMode::ExportPrompt { .. } => "EXPORT", - AppMode::CommandMode { .. } => "COMMAND", - AppMode::Help => "HELP", - AppMode::Quit => "QUIT", - } -} - -fn search_indicator(query: &str, active: bool) -> String { - if active { - format!(" /{}▌", query) - } else { - String::new() - } -} - -fn centered_popup(area: Rect, width: u16, height: u16) -> Rect { - let w = width.min(area.width); - let h = height.min(area.height); - let x = area.x + area.width.saturating_sub(w) / 2; - let y = area.y + area.height.saturating_sub(h) / 2; - Rect::new(x, y, w, h) -} - -fn draw_bottom_bar(f: &mut Frame, area: Rect, app: &App, is_cmd_mode: bool) { - if is_cmd_mode { - draw_command_bar(f, area, app); - } else { - draw_status(f, area, app); - } -} - -fn draw_overlays(f: &mut Frame, content_area: Rect, app: &App) { - let size = f.area(); - if matches!(app.mode, AppMode::Help) { - f.render_widget(HelpWidget, size); - } - if matches!(app.mode, AppMode::ImportWizard) { - if let Some(wizard) = &app.wizard { - f.render_widget(ImportWizardWidget::new(wizard), size); + for (i, (text, style)) in lines.iter().enumerate() { + if i >= inner.height as usize { + break; } - } - if matches!(app.mode, AppMode::ExportPrompt { .. }) { - draw_export_prompt(f, size, app); - } - if app.is_empty_model() && matches!(app.mode, AppMode::Normal | AppMode::CommandMode { .. }) { - draw_welcome(f, content_area, app); + f.render_widget( + Paragraph::new(*text).style(*style), + Rect::new( + inner.x + 1, + inner.y + i as u16, + inner.width.saturating_sub(2), + 1, + ), + ); } }