diff options
-rw-r--r-- | flake.nix | 2 | ||||
-rw-r--r-- | src/app/cursor.rs | 51 | ||||
-rw-r--r-- | src/app/impl_self.rs | 14 | ||||
-rw-r--r-- | src/app/impl_view.rs | 18 | ||||
-rw-r--r-- | src/app/message.rs | 3 | ||||
-rw-r--r-- | src/app/mod.rs | 3 | ||||
-rw-r--r-- | src/habit/bit.rs | 12 | ||||
-rw-r--r-- | src/habit/count.rs | 12 | ||||
-rw-r--r-- | src/habit/traits.rs | 15 | ||||
-rw-r--r-- | src/theme.rs | 10 | ||||
-rw-r--r-- | src/views.rs | 31 |
11 files changed, 151 insertions, 20 deletions
@@ -38,7 +38,7 @@ | |||
38 | nativeBuildInputs = [ | 38 | nativeBuildInputs = [ |
39 | rust | 39 | rust |
40 | pkgs.cargo | 40 | pkgs.cargo |
41 | pkgs.cargo | 41 | pkgs.openssl |
42 | pkgs.ncurses | 42 | pkgs.ncurses |
43 | ]; | 43 | ]; |
44 | }; | 44 | }; |
diff --git a/src/app/cursor.rs b/src/app/cursor.rs new file mode 100644 index 0000000..ed6bd65 --- /dev/null +++ b/src/app/cursor.rs | |||
@@ -0,0 +1,51 @@ | |||
1 | use chrono::{Duration, Local, NaiveDate}; | ||
2 | use cursive::direction::Absolute; | ||
3 | |||
4 | #[derive(Debug, Copy, Clone)] | ||
5 | pub struct Cursor(pub NaiveDate); | ||
6 | |||
7 | impl std::default::Default for Cursor { | ||
8 | fn default() -> Self { | ||
9 | Cursor::new() | ||
10 | } | ||
11 | } | ||
12 | |||
13 | impl Cursor { | ||
14 | pub fn new() -> Self { | ||
15 | Cursor { | ||
16 | 0: Local::now().naive_local().date(), | ||
17 | } | ||
18 | } | ||
19 | pub fn do_move(&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 | } | ||
diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 1114d50..abf5209 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs | |||
@@ -15,7 +15,7 @@ use crate::command::{Command, CommandLineError}; | |||
15 | use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; | 15 | use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; |
16 | use crate::utils::{self, GRID_WIDTH, VIEW_HEIGHT, VIEW_WIDTH}; | 16 | use crate::utils::{self, GRID_WIDTH, VIEW_HEIGHT, VIEW_WIDTH}; |
17 | 17 | ||
18 | use crate::app::{App, MessageKind, StatusLine}; | 18 | use crate::app::{App, Cursor, Message, MessageKind, StatusLine}; |
19 | 19 | ||
20 | impl App { | 20 | impl App { |
21 | pub fn new() -> Self { | 21 | pub fn new() -> Self { |
@@ -28,7 +28,8 @@ impl App { | |||
28 | _file_watcher: watcher, | 28 | _file_watcher: watcher, |
29 | file_event_recv: rx, | 29 | file_event_recv: rx, |
30 | view_month_offset: 0, | 30 | view_month_offset: 0, |
31 | message: "Type :add <habit-name> <goal> to get started, Ctrl-L to dismiss".into(), | 31 | cursor: Cursor::new(), |
32 | message: Message::startup(), | ||
32 | }; | 33 | }; |
33 | } | 34 | } |
34 | 35 | ||
@@ -85,6 +86,13 @@ impl App { | |||
85 | } | 86 | } |
86 | } | 87 | } |
87 | 88 | ||
89 | pub fn move_cursor(&mut self, d: Absolute) { | ||
90 | self.cursor.do_move(d); | ||
91 | for v in self.habits.iter_mut() { | ||
92 | v.move_cursor(d); | ||
93 | } | ||
94 | } | ||
95 | |||
88 | pub fn set_focus(&mut self, d: Absolute) { | 96 | pub fn set_focus(&mut self, d: Absolute) { |
89 | match d { | 97 | match d { |
90 | Absolute::Right => { | 98 | Absolute::Right => { |
@@ -129,7 +137,7 @@ impl App { | |||
129 | format!("{}", Local::now().naive_local().date().format("%d/%b/%y"),) | 137 | format!("{}", Local::now().naive_local().date().format("%d/%b/%y"),) |
130 | } else { | 138 | } else { |
131 | let months = self.view_month_offset; | 139 | let months = self.view_month_offset; |
132 | format!("{}", format!("{} months ago", months),) | 140 | format!("{}", format!("{} month(s) ago", months),) |
133 | }; | 141 | }; |
134 | 142 | ||
135 | StatusLine { | 143 | StatusLine { |
diff --git a/src/app/impl_view.rs b/src/app/impl_view.rs index 395cac4..0ec47f1 100644 --- a/src/app/impl_view.rs +++ b/src/app/impl_view.rs | |||
@@ -95,6 +95,24 @@ 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('w') => { | ||
100 | self.move_cursor(Absolute::Up); | ||
101 | return EventResult::Consumed(None); | ||
102 | } | ||
103 | Event::Char('a') => { | ||
104 | self.move_cursor(Absolute::Left); | ||
105 | return EventResult::Consumed(None); | ||
106 | } | ||
107 | Event::Char('s') => { | ||
108 | self.move_cursor(Absolute::Down); | ||
109 | return EventResult::Consumed(None); | ||
110 | } | ||
111 | Event::Char('d') => { | ||
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); |
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 | ||
37 | impl Message { | 37 | impl 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..9432f0d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs | |||
@@ -5,11 +5,13 @@ use notify::{DebouncedEvent, RecommendedWatcher}; | |||
5 | 5 | ||
6 | use crate::habit::HabitWrapper; | 6 | use crate::habit::HabitWrapper; |
7 | 7 | ||
8 | mod cursor; | ||
8 | mod impl_self; | 9 | mod impl_self; |
9 | mod impl_view; | 10 | mod impl_view; |
10 | mod message; | 11 | mod message; |
11 | 12 | ||
12 | pub struct StatusLine(String, String); | 13 | pub struct StatusLine(String, String); |
14 | pub use cursor::Cursor; | ||
13 | pub use message::{Message, MessageKind}; | 15 | pub use message::{Message, MessageKind}; |
14 | 16 | ||
15 | pub struct App { | 17 | pub struct App { |
@@ -20,6 +22,7 @@ pub struct App { | |||
20 | file_event_recv: Receiver<DebouncedEvent>, | 22 | file_event_recv: Receiver<DebouncedEvent>, |
21 | focus: usize, | 23 | focus: usize, |
22 | view_month_offset: u32, | 24 | view_month_offset: u32, |
25 | cursor: Cursor, | ||
23 | message: Message, | 26 | message: Message, |
24 | } | 27 | } |
25 | 28 | ||
diff --git a/src/habit/bit.rs b/src/habit/bit.rs index 2bbb0ac..7fe6fd9 100644 --- a/src/habit/bit.rs +++ b/src/habit/bit.rs | |||
@@ -1,8 +1,10 @@ | |||
1 | use std::collections::HashMap; | 1 | use std::collections::HashMap; |
2 | 2 | ||
3 | use chrono::NaiveDate; | 3 | use chrono::NaiveDate; |
4 | use cursive::direction::Absolute; | ||
4 | use serde::{Deserialize, Serialize}; | 5 | use serde::{Deserialize, Serialize}; |
5 | 6 | ||
7 | use crate::app::Cursor; | ||
6 | use crate::habit::prelude::default_auto; | 8 | use crate::habit::prelude::default_auto; |
7 | use crate::habit::traits::Habit; | 9 | use crate::habit::traits::Habit; |
8 | use crate::habit::{TrackEvent, ViewMode}; | 10 | use crate::habit::{TrackEvent, ViewMode}; |
@@ -45,6 +47,9 @@ pub struct Bit { | |||
45 | view_month_offset: u32, | 47 | view_month_offset: u32, |
46 | 48 | ||
47 | #[serde(skip)] | 49 | #[serde(skip)] |
50 | cursor: Cursor, | ||
51 | |||
52 | #[serde(skip)] | ||
48 | view_mode: ViewMode, | 53 | view_mode: ViewMode, |
49 | } | 54 | } |
50 | 55 | ||
@@ -56,6 +61,7 @@ impl Bit { | |||
56 | goal: CustomBool(true), | 61 | goal: CustomBool(true), |
57 | auto, | 62 | auto, |
58 | view_month_offset: 0, | 63 | view_month_offset: 0, |
64 | cursor: Cursor::new(), | ||
59 | view_mode: ViewMode::Day, | 65 | view_mode: ViewMode::Day, |
60 | }; | 66 | }; |
61 | } | 67 | } |
@@ -124,6 +130,12 @@ impl Habit for Bit { | |||
124 | fn view_month_offset(&self) -> u32 { | 130 | fn view_month_offset(&self) -> u32 { |
125 | self.view_month_offset | 131 | self.view_month_offset |
126 | } | 132 | } |
133 | fn move_cursor(&mut self, d: Absolute) { | ||
134 | self.cursor.do_move(d); | ||
135 | } | ||
136 | fn cursor(&self) -> Cursor { | ||
137 | self.cursor | ||
138 | } | ||
127 | fn set_view_mode(&mut self, mode: ViewMode) { | 139 | fn set_view_mode(&mut self, mode: ViewMode) { |
128 | self.view_mode = mode; | 140 | self.view_mode = mode; |
129 | } | 141 | } |
diff --git a/src/habit/count.rs b/src/habit/count.rs index d351758..b14354c 100644 --- a/src/habit/count.rs +++ b/src/habit/count.rs | |||
@@ -1,8 +1,10 @@ | |||
1 | use std::collections::HashMap; | 1 | use std::collections::HashMap; |
2 | 2 | ||
3 | use chrono::NaiveDate; | 3 | use chrono::NaiveDate; |
4 | use cursive::direction::Absolute; | ||
4 | use serde::{Deserialize, Serialize}; | 5 | use serde::{Deserialize, Serialize}; |
5 | 6 | ||
7 | use crate::app::Cursor; | ||
6 | use crate::habit::prelude::default_auto; | 8 | use crate::habit::prelude::default_auto; |
7 | use crate::habit::traits::Habit; | 9 | use crate::habit::traits::Habit; |
8 | use crate::habit::{TrackEvent, ViewMode}; | 10 | use crate::habit::{TrackEvent, ViewMode}; |
@@ -20,6 +22,9 @@ pub struct Count { | |||
20 | view_month_offset: u32, | 22 | view_month_offset: u32, |
21 | 23 | ||
22 | #[serde(skip)] | 24 | #[serde(skip)] |
25 | cursor: Cursor, | ||
26 | |||
27 | #[serde(skip)] | ||
23 | view_mode: ViewMode, | 28 | view_mode: ViewMode, |
24 | } | 29 | } |
25 | 30 | ||
@@ -31,6 +36,7 @@ impl Count { | |||
31 | goal, | 36 | goal, |
32 | auto, | 37 | auto, |
33 | view_month_offset: 0, | 38 | view_month_offset: 0, |
39 | cursor: Cursor::new(), | ||
34 | view_mode: ViewMode::Day, | 40 | view_mode: ViewMode::Day, |
35 | }; | 41 | }; |
36 | } | 42 | } |
@@ -101,6 +107,12 @@ impl Habit for Count { | |||
101 | fn view_month_offset(&self) -> u32 { | 107 | fn view_month_offset(&self) -> u32 { |
102 | self.view_month_offset | 108 | self.view_month_offset |
103 | } | 109 | } |
110 | fn move_cursor(&mut self, d: Absolute) { | ||
111 | self.cursor.do_move(d); | ||
112 | } | ||
113 | fn cursor(&self) -> Cursor { | ||
114 | self.cursor | ||
115 | } | ||
104 | fn set_view_mode(&mut self, mode: ViewMode) { | 116 | fn set_view_mode(&mut self, mode: ViewMode) { |
105 | self.view_mode = mode; | 117 | self.view_mode = mode; |
106 | } | 118 | } |
diff --git a/src/habit/traits.rs b/src/habit/traits.rs index 74fd00b..289fd95 100644 --- a/src/habit/traits.rs +++ b/src/habit/traits.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | use chrono::NaiveDate; | 1 | use chrono::NaiveDate; |
2 | use cursive::direction::Direction; | 2 | use cursive::direction::{Absolute, Direction}; |
3 | use cursive::event::{Event, EventResult}; | 3 | use cursive::event::{Event, EventResult}; |
4 | use cursive::{Printer, Vec2}; | 4 | use cursive::{Printer, Vec2}; |
5 | 5 | ||
6 | use typetag; | 6 | use typetag; |
7 | 7 | ||
8 | use crate::app::Cursor; | ||
8 | use crate::habit::{Bit, Count, TrackEvent, ViewMode}; | 9 | use crate::habit::{Bit, Count, TrackEvent, ViewMode}; |
9 | use crate::views::ShadowView; | 10 | use crate::views::ShadowView; |
10 | 11 | ||
@@ -24,6 +25,9 @@ pub trait Habit { | |||
24 | fn set_view_month_offset(&mut self, offset: u32); | 25 | fn set_view_month_offset(&mut self, offset: u32); |
25 | fn view_month_offset(&self) -> u32; | 26 | fn view_month_offset(&self) -> u32; |
26 | 27 | ||
28 | fn move_cursor(&mut self, d: Absolute); | ||
29 | fn cursor(&self) -> Cursor; | ||
30 | |||
27 | fn set_view_mode(&mut self, mode: ViewMode); | 31 | fn set_view_mode(&mut self, mode: ViewMode); |
28 | fn view_mode(&self) -> ViewMode; | 32 | fn view_mode(&self) -> ViewMode; |
29 | 33 | ||
@@ -44,6 +48,9 @@ pub trait HabitWrapper: erased_serde::Serialize { | |||
44 | fn set_view_month_offset(&mut self, offset: u32); | 48 | fn set_view_month_offset(&mut self, offset: u32); |
45 | fn view_month_offset(&self) -> u32; | 49 | fn view_month_offset(&self) -> u32; |
46 | 50 | ||
51 | fn move_cursor(&mut self, d: Absolute); | ||
52 | fn cursor(&self) -> Cursor; | ||
53 | |||
47 | fn set_view_mode(&mut self, mode: ViewMode); | 54 | fn set_view_mode(&mut self, mode: ViewMode); |
48 | fn view_mode(&self) -> ViewMode; | 55 | fn view_mode(&self) -> ViewMode; |
49 | 56 | ||
@@ -87,6 +94,12 @@ macro_rules! auto_habit_impl { | |||
87 | fn view_month_offset(&self) -> u32 { | 94 | fn view_month_offset(&self) -> u32 { |
88 | Habit::view_month_offset(self) | 95 | Habit::view_month_offset(self) |
89 | } | 96 | } |
97 | fn move_cursor(&mut self, d: Absolute) { | ||
98 | Habit::move_cursor(self, d) | ||
99 | } | ||
100 | fn cursor(&self) -> Cursor { | ||
101 | Habit::cursor(self) | ||
102 | } | ||
90 | fn set_view_mode(&mut self, mode: ViewMode) { | 103 | fn set_view_mode(&mut self, mode: ViewMode) { |
91 | Habit::set_view_mode(self, mode) | 104 | Habit::set_view_mode(self, mode) |
92 | } | 105 | } |
diff --git a/src/theme.rs b/src/theme.rs index 1d2cc36..e373b72 100644 --- a/src/theme.rs +++ b/src/theme.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use cursive::theme::Color::*; | 1 | use cursive::theme::Color::*; |
2 | use cursive::theme::PaletteColor::*; | 2 | use cursive::theme::PaletteColor::*; |
3 | use cursive::theme::{BorderStyle, Palette, Theme}; | 3 | use cursive::theme::{BorderStyle, ColorStyle, Palette, Style, Theme}; |
4 | 4 | ||
5 | pub fn pallete_gen() -> Palette { | 5 | pub fn pallete_gen() -> Palette { |
6 | let mut p = Palette::default(); | 6 | let mut p = Palette::default(); |
@@ -24,3 +24,11 @@ pub fn theme_gen() -> Theme { | |||
24 | t.palette = pallete_gen(); | 24 | t.palette = pallete_gen(); |
25 | return t; | 25 | return t; |
26 | } | 26 | } |
27 | |||
28 | pub fn cursor_gen(foreground: Style) -> Style { | ||
29 | Style::from(ColorStyle::new( | ||
30 | TerminalDefault, | ||
31 | Light(cursive::theme::BaseColor::Blue), | ||
32 | )) | ||
33 | .combine(foreground) | ||
34 | } | ||
diff --git a/src/views.rs b/src/views.rs index efd1391..a0beb2c 100644 --- a/src/views.rs +++ b/src/views.rs | |||
@@ -5,9 +5,10 @@ use cursive::view::View; | |||
5 | use cursive::{Printer, Vec2}; | 5 | use cursive::{Printer, Vec2}; |
6 | 6 | ||
7 | use chrono::prelude::*; | 7 | use chrono::prelude::*; |
8 | use chrono::{Duration, Local, NaiveDate}; | 8 | use chrono::{Local, NaiveDate}; |
9 | 9 | ||
10 | use crate::habit::{Bit, Count, Habit, TrackEvent, ViewMode}; | 10 | use crate::habit::{Bit, Count, Habit, TrackEvent, ViewMode}; |
11 | use crate::theme::cursor_gen; | ||
11 | use crate::utils::VIEW_WIDTH; | 12 | use crate::utils::VIEW_WIDTH; |
12 | 13 | ||
13 | use crate::CONFIGURATION; | 14 | use crate::CONFIGURATION; |
@@ -27,13 +28,15 @@ 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.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 | ||
@@ -43,8 +46,7 @@ where | |||
43 | 46 | ||
44 | let strikethrough = Style::from(Effect::Strikethrough); | 47 | let strikethrough = Style::from(Effect::Strikethrough); |
45 | 48 | ||
46 | let goal_status = | 49 | 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 | 50 | ||
49 | printer.with_style( | 51 | printer.with_style( |
50 | Style::merge(&[ | 52 | Style::merge(&[ |
@@ -110,11 +112,12 @@ where | |||
110 | let draw_day = |printer: &Printer| { | 112 | let draw_day = |printer: &Printer| { |
111 | let mut i = 0; | 113 | let mut i = 0; |
112 | while let Some(d) = NaiveDate::from_ymd_opt(year, month, i + 1) { | 114 | while let Some(d) = NaiveDate::from_ymd_opt(year, month, i + 1) { |
113 | let day_style; | 115 | let mut day_style = todo_style; |
114 | if self.reached_goal(d) { | 116 | if self.reached_goal(d) { |
115 | day_style = goal_reached_style; | 117 | day_style = goal_reached_style; |
116 | } else { | 118 | } |
117 | day_style = todo_style; | 119 | if d == now && printer.focused { |
120 | day_style = day_style.combine(cursor_gen(day_style)); | ||
118 | } | 121 | } |
119 | let coords: Vec2 = ((i % 7) * 3, i / 7 + 2).into(); | 122 | let coords: Vec2 = ((i % 7) * 3, i / 7 + 2).into(); |
120 | if let Some(c) = self.get_by_date(d) { | 123 | if let Some(c) = self.get_by_date(d) { |
@@ -146,7 +149,7 @@ where | |||
146 | } | 149 | } |
147 | 150 | ||
148 | fn on_event(&mut self, e: Event) -> EventResult { | 151 | fn on_event(&mut self, e: Event) -> EventResult { |
149 | let now = Local::now().naive_local().date(); | 152 | let now = self.cursor().0; |
150 | if self.is_auto() { | 153 | if self.is_auto() { |
151 | return EventResult::Ignored; | 154 | return EventResult::Ignored; |
152 | } | 155 | } |