aboutsummaryrefslogtreecommitdiff
path: root/src/ui/status.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/status.rs')
-rw-r--r--src/ui/status.rs135
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,
+ }
+}