use crate::{helper::weekday_to_str, Globals}; use chrono::{Datelike, Duration, NaiveDateTime, Timelike}; use egui::{Color32, Rect, Sense, Stroke, Ui, Vec2}; use egui_extras::{Size, TableBuilder}; use std::{collections::BTreeMap, ops::Range}; #[derive(Default)] pub struct Calendar { // offset: NaiveDate, } impl Calendar { pub fn ui(&mut self, ui: &mut Ui, g: &mut Globals) { let start_date = chrono::Utc::now().date_naive(); //self.offset; let end_date = start_date + chrono::Duration::days(7); let start_dt = start_date.and_hms(0, 0, 0); let end_dt = end_date.and_hms(0, 0, 0); let task_ids = g.tasks.keys().map(|e| e.to_owned()).collect::>(); let instances = BTreeMap::from_iter(task_ids.iter().filter_map(|id| { Some((id, g.get_instances_range(*id, start_dt, end_dt).to_owned()?)) })); let height = 1500.0; TableBuilder::new(ui) .column(Size::exact(50.0)) .columns(Size::remainder(), 7) .header(50.0, |mut tr| { tr.col(|_| {}); for d in 0..7 { tr.col(|ui| { ui.heading(weekday_to_str( (start_dt + Duration::days(d as i64)) .date() .weekday() .num_days_from_monday() .into(), )); }); } }) .body(|mut tb| { tb.row(height, |mut tr| { tr.col(|ui| { let (response, p) = ui.allocate_painter(Vec2::new(50.0, height), Sense::hover()); for h in 0..24 { p.text( response.rect.min + Vec2::new(0.0, h as f32 / 24.0 * height), egui::Align2::LEFT_TOP, format!("{h:02}:00"), egui::FontId::monospace(15.0), Color32::from_gray(150), ); } }); for d in 0..7 { tr.col(|ui| { let time = start_dt + Duration::days(d as i64); let time_end = time + Duration::days(1) - Duration::seconds(1); let instances_here = instances .iter() .map(|(id, rs)| { ( id, rs.iter() .filter(|r| r.overlaps(time..time_end)) .collect::>(), ) }) .filter(|(_, l)| l.len() != 0); ui.horizontal(|ui| { for (id, rs) in instances_here { let task = g.tasks.get(id).unwrap(); let (rect, response) = ui.allocate_exact_size( Vec2::new(10.0, height), Sense::hover(), ); for r in &rs { let r = r.start.unwrap_or(time)..r.end.unwrap_or(time_end); let rect_start = (r.start.hour() as f32 + (r.start.minute() as f32 / 60.0)) / 24.0 * height; let rect_end = (r.end.hour() as f32 + (r.end.minute() as f32 / 60.0)) / 24.0 * height; let rect = Rect::from_two_pos( rect.min + Vec2::new(0.0, rect_start), rect.min + Vec2::new(10.0, rect_end), ); if let Some(p) = response.hover_pos() { if rect.contains(p) { response.clone().on_hover_ui_at_pointer(|ui| { ui.heading(&task.name); ui.label(&format!( "from {} to {}", r.start, r.end )); if let Some(d) = &task.description { ui.label(d); } }); } } ui.painter().rect( rect, 0.0, Color32::KHAKI, Stroke::new(0.0, Color32::WHITE), ); } } }); }); } }); }); } } pub trait Overlaps { fn overlaps(&self, v: T) -> bool; } impl Overlaps for Range { fn overlaps(&self, v: NaiveDateTime) -> bool { self.start <= v && v < self.end } } impl Overlaps for Range> { fn overlaps(&self, v: NaiveDateTime) -> bool { match (self.start, self.end) { (Some(s), Some(e)) => s <= v && v < e, (Some(s), None) => s <= v, (None, Some(e)) => v < e, (None, None) => false, } } } impl Overlaps> for Range> { fn overlaps(&self, v: Range) -> bool { match (self.start, self.end) { (None, None) => false, (None, Some(e)) => v.start < e, (Some(s), None) => v.end > s, (Some(s), Some(e)) => v.start < e && v.end > s, } } }