use ratada::input::InputField;
use super::{Field, LABEL_WIDTH, MIXED, kind_label};
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Clear, Paragraph};
use crate::domain::sections::UNGROUPED;
use crate::theme::Skin;
use crate::tui::presentation::{FieldView, field_spans};
use crate::tui::skin::Colors;
use crate::tui::widgets::centered_rect;
struct LineCtx<'a> {
colors: &'a Colors,
skin: &'a Skin,
value_width: usize,
}
impl super::RepoForm {
pub fn render(&self, frame: &mut Frame, area: Rect, skin: &Skin) {
let colors = Colors::from_palette(&skin.palette);
let fields = self.fields();
let height = fields.len() as u16 + 4;
let rect = centered_rect(70, height, area);
frame.render_widget(Clear, rect);
let block = ratada::chrome::modal_block(skin, &self.title);
let inner_width = block.inner(rect).width as usize;
let ctx = LineCtx {
colors: &colors,
skin,
value_width: inner_width.saturating_sub(LABEL_WIDTH),
};
let mut lines: Vec<Line> = fields
.iter()
.enumerate()
.map(|(index, field)| self.field_line(*field, index, &ctx))
.collect();
lines.push(Line::raw(""));
lines.push(Line::from(Span::styled(
self.hint(),
Style::default().fg(colors.dim),
)));
let inner = block.inner(rect);
let total = lines.len();
let viewport = inner.height as usize;
let offset = ratada::nav::keep_visible(
ratada::nav::ScrollView {
total,
offset: 0,
viewport,
},
self.focus,
);
frame.render_widget(
Paragraph::new(lines)
.block(block)
.scroll((u16::try_from(offset).unwrap_or(0), 0)),
rect,
);
ratada::scroll::render_scrollbar(
frame,
inner,
skin,
ratada::nav::ScrollView {
total,
offset,
viewport,
},
);
}
fn hint(&self) -> &'static str {
if self.bulk {
"Tab field · \u{2190}\u{2192} kind · Space toggle · Enter on \
Section: pick · ^S save · Esc cancel"
} else {
"Tab field · \u{2190}\u{2192} kind · Space toggle · Enter on \
Section: pick · ^O path · ^S save · Esc cancel"
}
}
fn field_line(
&self,
field: Field,
index: usize,
ctx: &LineCtx,
) -> Line<'static> {
let colors = ctx.colors;
match field {
Field::Path => self.text_line("Path", &self.path, index, ctx),
Field::Name => self.text_line("Name", &self.name, index, ctx),
Field::Slug => self.text_line("Slug", &self.slug, index, ctx),
Field::Section => {
let label = self.section_label();
self.value_line("Section", &label, index, colors)
}
Field::Kind => {
self.choice_line("Kind", kind_label(self.kind), index, colors)
}
Field::Fav => self.fav_line(index, colors),
Field::Backup => self.backup_line(index, colors),
}
}
fn text_line(
&self,
label: &str,
input: &InputField,
index: usize,
ctx: &LineCtx,
) -> Line<'static> {
let mut spans = vec![self.label_span(label, index, ctx.colors)];
spans.extend(field_spans(FieldView {
field: input,
palette: &ctx.skin.palette,
width: ctx.value_width,
focused: index == self.focus,
}));
self.styled(spans, index, ctx.colors)
}
fn choice_line(
&self,
label: &str,
value: &str,
index: usize,
colors: &Colors,
) -> Line<'static> {
let spans = vec![
self.label_span(label, index, colors),
Span::styled(
format!("< {value} >"),
Style::default().fg(colors.accent),
),
];
self.styled(spans, index, colors)
}
fn value_line(
&self,
label: &str,
value: &str,
index: usize,
colors: &Colors,
) -> Line<'static> {
let spans = vec![
self.label_span(label, index, colors),
Span::styled(value.to_string(), Style::default().fg(colors.accent)),
];
self.styled(spans, index, colors)
}
fn fav_line(&self, index: usize, colors: &Colors) -> Line<'static> {
let mark = checkbox(self.fav, self.fav_mixed, self.fav_touched);
let spans = vec![
self.label_span("Fav", index, colors),
Span::raw(mark.to_string()),
];
self.styled(spans, index, colors)
}
fn backup_line(&self, index: usize, colors: &Colors) -> Line<'static> {
let mark = checkbox(
self.include_in_backup,
self.backup_mixed,
self.backup_touched,
);
let spans = vec![
self.label_span("Backup", index, colors),
Span::raw(mark.to_string()),
];
self.styled(spans, index, colors)
}
pub(super) fn section_label(&self) -> String {
if self.bulk && self.section_mixed && !self.section_touched {
return MIXED.to_string();
}
self.section
.clone()
.unwrap_or_else(|| UNGROUPED.to_string())
}
fn label_span(
&self,
label: &str,
index: usize,
colors: &Colors,
) -> Span<'static> {
let style = if index == self.focus {
Style::default()
.fg(colors.accent)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(colors.dim)
};
Span::styled(format!("{label:<LABEL_WIDTH$}"), style)
}
fn styled(
&self,
spans: Vec<Span<'static>>,
index: usize,
colors: &Colors,
) -> Line<'static> {
let line = Line::from(spans);
if index == self.focus {
line.style(Style::default().bg(colors.selection_bg))
} else {
line
}
}
}
pub(super) fn checkbox(on: bool, mixed: bool, touched: bool) -> &'static str {
if mixed && !touched {
"[-]"
} else if on {
"[x]"
} else {
"[ ]"
}
}