aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-02-21 05:13:32 +0000
committerAkshay <[email protected]>2021-02-21 05:13:32 +0000
commit38d9dfee224ebcd088c0ebecd9e1243994eea409 (patch)
tree1215766f07b1c2eaf68f20aba284e710eb9a24a3
parentad5bf181a176e64c9f70a292cad870e6e8110f09 (diff)
parent53f7a679a0cf7a510de13d67cf370988f71c0d08 (diff)
Merge branch 'cursor' into master
-rw-r--r--flake.nix12
-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
-rw-r--r--src/habit/bit.rs25
-rw-r--r--src/habit/count.rs25
-rw-r--r--src/habit/mod.rs27
-rw-r--r--src/habit/traits.rs46
-rw-r--r--src/keybinds.rs146
-rw-r--r--src/theme.rs6
-rw-r--r--src/utils.rs4
-rw-r--r--src/views.rs46
14 files changed, 382 insertions, 114 deletions
diff --git a/flake.nix b/flake.nix
index db94656..bc0a0e8 100644
--- a/flake.nix
+++ b/flake.nix
@@ -19,6 +19,11 @@
19 channel = "nightly"; 19 channel = "nightly";
20 sha256 = "LbKHsCOFXWpg/SEyACfzZuWjKbkXdH6EJKOPSGoO01E="; # set zeros after modifying channel or date 20 sha256 = "LbKHsCOFXWpg/SEyACfzZuWjKbkXdH6EJKOPSGoO01E="; # set zeros after modifying channel or date
21 }).rust; 21 }).rust;
22 rust-src = (mozilla.rustChannelOf {
23 date = "2020-12-23";
24 channel = "nightly";
25 sha256 = "LbKHsCOFXWpg/SEyACfzZuWjKbkXdH6EJKOPSGoO01E="; # set zeros after modifying channel or date
26 }).rust-src;
22 27
23 naersk-lib = naersk.lib."${system}".override { 28 naersk-lib = naersk.lib."${system}".override {
24 cargo = rust; 29 cargo = rust;
@@ -38,10 +43,15 @@
38 devShell = pkgs.mkShell { 43 devShell = pkgs.mkShell {
39 nativeBuildInputs = [ 44 nativeBuildInputs = [
40 rust 45 rust
46 rust-src
47 pkgs.rust-analyzer
41 pkgs.cargo 48 pkgs.cargo
42 pkgs.cargo 49 pkgs.openssl
43 pkgs.ncurses 50 pkgs.ncurses
44 ]; 51 ];
52 shellHook = ''
53 export RUST_SRC_PATH="${rust-src}/lib/rustlib/src/rust/library"
54 '';
45 }; 55 };
46 }); 56 });
47 } 57 }
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
diff --git a/src/habit/bit.rs b/src/habit/bit.rs
index 2bbb0ac..da64ece 100644
--- a/src/habit/bit.rs
+++ b/src/habit/bit.rs
@@ -1,11 +1,12 @@
1use std::collections::HashMap; 1use std::collections::HashMap;
2use std::default::Default;
2 3
3use chrono::NaiveDate; 4use chrono::NaiveDate;
4use serde::{Deserialize, Serialize}; 5use serde::{Deserialize, Serialize};
5 6
6use crate::habit::prelude::default_auto; 7use crate::habit::prelude::default_auto;
7use crate::habit::traits::Habit; 8use crate::habit::traits::Habit;
8use crate::habit::{TrackEvent, ViewMode}; 9use crate::habit::{InnerData, TrackEvent};
9use crate::CONFIGURATION; 10use crate::CONFIGURATION;
10 11
11#[derive(Copy, Clone, Debug, Serialize, Deserialize)] 12#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
@@ -42,10 +43,7 @@ pub struct Bit {
42 auto: bool, 43 auto: bool,
43 44
44 #[serde(skip)] 45 #[serde(skip)]
45 view_month_offset: u32, 46 inner_data: InnerData,
46
47 #[serde(skip)]
48 view_mode: ViewMode,
49} 47}
50 48
51impl Bit { 49impl Bit {
@@ -55,8 +53,7 @@ impl Bit {
55 stats: HashMap::new(), 53 stats: HashMap::new(),
56 goal: CustomBool(true), 54 goal: CustomBool(true),
57 auto, 55 auto,
58 view_month_offset: 0, 56 inner_data: Default::default(),
59 view_mode: ViewMode::Day,
60 }; 57 };
61 } 58 }
62} 59}
@@ -118,17 +115,11 @@ impl Habit for Bit {
118 } 115 }
119 } 116 }
120 } 117 }
121 fn set_view_month_offset(&mut self, offset: u32) { 118 fn inner_data_ref(&self) -> &InnerData {
122 self.view_month_offset = offset; 119 &self.inner_data
123 }
124 fn view_month_offset(&self) -> u32 {
125 self.view_month_offset
126 }
127 fn set_view_mode(&mut self, mode: ViewMode) {
128 self.view_mode = mode;
129 } 120 }
130 fn view_mode(&self) -> ViewMode { 121 fn inner_data_mut_ref(&mut self) -> &mut InnerData {
131 self.view_mode 122 &mut self.inner_data
132 } 123 }
133 fn is_auto(&self) -> bool { 124 fn is_auto(&self) -> bool {
134 self.auto 125 self.auto
diff --git a/src/habit/count.rs b/src/habit/count.rs
index d351758..09fd399 100644
--- a/src/habit/count.rs
+++ b/src/habit/count.rs
@@ -1,11 +1,12 @@
1use std::collections::HashMap; 1use std::collections::HashMap;
2use std::default::Default;
2 3
3use chrono::NaiveDate; 4use chrono::NaiveDate;
4use serde::{Deserialize, Serialize}; 5use serde::{Deserialize, Serialize};
5 6
6use crate::habit::prelude::default_auto; 7use crate::habit::prelude::default_auto;
7use crate::habit::traits::Habit; 8use crate::habit::traits::Habit;
8use crate::habit::{TrackEvent, ViewMode}; 9use crate::habit::{InnerData, TrackEvent};
9 10
10#[derive(Debug, Serialize, Deserialize)] 11#[derive(Debug, Serialize, Deserialize)]
11pub struct Count { 12pub struct Count {
@@ -17,10 +18,7 @@ pub struct Count {
17 auto: bool, 18 auto: bool,
18 19
19 #[serde(skip)] 20 #[serde(skip)]
20 view_month_offset: u32, 21 inner_data: InnerData,
21
22 #[serde(skip)]
23 view_mode: ViewMode,
24} 22}
25 23
26impl Count { 24impl Count {
@@ -30,8 +28,7 @@ impl Count {
30 stats: HashMap::new(), 28 stats: HashMap::new(),
31 goal, 29 goal,
32 auto, 30 auto,
33 view_month_offset: 0, 31 inner_data: Default::default(),
34 view_mode: ViewMode::Day,
35 }; 32 };
36 } 33 }
37} 34}
@@ -95,17 +92,11 @@ impl Habit for Count {
95 }; 92 };
96 } 93 }
97 } 94 }
98 fn set_view_month_offset(&mut self, offset: u32) { 95 fn inner_data_ref(&self) -> &InnerData {
99 self.view_month_offset = offset; 96 &self.inner_data
100 }
101 fn view_month_offset(&self) -> u32 {
102 self.view_month_offset
103 }
104 fn set_view_mode(&mut self, mode: ViewMode) {
105 self.view_mode = mode;
106 } 97 }
107 fn view_mode(&self) -> ViewMode { 98 fn inner_data_mut_ref(&mut self) -> &mut InnerData {
108 self.view_mode 99 &mut self.inner_data
109 } 100 }
110 fn is_auto(&self) -> bool { 101 fn is_auto(&self) -> bool {
111 self.auto 102 self.auto
diff --git a/src/habit/mod.rs b/src/habit/mod.rs
index 75e734a..d51abe5 100644
--- a/src/habit/mod.rs
+++ b/src/habit/mod.rs
@@ -1,3 +1,5 @@
1use std::default::Default;
2
1mod traits; 3mod traits;
2pub use traits::{Habit, HabitWrapper}; 4pub use traits::{Habit, HabitWrapper};
3 5
@@ -9,3 +11,28 @@ pub use bit::Bit;
9 11
10mod prelude; 12mod prelude;
11pub use prelude::{TrackEvent, ViewMode}; 13pub use prelude::{TrackEvent, ViewMode};
14
15use crate::app::Cursor;
16
17use cursive::direction::Absolute;
18
19#[derive(Debug, Default)]
20pub struct InnerData {
21 pub cursor: Cursor,
22 pub view_mode: ViewMode,
23}
24
25impl InnerData {
26 pub fn move_cursor(&mut self, d: Absolute) {
27 self.cursor.small_seek(d);
28 }
29 pub fn cursor(&self) -> Cursor {
30 self.cursor
31 }
32 pub fn set_view_mode(&mut self, mode: ViewMode) {
33 self.view_mode = mode;
34 }
35 pub fn view_mode(&self) -> ViewMode {
36 self.view_mode
37 }
38}
diff --git a/src/habit/traits.rs b/src/habit/traits.rs
index 74fd00b..24d941d 100644
--- a/src/habit/traits.rs
+++ b/src/habit/traits.rs
@@ -5,47 +5,41 @@ use cursive::{Printer, Vec2};
5 5
6use typetag; 6use typetag;
7 7
8use crate::habit::{Bit, Count, TrackEvent, ViewMode}; 8use crate::habit::{Bit, Count, InnerData, TrackEvent};
9use crate::views::ShadowView; 9use crate::views::ShadowView;
10 10
11pub trait Habit { 11pub trait Habit {
12 type HabitType; 12 type HabitType;
13 13
14 fn set_name(&mut self, name: impl AsRef<str>);
15 fn set_goal(&mut self, goal: Self::HabitType);
16 fn name(&self) -> String;
17 fn get_by_date(&self, date: NaiveDate) -> Option<&Self::HabitType>; 14 fn get_by_date(&self, date: NaiveDate) -> Option<&Self::HabitType>;
15 fn goal(&self) -> u32;
18 fn insert_entry(&mut self, date: NaiveDate, val: Self::HabitType); 16 fn insert_entry(&mut self, date: NaiveDate, val: Self::HabitType);
17 fn modify(&mut self, date: NaiveDate, event: TrackEvent);
18 fn name(&self) -> String;
19 fn reached_goal(&self, date: NaiveDate) -> bool; 19 fn reached_goal(&self, date: NaiveDate) -> bool;
20 fn remaining(&self, date: NaiveDate) -> u32; 20 fn remaining(&self, date: NaiveDate) -> u32;
21 fn goal(&self) -> u32; 21 fn set_goal(&mut self, goal: Self::HabitType);
22 fn modify(&mut self, date: NaiveDate, event: TrackEvent); 22 fn set_name(&mut self, name: impl AsRef<str>);
23
24 fn set_view_month_offset(&mut self, offset: u32);
25 fn view_month_offset(&self) -> u32;
26 23
27 fn set_view_mode(&mut self, mode: ViewMode); 24 fn inner_data_ref(&self) -> &InnerData;
28 fn view_mode(&self) -> ViewMode; 25 fn inner_data_mut_ref(&mut self) -> &mut InnerData;
29 26
30 fn is_auto(&self) -> bool; 27 fn is_auto(&self) -> bool;
31} 28}
32 29
33#[typetag::serde(tag = "type")] 30#[typetag::serde(tag = "type")]
34pub trait HabitWrapper: erased_serde::Serialize { 31pub trait HabitWrapper: erased_serde::Serialize {
35 fn remaining(&self, date: NaiveDate) -> u32; 32 fn draw(&self, printer: &Printer);
36 fn goal(&self) -> u32; 33 fn goal(&self) -> u32;
37 fn modify(&mut self, date: NaiveDate, event: TrackEvent); 34 fn modify(&mut self, date: NaiveDate, event: TrackEvent);
38 fn draw(&self, printer: &Printer); 35 fn name(&self) -> String;
39 fn on_event(&mut self, event: Event) -> EventResult; 36 fn on_event(&mut self, event: Event) -> EventResult;
37 fn remaining(&self, date: NaiveDate) -> u32;
40 fn required_size(&mut self, _: Vec2) -> Vec2; 38 fn required_size(&mut self, _: Vec2) -> Vec2;
41 fn take_focus(&mut self, _: Direction) -> bool; 39 fn take_focus(&mut self, _: Direction) -> bool;
42 fn name(&self) -> String;
43
44 fn set_view_month_offset(&mut self, offset: u32);
45 fn view_month_offset(&self) -> u32;
46 40
47 fn set_view_mode(&mut self, mode: ViewMode); 41 fn inner_data_ref(&self) -> &InnerData;
48 fn view_mode(&self) -> ViewMode; 42 fn inner_data_mut_ref(&mut self) -> &mut InnerData;
49 43
50 fn is_auto(&self) -> bool; 44 fn is_auto(&self) -> bool;
51} 45}
@@ -81,17 +75,11 @@ macro_rules! auto_habit_impl {
81 fn name(&self) -> String { 75 fn name(&self) -> String {
82 Habit::name(self) 76 Habit::name(self)
83 } 77 }
84 fn set_view_month_offset(&mut self, offset: u32) { 78 fn inner_data_ref(&self) -> &InnerData {
85 Habit::set_view_month_offset(self, offset) 79 Habit::inner_data_ref(self)
86 }
87 fn view_month_offset(&self) -> u32 {
88 Habit::view_month_offset(self)
89 }
90 fn set_view_mode(&mut self, mode: ViewMode) {
91 Habit::set_view_mode(self, mode)
92 } 80 }
93 fn view_mode(&self) -> ViewMode { 81 fn inner_data_mut_ref(&mut self) -> &mut InnerData {
94 Habit::view_mode(self) 82 Habit::inner_data_mut_ref(self)
95 } 83 }
96 fn is_auto(&self) -> bool { 84 fn is_auto(&self) -> bool {
97 Habit::is_auto(self) 85 Habit::is_auto(self)
diff --git a/src/keybinds.rs b/src/keybinds.rs
new file mode 100644
index 0000000..1b9c234
--- /dev/null
+++ b/src/keybinds.rs
@@ -0,0 +1,146 @@
1use std::convert::From;
2
3use cursive::event::Event as CursiveEvent;
4use serde::ser;
5use serde::{self, Deserialize, Serialize, Serializer};
6
7#[derive(Debug, PartialEq)]
8struct Event(CursiveEvent);
9
10macro_rules! event {
11 ($thing:expr) => {
12 Event { 0: $thing };
13 };
14}
15
16impl<T> From<T> for Event
17where
18 T: AsRef<str>,
19{
20 fn from(key: T) -> Self {
21 let key = key.as_ref();
22 if key.len() == 1 {
23 // single key
24 return event!(CursiveEvent::Char(key.chars().nth(0).unwrap()));
25 } else if (key.starts_with("c-") || key.starts_with("C-")) && key.len() == 3 {
26 // ctrl-key
27 return event!(CursiveEvent::CtrlChar(key.chars().nth(2).unwrap()));
28 } else {
29 panic!(
30 r"Invalid keybind in configuration!
31 (I intend to handle this error gracefully in the near future)"
32 );
33 }
34 }
35}
36
37enum Bind {
38 Char(char),
39 CtrlChar(char),
40 AltChar(char),
41}
42
43impl Serialize for Bind {
44 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45 where
46 S: Serializer,
47 {
48 match self {
49 Bind::Char(c) => serializer.serialize_newtype_variant("bind", 0, "regular", &c),
50 Bind::CtrlChar(c) => serializer.serialize_newtype_variant("bind", 0, "ctrl", &c),
51 Bind::AltChar(c) => serializer.serialize_newtype_variant("bind", 0, "alt", &c),
52 }
53 }
54}
55
56impl Deserialize for Bind {
57 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58 where
59 D: Deserializer<'de>,
60 {
61 eprintln!("hell = {:#?}", hell);
62 }
63}
64
65impl From<Bind> for CursiveEvent {
66 fn from(key: Bind) -> Self {
67 match key {
68 Bind::Char(c) => CursiveEvent::Char(c),
69 Bind::CtrlChar(c) => CursiveEvent::Char(c),
70 Bind::AltChar(c) => CursiveEvent::AltChar(c),
71 }
72 }
73}
74
75#[derive(Serialize, Deserialize)]
76pub struct KeyBinds {
77 grid: Movement,
78 cursor: Movement,
79 week_mode: Bind,
80 global_week_mode: Bind,
81}
82
83#[derive(Serialize, Deserialize)]
84pub struct Movement {
85 up: Bind,
86 down: Bind,
87 left: Bind,
88 right: Bind,
89}
90
91impl Movement {
92 pub fn new(left: char, down: char, up: char, right: char) -> Self {
93 return Movement {
94 up: Bind::Char(up),
95 down: Bind::Char(down),
96 left: Bind::Char(left),
97 right: Bind::Char(right),
98 };
99 }
100}
101
102impl std::default::Default for KeyBinds {
103 fn default() -> Self {
104 let grid = Movement::new('h', 'j', 'k', 'l');
105 let cursor = Movement::new('H', 'J', 'K', 'L');
106 return KeyBinds {
107 grid,
108 cursor,
109 week_mode: Bind::Char('v'),
110 global_week_mode: Bind::Char('V'),
111 };
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn normal_keybind() {
121 let bind = "X";
122 let expected = CursiveEvent::Char('X');
123 assert_eq!(Event::from(bind), event!(expected));
124 }
125
126 #[test]
127 fn control_keybind() {
128 let bind = "C-x";
129 let expected = CursiveEvent::CtrlChar('x');
130 assert_eq!(Event::from(bind), event!(expected));
131 }
132
133 #[test]
134 fn lower_case_control_keybind() {
135 let bind = "c-x";
136 let expected = CursiveEvent::CtrlChar('x');
137 assert_eq!(Event::from(bind), event!(expected));
138 }
139
140 #[test]
141 #[should_panic]
142 fn very_long_and_wrong_keybind() {
143 let bind = "alksdjfalkjdf";
144 Event::from(bind);
145 }
146}
diff --git a/src/theme.rs b/src/theme.rs
index 1d2cc36..879584c 100644
--- a/src/theme.rs
+++ b/src/theme.rs
@@ -1,4 +1,4 @@
1use cursive::theme::Color::*; 1use cursive::theme::Color::{self, *};
2use cursive::theme::PaletteColor::*; 2use cursive::theme::PaletteColor::*;
3use cursive::theme::{BorderStyle, Palette, Theme}; 3use cursive::theme::{BorderStyle, Palette, Theme};
4 4
@@ -24,3 +24,7 @@ pub fn theme_gen() -> Theme {
24 t.palette = pallete_gen(); 24 t.palette = pallete_gen();
25 return t; 25 return t;
26} 26}
27
28pub fn cursor_bg() -> Color {
29 Light(cursive::theme::BaseColor::Black)
30}
diff --git a/src/utils.rs b/src/utils.rs
index 2453aa6..f5a25c8 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -102,12 +102,12 @@ pub fn load_configuration_file() -> AppConfig {
102 if let Ok(ref mut f) = File::open(&cf) { 102 if let Ok(ref mut f) = File::open(&cf) {
103 let mut j = String::new(); 103 let mut j = String::new();
104 f.read_to_string(&mut j); 104 f.read_to_string(&mut j);
105 return toml::from_str(&j).unwrap(); 105 return toml::from_str(&j).unwrap_or_else(|e| panic!("Invalid config file: `{}`", e));
106 } else { 106 } else {
107 if let Ok(dc) = toml::to_string(&AppConfig::default()) { 107 if let Ok(dc) = toml::to_string(&AppConfig::default()) {
108 match OpenOptions::new().create(true).write(true).open(&cf) { 108 match OpenOptions::new().create(true).write(true).open(&cf) {
109 Ok(ref mut file) => file.write(dc.as_bytes()).unwrap(), 109 Ok(ref mut file) => file.write(dc.as_bytes()).unwrap(),
110 Err(_) => 0, 110 Err(_) => panic!("Unable to write config file to disk!"),
111 }; 111 };
112 } 112 }
113 return Default::default(); 113 return Default::default();
diff --git a/src/views.rs b/src/views.rs
index efd1391..b90ce2b 100644
--- a/src/views.rs
+++ b/src/views.rs
@@ -1,13 +1,14 @@
1use cursive::direction::Direction; 1use cursive::direction::Direction;
2use cursive::event::{Event, EventResult, Key}; 2use cursive::event::{Event, EventResult, Key};
3use cursive::theme::{Effect, Style}; 3use cursive::theme::{ColorStyle, Effect, Style};
4use cursive::view::View; 4use cursive::view::View;
5use cursive::{Printer, Vec2}; 5use cursive::{Printer, Vec2};
6 6
7use chrono::prelude::*; 7use chrono::prelude::*;
8use chrono::{Duration, Local, NaiveDate}; 8use chrono::{Local, NaiveDate};
9 9
10use crate::habit::{Bit, Count, Habit, TrackEvent, ViewMode}; 10use crate::habit::{Bit, Count, Habit, TrackEvent, ViewMode};
11use crate::theme::cursor_bg;
11use crate::utils::VIEW_WIDTH; 12use crate::utils::VIEW_WIDTH;
12 13
13use crate::CONFIGURATION; 14use crate::CONFIGURATION;
@@ -27,24 +28,24 @@ where
27 T::HabitType: std::fmt::Display, 28 T::HabitType: std::fmt::Display,
28{ 29{
29 fn draw(&self, printer: &Printer) { 30 fn draw(&self, printer: &Printer) {
30 let now = if self.view_month_offset() == 0 { 31 // let now = if self.view_month_offset() == 0 {
31 Local::today() 32 // Local::today()
32 } else { 33 // } else {
33 Local::today() 34 // Local::today()
34 .checked_sub_signed(Duration::weeks(4 * self.view_month_offset() as i64)) 35 // .checked_sub_signed(Duration::weeks(4 * self.view_month_offset() as i64))
35 .unwrap() 36 // .unwrap()
36 }; 37 // };
38 let now = self.inner_data_ref().cursor().0;
39 let is_today = now == Local::now().naive_local().date();
37 let year = now.year(); 40 let year = now.year();
38 let month = now.month(); 41 let month = now.month();
39 42
40 let goal_reached_style = Style::from(CONFIGURATION.reached_color()); 43 let goal_reached_style = Style::from(CONFIGURATION.reached_color());
41 let todo_style = Style::from(CONFIGURATION.todo_color());
42 let future_style = Style::from(CONFIGURATION.inactive_color()); 44 let future_style = Style::from(CONFIGURATION.inactive_color());
43 45
44 let strikethrough = Style::from(Effect::Strikethrough); 46 let strikethrough = Style::from(Effect::Strikethrough);
45 47
46 let goal_status = 48 let goal_status = is_today && self.reached_goal(Local::now().naive_local().date());
47 self.view_month_offset() == 0 && self.reached_goal(Local::now().naive_local().date());
48 49
49 printer.with_style( 50 printer.with_style(
50 Style::merge(&[ 51 Style::merge(&[
@@ -110,11 +111,20 @@ where
110 let draw_day = |printer: &Printer| { 111 let draw_day = |printer: &Printer| {
111 let mut i = 0; 112 let mut i = 0;
112 while let Some(d) = NaiveDate::from_ymd_opt(year, month, i + 1) { 113 while let Some(d) = NaiveDate::from_ymd_opt(year, month, i + 1) {
113 let day_style; 114 let mut day_style = Style::none();
115 let mut fs = future_style;
116 let grs = ColorStyle::front(CONFIGURATION.reached_color());
117 let ts = ColorStyle::front(CONFIGURATION.todo_color());
118 let cs = ColorStyle::back(cursor_bg());
119
114 if self.reached_goal(d) { 120 if self.reached_goal(d) {
115 day_style = goal_reached_style; 121 day_style = day_style.combine(Style::from(grs));
116 } else { 122 } else {
117 day_style = todo_style; 123 day_style = day_style.combine(Style::from(ts));
124 }
125 if d == now && printer.focused {
126 day_style = day_style.combine(cs);
127 fs = fs.combine(cs);
118 } 128 }
119 let coords: Vec2 = ((i % 7) * 3, i / 7 + 2).into(); 129 let coords: Vec2 = ((i % 7) * 3, i / 7 + 2).into();
120 if let Some(c) = self.get_by_date(d) { 130 if let Some(c) = self.get_by_date(d) {
@@ -122,7 +132,7 @@ where
122 p.print(coords, &format!("{:^3}", c)); 132 p.print(coords, &format!("{:^3}", c));
123 }); 133 });
124 } else { 134 } else {
125 printer.with_style(future_style, |p| { 135 printer.with_style(fs, |p| {
126 p.print(coords, &format!("{:^3}", CONFIGURATION.look.future_chr)); 136 p.print(coords, &format!("{:^3}", CONFIGURATION.look.future_chr));
127 }); 137 });
128 } 138 }
@@ -130,7 +140,7 @@ where
130 } 140 }
131 }; 141 };
132 142
133 match self.view_mode() { 143 match self.inner_data_ref().view_mode() {
134 ViewMode::Day => draw_day(printer), 144 ViewMode::Day => draw_day(printer),
135 ViewMode::Week => draw_week(printer), 145 ViewMode::Week => draw_week(printer),
136 _ => draw_day(printer), 146 _ => draw_day(printer),
@@ -146,7 +156,7 @@ where
146 } 156 }
147 157
148 fn on_event(&mut self, e: Event) -> EventResult { 158 fn on_event(&mut self, e: Event) -> EventResult {
149 let now = Local::now().naive_local().date(); 159 let now = self.inner_data_mut_ref().cursor().0;
150 if self.is_auto() { 160 if self.is_auto() {
151 return EventResult::Ignored; 161 return EventResult::Ignored;
152 } 162 }