From 3b0a5836fad437a6cb5c1b170a694ec562b5b019 Mon Sep 17 00:00:00 2001 From: Ilya Date: Tue, 12 Apr 2022 23:30:52 +0200 Subject: [PATCH] Improve scrolling calendar --- examples/test.rs | 13 ++-- src/calendar.rs | 197 ++++++++++++++++++++++++++++++++++++----------- src/style.rs | 1 + 3 files changed, 157 insertions(+), 54 deletions(-) diff --git a/examples/test.rs b/examples/test.rs index 092c03a..156534d 100644 --- a/examples/test.rs +++ b/examples/test.rs @@ -82,17 +82,16 @@ fn main() -> Result<(), core::convert::Infallible> { fg_color, bg_color, hi_color: TriColor::Red, + border_color: TriColor::Black, }; let calendar_week = ScrollingCalendar { - top_left: Point::new(400, 10), - size: Size::new(396, 396), + top_left: Point::new(400, 0), + size: Size::new(396, 480), style: calendar_style, - day: chrono::Local::now() - .naive_local() - .date() - .checked_add_signed(chrono::Duration::days(28 * 4 * 0 - 14)) - .unwrap(), + day: chrono::Local::now().naive_local().date(), + //.checked_add_signed(chrono::Duration::days(28 * 4 * 0 - 14)) + //.unwrap(), weeks: 5, }; calendar_week.draw(&mut display)?; diff --git a/src/calendar.rs b/src/calendar.rs index 4e1a1a7..0ccb8cf 100644 --- a/src/calendar.rs +++ b/src/calendar.rs @@ -27,11 +27,9 @@ fn font(height: u32) -> MonoFont<'static> { } pub struct CalendarDay { + pub style: ComponentStyle, pub top_left: Point, pub size: Size, - pub fg_color: C, - pub bg_color: C, - pub border: u32, pub day_of_month: u32, pub has_event: bool, } @@ -50,18 +48,21 @@ where // Adding based on border so that the borders overlap let date_rect = Rectangle::new( self.top_left, - self.size + Size::new(self.border / 2 + 1, self.border / 2 + 1), + self.size + Size::new(self.style.border / 2 + 1, self.style.border / 2 + 1), ); date_rect - .into_styled(PrimitiveStyle::with_fill(self.bg_color)) + .into_styled(PrimitiveStyle::with_fill(self.style.bg_color)) .draw(target)?; date_rect - .into_styled(PrimitiveStyle::with_stroke(self.fg_color, self.border)) + .into_styled(PrimitiveStyle::with_stroke( + self.style.border_color, + self.style.border, + )) .draw(target)?; let text = format!("{}", self.day_of_month); let date_font = font(self.size.height / 2); - let text_style = MonoTextStyle::new(&date_font, self.fg_color); + let text_style = MonoTextStyle::new(&date_font, self.style.fg_color); Text::new(&text, self.top_left, text_style) .align_to(&date_rect, horizontal::Center, vertical::Center) .draw(target)?; @@ -76,7 +77,7 @@ where self.size.height as i32 - event_padding, ), ) - .into_styled(PrimitiveStyle::with_stroke(self.fg_color, 2)) + .into_styled(PrimitiveStyle::with_stroke(self.style.fg_color, 2)) .draw(target)?; } @@ -203,12 +204,19 @@ where (fg_color, bg_color, border) = (self.bg_color, self.hi_color.unwrap(), 0); } - let calendar_day = CalendarDay { + let style = ComponentStyle { + fg_color, + bg_color, + hi_color: fg_color, + border_color: fg_color, border, + bezel: border, + }; + + let calendar_day = CalendarDay { + style, top_left: self.top_left + Point::new((width * x) as i32, (height * y) as i32), size: Size::new(width, height), - fg_color, - bg_color, day_of_month: i - empty_squares + 1, has_event: i - empty_squares + 1 == date_now.day(), }; @@ -219,47 +227,100 @@ where } } +#[derive(Clone)] pub struct ScrollingWeek { - day: chrono::NaiveDate, + year: i32, + week: u32, + start_of_month: bool, } impl ScrollingWeek { - pub fn succ(&self) -> ScrollingWeek { - let start_of_next_month = self - .day - .with_month(self.day.month() % 11 + 1) - .unwrap() - .with_day(1) + fn monday(&self) -> chrono::NaiveDate { + chrono::NaiveDate::from_isoywd(self.year, self.week, chrono::Weekday::Mon) + } + + fn first_day_of_week(&self) -> Option { + let monday = self.monday(); + let next_monday = self + .monday() + .checked_add_signed(chrono::Duration::days(7)) .unwrap(); - let days_until_next_month = - start_of_next_month.num_days_from_ce() - self.day.num_days_from_ce(); - let day_of_week = self.day.weekday().number_from_monday() as i32; + if !self.start_of_month || monday.month() == next_monday.month() { + return Some(monday); + } - let days_to_skip = if days_until_next_month < 7 { - days_until_next_month - } else { - 8 - day_of_week - }; + if !self.start_of_month && next_monday.day() == 1 { + return None; + } + + return next_monday.with_day(1); + } + + fn last_day_of_week(&self) -> Option { + return self + .succ() + .first_day_of_week()? + .checked_sub_signed(chrono::Duration::days(1)); + + match self.succ().first_day_of_week() { + None => self + .first_day_of_week()? + .checked_add_signed(chrono::Duration::days(6)), + Some(date) => date.checked_sub_signed(chrono::Duration::days(1)), + } + } + + pub fn from_date(date: chrono::NaiveDate) -> ScrollingWeek { + let week = date.iso_week().week(); ScrollingWeek { - day: self - .day - .checked_add_signed(chrono::Duration::days(days_to_skip as i64)) - .unwrap(), + year: date.year(), + week, + start_of_month: date.day() < 7, + } + } + + pub fn from_week(week: chrono::IsoWeek) -> ScrollingWeek { + let now = chrono::Local::today().naive_local(); + let monday = chrono::NaiveDate::from_isoywd(now.year(), week.week(), chrono::Weekday::Mon); + + ScrollingWeek { + year: now.year(), + week: week.week(), + start_of_month: monday.day() < 7, + } + } + + pub fn succ(&self) -> ScrollingWeek { + let monday = self.monday(); + let next_monday = monday + .checked_add_signed(chrono::Duration::days(7)) + .unwrap(); + + match self.start_of_month || monday.month() == next_monday.month() { + false => ScrollingWeek { + start_of_month: true, + ..*self + }, + true => ScrollingWeek { + year: next_monday.year(), + week: next_monday.iso_week().week(), + start_of_month: false, + }, } } pub fn month(&self) -> chrono::Month { - chrono::Month::from_u32(self.day.month()).unwrap() + chrono::Month::from_u32(self.monday().month()).unwrap() } pub fn week(&self) -> u32 { - self.day.iso_week().week() + self.monday().iso_week().week() } pub fn year(&self) -> i32 { - self.day.year() + self.monday().year() } } @@ -283,17 +344,14 @@ where D: DrawTarget, { let height = self.size.height / (1 + self.weeks * 2); - let mut week = ScrollingWeek { - day: chrono::NaiveDate::from_isoywd( - self.day.year(), - self.day.iso_week().week(), - chrono::Weekday::Mon, - ) - .checked_add_signed(chrono::Duration::days(-7 * self.weeks as i64)) - .unwrap(), - }; + let mut week = ScrollingWeek::from_date( + self + .day + .checked_sub_signed(chrono::Duration::days(self.weeks as i64 * 7)) + .unwrap(), + ); - for i in 0..self.weeks * 2 { + for i in 0..(1 + self.weeks * 2) { week = week.succ(); let calendar_week = CalendarWeek { @@ -303,6 +361,7 @@ where year: week.year(), month: week.month(), week: week.week(), + scroll_week: week.clone(), }; calendar_week.draw(target)?; } @@ -318,6 +377,7 @@ pub struct CalendarWeek { pub year: i32, pub month: chrono::Month, pub week: u32, + pub scroll_week: ScrollingWeek, } impl Drawable for CalendarWeek @@ -345,10 +405,55 @@ where false => sunday, }; + let first_day = self.scroll_week.first_day_of_week(); + let last_day = self.scroll_week.last_day_of_week(); + + if first_day.is_none() || last_day.is_none() { + return Ok(()); + } + + let first_day = first_day.unwrap(); + let last_day = last_day.unwrap(); + let width = self.size.width / 7; + if first_day.weekday().number_from_monday() > 4 { + let month = CalendarMonth { + top_left: self.top_left, + size: Size::new( + width * first_day.weekday().number_from_monday(), + self.size.height, + ), + border: self.style.border, + fg_color: self.style.fg_color, + bg_color: self.style.bg_color, + month: chrono::Month::from_u32(first_day.month()).unwrap(), + }; + month.draw(target)?; + } else if last_day.weekday().number_from_monday() < 4 { + let month = CalendarMonth { + top_left: self.top_left + + Point::new((width * last_day.weekday().number_from_monday()) as i32, 0), + size: Size::new( + width * (7 - last_day.weekday().number_from_monday()), + self.size.height, + ), + border: self.style.border, + fg_color: self.style.fg_color, + bg_color: self.style.bg_color, + month: chrono::Month::from_u32( + last_day + .checked_add_signed(chrono::Duration::days(1)) + .unwrap() + .month(), + ) + .unwrap(), + }; + month.draw(target)?; + } + let mut weekday = first_day.weekday(); - for i in first_day.weekday().number_from_monday()..(last_day.weekday().number_from_monday() + 1) + for _ in first_day.weekday().number_from_monday()..(last_day.weekday().number_from_monday() + 1) { let day = chrono::NaiveDate::from_isoywd(self.year, self.week, weekday); @@ -368,11 +473,9 @@ where }; let calendar_day = CalendarDay { + style, top_left: self.top_left + Point::new((width * weekday.num_days_from_monday()) as i32, 0), size: Size::new(width, self.size.height), - border: self.style.border, - fg_color, - bg_color, day_of_month: day.day(), has_event: day.num_days_from_ce() == today.num_days_from_ce(), }; diff --git a/src/style.rs b/src/style.rs index 80d8c01..d2edfd9 100644 --- a/src/style.rs +++ b/src/style.rs @@ -3,6 +3,7 @@ pub struct ComponentStyle { pub fg_color: C, pub bg_color: C, pub hi_color: C, + pub border_color: C, pub border: u32, pub bezel: u32, }