Refactor
* 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:
parent
a0406c0da7
commit
6f00d075c1
233
src/calendar.rs
233
src/calendar.rs
|
@ -14,121 +14,93 @@ use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget};
|
||||||
use iced_native::text;
|
use iced_native::text;
|
||||||
use iced_native::alignment;
|
use iced_native::alignment;
|
||||||
use iced_native::widget::Tree;
|
use iced_native::widget::Tree;
|
||||||
use chrono::{NaiveDate, Datelike};
|
use chrono::{NaiveDate, Datelike, Duration, Weekday, Local};
|
||||||
|
|
||||||
//use std::cmp;
|
//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)]
|
#[derive(Clone)]
|
||||||
pub struct CalendarParams {
|
pub struct CalendarParams {
|
||||||
weekday_on_first_of_january: i32, // 0 -> Monday .. 6 -> Sunday
|
show_weeks: bool,
|
||||||
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
|
|
||||||
header_fg: Color,
|
header_fg: Color,
|
||||||
header_bg: Color,
|
header_bg: Color,
|
||||||
day_text: Color,
|
day_text: Color,
|
||||||
day_text_other_month: Color,
|
day_text_other_month: Color,
|
||||||
day_weekend_bg: Color,
|
day_weekend_bg: Color,
|
||||||
|
day_today_bg: Color,
|
||||||
day_text_margin: f32,
|
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_width: f32,
|
||||||
week_column_font_size: f32,
|
week_column_font_size: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalendarMonthView {
|
impl CalendarMonthView {
|
||||||
pub fn new(params: &CalendarParams, first_day: NaiveDate) -> Self {
|
pub fn new(params: &CalendarParams, day: NaiveDate) -> Self {
|
||||||
let month: i32 = first_day.month0() as i32;
|
// 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 {
|
Self {
|
||||||
first_day,
|
first_day,
|
||||||
month,
|
first_day_in_view,
|
||||||
params: params.clone(),
|
params: params.clone(),
|
||||||
weekday_on_first: params.weekday_first_of_month(month),
|
weekday_on_first,
|
||||||
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,
|
|
||||||
week_column_width: 30.0,
|
week_column_width: 30.0,
|
||||||
week_column_font_size: 18.0,
|
week_column_font_size: 18.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_month(&mut self, month: i32) {
|
pub fn set_month(&mut self, day: NaiveDate) {
|
||||||
self.month = month;
|
// first day of the month
|
||||||
self.weekday_on_first = self.params.weekday_first_of_month(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(
|
fn draw_header(
|
||||||
|
@ -138,13 +110,15 @@ impl CalendarMonthView {
|
||||||
week_w: f32,
|
week_w: f32,
|
||||||
) {
|
) {
|
||||||
// paint background over full width
|
// paint background over full width
|
||||||
|
if self.params.header_bg != Color::TRANSPARENT {
|
||||||
renderer.fill_quad(renderer::Quad {
|
renderer.fill_quad(renderer::Quad {
|
||||||
bounds,
|
bounds,
|
||||||
border_radius: 0.0,
|
border_radius: 0.0,
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
self.header_bg);
|
self.params.header_bg);
|
||||||
|
}
|
||||||
|
|
||||||
// redefine bounds to skip the week column
|
// redefine bounds to skip the week column
|
||||||
let bounds = Rectangle {
|
let bounds = Rectangle {
|
||||||
|
@ -176,9 +150,9 @@ impl CalendarMonthView {
|
||||||
let t = days_of_week[weekday as usize];
|
let t = days_of_week[weekday as usize];
|
||||||
|
|
||||||
// color of text
|
// 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();
|
let y = bounds.center_y();
|
||||||
renderer.fill_text(text::Text {
|
renderer.fill_text(text::Text {
|
||||||
content : t,
|
content : t,
|
||||||
|
@ -205,34 +179,30 @@ impl CalendarMonthView {
|
||||||
h
|
h
|
||||||
} / 2.0;
|
} / 2.0;
|
||||||
|
|
||||||
for week in 0..6i32 {
|
let mut day = self.first_day;
|
||||||
|
|
||||||
|
for week in 0..6u32 {
|
||||||
// where to place the week number
|
// where to place the week number
|
||||||
let day_bounds = Rectangle {
|
let day_bounds = Rectangle {
|
||||||
x: bounds.x,
|
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,
|
width: r * 2.0,
|
||||||
height: r * 2.0
|
height: r * 2.0
|
||||||
};
|
};
|
||||||
|
|
||||||
// paint the background as weekend (inactive)
|
let week_of_first_day_of_month = day.iso_week().week();
|
||||||
// renderer.fill_quad(renderer::Quad {
|
day += Duration::weeks(1);
|
||||||
// bounds: day_bounds,
|
|
||||||
// border_radius: r,
|
|
||||||
// border_width: 0.0,
|
|
||||||
// border_color: Color::TRANSPARENT,
|
|
||||||
// },
|
|
||||||
// self.day_weekend_bg);
|
|
||||||
//
|
|
||||||
// render week cell text
|
// render week cell text
|
||||||
renderer.fill_text(text::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,
|
size: self.week_column_font_size,
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: day_bounds.center_x(),
|
x: day_bounds.center_x(),
|
||||||
y: day_bounds.y,
|
y: day_bounds.y,
|
||||||
..day_bounds
|
..day_bounds
|
||||||
},
|
},
|
||||||
color: self.day_text,
|
color: self.params.day_text,
|
||||||
font: Default::default(),
|
font: Default::default(),
|
||||||
horizontal_alignment: alignment::Horizontal::Center,
|
horizontal_alignment: alignment::Horizontal::Center,
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
vertical_alignment: alignment::Vertical::Top,
|
||||||
|
@ -255,25 +225,9 @@ impl CalendarMonthView {
|
||||||
let w: f32 = size.width / 7.0;
|
let w: f32 = size.width / 7.0;
|
||||||
let h: f32 = size.height / 6.0;
|
let h: f32 = size.height / 6.0;
|
||||||
|
|
||||||
let days_of_month = [
|
let mut current_day = self.first_day_in_view;
|
||||||
" 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 week in 0..6i32 {
|
||||||
for weekday in 0..7i32 {
|
for weekday in 0..7i32 {
|
||||||
let monthday = weekday + week * 7 - self.weekday_on_first;
|
|
||||||
let day_bounds = Rectangle {
|
let day_bounds = Rectangle {
|
||||||
x: (weekday as f32) * w + origin.x,
|
x: (weekday as f32) * w + origin.x,
|
||||||
y: (week as f32) * h + origin.y,
|
y: (week as f32) * h + origin.y,
|
||||||
|
@ -282,30 +236,40 @@ impl CalendarMonthView {
|
||||||
};
|
};
|
||||||
|
|
||||||
// label (day letter on row 0, day number on the rest)
|
// 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
|
// color of text
|
||||||
let fg = if monthday >= 0 && monthday <= 30 {
|
let fg = if current_day.month() == self.first_day.month() {
|
||||||
self.day_text
|
self.params.day_text
|
||||||
} else {
|
} 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
|
// where to place the day content
|
||||||
let x = day_bounds.x + self.day_text_margin;
|
let x = day_bounds.x + self.params.day_text_margin;
|
||||||
let y = day_bounds.y + self.day_text_margin;
|
let y = day_bounds.y + self.params.day_text_margin;
|
||||||
|
|
||||||
renderer.fill_quad(renderer::Quad {
|
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_radius: 0.0,
|
||||||
border_width: 0.0,
|
border_width: 1.0,
|
||||||
border_color: self.day_text_other_month,
|
border_color: self.params.day_text_other_month,
|
||||||
},
|
},
|
||||||
Color::WHITE);
|
bg_color);
|
||||||
|
|
||||||
// render day cell text
|
// render day cell text
|
||||||
renderer.fill_text(text::Text {
|
renderer.fill_text(text::Text {
|
||||||
content : t,
|
content,
|
||||||
size: font_size,
|
size: font_size,
|
||||||
bounds: Rectangle {x, y, ..day_bounds},
|
bounds: Rectangle {x, y, ..day_bounds},
|
||||||
color: fg,
|
color: fg,
|
||||||
|
@ -313,6 +277,7 @@ impl CalendarMonthView {
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
vertical_alignment: alignment::Vertical::Top,
|
||||||
});
|
});
|
||||||
|
current_day = current_day.succ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,7 +319,7 @@ where
|
||||||
let margin: f32 = 20.0;
|
let margin: f32 = 20.0;
|
||||||
|
|
||||||
// week column only visible if there is enough space
|
// 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
|
self.week_column_width
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -12,6 +12,7 @@ use iced::widget::{
|
||||||
Column, Row,
|
Column, Row,
|
||||||
Container,
|
Container,
|
||||||
Button, Text,
|
Button, Text,
|
||||||
|
pick_list,
|
||||||
};
|
};
|
||||||
//use iced::button;
|
//use iced::button;
|
||||||
use iced::theme;
|
use iced::theme;
|
||||||
|
@ -30,18 +31,57 @@ struct CalendarApp {
|
||||||
enum Message {
|
enum Message {
|
||||||
NextMonth,
|
NextMonth,
|
||||||
PrevMonth,
|
PrevMonth,
|
||||||
|
ViewModeSelected(ViewMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
//#[derive(Default)]
|
||||||
struct Controls {
|
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 {
|
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()
|
Row::new()
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
|
.push(
|
||||||
|
pick_list(
|
||||||
|
&Controls::MODES[..],
|
||||||
|
self.mode,
|
||||||
|
Message::ViewModeSelected,
|
||||||
|
).placeholder("mode")
|
||||||
|
)
|
||||||
.push(
|
.push(
|
||||||
Button::new(Text::new("<"))
|
Button::new(Text::new("<"))
|
||||||
.on_press(Message::PrevMonth)
|
.on_press(Message::PrevMonth)
|
||||||
|
@ -59,7 +99,7 @@ impl Controls {
|
||||||
.size(40),
|
.size(40),
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
Text::new("2022")
|
Text::new(year.to_string())
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.horizontal_alignment(iced_native::alignment::Horizontal::Right)
|
.horizontal_alignment(iced_native::alignment::Horizontal::Right)
|
||||||
.size(40),
|
.size(40),
|
||||||
|
@ -91,8 +131,11 @@ impl Sandbox for CalendarApp {
|
||||||
Message::NextMonth => {
|
Message::NextMonth => {
|
||||||
self.month = self.month + Months::new(1);
|
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> {
|
fn view(&self) -> Element<Message> {
|
||||||
|
@ -112,7 +155,7 @@ impl Sandbox for CalendarApp {
|
||||||
];
|
];
|
||||||
let content = Column::new()
|
let content = Column::new()
|
||||||
.align_items(Alignment::Fill)
|
.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))
|
.push(CalendarMonthView::new(&CalendarParams::new(), self.month))
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue