diff options
Diffstat (limited to 'src/app.rs')
-rw-r--r-- | src/app.rs | 75 |
1 files changed, 63 insertions, 12 deletions
@@ -7,7 +7,7 @@ use cursive::event::{Event, EventResult, Key}; | |||
7 | use cursive::view::View; | 7 | use cursive::view::View; |
8 | use cursive::{Printer, Vec2}; | 8 | use cursive::{Printer, Vec2}; |
9 | 9 | ||
10 | use chrono::NaiveDate; | 10 | use chrono::{Local, NaiveDate}; |
11 | 11 | ||
12 | use crate::habit::{Bit, Count, Habit, HabitWrapper}; | 12 | use crate::habit::{Bit, Count, Habit, HabitWrapper}; |
13 | use crate::Command; | 13 | use crate::Command; |
@@ -28,6 +28,8 @@ impl std::default::Default for ViewMode { | |||
28 | } | 28 | } |
29 | } | 29 | } |
30 | 30 | ||
31 | struct StatusLine(String, String); | ||
32 | |||
31 | #[derive(Serialize, Deserialize)] | 33 | #[derive(Serialize, Deserialize)] |
32 | pub struct App { | 34 | pub struct App { |
33 | habits: Vec<Box<dyn HabitWrapper>>, | 35 | habits: Vec<Box<dyn HabitWrapper>>, |
@@ -37,6 +39,9 @@ pub struct App { | |||
37 | 39 | ||
38 | #[serde(skip)] | 40 | #[serde(skip)] |
39 | view_mode: ViewMode, | 41 | view_mode: ViewMode, |
42 | |||
43 | #[serde(skip)] | ||
44 | view_month_offset: u32, | ||
40 | } | 45 | } |
41 | 46 | ||
42 | impl App { | 47 | impl App { |
@@ -45,6 +50,7 @@ impl App { | |||
45 | habits: vec![], | 50 | habits: vec![], |
46 | view_mode: ViewMode::Day, | 51 | view_mode: ViewMode::Day, |
47 | focus: 0, | 52 | focus: 0, |
53 | view_month_offset: 0, | ||
48 | }; | 54 | }; |
49 | } | 55 | } |
50 | 56 | ||
@@ -58,6 +64,22 @@ impl App { | |||
58 | } | 64 | } |
59 | } | 65 | } |
60 | 66 | ||
67 | pub fn sift_backward(&mut self) { | ||
68 | self.view_month_offset += 1; | ||
69 | for v in self.habits.iter_mut() { | ||
70 | v.set_view_month_offset(self.view_month_offset); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | pub fn sift_forward(&mut self) { | ||
75 | if self.view_month_offset > 0 { | ||
76 | self.view_month_offset -= 1; | ||
77 | for v in self.habits.iter_mut() { | ||
78 | v.set_view_month_offset(self.view_month_offset); | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | |||
61 | fn set_focus(&mut self, d: Absolute) { | 83 | fn set_focus(&mut self, d: Absolute) { |
62 | let grid_width = CONFIGURATION.grid_width; | 84 | let grid_width = CONFIGURATION.grid_width; |
63 | match d { | 85 | match d { |
@@ -89,13 +111,30 @@ impl App { | |||
89 | } | 111 | } |
90 | } | 112 | } |
91 | 113 | ||
92 | fn status(&self) -> String { | 114 | fn status(&self) -> StatusLine { |
93 | let today = chrono::Local::now().naive_utc().date(); | 115 | let today = chrono::Local::now().naive_utc().date(); |
94 | let remaining = self.habits.iter().map(|h| h.remaining(today)).sum::<u32>(); | 116 | let remaining = self.habits.iter().map(|h| h.remaining(today)).sum::<u32>(); |
95 | let total = self.habits.iter().map(|h| h.total()).sum::<u32>(); | 117 | let total = self.habits.iter().map(|h| h.total()).sum::<u32>(); |
96 | let completed = total - remaining; | 118 | let completed = total - remaining; |
97 | 119 | ||
98 | return format!("{} completed, {} remaining", completed, remaining); | 120 | let timestamp = if self.view_month_offset == 0 { |
121 | format!( | ||
122 | "{:>width$}", | ||
123 | Local::now().date().format("%d/%b/%y"), | ||
124 | width = CONFIGURATION.view_width * CONFIGURATION.grid_width | ||
125 | ) | ||
126 | } else { | ||
127 | format!( | ||
128 | "{:>width$}", | ||
129 | format!("{} months ago", self.view_month_offset), | ||
130 | width = CONFIGURATION.view_width * CONFIGURATION.grid_width | ||
131 | ) | ||
132 | }; | ||
133 | |||
134 | StatusLine { | ||
135 | 0: format!("Today: {} completed, {} remaining", completed, remaining), | ||
136 | 1: timestamp, | ||
137 | } | ||
99 | } | 138 | } |
100 | 139 | ||
101 | fn max_size(&self) -> Vec2 { | 140 | fn max_size(&self) -> Vec2 { |
@@ -132,12 +171,12 @@ impl App { | |||
132 | Command::Add(name, kind, goal) => { | 171 | Command::Add(name, kind, goal) => { |
133 | if kind == "count" { | 172 | if kind == "count" { |
134 | self.add_habit(Box::new(Count::new(name, goal.unwrap_or(0)))); | 173 | self.add_habit(Box::new(Count::new(name, goal.unwrap_or(0)))); |
135 | eprintln!("Found COUNT!"); | ||
136 | } else if kind == "bit" { | 174 | } else if kind == "bit" { |
137 | self.add_habit(Box::new(Bit::new(name))); | 175 | self.add_habit(Box::new(Bit::new(name))); |
138 | eprintln!("Found BIT!"); | ||
139 | } | 176 | } |
140 | } | 177 | } |
178 | Command::MonthNext => self.sift_forward(), | ||
179 | Command::MonthPrev => self.sift_backward(), | ||
141 | _ => { | 180 | _ => { |
142 | eprintln!("UNKNOWN COMMAND!"); | 181 | eprintln!("UNKNOWN COMMAND!"); |
143 | } | 182 | } |
@@ -162,31 +201,35 @@ impl View for App { | |||
162 | offset = offset.map_y(|y| y + CONFIGURATION.view_height).map_x(|_| 0); | 201 | offset = offset.map_y(|y| y + CONFIGURATION.view_height).map_x(|_| 0); |
163 | } | 202 | } |
164 | i.draw(&printer.offset(offset).focused(self.focus == idx)); | 203 | i.draw(&printer.offset(offset).focused(self.focus == idx)); |
165 | offset = offset.map_x(|x| x + CONFIGURATION.view_width); | 204 | offset = offset.map_x(|x| x + CONFIGURATION.view_width + 2); |
166 | } | 205 | } |
167 | offset = offset.map_x(|_| 0).map_y(|_| self.max_size().y - 3); | 206 | |
168 | printer.print(offset, &self.status()); | 207 | offset = offset.map_x(|_| 0).map_y(|_| self.max_size().y - 2); |
208 | printer.print(offset, &self.status().1); // right | ||
209 | printer.print(offset, &self.status().0); // left | ||
169 | } | 210 | } |
170 | 211 | ||
171 | fn required_size(&mut self, _: Vec2) -> Vec2 { | 212 | fn required_size(&mut self, _: Vec2) -> Vec2 { |
172 | let grid_width = CONFIGURATION.grid_width; | 213 | let grid_width = CONFIGURATION.grid_width; |
214 | let view_width = CONFIGURATION.view_width; | ||
215 | let view_height = CONFIGURATION.view_height; | ||
173 | let width = { | 216 | let width = { |
174 | if self.habits.len() > 0 { | 217 | if self.habits.len() > 0 { |
175 | grid_width * CONFIGURATION.view_width | 218 | grid_width * view_width |
176 | } else { | 219 | } else { |
177 | 0 | 220 | 0 |
178 | } | 221 | } |
179 | }; | 222 | }; |
180 | let height = { | 223 | let height = { |
181 | if self.habits.len() > 0 { | 224 | if self.habits.len() > 0 { |
182 | (CONFIGURATION.view_height as f64 | 225 | (view_height as f64 * (self.habits.len() as f64 / grid_width as f64).ceil()) |
183 | * (self.habits.len() as f64 / grid_width as f64).ceil()) | ||
184 | as usize | 226 | as usize |
227 | + 2 // to acoomodate statusline and commandline | ||
185 | } else { | 228 | } else { |
186 | 0 | 229 | 0 |
187 | } | 230 | } |
188 | }; | 231 | }; |
189 | Vec2::new(width, height + 2) | 232 | Vec2::new(width, height) |
190 | } | 233 | } |
191 | 234 | ||
192 | fn take_focus(&mut self, _: Direction) -> bool { | 235 | fn take_focus(&mut self, _: Direction) -> bool { |
@@ -235,6 +278,14 @@ impl View for App { | |||
235 | self.save_state(); | 278 | self.save_state(); |
236 | return EventResult::with_cb(|s| s.quit()); | 279 | return EventResult::with_cb(|s| s.quit()); |
237 | } | 280 | } |
281 | Event::CtrlChar('f') => { | ||
282 | self.sift_forward(); | ||
283 | return EventResult::Consumed(None); | ||
284 | } | ||
285 | Event::CtrlChar('b') => { | ||
286 | self.sift_backward(); | ||
287 | return EventResult::Consumed(None); | ||
288 | } | ||
238 | _ => self.habits[self.focus].on_event(e), | 289 | _ => self.habits[self.focus].on_event(e), |
239 | } | 290 | } |
240 | } | 291 | } |