aboutsummaryrefslogtreecommitdiff
path: root/src/app/impl_view.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/impl_view.rs')
-rw-r--r--src/app/impl_view.rs175
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 @@
1use std::f64;
2use std::fs::File;
3use std::io::prelude::*;
4use std::path::PathBuf;
5
6use cursive::direction::{Absolute, Direction};
7use cursive::event::{Event, EventResult, Key};
8use cursive::view::View;
9use cursive::{Printer, Vec2};
10use notify::DebouncedEvent;
11
12use crate::app::App;
13use crate::habit::{HabitWrapper, ViewMode};
14use crate::utils;
15use crate::CONFIGURATION;
16
17impl 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}