kimun_notes/components/dialogs/
mod.rs1pub use create_note_dialog::CreateNoteDialog;
2pub use delete_dialog::DeleteConfirmDialog;
3pub use file_ops_menu::FileOpsMenuDialog;
4pub use help_dialog::HelpDialog;
5pub use move_dialog::MoveDialog;
6pub use quick_note_modal::QuickNoteModal;
7pub use rename_dialog::RenameDialog;
8pub use workspace_switcher::WorkspaceSwitcherModal;
9
10use ratatui::Frame;
11use ratatui::layout::{Constraint, Direction, Layout, Rect};
12use ratatui::style::{Color, Modifier, Style};
13use ratatui::widgets::{Block, Borders, Paragraph, Widget};
14
15use crate::components::Component;
16use crate::components::event_state::EventState;
17use crate::components::events::{AppTx, InputEvent};
18use crate::settings::themes::Theme;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ValidationState {
27 Idle,
29 Pending,
31 Available,
33 Taken,
35}
36
37pub mod create_note_dialog;
38pub mod delete_dialog;
39pub mod file_ops_menu;
40pub mod help_dialog;
41pub mod move_dialog;
42pub mod quick_note_modal;
43pub mod rename_dialog;
44pub mod workspace_switcher;
45
46pub enum ActiveDialog {
47 Menu(FileOpsMenuDialog),
48 Delete(DeleteConfirmDialog),
49 Rename(RenameDialog),
50 Move(MoveDialog),
51 CreateNote(CreateNoteDialog),
52 Help(HelpDialog),
53 QuickNote(QuickNoteModal),
54 WorkspaceSwitcher(WorkspaceSwitcherModal),
55}
56
57impl ActiveDialog {
58 pub fn set_error(&mut self, msg: String) {
59 match self {
60 ActiveDialog::Menu(_) => {} ActiveDialog::Delete(d) => d.error = Some(msg),
62 ActiveDialog::Rename(d) => d.error = Some(msg),
63 ActiveDialog::Move(d) => d.error = Some(msg),
64 ActiveDialog::CreateNote(d) => d.error = Some(msg),
65 ActiveDialog::Help(_) => {}
66 ActiveDialog::QuickNote(d) => d.error = Some(msg),
67 ActiveDialog::WorkspaceSwitcher(_) => {} }
69 }
70}
71
72impl Component for ActiveDialog {
73 fn handle_input(&mut self, event: &InputEvent, tx: &AppTx) -> EventState {
74 let InputEvent::Key(key) = event else {
75 return EventState::NotConsumed;
76 };
77 match self {
78 ActiveDialog::Menu(d) => d.handle_key(*key, tx),
79 ActiveDialog::Delete(d) => d.handle_key(*key, tx),
80 ActiveDialog::Rename(d) => d.handle_key(*key, tx),
81 ActiveDialog::Move(d) => d.handle_key(*key, tx),
82 ActiveDialog::CreateNote(d) => d.handle_key(*key, tx),
83 ActiveDialog::Help(d) => d.handle_key(*key, tx),
84 ActiveDialog::QuickNote(d) => d.handle_key(*key, tx),
85 ActiveDialog::WorkspaceSwitcher(d) => d.handle_key(*key, tx),
86 }
87 }
88
89 fn render(&mut self, f: &mut Frame, rect: Rect, theme: &Theme, focused: bool) {
90 match self {
91 ActiveDialog::Menu(d) => d.render(f, rect, theme, focused),
92 ActiveDialog::Delete(d) => d.render(f, rect, theme, focused),
93 ActiveDialog::Rename(d) => d.render(f, rect, theme, focused),
94 ActiveDialog::Move(d) => d.render(f, rect, theme, focused),
95 ActiveDialog::CreateNote(d) => d.render(f, rect, theme, focused),
96 ActiveDialog::Help(d) => d.render(f, rect, theme, focused),
97 ActiveDialog::QuickNote(d) => d.render(f, rect, theme, focused),
98 ActiveDialog::WorkspaceSwitcher(d) => d.render(f, rect, theme, focused),
99 }
100 }
101}
102
103pub(super) fn render_path_row(f: &mut Frame, rect: Rect, path: &str, fg: Color, bg: Color) {
109 f.render_widget(
110 Paragraph::new(path).style(Style::default().fg(fg).bg(bg)),
111 rect,
112 );
113}
114
115pub(super) fn render_separator(f: &mut Frame, rect: Rect, fg_muted: Color, bg: Color) {
117 Block::default()
118 .borders(Borders::TOP)
119 .border_style(Style::default().fg(fg_muted))
120 .style(Style::default().bg(bg))
121 .render(rect, f.buffer_mut());
122}
123
124pub(super) fn render_error_row(f: &mut Frame, rect: Rect, msg: &str, bg: Color) {
126 f.render_widget(
127 Paragraph::new(format!(" Error: {msg}")).style(Style::default().fg(Color::Red).bg(bg)),
128 rect,
129 );
130}
131
132pub(super) fn render_confirm_hint(
135 f: &mut Frame,
136 rect: Rect,
137 enter_text: &str,
138 enter_active: bool,
139 fg: Color,
140 fg_muted: Color,
141 bg: Color,
142) {
143 let enter_style = if enter_active {
144 Style::default().fg(fg).bg(bg)
145 } else {
146 Style::default()
147 .fg(fg_muted)
148 .bg(bg)
149 .add_modifier(Modifier::DIM)
150 };
151 let chunks = Layout::default()
152 .direction(Direction::Horizontal)
153 .constraints([
154 Constraint::Length(enter_text.len() as u16 + 1),
155 Constraint::Min(1),
156 ])
157 .split(rect);
158 f.render_widget(Paragraph::new(enter_text).style(enter_style), chunks[0]);
159 f.render_widget(
160 Paragraph::new(" [Esc] Cancel").style(Style::default().fg(fg_muted).bg(bg)),
161 chunks[1],
162 );
163}
164
165pub(super) fn centered_rect(
170 percent_x: u16,
171 percent_y: u16,
172 area: ratatui::layout::Rect,
173) -> ratatui::layout::Rect {
174 let popup_height = (area.height as u32 * percent_y as u32 / 100) as u16;
175 let popup_width = (area.width as u32 * percent_x as u32 / 100) as u16;
176 ratatui::layout::Rect {
177 x: area.x + (area.width.saturating_sub(popup_width)) / 2,
178 y: area.y + (area.height.saturating_sub(popup_height)) / 2,
179 width: popup_width,
180 height: popup_height,
181 }
182}
183
184pub(super) fn fixed_centered_rect(
186 width: u16,
187 height: u16,
188 area: ratatui::layout::Rect,
189) -> ratatui::layout::Rect {
190 let w = width.min(area.width);
191 let h = height.min(area.height);
192 ratatui::layout::Rect {
193 x: area.x + (area.width.saturating_sub(w)) / 2,
194 y: area.y + (area.height.saturating_sub(h)) / 2,
195 width: w,
196 height: h,
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::keys::KeyBindings;
204
205 #[test]
206 fn active_dialog_help_variant_compiles() {
207 let dialog = HelpDialog::new(&KeyBindings::empty());
208 let _active: ActiveDialog = ActiveDialog::Help(dialog);
209 }
210}