From 6f00d075c1bb4422357b09d20c38530736c9d318 Mon Sep 17 00:00:00 2001 From: Fabrizio Iannetti Date: Sun, 16 Oct 2022 14:51:37 +0200 Subject: [PATCH] Refactor * use std::date instead of plain integers and own functions * add a drop down for view type (not functional) Signed-off-by: Fabrizio Iannetti --- src/calendar.rs | 251 +++++++++++++++++++++--------------------------- src/main.rs | 53 +++++++++- 2 files changed, 156 insertions(+), 148 deletions(-) diff --git a/src/calendar.rs b/src/calendar.rs index e099496..0f23c4a 100644 --- a/src/calendar.rs +++ b/src/calendar.rs @@ -14,121 +14,93 @@ use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget}; use iced_native::text; use iced_native::alignment; use iced_native::widget::Tree; -use chrono::{NaiveDate, Datelike}; +use chrono::{NaiveDate, Datelike, Duration, Weekday, Local}; //use std::cmp; //------------------------------------------------------------------------- -const DAYS_PER_MONTH: [i32;12] = [ - 31, // jan - 28, // feb - 31, // mar - 30, // apr - 31, // may - 30, // jun - 31, // jul - 31, // ago - 30, // sep - 31, // oct - 30, // nov - 31, // dec -]; - #[derive(Clone)] pub struct CalendarParams { - weekday_on_first_of_january: i32, // 0 -> Monday .. 6 -> Sunday - first_week_on_first_of_january: bool, - leap_year: bool, -} - -impl CalendarParams { - pub fn new() -> Self { - // temp: data for 2022 - Self { - weekday_on_first_of_january: 5, // saturday - first_week_on_first_of_january: false, - leap_year: false - } - } - - pub fn day_first_of_month(&self, month: i32) -> i32 { - let mut day : i32 = 0; - for i in 0..month { - day += DAYS_PER_MONTH[i as usize]; - if i == 1 && self.leap_year { - day += 1; - } - } - day - } - - pub fn monthday(&self, month: i32, day: i32) -> i32 { - let mut month_days: i32 = DAYS_PER_MONTH[month as usize]; - let mut month: i32 = month; - let mut day: i32 = day; - while day < 0 || day >= month_days { - if day < 0 { - month = (month + 12 - 1) % 12; - day = day + DAYS_PER_MONTH[month as usize]; - } else { - day = day - DAYS_PER_MONTH[month as usize]; - month = (month + 1) % 12; - } - month_days = DAYS_PER_MONTH[month as usize]; - } - day - } - - pub fn weekday(&self, month: i32, day: i32) -> i32 { - let day = self.day_first_of_month(month) + day; - (self.weekday_on_first_of_january + day) % 7 - } - - pub fn weekday_first_of_month(&self, month: i32) -> i32 { - self.weekday(month, 0) - } -} -//------------------------------------------------------------------------- - -pub struct CalendarMonthView { - first_day: NaiveDate, - month: i32, - params: CalendarParams, - weekday_on_first: i32, // 0 -> Monday .. 6 -> Sunday + show_weeks: bool, header_fg: Color, header_bg: Color, day_text: Color, day_text_other_month: Color, day_weekend_bg: Color, + day_today_bg: Color, day_text_margin: f32, +} + +impl CalendarParams { + pub fn new() -> Self { + Self { + show_weeks: true, + header_fg: Color::BLACK, + header_bg: Color::TRANSPARENT, + day_today_bg: Color::from_rgb8(214, 242, 252), + day_text: Color::BLACK, + day_text_other_month: Color::from_rgb8(220, 220, 220), +// day_background: Color::from_rgb8(230, 230, 255), + day_weekend_bg: Color::from_rgb8(245, 245, 245), + day_text_margin: 5.0, + } + } +} + +//------------------------------------------------------------------------- + +pub struct CalendarMonthView { + first_day: NaiveDate, + first_day_in_view: NaiveDate, + params: CalendarParams, + weekday_on_first: Weekday, week_column_width: f32, week_column_font_size: f32, } impl CalendarMonthView { - pub fn new(params: &CalendarParams, first_day: NaiveDate) -> Self { - let month: i32 = first_day.month0() as i32; - Self { + pub fn new(params: &CalendarParams, day: NaiveDate) -> Self { + // first day of the month + let first_day = if day.day() == 1 { + day + } else { + NaiveDate::from_ymd(day.year(), day.month(), 1) + }; + + // weekday on first day of the month + let weekday_on_first = first_day.weekday(); + + // first visible day in the view + let first_day_in_view = first_day - Duration::days(weekday_on_first.num_days_from_monday() as i64); + + Self { first_day, - month, + first_day_in_view, params: params.clone(), - weekday_on_first: params.weekday_first_of_month(month), - header_fg: Color::BLACK, - header_bg: Color::from_rgb8(214, 242, 252), - day_text: Color::BLACK, - day_text_other_month: Color::from_rgb8(220, 220, 220), -// day_background: Color::from_rgb8(230, 230, 255), - day_weekend_bg: Color::from_rgb8(230, 230, 230), - day_text_margin: 5.0, + weekday_on_first, week_column_width: 30.0, week_column_font_size: 18.0, } } - pub fn set_month(&mut self, month: i32) { - self.month = month; - self.weekday_on_first = self.params.weekday_first_of_month(month); + pub fn set_month(&mut self, day: NaiveDate) { + // first day of the month + let first_day = if day.day() == 1 { + day + } else { + NaiveDate::from_ymd(day.year(), day.month(), 1) + }; + + // weekday on first day of the month + let weekday_on_first = first_day.weekday(); + + // first visible day in the view + let first_day_in_view = first_day - Duration::days(weekday_on_first.num_days_from_monday() as i64); + + self.first_day = first_day; + self.weekday_on_first = weekday_on_first; + self.first_day_in_view = first_day_in_view; } fn draw_header( @@ -138,13 +110,15 @@ impl CalendarMonthView { week_w: f32, ) { // paint background over full width - renderer.fill_quad(renderer::Quad { - bounds, - border_radius: 0.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - self.header_bg); + if self.params.header_bg != Color::TRANSPARENT { + renderer.fill_quad(renderer::Quad { + bounds, + border_radius: 0.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + self.params.header_bg); + } // redefine bounds to skip the week column let bounds = Rectangle { @@ -176,9 +150,9 @@ impl CalendarMonthView { let t = days_of_week[weekday as usize]; // color of text - let fg = self.header_fg; + let fg = self.params.header_fg; - let x = bounds.x + self.day_text_margin; + let x = bounds.x + self.params.day_text_margin; let y = bounds.center_y(); renderer.fill_text(text::Text { content : t, @@ -205,34 +179,30 @@ impl CalendarMonthView { h } / 2.0; - for week in 0..6i32 { + let mut day = self.first_day; + + for week in 0..6u32 { // where to place the week number let day_bounds = Rectangle { x: bounds.x, - y: (week as f32) * h + bounds.y + self.day_text_margin, + y: (week as f32) * h + bounds.y + self.params.day_text_margin, width: r * 2.0, height: r * 2.0 }; - // paint the background as weekend (inactive) -// renderer.fill_quad(renderer::Quad { -// bounds: day_bounds, -// border_radius: r, -// border_width: 0.0, -// border_color: Color::TRANSPARENT, -// }, -// self.day_weekend_bg); -// + let week_of_first_day_of_month = day.iso_week().week(); + day += Duration::weeks(1); + // render week cell text renderer.fill_text(text::Text { - content : &week.to_string(), + content : &(week_of_first_day_of_month).to_string(), size: self.week_column_font_size, bounds: Rectangle { x: day_bounds.center_x(), y: day_bounds.y, ..day_bounds }, - color: self.day_text, + color: self.params.day_text, font: Default::default(), horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Top, @@ -255,57 +225,51 @@ impl CalendarMonthView { let w: f32 = size.width / 7.0; let h: f32 = size.height / 6.0; - let days_of_month = [ - " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", "10", - "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", - "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", - "31", - ]; - - // paint the background of the last two columns as weekend - renderer.fill_quad(renderer::Quad { - bounds: Rectangle {x: 5.0 * w + origin.x, y: origin.y, width: 2.0 * w, height: 6.0 * h}, - border_radius: 0.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - self.day_weekend_bg); - + let mut current_day = self.first_day_in_view; for week in 0..6i32 { for weekday in 0..7i32 { - let monthday = weekday + week * 7 - self.weekday_on_first; let day_bounds = Rectangle { x: (weekday as f32) * w + origin.x, y: (week as f32) * h + origin.y, - width: w, - height: h + width: w, + height: h }; // label (day letter on row 0, day number on the rest) - let t = days_of_month[self.params.monthday(self.month, monthday) as usize]; + let t = current_day.day().to_string(); + let content = t.as_str(); // color of text - let fg = if monthday >= 0 && monthday <= 30 { - self.day_text + let fg = if current_day.month() == self.first_day.month() { + self.params.day_text } else { - self.day_text_other_month + self.params.day_text_other_month + }; + + // background color of the day cell + let bg_color = if current_day == Local::today().naive_local() { + self.params.day_today_bg + } else if weekday > 4 { + self.params.day_weekend_bg + } else { + Color::TRANSPARENT }; // where to place the day content - let x = day_bounds.x + self.day_text_margin; - let y = day_bounds.y + self.day_text_margin; + let x = day_bounds.x + self.params.day_text_margin; + let y = day_bounds.y + self.params.day_text_margin; renderer.fill_quad(renderer::Quad { - bounds: day_bounds, + bounds: Rectangle {width: day_bounds.width + 0.5, height: day_bounds.height + 0.5, ..day_bounds}, border_radius: 0.0, - border_width: 0.0, - border_color: self.day_text_other_month, + border_width: 1.0, + border_color: self.params.day_text_other_month, }, - Color::WHITE); + bg_color); // render day cell text renderer.fill_text(text::Text { - content : t, + content, size: font_size, bounds: Rectangle {x, y, ..day_bounds}, color: fg, @@ -313,6 +277,7 @@ impl CalendarMonthView { horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, }); + current_day = current_day.succ(); } } } @@ -354,7 +319,7 @@ where let margin: f32 = 20.0; // week column only visible if there is enough space - let week_w = if size.width > self.week_column_width { + let week_w = if self.params.show_weeks && size.width > self.week_column_width { self.week_column_width } else { 0.0 diff --git a/src/main.rs b/src/main.rs index db1ba1e..2c09df0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use iced::widget::{ Column, Row, Container, Button, Text, + pick_list, }; //use iced::button; use iced::theme; @@ -30,18 +31,57 @@ struct CalendarApp { enum Message { NextMonth, PrevMonth, + ViewModeSelected(ViewMode), } -#[derive(Default)] +//#[derive(Default)] struct Controls { + mode: Option, } +impl Default for Controls { + fn default() -> Controls { + Controls { + mode : Some(ViewMode::Month) + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ViewMode { + Month, + Year, + Day, +} + +impl std::fmt::Display for ViewMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + ViewMode::Month => "Month", + ViewMode::Year => "Year", + ViewMode::Day => "Day", + } + ) + } +} impl Controls { - fn view(&self, month_name: &str) -> Element { + const MODES : [ViewMode; 3] = [ViewMode::Month, ViewMode::Year, ViewMode::Day]; + + fn view<'a>(&'a self, month_name: &'a str, year: i32) -> Element { Row::new() .align_items(Alignment::Center) .padding(5) .spacing(10) + .push( + pick_list( + &Controls::MODES[..], + self.mode, + Message::ViewModeSelected, + ).placeholder("mode") + ) .push( Button::new(Text::new("<")) .on_press(Message::PrevMonth) @@ -59,7 +99,7 @@ impl Controls { .size(40), ) .push( - Text::new("2022") + Text::new(year.to_string()) .width(Length::Fill) .horizontal_alignment(iced_native::alignment::Horizontal::Right) .size(40), @@ -91,8 +131,11 @@ impl Sandbox for CalendarApp { Message::NextMonth => { self.month = self.month + Months::new(1); } + Message::ViewModeSelected(mode) => { + self.controls.mode = Some(mode); + } } - println!("month={}", self.month); + //println!("month={}", self.month); } fn view(&self) -> Element { @@ -112,7 +155,7 @@ impl Sandbox for CalendarApp { ]; let content = Column::new() .align_items(Alignment::Fill) - .push(self.controls.view(MONTH_NAMES[self.month.month() as usize])) + .push(self.controls.view(MONTH_NAMES[self.month.month0() as usize], self.month.year())) .push(CalendarMonthView::new(&CalendarParams::new(), self.month)) ;