kimun_notes/components/preferences/
sorting_section.rs1use 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
12use super::{ClickMap, text_rect};
13use ratatui::text::{Line, Span};
14use unicode_width::UnicodeWidthStr;
15
16pub struct SortingSection {
17 pub default_sort_field: SortFieldSetting,
18 pub default_sort_order: SortOrderSetting,
19 pub journal_sort_field: SortFieldSetting,
20 pub journal_sort_order: SortOrderSetting,
21 list_state: ListState,
22 clicks: ClickMap,
23}
24
25impl SortingSection {
26 pub fn new(
27 default_sort_field: SortFieldSetting,
28 default_sort_order: SortOrderSetting,
29 journal_sort_field: SortFieldSetting,
30 journal_sort_order: SortOrderSetting,
31 ) -> Self {
32 let mut list_state = ListState::default();
33 list_state.select(Some(0));
34 Self {
35 default_sort_field,
36 default_sort_order,
37 journal_sort_field,
38 journal_sort_order,
39 list_state,
40 clicks: ClickMap::default(),
41 }
42 }
43
44 pub fn handle_mouse(
47 &mut self,
48 m: &ratatui::crossterm::event::MouseEvent,
49 ) -> Option<ratatui::crossterm::event::KeyEvent> {
50 use ratatui::crossterm::event::KeyCode;
51 self.clicks
52 .resolve(m, self.list_state.selected(), Some(KeyCode::Enter))
53 .into_key(|row| self.list_state.select(Some(row)))
54 }
55
56 fn value_row(
59 &mut self,
60 inner: Rect,
61 line: usize,
62 row: usize,
63 label: &'static str,
64 value: &str,
65 theme: &Theme,
66 ) -> ListItem<'static> {
67 let r = if (line as u16) < inner.height {
68 Rect::new(inner.x, inner.y + line as u16, inner.width, 1)
69 } else {
70 Rect::default()
71 };
72 let chip = format!("[{value}]");
73 self.clicks.row(r, row);
74 self.clicks.control(
75 text_rect(r, label.width() as u16, chip.width() as u16),
76 row,
77 ratatui::crossterm::event::KeyCode::Enter,
78 );
79 ListItem::new(Line::from(vec![
80 Span::raw(label),
81 Span::styled(chip, theme.action()),
82 ]))
83 .style(Style::default().fg(theme.fg.to_ratatui()))
84 }
85
86 const ROW_COUNT: usize = 4;
87
88 fn cycle_field(f: SortFieldSetting) -> SortFieldSetting {
89 match f {
90 SortFieldSetting::Name => SortFieldSetting::Title,
91 SortFieldSetting::Title => SortFieldSetting::Name,
92 }
93 }
94
95 fn cycle_order(o: SortOrderSetting) -> SortOrderSetting {
96 match o {
97 SortOrderSetting::Ascending => SortOrderSetting::Descending,
98 SortOrderSetting::Descending => SortOrderSetting::Ascending,
99 }
100 }
101
102 fn field_label(f: SortFieldSetting) -> &'static str {
103 match f {
104 SortFieldSetting::Name => "Name",
105 SortFieldSetting::Title => "Title",
106 }
107 }
108
109 fn order_label(o: SortOrderSetting) -> &'static str {
110 match o {
111 SortOrderSetting::Ascending => "Ascending",
112 SortOrderSetting::Descending => "Descending",
113 }
114 }
115}
116
117impl Component for SortingSection {
118 fn handle_input(&mut self, event: &InputEvent, _tx: &AppTx) -> EventState {
119 let InputEvent::Key(key) = event else {
120 return EventState::NotConsumed;
121 };
122 let selected = self.list_state.selected().unwrap_or(0);
123 match key.code {
124 ratatui::crossterm::event::KeyCode::Up
125 | ratatui::crossterm::event::KeyCode::Char('k') => {
126 self.list_state
127 .select(Some((selected + Self::ROW_COUNT - 1) % Self::ROW_COUNT));
128 EventState::Consumed
129 }
130 ratatui::crossterm::event::KeyCode::Down
131 | ratatui::crossterm::event::KeyCode::Char('j') => {
132 self.list_state
133 .select(Some((selected + 1) % Self::ROW_COUNT));
134 EventState::Consumed
135 }
136 ratatui::crossterm::event::KeyCode::Enter
137 | ratatui::crossterm::event::KeyCode::Char(' ') => {
138 match selected {
139 0 => self.default_sort_field = Self::cycle_field(self.default_sort_field),
140 1 => self.default_sort_order = Self::cycle_order(self.default_sort_order),
141 2 => self.journal_sort_field = Self::cycle_field(self.journal_sort_field),
142 3 => self.journal_sort_order = Self::cycle_order(self.journal_sort_order),
143 _ => {}
144 }
145 EventState::Consumed
146 }
147 _ => EventState::NotConsumed,
148 }
149 }
150
151 fn render(&mut self, f: &mut Frame, rect: Rect, theme: &Theme, focused: bool) {
152 let border_style = theme.border_style(focused);
153
154 let outer = Block::default()
156 .title("Sorting")
157 .borders(Borders::ALL)
158 .border_style(border_style)
159 .style(theme.base_style());
160 let inner = outer.inner(rect);
161 f.render_widget(outer, rect);
162
163 let halves = Layout::default()
166 .direction(Direction::Vertical)
167 .constraints([Constraint::Length(4), Constraint::Length(4)])
168 .split(inner);
169
170 let selected = self.list_state.selected().unwrap_or(0);
171 let highlight = Style::default()
172 .fg(theme.selection_fg.to_ratatui())
173 .bg(theme.selection_bg.to_ratatui());
174
175 self.clicks.clear();
177 let default_block = Block::default()
178 .title("Default")
179 .borders(Borders::ALL)
180 .border_style(theme.border_style(focused && selected < 2))
181 .style(theme.base_style());
182 let default_inner = default_block.inner(halves[0]);
183 let default_items = vec![
184 self.value_row(
185 default_inner,
186 0,
187 0,
188 " Sort field: ",
189 Self::field_label(self.default_sort_field),
190 theme,
191 ),
192 self.value_row(
193 default_inner,
194 1,
195 1,
196 " Sort order: ",
197 Self::order_label(self.default_sort_order),
198 theme,
199 ),
200 ];
201 let mut default_state = ListState::default();
202 default_state.select(if selected < 2 { Some(selected) } else { None });
203 f.render_stateful_widget(
204 List::new(default_items)
205 .block(default_block)
206 .highlight_style(highlight),
207 halves[0],
208 &mut default_state,
209 );
210
211 let journal_block = Block::default()
213 .title("Journal")
214 .borders(Borders::ALL)
215 .border_style(theme.border_style(focused && selected >= 2))
216 .style(theme.base_style());
217 let journal_inner = journal_block.inner(halves[1]);
218 let journal_items = vec![
219 self.value_row(
220 journal_inner,
221 0,
222 2,
223 " Sort field: ",
224 Self::field_label(self.journal_sort_field),
225 theme,
226 ),
227 self.value_row(
228 journal_inner,
229 1,
230 3,
231 " Sort order: ",
232 Self::order_label(self.journal_sort_order),
233 theme,
234 ),
235 ];
236 let mut journal_state = ListState::default();
237 journal_state.select(if selected >= 2 {
238 Some(selected - 2)
239 } else {
240 None
241 });
242 f.render_stateful_widget(
243 List::new(journal_items)
244 .block(journal_block)
245 .highlight_style(highlight),
246 halves[1],
247 &mut journal_state,
248 );
249 }
250}