cardinal_core/
workspace.rs1use crate::command::AgendaRange;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct WorkspaceState {
10 pub view: View,
11 pub selected_index: usize,
12 pub status: Option<String>,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum View {
17 Home,
18 Inboxes,
19 MailList { mailbox: String },
20 MailReader { message_id: String },
21 Calendars,
22 Agenda { range: AgendaRange },
23 EventReader { event_id: String },
24 SearchResults { query: String },
25 Compose,
26}
27
28impl Default for WorkspaceState {
29 fn default() -> Self {
30 Self {
31 view: View::Home,
32 selected_index: 0,
33 status: None,
34 }
35 }
36}
37
38impl WorkspaceState {
39 pub fn select_next(&mut self, len: usize) {
40 if len == 0 {
41 self.selected_index = 0;
42 return;
43 }
44
45 self.selected_index = (self.selected_index + 1).min(len - 1);
46 }
47
48 pub fn select_previous(&mut self) {
49 self.selected_index = self.selected_index.saturating_sub(1);
50 }
51
52 pub fn select_top(&mut self) {
53 self.selected_index = 0;
54 }
55
56 pub fn select_bottom(&mut self, len: usize) {
57 self.selected_index = len.saturating_sub(1);
58 }
59
60 pub fn set_view(&mut self, view: View) {
61 self.view = view;
62 self.selected_index = 0;
63 }
64
65 pub fn set_status(&mut self, status: impl Into<String>) {
66 self.status = Some(status.into());
67 }
68
69 pub fn clear_status(&mut self) {
70 self.status = None;
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn select_next_stops_at_end() {
80 let mut state = WorkspaceState::default();
81 state.select_next(2);
82 state.select_next(2);
83 state.select_next(2);
84 assert_eq!(state.selected_index, 1);
85 }
86
87 #[test]
88 fn select_previous_saturates_at_zero() {
89 let mut state = WorkspaceState::default();
90 state.select_previous();
91 assert_eq!(state.selected_index, 0);
92 }
93
94 #[test]
95 fn select_next_zero_len_resets_to_zero() {
96 let mut state = WorkspaceState {
97 selected_index: 4,
98 ..WorkspaceState::default()
99 };
100 state.select_next(0);
101 assert_eq!(state.selected_index, 0);
102 }
103
104 #[test]
105 fn select_top_resets_selection() {
106 let mut state = WorkspaceState {
107 selected_index: 8,
108 ..WorkspaceState::default()
109 };
110 state.select_top();
111 assert_eq!(state.selected_index, 0);
112 }
113
114 #[test]
115 fn select_bottom_tracks_list_len_and_saturates() {
116 let mut state = WorkspaceState::default();
117 state.select_bottom(3);
118 assert_eq!(state.selected_index, 2);
119
120 state.select_bottom(0);
121 assert_eq!(state.selected_index, 0);
122 }
123
124 #[test]
125 fn status_set_and_clear_round_trip() {
126 let mut state = WorkspaceState::default();
127 state.set_status("sync ok");
128 assert_eq!(state.status, Some("sync ok".to_owned()));
129 state.clear_status();
130 assert_eq!(state.status, None);
131 }
132
133 #[test]
134 fn set_view_resets_selection() {
135 let mut state = WorkspaceState::default();
136 state.select_next(10);
137 state.set_view(View::Inboxes);
138 assert_eq!(state.selected_index, 0);
139 assert_eq!(state.view, View::Inboxes);
140 }
141}