aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/app')
-rw-r--r--src/app/cursor.rs71
-rw-r--r--src/app/impl_self.rs50
-rw-r--r--src/app/impl_view.rs31
-rw-r--r--src/app/message.rs3
-rw-r--r--src/app/mod.rs4
5 files changed, 130 insertions, 29 deletions
diff --git a/src/app/cursor.rs b/src/app/cursor.rs
new file mode 100644
index 0000000..f76d591
--- /dev/null
+++ b/src/app/cursor.rs
@@ -0,0 +1,71 @@
1use chrono::{Duration, Local, NaiveDate};
2use cursive::direction::Absolute;
3
4#[derive(Debug, Copy, Clone)]
5pub struct Cursor(pub NaiveDate);
6
7impl std::default::Default for Cursor {
8 fn default() -> Self {
9 Cursor::new()
10 }
11}
12
13impl Cursor {
14 pub fn new() -> Self {
15 Cursor {
16 0: Local::now().naive_local().date(),
17 }
18 }
19 pub fn small_seek(&mut self, d: Absolute) {
20 let today = Local::now().naive_local().date();
21 let cursor = self.0;
22 match d {
23 Absolute::Right => {
24 // forward by 1 day
25 let next = cursor.succ_opt().unwrap_or(cursor);
26 if next <= today {
27 self.0 = next;
28 }
29 }
30 Absolute::Left => {
31 // backward by 1 day
32 // assumes an infinite past
33 self.0 = cursor.pred_opt().unwrap_or(cursor);
34 }
35 Absolute::Down => {
36 // forward by 1 week
37 let next = cursor.checked_add_signed(Duration::weeks(1)).unwrap();
38 if next <= today {
39 self.0 = next;
40 }
41 }
42 Absolute::Up => {
43 // backward by 1 week
44 // assumes an infinite past
45 let next = cursor.checked_sub_signed(Duration::weeks(1)).unwrap();
46 self.0 = next;
47 }
48 Absolute::None => {}
49 }
50 }
51 fn long_seek(&mut self, offset: Duration) {
52 let cursor = self.0;
53 let today = Local::now().naive_local().date();
54 let next = cursor.checked_add_signed(offset).unwrap_or(cursor);
55
56 if next <= today {
57 self.0 = next;
58 } else {
59 self.0 = today;
60 }
61 }
62 pub fn month_forward(&mut self) {
63 self.long_seek(Duration::weeks(4));
64 }
65 pub fn month_backward(&mut self) {
66 self.long_seek(Duration::weeks(-4));
67 }
68 pub fn reset(&mut self) {
69 self.0 = Local::now().naive_local().date();
70 }
71}
diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs
index 1114d50..d5f93ff 100644
--- a/src/app/impl_self.rs
+++ b/src/app/impl_self.rs
@@ -6,7 +6,7 @@ use std::path::PathBuf;
6use std::sync::mpsc::channel; 6use std::sync::mpsc::channel;
7use std::time::Duration; 7use std::time::Duration;
8 8
9use chrono::Local; 9use chrono::{Local, NaiveDate};
10use cursive::direction::Absolute; 10use cursive::direction::Absolute;
11use cursive::Vec2; 11use cursive::Vec2;
12use notify::{watcher, RecursiveMode, Watcher}; 12use notify::{watcher, RecursiveMode, Watcher};
@@ -15,7 +15,7 @@ use crate::command::{Command, CommandLineError};
15use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; 15use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode};
16use crate::utils::{self, GRID_WIDTH, VIEW_HEIGHT, VIEW_WIDTH}; 16use crate::utils::{self, GRID_WIDTH, VIEW_HEIGHT, VIEW_WIDTH};
17 17
18use crate::app::{App, MessageKind, StatusLine}; 18use crate::app::{App, Cursor, Message, MessageKind, StatusLine};
19 19
20impl App { 20impl App {
21 pub fn new() -> Self { 21 pub fn new() -> Self {
@@ -27,8 +27,8 @@ impl App {
27 focus: 0, 27 focus: 0,
28 _file_watcher: watcher, 28 _file_watcher: watcher,
29 file_event_recv: rx, 29 file_event_recv: rx,
30 view_month_offset: 0, 30 cursor: Cursor::new(),
31 message: "Type :add <habit-name> <goal> to get started, Ctrl-L to dismiss".into(), 31 message: Message::startup(),
32 }; 32 };
33 } 33 }
34 34
@@ -53,35 +53,42 @@ impl App {
53 if self.habits.is_empty() { 53 if self.habits.is_empty() {
54 return ViewMode::Day; 54 return ViewMode::Day;
55 } 55 }
56 return self.habits[self.focus].view_mode(); 56 return self.habits[self.focus].inner_data_ref().view_mode();
57 } 57 }
58 58
59 pub fn set_mode(&mut self, mode: ViewMode) { 59 pub fn set_mode(&mut self, mode: ViewMode) {
60 if !self.habits.is_empty() { 60 if !self.habits.is_empty() {
61 self.habits[self.focus].set_view_mode(mode); 61 self.habits[self.focus]
62 .inner_data_mut_ref()
63 .set_view_mode(mode);
62 } 64 }
63 } 65 }
64 66
65 pub fn set_view_month_offset(&mut self, offset: u32) { 67 pub fn sift_backward(&mut self) {
66 self.view_month_offset = offset; 68 self.cursor.month_backward();
67 for v in self.habits.iter_mut() { 69 for v in self.habits.iter_mut() {
68 v.set_view_month_offset(offset); 70 v.inner_data_mut_ref().cursor.month_backward();
69 } 71 }
70 } 72 }
71 73
72 pub fn sift_backward(&mut self) { 74 pub fn sift_forward(&mut self) {
73 self.view_month_offset += 1; 75 self.cursor.month_forward();
74 for v in self.habits.iter_mut() { 76 for v in self.habits.iter_mut() {
75 v.set_view_month_offset(self.view_month_offset); 77 v.inner_data_mut_ref().cursor.month_forward();
76 } 78 }
77 } 79 }
78 80
79 pub fn sift_forward(&mut self) { 81 pub fn reset_cursor(&mut self) {
80 if self.view_month_offset > 0 { 82 self.cursor.reset();
81 self.view_month_offset -= 1; 83 for v in self.habits.iter_mut() {
82 for v in self.habits.iter_mut() { 84 v.inner_data_mut_ref().cursor.reset();
83 v.set_view_month_offset(self.view_month_offset); 85 }
84 } 86 }
87
88 pub fn move_cursor(&mut self, d: Absolute) {
89 self.cursor.small_seek(d);
90 for v in self.habits.iter_mut() {
91 v.inner_data_mut_ref().move_cursor(d);
85 } 92 }
86 } 93 }
87 94
@@ -125,11 +132,12 @@ impl App {
125 let total = self.habits.iter().map(|h| h.goal()).sum::<u32>(); 132 let total = self.habits.iter().map(|h| h.goal()).sum::<u32>();
126 let completed = total - remaining; 133 let completed = total - remaining;
127 134
128 let timestamp = if self.view_month_offset == 0 { 135 let timestamp = if self.cursor.0 == today {
129 format!("{}", Local::now().naive_local().date().format("%d/%b/%y"),) 136 format!("{}", Local::now().naive_local().date().format("%d/%b/%y"),)
130 } else { 137 } else {
131 let months = self.view_month_offset; 138 let since = NaiveDate::signed_duration_since(today, self.cursor.0).num_days();
132 format!("{}", format!("{} months ago", months),) 139 let plural = if since == 1 { "" } else { "s" };
140 format!("{} ({} day{} ago)", self.cursor.0, since, plural)
133 }; 141 };
134 142
135 StatusLine { 143 StatusLine {
diff --git a/src/app/impl_view.rs b/src/app/impl_view.rs
index 395cac4..c369d8f 100644
--- a/src/app/impl_view.rs
+++ b/src/app/impl_view.rs
@@ -95,11 +95,29 @@ impl View for App {
95 self.set_focus(Absolute::Down); 95 self.set_focus(Absolute::Down);
96 return EventResult::Consumed(None); 96 return EventResult::Consumed(None);
97 } 97 }
98
99 Event::Char('K') => {
100 self.move_cursor(Absolute::Up);
101 return EventResult::Consumed(None);
102 }
103 Event::Char('H') => {
104 self.move_cursor(Absolute::Left);
105 return EventResult::Consumed(None);
106 }
107 Event::Char('J') => {
108 self.move_cursor(Absolute::Down);
109 return EventResult::Consumed(None);
110 }
111 Event::Char('L') => {
112 self.move_cursor(Absolute::Right);
113 return EventResult::Consumed(None);
114 }
115
98 Event::Char('v') => { 116 Event::Char('v') => {
99 if self.habits.is_empty() { 117 if self.habits.is_empty() {
100 return EventResult::Consumed(None); 118 return EventResult::Consumed(None);
101 } 119 }
102 if self.habits[self.focus].view_mode() == ViewMode::Week { 120 if self.habits[self.focus].inner_data_ref().view_mode() == ViewMode::Week {
103 self.set_mode(ViewMode::Day) 121 self.set_mode(ViewMode::Day)
104 } else { 122 } else {
105 self.set_mode(ViewMode::Week) 123 self.set_mode(ViewMode::Week)
@@ -108,14 +126,15 @@ impl View for App {
108 } 126 }
109 Event::Char('V') => { 127 Event::Char('V') => {
110 for habit in self.habits.iter_mut() { 128 for habit in self.habits.iter_mut() {
111 habit.set_view_mode(ViewMode::Week); 129 habit.inner_data_mut_ref().set_view_mode(ViewMode::Week);
112 } 130 }
113 return EventResult::Consumed(None); 131 return EventResult::Consumed(None);
114 } 132 }
115 Event::Key(Key::Esc) => { 133 Event::Key(Key::Esc) => {
116 for habit in self.habits.iter_mut() { 134 for habit in self.habits.iter_mut() {
117 habit.set_view_mode(ViewMode::Day); 135 habit.inner_data_mut_ref().set_view_mode(ViewMode::Day);
118 } 136 }
137 self.reset_cursor();
119 return EventResult::Consumed(None); 138 return EventResult::Consumed(None);
120 } 139 }
121 140
@@ -131,7 +150,7 @@ impl View for App {
131 return EventResult::Consumed(None); 150 return EventResult::Consumed(None);
132 } 151 }
133 Event::Char('}') => { 152 Event::Char('}') => {
134 self.set_view_month_offset(0); 153 self.reset_cursor();
135 return EventResult::Consumed(None); 154 return EventResult::Consumed(None);
136 } 155 }
137 Event::CtrlChar('l') => { 156 Event::CtrlChar('l') => {
@@ -141,14 +160,12 @@ impl View for App {
141 } 160 }
142 161
143 /* Every keybind that is not caught by App trickles 162 /* Every keybind that is not caught by App trickles
144 * down to the focused habit. We sift back to today 163 * down to the focused habit.
145 * before performing any action, "refocusing" the cursor
146 * */ 164 * */
147 _ => { 165 _ => {
148 if self.habits.is_empty() { 166 if self.habits.is_empty() {
149 return EventResult::Ignored; 167 return EventResult::Ignored;
150 } 168 }
151 self.set_view_month_offset(0);
152 self.habits[self.focus].on_event(e) 169 self.habits[self.focus].on_event(e)
153 } 170 }
154 } 171 }
diff --git a/src/app/message.rs b/src/app/message.rs
index 65f0a5c..a1d3d57 100644
--- a/src/app/message.rs
+++ b/src/app/message.rs
@@ -35,6 +35,9 @@ pub struct Message {
35} 35}
36 36
37impl Message { 37impl Message {
38 pub fn startup() -> Self {
39 "Type :add <habit-name> <goal> to get started, Ctrl-L to dismiss".into()
40 }
38 pub fn contents(&self) -> &str { 41 pub fn contents(&self) -> &str {
39 &self.msg 42 &self.msg
40 } 43 }
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 2aecb33..726a656 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -5,11 +5,13 @@ use notify::{DebouncedEvent, RecommendedWatcher};
5 5
6use crate::habit::HabitWrapper; 6use crate::habit::HabitWrapper;
7 7
8mod cursor;
8mod impl_self; 9mod impl_self;
9mod impl_view; 10mod impl_view;
10mod message; 11mod message;
11 12
12pub struct StatusLine(String, String); 13pub struct StatusLine(String, String);
14pub use cursor::Cursor;
13pub use message::{Message, MessageKind}; 15pub use message::{Message, MessageKind};
14 16
15pub struct App { 17pub struct App {
@@ -19,7 +21,7 @@ pub struct App {
19 _file_watcher: RecommendedWatcher, 21 _file_watcher: RecommendedWatcher,
20 file_event_recv: Receiver<DebouncedEvent>, 22 file_event_recv: Receiver<DebouncedEvent>,
21 focus: usize, 23 focus: usize,
22 view_month_offset: u32, 24 cursor: Cursor,
23 message: Message, 25 message: Message,
24} 26}
25 27