diff --git a/Cargo.lock b/Cargo.lock index da2ca2f..aaa65aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "ab_glyph" version = "0.2.21" @@ -525,12 +519,12 @@ dependencies = [ [[package]] name = "cosmic-text" version = "0.8.0" -source = "git+https://github.com/hecrj/cosmic-text.git?rev=b85d6a4f2376f8a8a7dadc0f8bcb89d4db10a1c9#b85d6a4f2376f8a8a7dadc0f8bcb89d4db10a1c9" +source = "git+https://github.com/hecrj/cosmic-text.git?rev=e8b10fd675832cb9c1cc9de30922beb4cf883876#e8b10fd675832cb9c1cc9de30922beb4cf883876" dependencies = [ + "aliasable", "fontdb", "libm", "log 0.4.18", - "ouroboros", "rangemap", "rustybuzz", "swash", @@ -657,14 +651,15 @@ dependencies = [ [[package]] name = "fontdb" -version = "0.13.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237ff9f0813bbfc9de836016472e0c9ae7802f174a51594607e5f4ff334cb2f5" +checksum = "af8d8cbea8f21307d7e84bca254772981296f058a1d36b461bf4d83a7499fc9e" dependencies = [ "log 0.4.18", - "memmap2", + "memmap2 0.6.2", "slotmap", - "ttf-parser 0.18.1", + "tinyvec", + "ttf-parser", ] [[package]] @@ -826,7 +821,7 @@ dependencies = [ [[package]] name = "glyphon" version = "0.2.0" -source = "git+https://github.com/hecrj/glyphon.git?rev=26f92369da3704988e3e27f0b35e705c6b2de203#26f92369da3704988e3e27f0b35e705c6b2de203" +source = "git+https://github.com/hecrj/glyphon.git?rev=8dbf36020e5759fa9144517b321372266160113e#8dbf36020e5759fa9144517b321372266160113e" dependencies = [ "cosmic-text", "etagere", @@ -1342,6 +1337,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -1660,36 +1664,13 @@ dependencies = [ "redox_syscall 0.3.5", ] -[[package]] -name = "ouroboros" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" -dependencies = [ - "aliasable", - "ouroboros_macro", -] - -[[package]] -name = "ouroboros_macro" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" -dependencies = [ - "Inflector", - "proc-macro-error", - "proc-macro2 1.0.59", - "quote 1.0.28", - "syn 1.0.109", -] - [[package]] name = "owned_ttf_parser" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" dependencies = [ - "ttf-parser 0.19.0", + "ttf-parser", ] [[package]] @@ -1864,30 +1845,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2 1.0.59", - "quote 1.0.28", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2 1.0.59", - "quote 1.0.28", - "version_check", -] - [[package]] name = "proc-macro2" version = "0.2.3" @@ -2064,15 +2021,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustybuzz" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a" +checksum = "82eea22c8f56965eeaf3a209b3d24508256c7b920fb3b6211b8ba0f7c0583250" dependencies = [ "bitflags 1.3.2", "bytemuck", "libm", "smallvec", - "ttf-parser 0.18.1", + "ttf-parser", "unicode-bidi-mirroring", "unicode-ccc", "unicode-general-category", @@ -2099,7 +2056,7 @@ checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" dependencies = [ "ab_glyph", "log 0.4.18", - "memmap2", + "memmap2 0.5.10", "smithay-client-toolkit", "tiny-skia 0.8.4", ] @@ -2151,7 +2108,7 @@ dependencies = [ "dlib", "lazy_static", "log 0.4.18", - "memmap2", + "memmap2 0.5.10", "nix 0.24.3", "pkg-config", "wayland-client 0.29.5", @@ -2388,6 +2345,21 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "0.6.2" @@ -2405,12 +2377,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "ttf-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" - [[package]] name = "ttf-parser" version = "0.19.0" diff --git a/src/model/events.rs b/src/model/events.rs index d0bef44..7f56392 100644 --- a/src/model/events.rs +++ b/src/model/events.rs @@ -3,7 +3,7 @@ use std::vec::Vec; use std::default::Default; use std::string::String; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Event { pub text: String, pub begin: NaiveDate, @@ -21,9 +21,24 @@ pub struct Event { //} // impl Event { - fn is_in_day(&self, day: NaiveDate) -> bool { + pub fn is_in_day(&self, day: NaiveDate) -> bool { day >= self.begin && day <= self.end } + + pub fn is_in_days(&self, first_day: NaiveDate, last_day: NaiveDate) -> bool { + let mut day = first_day; + while day <= last_day { + if self.is_in_day(day) { + return true + } + day = day.succ_opt().unwrap(); + } + false + } + + pub fn span_days(&self) -> i64 { + (self.end - self.begin).num_days() + } } pub struct EventsCollection { @@ -45,6 +60,21 @@ impl EventsCollection { begin: NaiveDate::from_ymd_opt(2023, 07, 31).unwrap(), end: NaiveDate::from_ymd_opt(2023, 09, 11).unwrap(), }, + Event { + text: String::from("event one"), + begin: NaiveDate::from_ymd_opt(2023, 07, 31).unwrap(), + end: NaiveDate::from_ymd_opt(2023, 08, 03).unwrap(), + }, + Event { + text: String::from("event two"), + begin: NaiveDate::from_ymd_opt(2023, 08, 02).unwrap(), + end: NaiveDate::from_ymd_opt(2023, 08, 04).unwrap(), + }, + Event { + text: String::from("event three"), + begin: NaiveDate::from_ymd_opt(2023, 08, 05).unwrap(), + end: NaiveDate::from_ymd_opt(2023, 08, 06).unwrap(), + }, ], } } @@ -67,6 +97,36 @@ impl EventsCollection { } events_in_day } + + pub fn starting_at(&self, day: NaiveDate) -> Vec { + let mut events_in_day = Vec::new(); + for ev_day in &self.events { + if ev_day.begin == day { + events_in_day.push(ev_day.clone()); + } + } + events_in_day + } + + pub fn starting_before(&self, day: NaiveDate) -> Vec { + let mut events_in_day = Vec::new(); + for ev_day in &self.events { + if ev_day.begin < day { + events_in_day.push(ev_day.clone()); + } + } + events_in_day + } + + pub fn within(&self, first_day: NaiveDate, last_day: NaiveDate) -> Vec { + let mut events_in_day = Vec::new(); + for ev_day in &self.events { + if ev_day.is_in_days(first_day, last_day) { + events_in_day.push(ev_day.clone()); + } + } + events_in_day + } } impl Default for EventsCollection { diff --git a/src/ui/basics.rs b/src/ui/basics.rs index 99ffa97..74292a0 100644 --- a/src/ui/basics.rs +++ b/src/ui/basics.rs @@ -1,5 +1,5 @@ -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Cell { // display coordinates of the cell pub x : f32, @@ -46,6 +46,14 @@ impl CellGrid { } } + pub fn num_rows(&self) -> u32 { + return self.num_y; + } + + pub fn num_cols(&self) -> u32 { + return self.num_x; + } + pub fn rows(&self) -> Self { CellGrid::new(self.x, self.y, self.width, self.height, 1, self.num_y) } diff --git a/src/ui/calendar.rs b/src/ui/calendar.rs index 3ee19ad..e80875a 100644 --- a/src/ui/calendar.rs +++ b/src/ui/calendar.rs @@ -12,9 +12,9 @@ use iced::advanced::{layout, renderer}; use iced::advanced::widget::{Tree, Widget}; use iced::{Color, Element, Length, Point, Rectangle, alignment}; use iced::advanced::text::{self, Text, LineHeight, Shaping}; -use chrono::{NaiveDate, Datelike, Duration, Local}; +use chrono::{NaiveDate, Datelike, Duration, Local, Months}; use super::basics::CellGrid; -use crate::model::events::EventsCollection; +use crate::model::events::{EventsCollection, Event}; use iced::mouse; #[cfg(feature = "tracing")] @@ -84,44 +84,85 @@ impl CalendarParams { //------------------------------------------------------------------------- -fn render_events_in_day( +fn render_events_in_row( params: &CalendarParams, renderer: &mut impl text::Renderer, - current_day: NaiveDate, - day_bounds: Rectangle, + first_day: NaiveDate, + num_days: i64, + row_bounds: Rectangle, font_size: f32, fg: Color, content: &str, events: &EventsCollection ) { + if num_days < 1 { + return + } + + #[derive(Debug)] + struct EventBar<'a> { + ev: &'a Event, + bounds: Rectangle, + } // render events, if enough space - let day_text_size = renderer.measure( + let day_text_height = renderer.measure( content, font_size, LineHeight::default(), renderer.default_font(), - day_bounds.size(), - Shaping::default()); + row_bounds.size(), + Shaping::default()).1; + + let last_day = first_day + Duration::days(num_days - 1); + let all_events = events.within(first_day, last_day); + let x = row_bounds.x; + let y = row_bounds.y + params.day_text_margin + day_text_height; let ev_height = params.ev_height; + let mut ev_y: f32 = 0.0; - let y = day_bounds.y + params.day_text_margin; - for ev_day in events.for_day(current_day) { - if day_bounds.height - day_text_size.1 > ev_y + ev_height { - let ev_bounds = Rectangle { - y: y + ev_y + day_text_size.1, - height: ev_height, - ..day_bounds - }; + + let mut ev_bars : Vec = all_events.iter().map(|e| EventBar{ev: e, bounds: Rectangle {x, y, width: 0.0, height: ev_height}}).collect(); + + // TODO: incompatible types num_days, grid num_cols + let row_grid = CellGrid::new(row_bounds.x, row_bounds.y, row_bounds.width, row_bounds.height, num_days.try_into().unwrap(), 1); + + let mut current_day = first_day; + + // update event bars + for cell in row_grid { + ev_y = y; + for ev_bar in ev_bars.iter_mut() { + if ev_bar.ev.begin == current_day || (ev_bar.ev.begin < first_day && current_day == first_day) { + // start of event + ev_bar.bounds.x = cell.x; + ev_bar.bounds.y = ev_y; + } else if ev_bar.ev.end == current_day { + // end of event -> set width + ev_bar.bounds.width = cell.x + cell.width - ev_bar.bounds.x; + } + if ev_bar.ev.is_in_day(current_day) { + ev_y += ev_height; + } + } + current_day = current_day.succ_opt().unwrap(); + } + + for ev_bar in &mut ev_bars { + // close events that exceed the row + if ev_bar.ev.end >= current_day { + ev_bar.bounds.width = row_bounds.x + row_bounds.width - ev_bar.bounds.x; + } + if row_bounds.y + row_bounds.height > ev_bar.bounds.y + ev_bar.bounds.height { renderer.fill_quad(renderer::Quad { - bounds: ev_bounds, + bounds: ev_bar.bounds, border_radius: 0.0.into(), border_width: 1.0, border_color: params.day_other_month_fg, }, params.ev_bg); renderer.fill_text(Text { - content: ev_day.text.as_str(), - bounds: ev_bounds, + content: ev_bar.ev.text.as_str(), + bounds: ev_bar.bounds, size: params.ev_fontsize, line_height: LineHeight::default(), color: fg, @@ -296,71 +337,82 @@ impl<'a> CalendarMonthView<'a> { let mut current_day = self.first_day_in_view; 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 + for row in grid.rows() { + let row_first_day = current_day; + let row_grid = CellGrid::new(row.x, row.y, row.width, row.height, 7, 1); + for cell in row_grid { + let day_bounds = Rectangle { + x: cell.x, + y: cell.y, + width: cell.width, + height : cell.height + }; + + // 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 = if current_day.month() == self.first_day.month() { + self.params.day_fg + } else { + self.params.day_other_month_fg + }; + + // background color of the day cell + let bg_color = if current_day == Local::now().date_naive() { + self.params.day_today_bg + } else if current_day.weekday().num_days_from_monday() > 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: 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_other_month_fg, + }, + bg_color); + + // render day cell text + renderer.fill_text(Text { + content, + bounds: Rectangle {x, y, ..day_bounds}, + size: font_size, + line_height: LineHeight::default(), + color: fg, + font: renderer.default_font(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: Shaping::default() + }); + + current_day = current_day.succ_opt().unwrap(); + } + let row_bounds = Rectangle { + x: row.x, + y: row.y, + width: row.width, + height : row.height }; - - // 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 = if current_day.month() == self.first_day.month() { - self.params.day_fg - } else { - self.params.day_other_month_fg - }; - - // background color of the day cell - let bg_color = if current_day == Local::now().date_naive() { - self.params.day_today_bg - } else if current_day.weekday().num_days_from_monday() > 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: 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_other_month_fg, - }, - bg_color); - - // render day cell text - renderer.fill_text(Text { - content, - bounds: Rectangle {x, y, ..day_bounds}, - size: font_size, - line_height: LineHeight::default(), - color: fg, - font: renderer.default_font(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - shaping: Shaping::default() - }); - - render_events_in_day( + let content = "10"; + render_events_in_row( &self.params, renderer, - current_day, - day_bounds, + row_first_day, + 6, + row_bounds, font_size, - fg, + self.params.day_fg, content, self.events ); - - current_day = current_day.succ_opt().unwrap(); } } @@ -605,77 +657,94 @@ impl<'a> CalendarYearView<'a> { let font_size = renderer.default_size() as f32; 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 row in grid.rows() { + let row_grid = CellGrid::new(row.x, row.y, row.width, row.height, 42, 1); // the row index is the month - let month = cell.pos_y; + let month = row.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)); + let mut row_bounds: Rectangle = Rectangle{x: row.x, y: row.y, width: row.width, height: row.height}; + let row_days = ((first_day_of_month + Months::new(1)) - first_day_of_month).num_days(); - if current_day.month0() == month { - let weekday = current_day.weekday().num_days_from_monday(); + for cell in row_grid { - // 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_fg; - - // background color of the day cell - let bg_color = if current_day == Local::now().date_naive() { - self.params.day_today_bg - } else if weekday > 4 { - self.params.day_weekend_bg - } else { - Color::TRANSPARENT + let day_bounds = Rectangle { + x: cell.x, + y: cell.y, + width: cell.width, + height : cell.height }; + let current_day = first_day_of_month + Duration::days((cell.pos_x as i64) - (first_weekday as i64)); - // 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; + if current_day.month0() == month { + if current_day.day() == 1 { + let diff = cell.x - row_bounds.x; + row_bounds.x = cell.x; + row_bounds.width -= diff; + } else if current_day.day() == row_days as u32 { + row_bounds.width = cell.x - row_bounds.x + cell.width; + } + let weekday = current_day.weekday().num_days_from_monday(); - renderer.fill_quad(renderer::Quad { - bounds: day_bounds, - border_radius: 0.0.into(), - border_width: 1.0, - border_color: self.params.day_other_month_fg, - }, - bg_color); + // label (day letter on row 0, day number on the rest) + let t = current_day.day().to_string(); + let content = t.as_str(); - // render day cell text - renderer.fill_text(Text { - content, - bounds: Rectangle {x, y, ..day_bounds}, - size: font_size, - line_height: LineHeight::default(), - color: fg, - font: renderer.default_font(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - shaping: Shaping::default(), - }); + // color of text + let fg = self.params.day_fg; - render_events_in_day( + // background color of the day cell + let bg_color = if current_day == Local::now().date_naive() { + 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_other_month_fg, + }, + bg_color); + + // render day cell text + renderer.fill_text(Text { + content, + bounds: Rectangle {x, y, ..day_bounds}, + size: font_size, + line_height: LineHeight::default(), + color: fg, + font: renderer.default_font(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: Shaping::default(), + }); + + } + } +// let num_days = grid.num_cols() as i64; // ((current_day + Months::new(1)) - current_day).num_days(); + let content = "10"; + render_events_in_row( &self.params, renderer, - current_day, - day_bounds, + first_day_of_month, + row_days as i64, + row_bounds, font_size, - fg, + self.params.day_fg, content, self.events ); } - } } fn compute_month_col_width(&self, renderer: &mut impl text::Renderer) -> f32 {