summaryrefslogtreecommitdiff
path: root/src/ui/widgets.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-22 17:01:25 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-22 17:01:25 +0300
commit4b482f37a1e233b9aca6bf99b12e8b4da3e5c05e (patch)
tree69d1a56ad6335547eec9727691310cf96ebe9623 /src/ui/widgets.rs
parent5bd939fbfc92a8073d0eac11dfb02a31ea638b15 (diff)
feat: add terminal-delivered client notifications
Diffstat (limited to 'src/ui/widgets.rs')
-rw-r--r--src/ui/widgets.rs87
1 files changed, 78 insertions, 9 deletions
diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs
index 354b7884..ef416134 100644
--- a/src/ui/widgets.rs
+++ b/src/ui/widgets.rs
@@ -2,7 +2,7 @@ use ratatui::{
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
- widgets::{Block, Borders, Clear, Paragraph},
+ widgets::{Block, Borders, Clear, Paragraph, Wrap},
Frame,
};
@@ -60,14 +60,10 @@ pub(super) fn render_modal_shell(
}
pub(super) fn render_modal_header(frame: &mut Frame, area: Rect, title: &str, p: &Palette) {
- let line = Line::from(vec![
- Span::styled(
- title,
- Style::default().fg(p.text).add_modifier(Modifier::BOLD),
- ),
- Span::raw(" "),
- Span::styled("⬆", Style::default().fg(p.accent)),
- ]);
+ let line = Line::from(vec![Span::styled(
+ title,
+ Style::default().fg(p.text).add_modifier(Modifier::BOLD),
+ )]);
frame.render_widget(Paragraph::new(line), area);
}
@@ -180,6 +176,79 @@ pub(super) fn render_action_button(
);
}
+pub(crate) fn render_modal_description(frame: &mut Frame, area: Rect, text: &str, style: Style) {
+ frame.render_widget(
+ Paragraph::new(format!(" {text}"))
+ .style(style)
+ .wrap(Wrap { trim: false }),
+ area,
+ );
+}
+
+pub(crate) fn modal_choice_rows(area: Rect, count: usize, row_height: u16) -> Vec<Rect> {
+ let mut rows = Vec::with_capacity(count);
+ let mut y = area.y;
+ for _ in 0..count {
+ if y >= area.y + area.height {
+ break;
+ }
+ let remaining = area.y + area.height - y;
+ let height = row_height.min(remaining);
+ rows.push(Rect::new(area.x, y, area.width, height));
+ y = y.saturating_add(row_height);
+ }
+ rows
+}
+
+pub(crate) fn render_modal_choice_list<T>(
+ frame: &mut Frame,
+ area: Rect,
+ title: &str,
+ description: &str,
+ options: &[(&str, T)],
+ current_value: T,
+ selected_idx: usize,
+ p: &Palette,
+ row_height: u16,
+) where
+ T: Copy + PartialEq,
+{
+ let [desc_area, _, list_area] = Layout::vertical([
+ Constraint::Length(2),
+ Constraint::Length(1),
+ Constraint::Min(2),
+ ])
+ .areas::<3>(area);
+
+ render_modal_description(
+ frame,
+ desc_area,
+ description,
+ Style::default().fg(p.overlay1),
+ );
+
+ let rows = modal_choice_rows(list_area, options.len(), row_height);
+ for (idx, ((label, value), row)) in options.iter().zip(rows.iter()).enumerate() {
+ let is_active = *value == current_value;
+ let is_selected = idx == selected_idx;
+ let marker = if is_active { " ✓" } else { "" };
+ let style = if is_selected {
+ Style::default()
+ .bg(p.surface0)
+ .fg(p.text)
+ .add_modifier(Modifier::BOLD)
+ } else {
+ Style::default().fg(p.subtext0)
+ };
+ frame.render_widget(
+ Paragraph::new(format!(" {title}: {label}{marker}"))
+ .style(style)
+ .wrap(Wrap { trim: false }),
+ *row,
+ );
+ }
+}
+
pub(super) fn centered_button_row(
inner: Rect,
widths: &[u16],