diff options
Diffstat (limited to 'src/app/impl_view.rs')
-rw-r--r-- | src/app/impl_view.rs | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/src/app/impl_view.rs b/src/app/impl_view.rs new file mode 100644 index 0000000..904403b --- /dev/null +++ b/src/app/impl_view.rs | |||
@@ -0,0 +1,175 @@ | |||
1 | use std::f64; | ||
2 | use std::fs::File; | ||
3 | use std::io::prelude::*; | ||
4 | use std::path::PathBuf; | ||
5 | |||
6 | use cursive::direction::{Absolute, Direction}; | ||
7 | use cursive::event::{Event, EventResult, Key}; | ||
8 | use cursive::view::View; | ||
9 | use cursive::{Printer, Vec2}; | ||
10 | use notify::DebouncedEvent; | ||
11 | |||
12 | use crate::app::App; | ||
13 | use crate::habit::{HabitWrapper, ViewMode}; | ||
14 | use crate::utils; | ||
15 | use crate::CONFIGURATION; | ||
16 | |||
17 | impl View for App { | ||
18 | fn draw(&self, printer: &Printer) { | ||
19 | let grid_width = CONFIGURATION.grid_width; | ||
20 | let view_width = CONFIGURATION.view_width; | ||
21 | let view_height = CONFIGURATION.view_height; | ||
22 | let mut offset = Vec2::zero(); | ||
23 | for (idx, habit) in self.habits.iter().enumerate() { | ||
24 | if idx >= grid_width && idx % grid_width == 0 { | ||
25 | offset = offset.map_y(|y| y + view_height).map_x(|_| 0); | ||
26 | } | ||
27 | habit.draw(&printer.offset(offset).focused(self.focus == idx)); | ||
28 | offset = offset.map_x(|x| x + view_width + 2); | ||
29 | } | ||
30 | |||
31 | offset = offset.map_x(|_| 0).map_y(|_| self.max_size().y - 2); | ||
32 | |||
33 | let status = self.status(); | ||
34 | printer.print(offset, &status.0); // left status | ||
35 | |||
36 | let full = self.max_size().x; | ||
37 | offset = offset.map_x(|_| full - status.1.len()); | ||
38 | printer.print(offset, &status.1); // right status | ||
39 | } | ||
40 | |||
41 | fn required_size(&mut self, _: Vec2) -> Vec2 { | ||
42 | let grid_width = CONFIGURATION.grid_width; | ||
43 | let view_width = CONFIGURATION.view_width; | ||
44 | let view_height = CONFIGURATION.view_height; | ||
45 | let width = { | ||
46 | if self.habits.len() > 0 { | ||
47 | grid_width * (view_width + 2) | ||
48 | } else { | ||
49 | 0 | ||
50 | } | ||
51 | }; | ||
52 | let height = { | ||
53 | if self.habits.len() > 0 { | ||
54 | (view_height as f64 * (self.habits.len() as f64 / grid_width as f64).ceil()) | ||
55 | as usize | ||
56 | + 2 // to acoomodate statusline and message line | ||
57 | } else { | ||
58 | 0 | ||
59 | } | ||
60 | }; | ||
61 | Vec2::new(width, height) | ||
62 | } | ||
63 | |||
64 | fn take_focus(&mut self, _: Direction) -> bool { | ||
65 | false | ||
66 | } | ||
67 | |||
68 | fn on_event(&mut self, e: Event) -> EventResult { | ||
69 | match self.file_event_recv.try_recv() { | ||
70 | Ok(DebouncedEvent::Write(_)) => { | ||
71 | let read_from_file = |file: PathBuf| -> Vec<Box<dyn HabitWrapper>> { | ||
72 | if let Ok(ref mut f) = File::open(file) { | ||
73 | let mut j = String::new(); | ||
74 | f.read_to_string(&mut j); | ||
75 | return serde_json::from_str(&j).unwrap(); | ||
76 | } else { | ||
77 | return Vec::new(); | ||
78 | } | ||
79 | }; | ||
80 | let auto = read_from_file(utils::auto_habit_file()); | ||
81 | self.habits.retain(|x| !x.is_auto()); | ||
82 | self.habits.extend(auto); | ||
83 | } | ||
84 | _ => {} | ||
85 | }; | ||
86 | match e { | ||
87 | Event::Key(Key::Right) | Event::Key(Key::Tab) | Event::Char('l') => { | ||
88 | self.set_focus(Absolute::Right); | ||
89 | return EventResult::Consumed(None); | ||
90 | } | ||
91 | Event::Key(Key::Left) | Event::Shift(Key::Tab) | Event::Char('h') => { | ||
92 | self.set_focus(Absolute::Left); | ||
93 | return EventResult::Consumed(None); | ||
94 | } | ||
95 | Event::Key(Key::Up) | Event::Char('k') => { | ||
96 | self.set_focus(Absolute::Up); | ||
97 | return EventResult::Consumed(None); | ||
98 | } | ||
99 | Event::Key(Key::Down) | Event::Char('j') => { | ||
100 | self.set_focus(Absolute::Down); | ||
101 | return EventResult::Consumed(None); | ||
102 | } | ||
103 | Event::Char('d') => { | ||
104 | if self.habits.is_empty() { | ||
105 | return EventResult::Consumed(None); | ||
106 | } | ||
107 | self.habits.remove(self.focus); | ||
108 | self.focus = self.focus.checked_sub(1).unwrap_or(0); | ||
109 | return EventResult::Consumed(None); | ||
110 | } | ||
111 | Event::Char('w') => { | ||
112 | // helper bind to test write to file | ||
113 | let j = serde_json::to_string_pretty(&self.habits).unwrap(); | ||
114 | let mut file = File::create("foo.txt").unwrap(); | ||
115 | file.write_all(j.as_bytes()).unwrap(); | ||
116 | return EventResult::Consumed(None); | ||
117 | } | ||
118 | Event::Char('q') => { | ||
119 | self.save_state(); | ||
120 | return EventResult::with_cb(|s| s.quit()); | ||
121 | } | ||
122 | Event::Char('v') => { | ||
123 | if self.habits.is_empty() { | ||
124 | return EventResult::Consumed(None); | ||
125 | } | ||
126 | if self.habits[self.focus].view_mode() == ViewMode::Week { | ||
127 | self.set_mode(ViewMode::Day) | ||
128 | } else { | ||
129 | self.set_mode(ViewMode::Week) | ||
130 | } | ||
131 | return EventResult::Consumed(None); | ||
132 | } | ||
133 | Event::Char('V') => { | ||
134 | for habit in self.habits.iter_mut() { | ||
135 | habit.set_view_mode(ViewMode::Week); | ||
136 | } | ||
137 | return EventResult::Consumed(None); | ||
138 | } | ||
139 | Event::Key(Key::Esc) => { | ||
140 | for habit in self.habits.iter_mut() { | ||
141 | habit.set_view_mode(ViewMode::Day); | ||
142 | } | ||
143 | return EventResult::Consumed(None); | ||
144 | } | ||
145 | |||
146 | /* We want sifting to be an app level function, | ||
147 | * that later trickles down into each habit | ||
148 | * */ | ||
149 | Event::Char(']') => { | ||
150 | self.sift_forward(); | ||
151 | return EventResult::Consumed(None); | ||
152 | } | ||
153 | Event::Char('[') => { | ||
154 | self.sift_backward(); | ||
155 | return EventResult::Consumed(None); | ||
156 | } | ||
157 | Event::Char('}') => { | ||
158 | self.set_view_month_offset(0); | ||
159 | return EventResult::Consumed(None); | ||
160 | } | ||
161 | |||
162 | /* Every keybind that is not caught by App trickles | ||
163 | * down to the focused habit. We sift back to today | ||
164 | * before performing any action, "refocusing" the cursor | ||
165 | * */ | ||
166 | _ => { | ||
167 | if self.habits.is_empty() { | ||
168 | return EventResult::Ignored; | ||
169 | } | ||
170 | self.set_view_month_offset(0); | ||
171 | self.habits[self.focus].on_event(e) | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | } | ||