// For now, to implement a custom native widget you will need to add // `iced_native` and `iced_wgpu` to your dependencies. // // Then, you simply need to define your widget type and implement the // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. // // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. use iced_native::layout::{self, Layout}; use iced_native::renderer; use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget}; use iced_native::text; use iced_native::alignment; use iced_native::widget::Tree; //------------------------------------------------------------------------- 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 { month: i32, params: CalendarParams, weekday_on_first: i32, // 0 -> Monday .. 6 -> Sunday header_fg: Color, header_bg: Color, day_text: Color, day_text_other_month: Color, day_weekend_bg: Color, } impl CalendarMonthView { pub fn new(params: &CalendarParams, month: i32) -> Self { Self { month, 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(250, 250, 250), } } pub fn set_month(&mut self, month: i32) { self.month = month; self.weekday_on_first = self.params.weekday_first_of_month(month); } fn draw_header( &self, renderer: &mut impl text::Renderer, bounds: Rectangle, ) { 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; let h: f32 = bounds.height; let days_of_week = ["Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Dom"]; renderer.fill_quad(renderer::Quad { bounds, border_radius: 0.0, border_width: 0.0, border_color: Color::TRANSPARENT, }, self.header_bg); for weekday in 0..7i32 { let bounds = Rectangle { x: (weekday as f32) * w + origin.x, y: origin.y, width: w, height: h }; // label (day letter on row 0, day number on the rest) let t = days_of_week[weekday as usize]; // color of text let fg = self.header_fg; let x = bounds.center_x(); let y = bounds.center_y(); renderer.fill_text(text::Text { content : t, size: font_size, bounds: Rectangle {x, y, ..bounds}, color: fg, font: Default::default(), horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, }); } } fn draw_days( &self, 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 / 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); 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 + 2.0) + origin.x, y: (week as f32) * (h + 2.0) + origin.y, 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]; // color of text let fg = if monthday >= 0 && monthday <= 30 { self.day_text } else { self.day_text_other_month }; let x = day_bounds.center_x(); let y = day_bounds.center_y(); renderer.fill_text(text::Text { content : t, size: font_size, bounds: Rectangle {x, y, ..day_bounds}, color: fg, font: Default::default(), horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Center, }); } } } } // CalendarMonthView impl Widget for CalendarMonthView where Renderer: text::Renderer, { fn width(&self) -> Length { Length::Shrink } fn height(&self) -> Length { Length::Shrink } fn layout( &self, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout::Node::new(limits.max()) } fn draw( &self, _state: &Tree, renderer: &mut Renderer, _theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, _viewport: &Rectangle, ) { let size: Size = layout.bounds().size(); let origin = layout.bounds().position(); let margin: f32 = 20.0; // font and header dimension let font_size = renderer.default_size() as f32; 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}); // monthly calendar cells let x = origin.x; let y = origin.y + first_row_h; let width = size.width; let height = size.height - first_row_h; self.draw_days(renderer, Rectangle{x, y, width, height}); } } impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> where Renderer: text::Renderer, { fn from(circle: CalendarMonthView) -> Self { Self::new(circle) } }