rs-calendar/src/calendar.rs

304 lines
9.0 KiB
Rust

// 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;
//-------------------------------------------------------------------------
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<Message, Renderer> Widget<Message, Renderer> 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,
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<CalendarMonthView> for Element<'a, Message, Renderer>
where
Renderer: text::Renderer,
{
fn from(circle: CalendarMonthView) -> Self {
Self::new(circle)
}
}