diff --git a/src/app.rs b/src/app.rs index bc6841b..f12e4bf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -55,6 +55,12 @@ pub struct App { /// Kill confirmation: (selected_index, timestamp). Expires after 2s. kill_confirm: Option<(usize, Instant)>, pub theme: Theme, + /// Panel visibility toggles (1-5 keys, btop-style) + pub show_context: bool, + pub show_quota: bool, + pub show_tokens: bool, + pub show_ports: bool, + pub show_sessions: bool, } impl App { @@ -79,6 +85,22 @@ impl App { status_msg: None, kill_confirm: None, theme, + show_context: true, + show_quota: true, + show_tokens: true, + show_ports: true, + show_sessions: true, + } + } + + pub fn toggle_panel(&mut self, panel: u8) { + match panel { + 1 => self.show_context = !self.show_context, + 2 => self.show_quota = !self.show_quota, + 3 => self.show_tokens = !self.show_tokens, + 4 => self.show_ports = !self.show_ports, + 5 => self.show_sessions = !self.show_sessions, + _ => {} } } diff --git a/src/main.rs b/src/main.rs index a64e0d0..b0f23a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,6 +117,7 @@ fn run_app(terminal: &mut Terminal>, demo_mode: boo KeyCode::Char('x') if !demo_mode => app.kill_selected(), KeyCode::Char('X') if !demo_mode => app.kill_orphan_ports(), KeyCode::Char('t') => app.cycle_theme(), + KeyCode::Char(c @ '1'..='5') => app.toggle_panel(c as u8 - b'0'), KeyCode::Enter if !demo_mode => { if let Some(msg) = app.jump_to_session() { app.set_status(msg); diff --git a/src/ui/footer.rs b/src/ui/footer.rs index d72e845..8b5c4fe 100644 --- a/src/ui/footer.rs +++ b/src/ui/footer.rs @@ -26,6 +26,8 @@ pub(crate) fn draw_footer(f: &mut Frame, app: &App, area: Rect, theme: &Theme) { spans.push(Span::styled(" refresh ", Style::default().fg(theme.main_fg))); spans.push(Span::styled("t", Style::default().fg(theme.hi_fg))); spans.push(Span::styled(" theme ", Style::default().fg(theme.main_fg))); + spans.push(Span::styled("1-5", Style::default().fg(theme.hi_fg))); + spans.push(Span::styled(" panels ", Style::default().fg(theme.main_fg))); // Show transient status message or default "2s auto" let status_text = app.status_msg.as_ref() diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 2929a38..ff0699d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -288,24 +288,38 @@ pub fn draw(f: &mut Frame, app: &App) { const CONTEXT_MIN: u16 = 5; const FIXED: u16 = 2; // header + footer - let mid_h_ideal: u16 = 8; + // Check which mid panels are visible + let any_mid = app.show_quota || app.show_tokens || app.show_ports; + + let mid_h_ideal: u16 = if any_mid { 8 } else { 0 }; // Sessions: border(2) + header(1) + 2 rows/session + detail area - let sessions_ideal: u16 = (app.sessions.len() as u16 * 2 + 7).max(8); + let sessions_ideal: u16 = if app.show_sessions { + (app.sessions.len() as u16 * 2 + 7).max(8) + } else { + 0 + }; let context_ideal: u16 = (app.sessions.len() as u16 + 4).clamp(5, 10); let available = h.saturating_sub(FIXED); const MID_MIN: u16 = 6; - // 1) Reserve mid minimum first, then sessions get the rest - let mid_reserved = MID_MIN.min(available); + let mid_reserved = if any_mid { MID_MIN.min(available) } else { 0 }; let sessions_budget = available.saturating_sub(mid_reserved); - let sessions_h = sessions_ideal.min(sessions_budget).max(5.min(sessions_budget)); - // 2) Mid gets ideal from remaining (at least the reserved minimum) + let sessions_h = if app.show_sessions { + sessions_ideal.min(sessions_budget).max(5.min(sessions_budget)) + } else { + 0 + }; let after_sessions = available.saturating_sub(sessions_h); - let mid_h = mid_h_ideal.min(after_sessions).max(mid_reserved.min(after_sessions)); - // 3) Context only if sessions are fully satisfied and surplus >= CONTEXT_MIN + let mid_h = if any_mid { + mid_h_ideal.min(after_sessions).max(mid_reserved.min(after_sessions)) + } else { + 0 + }; let surplus = available.saturating_sub(sessions_h + mid_h); - let context_h = if sessions_h >= sessions_ideal && surplus >= CONTEXT_MIN { + let context_h = if app.show_context && sessions_h >= sessions_ideal && surplus >= CONTEXT_MIN { context_ideal.min(surplus) + } else if app.show_context && !app.show_sessions && surplus >= CONTEXT_MIN { + context_ideal.min(available.saturating_sub(mid_h)) } else { 0 }; @@ -319,7 +333,9 @@ pub fn draw(f: &mut Frame, app: &App) { if mid_h > 0 { constraints[n] = Constraint::Length(mid_h); n += 1; } - constraints[n] = Constraint::Min(sessions_h); n += 1; + if sessions_h > 0 { + constraints[n] = Constraint::Min(sessions_h); n += 1; + } constraints[n] = Constraint::Length(1); // footer n += 1; @@ -338,25 +354,35 @@ pub fn draw(f: &mut Frame, app: &App) { } if mid_h > 0 { + // Build constraints for visible mid panels only + let mut mid_constraints: Vec = Vec::new(); + if app.show_quota { mid_constraints.push(Constraint::Ratio(1, mid_constraints.len() as u32 + 1)); } + if app.show_tokens { mid_constraints.push(Constraint::Ratio(1, mid_constraints.len() as u32 + 1)); } + // projects always visible when mid row is shown + mid_constraints.push(Constraint::Ratio(1, mid_constraints.len() as u32 + 1)); + if app.show_ports { mid_constraints.push(Constraint::Ratio(1, mid_constraints.len() as u32 + 1)); } + + // Recalculate even distribution + let count = mid_constraints.len() as u32; + mid_constraints = mid_constraints.iter().map(|_| Constraint::Ratio(1, count)).collect(); + let mid_panels = Layout::default() .direction(Direction::Horizontal) - .constraints([ - Constraint::Percentage(25), // quota (rate limit) - Constraint::Percentage(25), // tokens - Constraint::Percentage(25), // projects - Constraint::Percentage(25), // ports - ]) + .constraints(mid_constraints) .split(chunks[idx]); - quota::draw_quota_panel(f, app, mid_panels[0], theme); - tokens::draw_tokens_panel(f, app, mid_panels[1], theme); - projects::draw_projects_panel(f, app, mid_panels[2], theme); - ports::draw_ports_panel(f, app, mid_panels[3], theme); + let mut mi = 0; + if app.show_quota { quota::draw_quota_panel(f, app, mid_panels[mi], theme); mi += 1; } + if app.show_tokens { tokens::draw_tokens_panel(f, app, mid_panels[mi], theme); mi += 1; } + projects::draw_projects_panel(f, app, mid_panels[mi], theme); mi += 1; + if app.show_ports { ports::draw_ports_panel(f, app, mid_panels[mi], theme); } idx += 1; } - sessions::draw_sessions_panel(f, app, chunks[idx], theme); - idx += 1; + if sessions_h > 0 { + sessions::draw_sessions_panel(f, app, chunks[idx], theme); + idx += 1; + } footer::draw_footer(f, app, chunks[idx], theme); }