use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::Style;
use ratatui::widgets::{Block, Borders, List, ListItem, ListState};
use crate::components::Component;
use crate::components::event_state::EventState;
use crate::components::events::{AppTx, InputEvent};
use crate::settings::themes::Theme;
use crate::settings::{SortFieldSetting, SortOrderSetting};
use super::{ClickMap, text_rect};
use ratatui::text::{Line, Span};
use unicode_width::UnicodeWidthStr;
pub struct SortingSection {
pub default_sort_field: SortFieldSetting,
pub default_sort_order: SortOrderSetting,
pub journal_sort_field: SortFieldSetting,
pub journal_sort_order: SortOrderSetting,
list_state: ListState,
clicks: ClickMap,
}
impl SortingSection {
pub fn new(
default_sort_field: SortFieldSetting,
default_sort_order: SortOrderSetting,
journal_sort_field: SortFieldSetting,
journal_sort_order: SortOrderSetting,
) -> Self {
let mut list_state = ListState::default();
list_state.select(Some(0));
Self {
default_sort_field,
default_sort_order,
journal_sort_field,
journal_sort_order,
list_state,
clicks: ClickMap::default(),
}
}
pub fn handle_mouse(
&mut self,
m: &ratatui::crossterm::event::MouseEvent,
) -> Option<ratatui::crossterm::event::KeyEvent> {
use ratatui::crossterm::event::KeyCode;
self.clicks
.resolve(m, self.list_state.selected(), Some(KeyCode::Enter))
.into_key(|row| self.list_state.select(Some(row)))
}
fn value_row(
&mut self,
inner: Rect,
line: usize,
row: usize,
label: &'static str,
value: &str,
theme: &Theme,
) -> ListItem<'static> {
let r = if (line as u16) < inner.height {
Rect::new(inner.x, inner.y + line as u16, inner.width, 1)
} else {
Rect::default()
};
let chip = format!("[{value}]");
self.clicks.row(r, row);
self.clicks.control(
text_rect(r, label.width() as u16, chip.width() as u16),
row,
ratatui::crossterm::event::KeyCode::Enter,
);
ListItem::new(Line::from(vec![
Span::raw(label),
Span::styled(chip, theme.action()),
]))
.style(Style::default().fg(theme.fg.to_ratatui()))
}
const ROW_COUNT: usize = 4;
fn cycle_field(f: SortFieldSetting) -> SortFieldSetting {
match f {
SortFieldSetting::Name => SortFieldSetting::Title,
SortFieldSetting::Title => SortFieldSetting::Name,
}
}
fn cycle_order(o: SortOrderSetting) -> SortOrderSetting {
match o {
SortOrderSetting::Ascending => SortOrderSetting::Descending,
SortOrderSetting::Descending => SortOrderSetting::Ascending,
}
}
fn field_label(f: SortFieldSetting) -> &'static str {
match f {
SortFieldSetting::Name => "Name",
SortFieldSetting::Title => "Title",
}
}
fn order_label(o: SortOrderSetting) -> &'static str {
match o {
SortOrderSetting::Ascending => "Ascending",
SortOrderSetting::Descending => "Descending",
}
}
}
impl Component for SortingSection {
fn handle_input(&mut self, event: &InputEvent, _tx: &AppTx) -> EventState {
let InputEvent::Key(key) = event else {
return EventState::NotConsumed;
};
let selected = self.list_state.selected().unwrap_or(0);
match key.code {
ratatui::crossterm::event::KeyCode::Up
| ratatui::crossterm::event::KeyCode::Char('k') => {
self.list_state
.select(Some((selected + Self::ROW_COUNT - 1) % Self::ROW_COUNT));
EventState::Consumed
}
ratatui::crossterm::event::KeyCode::Down
| ratatui::crossterm::event::KeyCode::Char('j') => {
self.list_state
.select(Some((selected + 1) % Self::ROW_COUNT));
EventState::Consumed
}
ratatui::crossterm::event::KeyCode::Enter
| ratatui::crossterm::event::KeyCode::Char(' ') => {
match selected {
0 => self.default_sort_field = Self::cycle_field(self.default_sort_field),
1 => self.default_sort_order = Self::cycle_order(self.default_sort_order),
2 => self.journal_sort_field = Self::cycle_field(self.journal_sort_field),
3 => self.journal_sort_order = Self::cycle_order(self.journal_sort_order),
_ => {}
}
EventState::Consumed
}
_ => EventState::NotConsumed,
}
}
fn render(&mut self, f: &mut Frame, rect: Rect, theme: &Theme, focused: bool) {
let border_style = theme.border_style(focused);
let outer = Block::default()
.title("Sorting")
.borders(Borders::ALL)
.border_style(border_style)
.style(theme.base_style());
let inner = outer.inner(rect);
f.render_widget(outer, rect);
let halves = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(4), Constraint::Length(4)])
.split(inner);
let selected = self.list_state.selected().unwrap_or(0);
let highlight = Style::default()
.fg(theme.selection_fg.to_ratatui())
.bg(theme.selection_bg.to_ratatui());
self.clicks.clear();
let default_block = Block::default()
.title("Default")
.borders(Borders::ALL)
.border_style(theme.border_style(focused && selected < 2))
.style(theme.base_style());
let default_inner = default_block.inner(halves[0]);
let default_items = vec![
self.value_row(
default_inner,
0,
0,
" Sort field: ",
Self::field_label(self.default_sort_field),
theme,
),
self.value_row(
default_inner,
1,
1,
" Sort order: ",
Self::order_label(self.default_sort_order),
theme,
),
];
let mut default_state = ListState::default();
default_state.select(if selected < 2 { Some(selected) } else { None });
f.render_stateful_widget(
List::new(default_items)
.block(default_block)
.highlight_style(highlight),
halves[0],
&mut default_state,
);
let journal_block = Block::default()
.title("Journal")
.borders(Borders::ALL)
.border_style(theme.border_style(focused && selected >= 2))
.style(theme.base_style());
let journal_inner = journal_block.inner(halves[1]);
let journal_items = vec![
self.value_row(
journal_inner,
0,
2,
" Sort field: ",
Self::field_label(self.journal_sort_field),
theme,
),
self.value_row(
journal_inner,
1,
3,
" Sort order: ",
Self::order_label(self.journal_sort_order),
theme,
),
];
let mut journal_state = ListState::default();
journal_state.select(if selected >= 2 {
Some(selected - 2)
} else {
None
});
f.render_stateful_widget(
List::new(journal_items)
.block(journal_block)
.highlight_style(highlight),
halves[1],
&mut journal_state,
);
}
}