diff --git a/Cargo.lock b/Cargo.lock index e6757f3..c7250c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -941,7 +941,7 @@ dependencies = [ [[package]] name = "iced" -version = "0.5.2" +version = "0.6.0" dependencies = [ "iced_core", "iced_futures", @@ -954,7 +954,7 @@ dependencies = [ [[package]] name = "iced_core" -version = "0.6.1" +version = "0.6.2" dependencies = [ "bitflags", "palette", @@ -973,7 +973,7 @@ dependencies = [ [[package]] name = "iced_graphics" -version = "0.4.0" +version = "0.5.0" dependencies = [ "bitflags", "bytemuck", @@ -987,7 +987,7 @@ dependencies = [ [[package]] name = "iced_native" -version = "0.6.1" +version = "0.7.0" dependencies = [ "iced_core", "iced_futures", @@ -999,7 +999,7 @@ dependencies = [ [[package]] name = "iced_style" -version = "0.5.0" +version = "0.5.1" dependencies = [ "iced_core", "once_cell", @@ -1008,7 +1008,7 @@ dependencies = [ [[package]] name = "iced_wgpu" -version = "0.6.1" +version = "0.7.0" dependencies = [ "bitflags", "bytemuck", @@ -1027,7 +1027,7 @@ dependencies = [ [[package]] name = "iced_winit" -version = "0.5.1" +version = "0.6.0" dependencies = [ "iced_futures", "iced_graphics", diff --git a/src/main.rs b/src/main.rs index cf78e35..75c0303 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,9 @@ use chrono::{Datelike, NaiveDate, Months, Utc}; use calendar::{CalendarMonthView, CalendarYearView, CalendarParams }; use iced::{ - Alignment, - Element, Length, Sandbox, Settings, + executor, + Alignment, Application, Command, + Element, Length, Settings, }; use iced::widget::{ Column, Row, @@ -62,11 +63,11 @@ impl std::fmt::Display for ViewMode { match self { ViewMode::Month => "Month", ViewMode::Year => "Year", -// ViewMode::Day => "Day", } ) } } + impl Controls { const MODES : [ViewMode; 2] = [ViewMode::Month, ViewMode::Year]; @@ -108,22 +109,25 @@ impl Controls { } } -impl Sandbox for CalendarApp { +impl Application for CalendarApp { + type Executor = executor::Default; type Message = Message; + type Theme = iced::Theme; + type Flags = (); - fn new() -> Self { + fn new(_flags: Self::Flags) -> (Self, Command) { let month = Utc::today().naive_local(); - CalendarApp { + (CalendarApp { month, ..CalendarApp::default() - } + }, Command::none()) } fn title(&self) -> String { String::from("Calendar") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command { match message { Message::PrevMonth => { self.month = self.month - Months::new(1); @@ -135,6 +139,7 @@ impl Sandbox for CalendarApp { self.controls.mode = Some(mode); } } + Command::none() } fn view(&self) -> Element { @@ -161,7 +166,7 @@ impl Sandbox for CalendarApp { Some(ViewMode::Year) => content = content.push(CalendarYearView::new(&CalendarParams::new(), self.month)), Some(ViewMode::Month) | None => content = content.push(CalendarMonthView::new(&CalendarParams::new(), self.month)), }; - + Container::new(content) .width(Length::Fill) .height(Length::Fill) diff --git a/src/ui/basics.rs b/src/ui/basics.rs index f9bdfce..07843c0 100644 --- a/src/ui/basics.rs +++ b/src/ui/basics.rs @@ -1,6 +1,5 @@ - -pub struct CellAxis { +struct CellAxis { min: f32, max: f32, num: u32, @@ -16,6 +15,10 @@ impl CellAxis { idx: 0 } } + + pub fn reset(&mut self) { + self.idx = 0; + } } impl Iterator for CellAxis { @@ -26,9 +29,90 @@ impl Iterator for CellAxis { let beg: f32 = self.min + ((self.max - self.min) * self.idx as f32) / self.num as f32; let end: f32 = self.min + ((self.max - self.min) * (self.idx + 1) as f32) / self.num as f32; self.idx += 1; - + return Some((beg.trunc(), end.trunc())); } return None; } } + +#[derive(Copy, Clone)] +pub struct Cell { + pub x : f32, + pub y : f32, + pub width : f32, + pub height : f32, + + pub pos_x: u32, + pub pos_y: u32, +} + +pub struct CellGrid { + x_axis: CellAxis, + y_axis: CellAxis, + curr: Cell, +} + +impl CellGrid { + pub fn new(x: f32, y: f32, width: f32, height: f32, num_x: u32, num_y: u32) -> Self { + let x_axis = CellAxis::new(x, width, num_x); + let y_axis = CellAxis::new(y, height, num_y); + Self { + x_axis, + y_axis, + curr: Cell {x, y, width, height, pos_x: 0, pos_y: 0} + } + } +} + +impl Iterator for CellGrid { + type Item = Cell; + + fn next(&mut self) -> Option { + if self.y_axis.idx == 0 && self.x_axis.idx == 0 { + if self.y_axis.num == 0 || self.x_axis.num == 0 { + return None + } + self.curr.pos_y = self.y_axis.idx; + let y_next = self.y_axis.next().unwrap(); + self.curr.y = y_next.0; + self.curr.height = y_next.1 - y_next.0; + } + self.curr.pos_x = self.x_axis.idx; + let x_next = self.x_axis.next(); + match x_next { + None => { + // end of x-axis, move down the y-axis + self.curr.pos_y = self.y_axis.idx; + let y_next = self.y_axis.next(); + + match y_next { + None => { + None + }, + Some(y_val) => { + // set new vertical span + self.curr.y = y_val.0; + self.curr.height = y_val.1 - y_val.0; + + // reset iteration on x axis + self.x_axis.reset(); + self.curr.pos_x = 0; + + // get first horizontal span + let x_val = self.x_axis.next().unwrap(); + self.curr.x = x_val.0; + self.curr.width = x_val.1 - x_val.0; + + Some(self.curr) + } + } + }, + Some(x_val) => { + self.curr.x = x_val.0; + self.curr.width = x_val.1 - x_val.0; + Some(self.curr) + } + } + } +} \ No newline at end of file diff --git a/src/ui/calendar.rs b/src/ui/calendar.rs index f55efff..d6f94d5 100644 --- a/src/ui/calendar.rs +++ b/src/ui/calendar.rs @@ -10,12 +10,12 @@ use iced_native::layout::{self, Layout}; use iced_native::renderer; -use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget}; +use iced_native::{Color, Element, Length, Point, Rectangle, Widget}; use iced_native::text; use iced_native::alignment; use iced_native::widget::Tree; use chrono::{NaiveDate, Datelike, Duration, Weekday, Local}; -use super::basics::CellAxis; +use super::basics::CellGrid; const MONTH_NAMES: [&str;12] = [ "gen", @@ -32,6 +32,8 @@ const MONTH_NAMES: [&str;12] = [ "dic", ]; +const DAY_NAMES: [&str;7] = ["LUN", "MAR", "MER", "GIO", "VEN", "SAB", "DOM"]; + //------------------------------------------------------------------------- #[derive(Clone)] @@ -55,7 +57,6 @@ impl CalendarParams { 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, } @@ -81,7 +82,7 @@ impl CalendarMonthView { } else { NaiveDate::from_ymd(day.year(), day.month(), 1) }; - + // weekday on first day of the month let weekday_on_first = first_day.weekday(); @@ -92,7 +93,7 @@ impl CalendarMonthView { first_day, first_day_in_view, params: params.clone(), - weekday_on_first, + weekday_on_first, week_column_width: 30.0, week_column_font_size: 18.0, } @@ -105,7 +106,7 @@ impl CalendarMonthView { } else { NaiveDate::from_ymd(day.year(), day.month(), 1) }; - + // weekday on first day of the month let weekday_on_first = first_day.weekday(); @@ -130,35 +131,32 @@ impl CalendarMonthView { border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, - }, + }, self.params.header_bg); } - + // redefine bounds to skip the week column let bounds = Rectangle { - x: bounds.x + week_w, - y: bounds.y, - width: bounds.width - week_w, + x: bounds.x + week_w, + y: bounds.y, + width: bounds.width - week_w, height: bounds.height}; // font dimension let font_size = renderer.default_size() as f32; - let days_of_week = ["LUN", "MAR", "MER", "GIO", "VEN", "SAB", "DOM"]; + let h_axis = CellGrid::new(bounds.x, bounds.y, bounds.width, bounds.height, 7, 1); - let h_axis = CellAxis::new(bounds.x, bounds.width, 7); - - let mut day_num: usize = 0; - for weekday in h_axis { - let bounds = Rectangle { - x: weekday.0+ self.params.day_text_margin, + for cell in h_axis { + let bounds = Rectangle { + x: cell.x + self.params.day_text_margin, y: bounds.center_y(), - width: weekday.1 - weekday.0, + width: cell.width, height: bounds.height }; // label (day letter on row 0, day number on the rest) - let t = days_of_week[day_num]; + let t = DAY_NAMES[cell.pos_x as usize]; // color of text let fg = self.params.header_fg; @@ -172,7 +170,6 @@ impl CalendarMonthView { horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, }); - day_num += 1; } } @@ -181,24 +178,16 @@ impl CalendarMonthView { renderer: &mut impl text::Renderer, bounds: Rectangle, ) { - // dimensions of each box representing a week number - let h: f32 = bounds.height / 6.0; - let r: f32 = if h > self.week_column_width { - self.week_column_width - } else { - h - } / 2.0; - let mut day = self.first_day; - let v_axis = CellAxis::new(bounds.y, bounds.height, 6); - for week_row in v_axis { + let v_axis = CellGrid::new(bounds.x, bounds.y, self.week_column_width, bounds.height, 1, 6); + for cell in v_axis { // where to place the week number - let day_bounds = Rectangle { - x: bounds.x, - y: week_row.0 + self.params.day_text_margin, - width: r * 2.0, - height: r * 2.0 + let day_bounds = Rectangle { + x: bounds.x, + y: cell.y + self.params.day_text_margin, + width: cell.width, + height: cell.height }; let week_of_first_day_of_month = day.iso_week().week(); @@ -226,69 +215,63 @@ impl CalendarMonthView { renderer: &mut impl text::Renderer, bounds: Rectangle, ) { - let size: Size = bounds.size(); - let origin = bounds.position(); - // font dimension let font_size = renderer.default_size() as f32; let mut current_day = self.first_day_in_view; - let v_axis = CellAxis::new(origin.y, size.height, 6); - for row in v_axis { - let h_axis = CellAxis::new(origin.x, size.width, 7); - for col in h_axis { - let day_bounds = Rectangle { - x: col.0, - y: row.0, - width: col.1 - col.0, - height : row.1 - row.0 - }; - - // label (day letter on row 0, day number on the rest) - let t = current_day.day().to_string(); - let content = t.as_str(); + let grid = CellGrid::new(bounds.x, bounds.y, bounds.width, bounds.height, 7, 6); + for cell in grid { + let day_bounds = Rectangle { + x: cell.x, + y: cell.y, + width: cell.width, + height : cell.height + }; - // color of text - let fg = if current_day.month() == self.first_day.month() { - self.params.day_text - } else { - self.params.day_text_other_month - }; + // label (day letter on row 0, day number on the rest) + let t = current_day.day().to_string(); + let content = t.as_str(); - // background color of the day cell - let bg_color = if current_day == Local::today().naive_local() { - self.params.day_today_bg - } else if current_day.weekday().num_days_from_monday() > 4 { - self.params.day_weekend_bg - } else { - Color::TRANSPARENT - }; + // color of text + let fg = if current_day.month() == self.first_day.month() { + self.params.day_text + } else { + self.params.day_text_other_month + }; - // where to place the day content - let x = day_bounds.x + self.params.day_text_margin; - let y = day_bounds.y + self.params.day_text_margin; + // background color of the day cell + let bg_color = if current_day == Local::today().naive_local() { + self.params.day_today_bg + } else if current_day.weekday().num_days_from_monday() > 4 { + self.params.day_weekend_bg + } else { + Color::TRANSPARENT + }; - renderer.fill_quad(renderer::Quad { - bounds: Rectangle {width: day_bounds.width + 0.5, height: day_bounds.height + 0.5, ..day_bounds}, - border_radius: 0.0.into(), - border_width: 1.0, - border_color: self.params.day_text_other_month, - }, - bg_color); + // where to place the day content + let x = day_bounds.x + self.params.day_text_margin; + let y = day_bounds.y + self.params.day_text_margin; - // render day cell text - renderer.fill_text(text::Text { - content, - size: font_size, - bounds: Rectangle {x, y, ..day_bounds}, - color: fg, - font: Default::default(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - }); - current_day = current_day.succ(); - } + renderer.fill_quad(renderer::Quad { + bounds: Rectangle {width: day_bounds.width + 0.5, height: day_bounds.height + 0.5, ..day_bounds}, + border_radius: 0.0.into(), + border_width: 1.0, + border_color: self.params.day_text_other_month, + }, + bg_color); + + // render day cell text + renderer.fill_text(text::Text { + content, + size: font_size, + bounds: Rectangle {x, y, ..day_bounds}, + color: fg, + font: Default::default(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }); + current_day = current_day.succ(); } } @@ -324,12 +307,11 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let size: Size = layout.bounds().size(); - let origin = layout.bounds().position(); + let bounds = layout.bounds(); let margin: f32 = 20.0; // week column only visible if there is enough space - let week_w = if self.params.show_weeks && size.width > self.week_column_width { + let week_w = if self.params.show_weeks && bounds.width > self.week_column_width { self.week_column_width } else { 0.0 @@ -340,26 +322,22 @@ where let first_row_h = font_size + margin; // header - let x = origin.x; - let y = origin.y; - let width = size.width; - let height = first_row_h; - self.draw_header(renderer, Rectangle {x, y, width, height}, week_w); + self.draw_header(renderer, Rectangle {height: first_row_h, ..bounds}, week_w); // week column if week_w > 0.0 { - let x = origin.x; - let y = origin.y + first_row_h; + let x = bounds.x; + let y = bounds.y + first_row_h; let width = self.week_column_width; - let height = size.height - first_row_h; + let height = bounds.height - first_row_h; self.draw_week_column(renderer, Rectangle{x, y, width, height}); } - + // monthly calendar cells - let x = origin.x + week_w; - let y = origin.y + first_row_h; - let width = size.width - week_w; - let height = size.height - first_row_h; + let x = bounds.x + week_w; + let y = bounds.y + first_row_h; + let width = bounds.width - week_w; + let height = bounds.height - first_row_h; self.draw_days(renderer, Rectangle{x, y, width, height}); } } @@ -388,7 +366,7 @@ impl CalendarYearView { pub fn new(params: &CalendarParams, day: NaiveDate) -> Self { // first day of the year let first_day = NaiveDate::from_ymd(day.year(), 1, 1); - + // weekday on first day of the year let weekday_on_first = first_day.weekday(); @@ -399,7 +377,7 @@ impl CalendarYearView { first_day, first_day_in_view, params: params.clone(), - weekday_on_first, + weekday_on_first, month_column_font_size: 24.0, margin: 10.0 } @@ -408,7 +386,7 @@ impl CalendarYearView { pub fn set_year(&mut self, day: NaiveDate) { // first day of the year self.first_day = NaiveDate::from_ymd(day.year(), 1, 1); - + // weekday on first day of the year self.weekday_on_first = self.first_day.weekday(); @@ -429,37 +407,32 @@ impl CalendarYearView { border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, - }, + }, self.params.header_bg); } - + // redefine bounds to skip the week column let bounds = Rectangle { - x: bounds.x + week_w, - y: bounds.y, - width: bounds.width - week_w, + x: bounds.x + week_w, + y: bounds.y, + width: bounds.width - week_w, height: bounds.height}; - let origin = bounds.position(); - // font dimension let font_size = renderer.default_size() as f32; - // dimensions of each box representing a day - let w: f32 = (bounds.width / (7.0 * 6.0)).trunc(); - let h: f32 = bounds.height; - let days_of_week = ["L", "M", "M", "G", "V", "S", "D"]; - for col in 0..42i32 { - let bounds = Rectangle { - x: (col as f32) * w + origin.x + 0.5, - y: origin.y + 0.5, - width: w, - height: h + let grid = CellGrid::new(bounds.x, bounds.y, bounds.width, bounds.height, 42, 1); + for cell in grid { + let bounds = Rectangle { + x: cell.x + 0.5, + y: cell.y + 0.5, + width: cell.width, + height: cell.height }; - let weekday = (col as usize) % 7; + let weekday = (cell.pos_x as usize) % 7; // background color of the day cell let bg_color = if weekday > 4 { @@ -473,7 +446,7 @@ impl CalendarYearView { border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, - }, + }, bg_color); // label (day letter on row 0, day number on the rest) @@ -506,11 +479,11 @@ impl CalendarYearView { for month in 0..12usize { // where to place the month name - let month_name_bounds = Rectangle { - x: bounds.x, - y: (month as f32) * h + bounds.y + self.params.day_text_margin, - width: bounds.width, - height: h + let month_name_bounds = Rectangle { + x: bounds.x, + y: (month as f32) * h + bounds.y + self.params.day_text_margin, + width: bounds.width, + height: h }; // render month name @@ -535,70 +508,67 @@ impl CalendarYearView { renderer: &mut impl text::Renderer, bounds: Rectangle, ) { - let size: Size = bounds.size(); - let origin = bounds.position(); - // font dimension let font_size = renderer.default_size() as f32; - // dimensions of each box representing a day - let w: f32 = (size.width / 42.0).trunc(); - let h: f32 = (size.height / 12.0).trunc(); + let grid = CellGrid::new(bounds.x, bounds.y, bounds.width, bounds.height, 42, 12); + for cell in grid { + let day_bounds = Rectangle { + x: cell.x + 0.5, + y: cell.y + 0.5, + width: cell.width, + height: cell.height + }; - for current_day in self.first_day.iter_days() { - if current_day.year() != self.first_day.year() { - break; + // the row index is the month + let month = cell.pos_y; + let first_day_of_month = self.first_day.with_day0(0).unwrap().with_month0(month).unwrap(); + + let first_weekday = first_day_of_month.weekday().num_days_from_monday(); + let current_day = first_day_of_month + Duration::days((cell.pos_x as i64) - (first_weekday as i64)); + + if current_day.month0() == month { + let weekday = current_day.weekday().num_days_from_monday(); + + // label (day letter on row 0, day number on the rest) + let t = current_day.day().to_string(); + let content = t.as_str(); + + // color of text + let fg = self.params.day_text; + + // 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.params.day_text_margin; + let y = day_bounds.y + self.params.day_text_margin; + + renderer.fill_quad(renderer::Quad { + bounds: day_bounds, + border_radius: 0.0.into(), + border_width: 1.0, + border_color: self.params.day_text_other_month, + }, + bg_color); + + // render day cell text + renderer.fill_text(text::Text { + content, + size: font_size, + bounds: Rectangle {x, y, ..day_bounds}, + color: fg, + font: Default::default(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }); } - let month = current_day.month0(); - let weekday = current_day.weekday().num_days_from_monday(); - let first_day_of_month = current_day.with_day0(0).unwrap().weekday().num_days_from_monday(); - let monthday = current_day.day0() + first_day_of_month; - let day_bounds = Rectangle { - x: (monthday as f32) * w + origin.x + 0.5, - y: (month as f32) * h + origin.y + 0.5, - width: w, - height: h - }; - - - // label (day letter on row 0, day number on the rest) - let t = current_day.day().to_string(); - let content = t.as_str(); - - // color of text - let fg = self.params.day_text; - - // 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.params.day_text_margin; - let y = day_bounds.y + self.params.day_text_margin; - - renderer.fill_quad(renderer::Quad { - bounds: day_bounds, - border_radius: 0.0.into(), - border_width: 1.0, - border_color: self.params.day_text_other_month, - }, - bg_color); - - // render day cell text - renderer.fill_text(text::Text { - content, - size: font_size, - bounds: Rectangle {x, y, ..day_bounds}, - color: fg, - font: Default::default(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - }); } } @@ -645,8 +615,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let size: Size = layout.bounds().size(); - let origin = layout.bounds().position(); + let bounds = layout.bounds(); let margin: f32 = 20.0; // week column only visible if there is enough space @@ -662,26 +631,22 @@ where let first_row_h = font_size + margin; // header - let x = origin.x; - let y = origin.y; - let width = size.width; - let height = first_row_h; - self.draw_header(renderer, Rectangle {x, y, width, height}, month_w); + self.draw_header(renderer, Rectangle {height: first_row_h, ..bounds}, month_w); // month column - if month_w > 0.0 && size.width > month_w { - let x = origin.x; - let y = origin.y + first_row_h; + if month_w > 0.0 && bounds.width > month_w { + let x = bounds.x; + let y = bounds.y + first_row_h; let width = month_w; - let height = size.height - first_row_h; + let height = bounds.height - first_row_h; self.draw_month_column(renderer, Rectangle{x, y, width, height}); } - + // monthly calendar cells - let x = origin.x + month_w; - let y = origin.y + first_row_h; - let width = size.width - month_w; - let height = size.height - first_row_h; + let x = bounds.x + month_w; + let y = bounds.y + first_row_h; + let width = bounds.width - month_w; + let height = bounds.height - first_row_h; self.draw_days(renderer, Rectangle{x, y, width, height}); } }