diff options
Diffstat (limited to 'src/ui/status.rs')
| -rw-r--r-- | src/ui/status.rs | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/src/ui/status.rs b/src/ui/status.rs new file mode 100644 index 00000000..2eece8a0 --- /dev/null +++ b/src/ui/status.rs @@ -0,0 +1,135 @@ +use ratatui::{ + layout::{Constraint, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, Clear, Paragraph}, + Frame, +}; + +use super::widgets::panel_contrast_fg; +use crate::{ + app::state::{Palette, ToastKind, ToastNotification}, + detect::AgentState, +}; + +pub(super) fn render_toast_notification( + frame: &mut Frame, + area: Rect, + toast: &ToastNotification, + offset_for_warning: bool, + p: &Palette, +) { + let dot_color = match toast.kind { + ToastKind::NeedsAttention => p.red, + ToastKind::Finished => p.blue, + ToastKind::UpdateInstalled => p.accent, + }; + let content_width = (toast.title.len().max(toast.context.len()) as u16) + 4; + let width = content_width.saturating_add(2).min(area.width); + let height = 4u16.min(area.height); + let x = area.x + area.width.saturating_sub(width); + let y = area.y + + area + .height + .saturating_sub(height + if offset_for_warning { 1 } else { 0 }); + let toast_area = Rect::new(x, y, width, height); + + frame.render_widget(Clear, toast_area); + let block = Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(p.overlay0)) + .style(Style::default().bg(p.panel_bg)); + let inner = block.inner(toast_area); + frame.render_widget(block, toast_area); + + if inner.height < 2 { + return; + } + + let [title_row, context_row] = + Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(inner); + + let title = Line::from(vec![ + Span::styled("●", Style::default().fg(dot_color)), + Span::raw(" "), + Span::styled( + &toast.title, + Style::default().fg(p.text).add_modifier(Modifier::BOLD), + ), + ]); + let context = Line::from(vec![ + Span::styled(" ", Style::default().fg(p.overlay0)), + Span::styled(&toast.context, Style::default().fg(p.overlay0)), + ]); + + frame.render_widget(Paragraph::new(title), title_row); + frame.render_widget(Paragraph::new(context), context_row); +} + +pub(super) fn render_config_diagnostic(frame: &mut Frame, area: Rect, message: &str, p: &Palette) { + let text = format!(" config warning: {message} "); + let width = text.len() as u16 + 2; + let notif_area = Rect::new( + area.x + area.width.saturating_sub(width.min(area.width)), + area.y, + width.min(area.width), + 1, + ); + + frame.render_widget(Clear, notif_area); + frame.render_widget( + Paragraph::new(Span::styled( + text, + Style::default() + .fg(panel_contrast_fg(p)) + .bg(p.yellow) + .add_modifier(Modifier::BOLD), + )), + notif_area, + ); +} + +pub(super) fn state_dot(state: AgentState, seen: bool, p: &Palette) -> (&'static str, Style) { + match (state, seen) { + (AgentState::Blocked, _) => ("●", Style::default().fg(p.red)), + (AgentState::Working, _) => ("●", Style::default().fg(p.yellow)), + (AgentState::Idle, false) => ("●", Style::default().fg(p.teal)), + (AgentState::Idle, true) => ("○", Style::default().fg(p.green)), + (AgentState::Unknown, _) => ("·", Style::default().fg(p.overlay0)), + } +} + +pub(super) fn agent_icon( + state: AgentState, + seen: bool, + tick: u32, + p: &Palette, +) -> (&'static str, Style) { + match (state, seen) { + (AgentState::Blocked, _) => ("◉", Style::default().fg(p.red)), + (AgentState::Working, _) => (super::spinner_frame(tick), Style::default().fg(p.yellow)), + (AgentState::Idle, false) => ("●", Style::default().fg(p.teal)), + (AgentState::Idle, true) => ("✓", Style::default().fg(p.green)), + (AgentState::Unknown, _) => ("○", Style::default().fg(p.overlay0)), + } +} + +pub(super) fn state_label(state: AgentState, seen: bool) -> &'static str { + match (state, seen) { + (AgentState::Blocked, _) => "blocked", + (AgentState::Working, _) => "working", + (AgentState::Idle, false) => "done", + (AgentState::Idle, true) => "idle", + (AgentState::Unknown, _) => "idle", + } +} + +pub(super) fn state_label_color(state: AgentState, seen: bool, p: &Palette) -> Color { + match (state, seen) { + (AgentState::Blocked, _) => p.red, + (AgentState::Working, _) => p.yellow, + (AgentState::Idle, false) => p.teal, + (AgentState::Idle, true) => p.green, + (AgentState::Unknown, _) => p.overlay0, + } +} |
