1use crate::modal::Stack;
17use crate::widgets::{EditMode, Editor};
18use plotting::ChartSpec;
19use sqlx::types::Uuid;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Tab {
23 Accounts,
24 Transactions,
25 Commodities,
26 Reports,
27 Config,
28}
29
30impl Tab {
31 pub const ALL: [Tab; 5] = [
32 Tab::Accounts,
33 Tab::Transactions,
34 Tab::Commodities,
35 Tab::Reports,
36 Tab::Config,
37 ];
38
39 #[must_use]
40 pub fn label(self) -> &'static str {
41 match self {
42 Tab::Accounts => "Accounts",
43 Tab::Transactions => "Transactions",
44 Tab::Commodities => "Commodities",
45 Tab::Reports => "Reports",
46 Tab::Config => "Config",
47 }
48 }
49}
50
51#[derive(Debug)]
52pub struct App {
53 pub user_id: Uuid,
54 pub active_tab: Tab,
55 pub modals: Stack,
56 pub command_line: Editor,
57 pub command_line_active: bool,
58 pub edit_mode: EditMode,
59 pub status: String,
60 pub should_quit: bool,
61 pending_chart: Option<ChartSpec>,
66}
67
68impl App {
69 #[must_use]
70 pub fn new(user_id: Uuid, edit_mode: EditMode) -> Self {
71 Self {
72 user_id,
73 active_tab: Tab::Reports,
74 modals: Stack::new(),
75 command_line: Editor::new(edit_mode),
76 command_line_active: false,
77 edit_mode,
78 status: String::new(),
79 should_quit: false,
80 pending_chart: None,
81 }
82 }
83
84 pub fn queue_chart(&mut self, spec: ChartSpec) {
86 self.pending_chart = Some(spec);
87 }
88
89 pub fn take_pending_chart(&mut self) -> Option<ChartSpec> {
91 self.pending_chart.take()
92 }
93
94 pub fn next_tab(&mut self) {
95 let idx = Tab::ALL
96 .iter()
97 .position(|t| *t == self.active_tab)
98 .unwrap_or(0);
99 self.active_tab = Tab::ALL[(idx + 1) % Tab::ALL.len()];
100 }
101
102 pub fn previous_tab(&mut self) {
103 let idx = Tab::ALL
104 .iter()
105 .position(|t| *t == self.active_tab)
106 .unwrap_or(0);
107 let len = Tab::ALL.len();
108 self.active_tab = Tab::ALL[(idx + len - 1) % len];
109 }
110
111 pub fn switch_tab(&mut self, tab: Tab) {
112 self.active_tab = tab;
113 }
114
115 pub fn open_command_line(&mut self) {
116 self.command_line = Editor::new(self.edit_mode);
117 self.command_line_active = true;
118 }
119
120 pub fn close_command_line(&mut self) {
121 self.command_line_active = false;
122 }
123
124 pub fn set_status(&mut self, msg: impl Into<String>) {
125 self.status = msg.into();
126 }
127
128 pub fn request_quit(&mut self) {
129 self.should_quit = true;
130 }
131
132 pub fn set_edit_mode(&mut self, mode: EditMode) {
133 self.edit_mode = mode;
134 self.command_line.set_mode(mode);
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 fn make() -> App {
143 App::new(Uuid::new_v4(), EditMode::Emacs)
144 }
145
146 #[test]
147 fn next_tab_wraps_around() {
148 let mut app = make();
149 app.active_tab = Tab::Config;
150 app.next_tab();
151 assert_eq!(app.active_tab, Tab::Accounts);
152 }
153
154 #[test]
155 fn previous_tab_wraps_around() {
156 let mut app = make();
157 app.active_tab = Tab::Accounts;
158 app.previous_tab();
159 assert_eq!(app.active_tab, Tab::Config);
160 }
161
162 #[test]
163 fn next_tab_advances_in_order() {
164 let mut app = make();
165 app.active_tab = Tab::Accounts;
166 app.next_tab();
167 assert_eq!(app.active_tab, Tab::Transactions);
168 app.next_tab();
169 assert_eq!(app.active_tab, Tab::Commodities);
170 }
171
172 #[test]
173 fn switch_tab_sets_target() {
174 let mut app = make();
175 app.switch_tab(Tab::Reports);
176 assert_eq!(app.active_tab, Tab::Reports);
177 }
178
179 #[test]
180 fn open_and_close_command_line() {
181 let mut app = make();
182 assert!(!app.command_line_active);
183 app.open_command_line();
184 assert!(app.command_line_active);
185 app.close_command_line();
186 assert!(!app.command_line_active);
187 }
188
189 #[test]
190 fn request_quit_sets_flag() {
191 let mut app = make();
192 assert!(!app.should_quit);
193 app.request_quit();
194 assert!(app.should_quit);
195 }
196
197 #[test]
198 fn set_edit_mode_propagates_to_command_line() {
199 let mut app = make();
200 app.open_command_line();
201 app.command_line.insert_char('x');
202 app.set_edit_mode(EditMode::Vim);
203 assert_eq!(app.command_line.mode(), EditMode::Vim);
204 }
205}