* use std::date instead of plain integers and own functions
* add a drop down for view type (not functional)

Signed-off-by: Fabrizio Iannetti <fabrizio.iannetti@gmail.com>
This commit is contained in:
Fabrizio Iannetti 2022-10-16 14:51:37 +02:00
parent a0406c0da7
commit 6f00d075c1
2 changed files with 156 additions and 148 deletions

View File

@ -14,121 +14,93 @@ use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget};
use iced_native::text;
use iced_native::alignment;
use iced_native::widget::Tree;
use chrono::{NaiveDate, Datelike};
use chrono::{NaiveDate, Datelike, Duration, Weekday, Local};
//use std::cmp;
//-------------------------------------------------------------------------
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 {
first_day: NaiveDate,
month: i32,
params: CalendarParams,
weekday_on_first: i32, // 0 -> Monday .. 6 -> Sunday
show_weeks: bool,
header_fg: Color,
header_bg: Color,
day_text: Color,
day_text_other_month: Color,
day_weekend_bg: Color,
day_today_bg: Color,
day_text_margin: f32,
}
impl CalendarParams {
pub fn new() -> Self {
Self {
show_weeks: true,
header_fg: Color::BLACK,
header_bg: Color::TRANSPARENT,
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,
}
}
}
//-------------------------------------------------------------------------
pub struct CalendarMonthView {
first_day: NaiveDate,
first_day_in_view: NaiveDate,
params: CalendarParams,
weekday_on_first: Weekday,
week_column_width: f32,
week_column_font_size: f32,
}
impl CalendarMonthView {
pub fn new(params: &CalendarParams, first_day: NaiveDate) -> Self {
let month: i32 = first_day.month0() as i32;
pub fn new(params: &CalendarParams, day: NaiveDate) -> Self {
// first day of the month
let first_day = if day.day() == 1 {
day
} else {
NaiveDate::from_ymd(day.year(), day.month(), 1)
};
// weekday on first day of the month
let weekday_on_first = first_day.weekday();
// first visible day in the view
let first_day_in_view = first_day - Duration::days(weekday_on_first.num_days_from_monday() as i64);
Self {
first_day,
month,
first_day_in_view,
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(230, 230, 230),
day_text_margin: 5.0,
weekday_on_first,
week_column_width: 30.0,
week_column_font_size: 18.0,
}
}
pub fn set_month(&mut self, month: i32) {
self.month = month;
self.weekday_on_first = self.params.weekday_first_of_month(month);
pub fn set_month(&mut self, day: NaiveDate) {
// first day of the month
let first_day = if day.day() == 1 {
day
} else {
NaiveDate::from_ymd(day.year(), day.month(), 1)
};
// weekday on first day of the month
let weekday_on_first = first_day.weekday();
// first visible day in the view
let first_day_in_view = first_day - Duration::days(weekday_on_first.num_days_from_monday() as i64);
self.first_day = first_day;
self.weekday_on_first = weekday_on_first;
self.first_day_in_view = first_day_in_view;
}
fn draw_header(
@ -138,13 +110,15 @@ impl CalendarMonthView {
week_w: f32,
) {
// paint background over full width
renderer.fill_quad(renderer::Quad {
bounds,
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
self.header_bg);
if self.params.header_bg != Color::TRANSPARENT {
renderer.fill_quad(renderer::Quad {
bounds,
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
self.params.header_bg);
}
// redefine bounds to skip the week column
let bounds = Rectangle {
@ -176,9 +150,9 @@ impl CalendarMonthView {
let t = days_of_week[weekday as usize];
// color of text
let fg = self.header_fg;
let fg = self.params.header_fg;
let x = bounds.x + self.day_text_margin;
let x = bounds.x + self.params.day_text_margin;
let y = bounds.center_y();
renderer.fill_text(text::Text {
content : t,
@ -205,34 +179,30 @@ impl CalendarMonthView {
h
} / 2.0;
for week in 0..6i32 {
let mut day = self.first_day;
for week in 0..6u32 {
// where to place the week number
let day_bounds = Rectangle {
x: bounds.x,
y: (week as f32) * h + bounds.y + self.day_text_margin,
y: (week as f32) * h + bounds.y + self.params.day_text_margin,
width: r * 2.0,
height: r * 2.0
};
// paint the background as weekend (inactive)
// renderer.fill_quad(renderer::Quad {
// bounds: day_bounds,
// border_radius: r,
// border_width: 0.0,
// border_color: Color::TRANSPARENT,
// },
// self.day_weekend_bg);
//
let week_of_first_day_of_month = day.iso_week().week();
day += Duration::weeks(1);
// render week cell text
renderer.fill_text(text::Text {
content : &week.to_string(),
content : &(week_of_first_day_of_month).to_string(),
size: self.week_column_font_size,
bounds: Rectangle {
x: day_bounds.center_x(),
y: day_bounds.y,
..day_bounds
},
color: self.day_text,
color: self.params.day_text,
font: Default::default(),
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Top,
@ -255,25 +225,9 @@ impl CalendarMonthView {
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);
let mut current_day = self.first_day_in_view;
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 + origin.x,
y: (week as f32) * h + origin.y,
@ -282,30 +236,40 @@ impl CalendarMonthView {
};
// label (day letter on row 0, day number on the rest)
let t = days_of_month[self.params.monthday(self.month, monthday) as usize];
let t = current_day.day().to_string();
let content = t.as_str();
// color of text
let fg = if monthday >= 0 && monthday <= 30 {
self.day_text
let fg = if current_day.month() == self.first_day.month() {
self.params.day_text
} else {
self.day_text_other_month
self.params.day_text_other_month
};
// 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.day_text_margin;
let y = day_bounds.y + self.day_text_margin;
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,
bounds: Rectangle {width: day_bounds.width + 0.5, height: day_bounds.height + 0.5, ..day_bounds},
border_radius: 0.0,
border_width: 0.0,
border_color: self.day_text_other_month,
border_width: 1.0,
border_color: self.params.day_text_other_month,
},
Color::WHITE);
bg_color);
// render day cell text
renderer.fill_text(text::Text {
content : t,
content,
size: font_size,
bounds: Rectangle {x, y, ..day_bounds},
color: fg,
@ -313,6 +277,7 @@ impl CalendarMonthView {
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
});
current_day = current_day.succ();
}
}
}
@ -354,7 +319,7 @@ where
let margin: f32 = 20.0;
// week column only visible if there is enough space
let week_w = if size.width > self.week_column_width {
let week_w = if self.params.show_weeks && size.width > self.week_column_width {
self.week_column_width
} else {
0.0

View File

@ -12,6 +12,7 @@ use iced::widget::{
Column, Row,
Container,
Button, Text,
pick_list,
};
//use iced::button;
use iced::theme;
@ -30,18 +31,57 @@ struct CalendarApp {
enum Message {
NextMonth,
PrevMonth,
ViewModeSelected(ViewMode),
}
#[derive(Default)]
//#[derive(Default)]
struct Controls {
mode: Option<ViewMode>,
}
impl Default for Controls {
fn default() -> Controls {
Controls {
mode : Some(ViewMode::Month)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViewMode {
Month,
Year,
Day,
}
impl std::fmt::Display for ViewMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
ViewMode::Month => "Month",
ViewMode::Year => "Year",
ViewMode::Day => "Day",
}
)
}
}
impl Controls {
fn view(&self, month_name: &str) -> Element<Message> {
const MODES : [ViewMode; 3] = [ViewMode::Month, ViewMode::Year, ViewMode::Day];
fn view<'a>(&'a self, month_name: &'a str, year: i32) -> Element<Message> {
Row::new()
.align_items(Alignment::Center)
.padding(5)
.spacing(10)
.push(
pick_list(
&Controls::MODES[..],
self.mode,
Message::ViewModeSelected,
).placeholder("mode")
)
.push(
Button::new(Text::new("<"))
.on_press(Message::PrevMonth)
@ -59,7 +99,7 @@ impl Controls {
.size(40),
)
.push(
Text::new("2022")
Text::new(year.to_string())
.width(Length::Fill)
.horizontal_alignment(iced_native::alignment::Horizontal::Right)
.size(40),
@ -91,8 +131,11 @@ impl Sandbox for CalendarApp {
Message::NextMonth => {
self.month = self.month + Months::new(1);
}
Message::ViewModeSelected(mode) => {
self.controls.mode = Some(mode);
}
}
println!("month={}", self.month);
//println!("month={}", self.month);
}
fn view(&self) -> Element<Message> {
@ -112,7 +155,7 @@ impl Sandbox for CalendarApp {
];
let content = Column::new()
.align_items(Alignment::Fill)
.push(self.controls.view(MONTH_NAMES[self.month.month() as usize]))
.push(self.controls.view(MONTH_NAMES[self.month.month0() as usize], self.month.year()))
.push(CalendarMonthView::new(&CalendarParams::new(), self.month))
;