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
251
src/calendar.rs
251
src/calendar.rs
|
@ -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;
|
||||
Self {
|
||||
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,57 +225,51 @@ 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,
|
||||
width: w,
|
||||
height: h
|
||||
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];
|
||||
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
|
||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -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))
|
||||
;
|
||||
|
||||
|
|
Loading…
Reference in New Issue