Skip to main content

kimun_notes/components/preferences/
sorting_section.rs

1use ratatui::Frame;
2use ratatui::layout::{Constraint, Direction, Layout, Rect};
3use ratatui::style::Style;
4use ratatui::widgets::{Block, Borders, List, ListItem, ListState};
5
6use crate::components::Component;
7use crate::components::event_state::EventState;
8use crate::components::events::{AppTx, InputEvent};
9use crate::settings::themes::Theme;
10use crate::settings::{SortFieldSetting, SortOrderSetting};
11
12pub struct SortingSection {
13    pub default_sort_field: SortFieldSetting,
14    pub default_sort_order: SortOrderSetting,
15    pub journal_sort_field: SortFieldSetting,
16    pub journal_sort_order: SortOrderSetting,
17    list_state: ListState,
18}
19
20impl SortingSection {
21    pub fn new(
22        default_sort_field: SortFieldSetting,
23        default_sort_order: SortOrderSetting,
24        journal_sort_field: SortFieldSetting,
25        journal_sort_order: SortOrderSetting,
26    ) -> Self {
27        let mut list_state = ListState::default();
28        list_state.select(Some(0));
29        Self {
30            default_sort_field,
31            default_sort_order,
32            journal_sort_field,
33            journal_sort_order,
34            list_state,
35        }
36    }
37
38    const ROW_COUNT: usize = 4;
39
40    fn cycle_field(f: SortFieldSetting) -> SortFieldSetting {
41        match f {
42            SortFieldSetting::Name => SortFieldSetting::Title,
43            SortFieldSetting::Title => SortFieldSetting::Name,
44        }
45    }
46
47    fn cycle_order(o: SortOrderSetting) -> SortOrderSetting {
48        match o {
49            SortOrderSetting::Ascending => SortOrderSetting::Descending,
50            SortOrderSetting::Descending => SortOrderSetting::Ascending,
51        }
52    }
53
54    fn field_label(f: SortFieldSetting) -> &'static str {
55        match f {
56            SortFieldSetting::Name => "Name",
57            SortFieldSetting::Title => "Title",
58        }
59    }
60
61    fn order_label(o: SortOrderSetting) -> &'static str {
62        match o {
63            SortOrderSetting::Ascending => "Ascending",
64            SortOrderSetting::Descending => "Descending",
65        }
66    }
67}
68
69impl Component for SortingSection {
70    fn handle_input(&mut self, event: &InputEvent, _tx: &AppTx) -> EventState {
71        let InputEvent::Key(key) = event else {
72            return EventState::NotConsumed;
73        };
74        let selected = self.list_state.selected().unwrap_or(0);
75        match key.code {
76            ratatui::crossterm::event::KeyCode::Up
77            | ratatui::crossterm::event::KeyCode::Char('k') => {
78                self.list_state
79                    .select(Some((selected + Self::ROW_COUNT - 1) % Self::ROW_COUNT));
80                EventState::Consumed
81            }
82            ratatui::crossterm::event::KeyCode::Down
83            | ratatui::crossterm::event::KeyCode::Char('j') => {
84                self.list_state
85                    .select(Some((selected + 1) % Self::ROW_COUNT));
86                EventState::Consumed
87            }
88            ratatui::crossterm::event::KeyCode::Enter
89            | ratatui::crossterm::event::KeyCode::Char(' ') => {
90                match selected {
91                    0 => self.default_sort_field = Self::cycle_field(self.default_sort_field),
92                    1 => self.default_sort_order = Self::cycle_order(self.default_sort_order),
93                    2 => self.journal_sort_field = Self::cycle_field(self.journal_sort_field),
94                    3 => self.journal_sort_order = Self::cycle_order(self.journal_sort_order),
95                    _ => {}
96                }
97                EventState::Consumed
98            }
99            _ => EventState::NotConsumed,
100        }
101    }
102
103    fn render(&mut self, f: &mut Frame, rect: Rect, theme: &Theme, focused: bool) {
104        let border_style = theme.border_style(focused);
105
106        // Outer container
107        let outer = Block::default()
108            .title("Sorting")
109            .borders(Borders::ALL)
110            .border_style(border_style)
111            .style(theme.base_style());
112        let inner = outer.inner(rect);
113        f.render_widget(outer, rect);
114
115        // Stack the two sub-groups vertically inside the outer block.
116        // Each sub-block needs 2 content rows + 2 border rows = 4 rows.
117        let halves = Layout::default()
118            .direction(Direction::Vertical)
119            .constraints([Constraint::Length(4), Constraint::Length(4)])
120            .split(inner);
121
122        let selected = self.list_state.selected().unwrap_or(0);
123        let highlight = Style::default()
124            .fg(theme.selection_fg.to_ratatui())
125            .bg(theme.selection_bg.to_ratatui());
126
127        // ── Default sub-block (rows 0–1) ───────────────────────────────────
128        let default_items = vec![
129            ListItem::new(format!(
130                "  Sort field:  [{}]",
131                Self::field_label(self.default_sort_field)
132            ))
133            .style(Style::default().fg(theme.fg.to_ratatui())),
134            ListItem::new(format!(
135                "  Sort order:  [{}]",
136                Self::order_label(self.default_sort_order)
137            ))
138            .style(Style::default().fg(theme.fg.to_ratatui())),
139        ];
140        let mut default_state = ListState::default();
141        default_state.select(if selected < 2 { Some(selected) } else { None });
142        let default_block = Block::default()
143            .title("Default")
144            .borders(Borders::ALL)
145            .border_style(theme.border_style(focused && selected < 2))
146            .style(theme.base_style());
147        f.render_stateful_widget(
148            List::new(default_items)
149                .block(default_block)
150                .highlight_style(highlight),
151            halves[0],
152            &mut default_state,
153        );
154
155        // ── Journal sub-block (rows 2–3) ────────────────────────────────────
156        let journal_items = vec![
157            ListItem::new(format!(
158                "  Sort field:  [{}]",
159                Self::field_label(self.journal_sort_field)
160            ))
161            .style(Style::default().fg(theme.fg.to_ratatui())),
162            ListItem::new(format!(
163                "  Sort order:  [{}]",
164                Self::order_label(self.journal_sort_order)
165            ))
166            .style(Style::default().fg(theme.fg.to_ratatui())),
167        ];
168        let mut journal_state = ListState::default();
169        journal_state.select(if selected >= 2 {
170            Some(selected - 2)
171        } else {
172            None
173        });
174        let journal_block = Block::default()
175            .title("Journal")
176            .borders(Borders::ALL)
177            .border_style(theme.border_style(focused && selected >= 2))
178            .style(theme.base_style());
179        f.render_stateful_widget(
180            List::new(journal_items)
181                .block(journal_block)
182                .highlight_style(highlight),
183            halves[1],
184            &mut journal_state,
185        );
186    }
187}