refactor(calendar): use jiff instead of chrono
This refactors the calendar widget to use `jiff` instead of `chrono`. Also mostly matches the design of the widget to the time applet.
This commit is contained in:
parent
b05f040e5f
commit
990e2e291b
5 changed files with 111 additions and 123 deletions
|
|
@ -104,7 +104,7 @@ async-fs = { version = "2.2", optional = true }
|
||||||
async-std = { version = "1.13", optional = true }
|
async-std = { version = "1.13", optional = true }
|
||||||
auto_enums = "0.8.7"
|
auto_enums = "0.8.7"
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", optional = true }
|
||||||
chrono = "0.4.43"
|
jiff = "0.2"
|
||||||
cosmic-config = { path = "cosmic-config" }
|
cosmic-config = { path = "cosmic-config" }
|
||||||
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
||||||
# Internationalization
|
# Internationalization
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "calendar"
|
name = "calendar"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.42"
|
jiff = "0.2"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
path = "../../"
|
path = "../../"
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
//! Calendar widget example
|
//! Calendar widget example
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::widget::calendar::CalendarModel;
|
use cosmic::widget::calendar::CalendarModel;
|
||||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
use cosmic::{ApplicationExt, Element, executor, iced};
|
||||||
|
use jiff::civil::{Date, Weekday};
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// Messages that are used specifically by our [`App`].
|
/// Messages that are used specifically by our [`App`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
DateSelected(NaiveDate),
|
DateSelected(Date),
|
||||||
PrevMonth,
|
PrevMonth,
|
||||||
NextMonth,
|
NextMonth,
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +92,7 @@ impl cosmic::Application for App {
|
||||||
|date| Message::DateSelected(date),
|
|date| Message::DateSelected(date),
|
||||||
|| Message::PrevMonth,
|
|| Message::PrevMonth,
|
||||||
|| Message::NextMonth,
|
|| Message::NextMonth,
|
||||||
chrono::Weekday::Sun,
|
Weekday::Sunday,
|
||||||
);
|
);
|
||||||
|
|
||||||
content = content.push(calendar);
|
content = content.push(calendar);
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,17 @@ september = September { $year }
|
||||||
october = October { $year }
|
october = October { $year }
|
||||||
november = November { $year }
|
november = November { $year }
|
||||||
december = December { $year }
|
december = December { $year }
|
||||||
monday = Mon
|
monday = Monday
|
||||||
tuesday = Tue
|
mon = Mon
|
||||||
wednesday = Wed
|
tuesday = Tuesday
|
||||||
thursday = Thu
|
tue = Tue
|
||||||
friday = Fri
|
wednesday = Wednesday
|
||||||
saturday = Sat
|
wed = Wed
|
||||||
sunday = Sun
|
thursday = Thursday
|
||||||
|
thu = Thu
|
||||||
|
friday = Friday
|
||||||
|
fri = Fri
|
||||||
|
saturday = Saturday
|
||||||
|
sat = Sat
|
||||||
|
sunday = Sunday
|
||||||
|
sun = Sun
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,20 @@
|
||||||
|
|
||||||
//! A widget that displays an interactive calendar.
|
//! A widget that displays an interactive calendar.
|
||||||
|
|
||||||
use std::cmp;
|
|
||||||
|
|
||||||
use crate::fl;
|
use crate::fl;
|
||||||
use crate::iced_core::{Alignment, Length, Padding};
|
use crate::iced_core::{Alignment, Length};
|
||||||
use crate::widget::{Grid, button, column, grid, icon, row, text};
|
use crate::widget::{button, column, grid, icon, row, text};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use chrono::{Datelike, Days, Local, Month, Months, NaiveDate, Weekday};
|
|
||||||
use iced::alignment::Vertical;
|
use iced::alignment::Vertical;
|
||||||
|
use jiff::{
|
||||||
|
ToSpan,
|
||||||
|
civil::{Date, Weekday},
|
||||||
|
};
|
||||||
|
|
||||||
/// A widget that displays an interactive calendar.
|
/// A widget that displays an interactive calendar.
|
||||||
pub fn calendar<M>(
|
pub fn calendar<M>(
|
||||||
model: &CalendarModel,
|
model: &CalendarModel,
|
||||||
on_select: impl Fn(NaiveDate) -> M + 'static,
|
on_select: impl Fn(Date) -> M + 'static,
|
||||||
on_prev: impl Fn() -> M + 'static,
|
on_prev: impl Fn() -> M + 'static,
|
||||||
on_next: impl Fn() -> M + 'static,
|
on_next: impl Fn() -> M + 'static,
|
||||||
first_day_of_week: Weekday,
|
first_day_of_week: Weekday,
|
||||||
|
|
@ -29,61 +30,40 @@ pub fn calendar<M>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_day(date_selected: NaiveDate, day: u32) -> NaiveDate {
|
pub fn set_day(date_selected: Date, day: i8) -> Date {
|
||||||
let current = date_selected.day();
|
date_selected
|
||||||
|
.with()
|
||||||
let new_date = match current.cmp(&day) {
|
.day(day)
|
||||||
cmp::Ordering::Less => date_selected.checked_add_days(Days::new((day - current) as u64)),
|
.build()
|
||||||
|
.unwrap_or(date_selected)
|
||||||
cmp::Ordering::Greater => date_selected.checked_sub_days(Days::new((current - day) as u64)),
|
|
||||||
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(new) = new_date {
|
|
||||||
new
|
|
||||||
} else {
|
|
||||||
date_selected
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||||
pub struct CalendarModel {
|
pub struct CalendarModel {
|
||||||
pub selected: NaiveDate,
|
pub selected: Date,
|
||||||
pub visible: NaiveDate,
|
pub visible: Date,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalendarModel {
|
impl CalendarModel {
|
||||||
pub fn now() -> Self {
|
pub fn now() -> Self {
|
||||||
let now = Local::now();
|
let now = jiff::Zoned::now().date();
|
||||||
let naive_now = NaiveDate::from(now.naive_local());
|
|
||||||
CalendarModel {
|
CalendarModel {
|
||||||
selected: naive_now,
|
selected: now,
|
||||||
visible: naive_now,
|
visible: now,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(selected: NaiveDate, visible: NaiveDate) -> Self {
|
pub fn new(selected: Date, visible: Date) -> Self {
|
||||||
CalendarModel { selected, visible }
|
CalendarModel { selected, visible }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_prev_month(&mut self) {
|
pub fn show_prev_month(&mut self) {
|
||||||
let prev_month_date = self
|
self.visible = self.visible.checked_sub(1.month()).expect("valid date");
|
||||||
.visible
|
|
||||||
.checked_sub_months(Months::new(1))
|
|
||||||
.expect("valid naivedate");
|
|
||||||
|
|
||||||
self.visible = prev_month_date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_next_month(&mut self) {
|
pub fn show_next_month(&mut self) {
|
||||||
let next_month_date = self
|
self.visible = self.visible.checked_add(1.month()).expect("valid date");
|
||||||
.visible
|
|
||||||
.checked_add_months(Months::new(1))
|
|
||||||
.expect("valid naivedate");
|
|
||||||
|
|
||||||
self.visible = next_month_date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -99,7 +79,7 @@ impl CalendarModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_selected_visible(&mut self, selected: NaiveDate) {
|
pub fn set_selected_visible(&mut self, selected: Date) {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
self.visible = self.selected;
|
self.visible = self.selected;
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +87,7 @@ impl CalendarModel {
|
||||||
|
|
||||||
pub struct Calendar<'a, M> {
|
pub struct Calendar<'a, M> {
|
||||||
model: &'a CalendarModel,
|
model: &'a CalendarModel,
|
||||||
on_select: Box<dyn Fn(NaiveDate) -> M>,
|
on_select: Box<dyn Fn(Date) -> M>,
|
||||||
on_prev: Box<dyn Fn() -> M>,
|
on_prev: Box<dyn Fn() -> M>,
|
||||||
on_next: Box<dyn Fn() -> M>,
|
on_next: Box<dyn Fn() -> M>,
|
||||||
first_day_of_week: Weekday,
|
first_day_of_week: Weekday,
|
||||||
|
|
@ -121,45 +101,57 @@ where
|
||||||
macro_rules! translate_month {
|
macro_rules! translate_month {
|
||||||
($month:expr, $year:expr) => {{
|
($month:expr, $year:expr) => {{
|
||||||
match $month {
|
match $month {
|
||||||
chrono::Month::January => fl!("january", year = $year),
|
1 => fl!("january", year = $year),
|
||||||
chrono::Month::February => fl!("february", year = $year),
|
2 => fl!("february", year = $year),
|
||||||
chrono::Month::March => fl!("march", year = $year),
|
3 => fl!("march", year = $year),
|
||||||
chrono::Month::April => fl!("april", year = $year),
|
4 => fl!("april", year = $year),
|
||||||
chrono::Month::May => fl!("may", year = $year),
|
5 => fl!("may", year = $year),
|
||||||
chrono::Month::June => fl!("june", year = $year),
|
6 => fl!("june", year = $year),
|
||||||
chrono::Month::July => fl!("july", year = $year),
|
7 => fl!("july", year = $year),
|
||||||
chrono::Month::August => fl!("august", year = $year),
|
8 => fl!("august", year = $year),
|
||||||
chrono::Month::September => fl!("september", year = $year),
|
9 => fl!("september", year = $year),
|
||||||
chrono::Month::October => fl!("october", year = $year),
|
10 => fl!("october", year = $year),
|
||||||
chrono::Month::November => fl!("november", year = $year),
|
11 => fl!("november", year = $year),
|
||||||
chrono::Month::December => fl!("december", year = $year),
|
12 => fl!("december", year = $year),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
macro_rules! translate_weekday {
|
macro_rules! translate_weekday {
|
||||||
($weekday:expr) => {{
|
($weekday:expr, short) => {{
|
||||||
match $weekday {
|
match $weekday {
|
||||||
Weekday::Mon => fl!("monday"),
|
Weekday::Monday => fl!("mon"),
|
||||||
Weekday::Tue => fl!("tuesday"),
|
Weekday::Tuesday => fl!("tue"),
|
||||||
Weekday::Wed => fl!("wednesday"),
|
Weekday::Wednesday => fl!("wed"),
|
||||||
Weekday::Thu => fl!("thursday"),
|
Weekday::Thursday => fl!("thu"),
|
||||||
Weekday::Fri => fl!("friday"),
|
Weekday::Friday => fl!("fri"),
|
||||||
Weekday::Sat => fl!("saturday"),
|
Weekday::Saturday => fl!("sat"),
|
||||||
Weekday::Sun => fl!("sunday"),
|
Weekday::Sunday => fl!("sun"),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
($weekday:expr, long) => {{
|
||||||
|
match $weekday {
|
||||||
|
Weekday::Monday => fl!("monday"),
|
||||||
|
Weekday::Tuesday => fl!("tuesday"),
|
||||||
|
Weekday::Wednesday => fl!("wednesday"),
|
||||||
|
Weekday::Thursday => fl!("thursday"),
|
||||||
|
Weekday::Friday => fl!("friday"),
|
||||||
|
Weekday::Saturday => fl!("saturday"),
|
||||||
|
Weekday::Sunday => fl!("sunday"),
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
let date = text(translate_month!(
|
let date = text(translate_month!(
|
||||||
Month::try_from(this.model.visible.month() as u8)
|
this.model.visible.month(),
|
||||||
.expect("Previously valid month is suddenly invalid"),
|
|
||||||
this.model.visible.year()
|
this.model.visible.year()
|
||||||
))
|
))
|
||||||
.size(18);
|
.size(18);
|
||||||
|
|
||||||
let day = text::body(translate_weekday!(this.model.visible.weekday()));
|
let day = text::body(translate_weekday!(this.model.visible.weekday(), long));
|
||||||
|
|
||||||
let month_controls = row::with_capacity(2)
|
let month_controls = row::with_capacity(2)
|
||||||
|
.spacing(8)
|
||||||
.push(
|
.push(
|
||||||
icon::from_name("go-previous-symbolic")
|
icon::from_name("go-previous-symbolic")
|
||||||
.apply(button::icon)
|
.apply(button::icon)
|
||||||
|
|
@ -171,46 +163,49 @@ where
|
||||||
.on_press((this.on_next)()),
|
.on_press((this.on_next)()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calender
|
// Calendar
|
||||||
let mut calendar_grid: Grid<'_, Message> =
|
let mut calendar_grid = grid().padding([0, 12].into()).width(Length::Fill);
|
||||||
grid().padding([0, 12].into()).width(Length::Fill);
|
|
||||||
|
|
||||||
let mut first_day_of_week = this.first_day_of_week;
|
let mut first_day_of_week = this.first_day_of_week;
|
||||||
for _ in 0..7 {
|
for _ in 0..7 {
|
||||||
calendar_grid = calendar_grid.push(
|
calendar_grid = calendar_grid.push(
|
||||||
text(translate_weekday!(first_day_of_week))
|
text::caption(translate_weekday!(first_day_of_week, short))
|
||||||
.size(12)
|
.width(Length::Fixed(44.0))
|
||||||
.width(Length::Fixed(36.0))
|
|
||||||
.align_x(Alignment::Center),
|
.align_x(Alignment::Center),
|
||||||
);
|
);
|
||||||
|
|
||||||
first_day_of_week = first_day_of_week.succ();
|
first_day_of_week = first_day_of_week.next();
|
||||||
}
|
}
|
||||||
calendar_grid = calendar_grid.insert_row();
|
calendar_grid = calendar_grid.insert_row();
|
||||||
|
|
||||||
let monday = get_calender_first(
|
let first = get_calendar_first(
|
||||||
this.model.visible.year(),
|
this.model.visible.year(),
|
||||||
this.model.visible.month(),
|
this.model.visible.month(),
|
||||||
first_day_of_week,
|
this.first_day_of_week,
|
||||||
);
|
);
|
||||||
let mut day_iter = monday.iter_days();
|
|
||||||
|
let today = jiff::Zoned::now().date();
|
||||||
for i in 0..42 {
|
for i in 0..42 {
|
||||||
if i > 0 && i % 7 == 0 {
|
if i > 0 && i % 7 == 0 {
|
||||||
calendar_grid = calendar_grid.insert_row();
|
calendar_grid = calendar_grid.insert_row();
|
||||||
}
|
}
|
||||||
|
|
||||||
let date = day_iter.next().unwrap();
|
let date = first
|
||||||
let is_currently_viewed_month = date.month() == this.model.visible.month()
|
.checked_add(i.days())
|
||||||
&& date.year_ce() == this.model.visible.year_ce();
|
.expect("valid date in calendar range");
|
||||||
let is_currently_selected_month = date.month() == this.model.selected.month()
|
let is_currently_viewed_month =
|
||||||
&& date.year_ce() == this.model.selected.year_ce();
|
date.first_of_month() == this.model.visible.first_of_month();
|
||||||
|
let is_currently_selected_month =
|
||||||
|
date.first_of_month() == this.model.selected.first_of_month();
|
||||||
let is_currently_selected_day =
|
let is_currently_selected_day =
|
||||||
date.day() == this.model.selected.day() && is_currently_selected_month;
|
date.day() == this.model.selected.day() && is_currently_selected_month;
|
||||||
|
let is_today = date == today;
|
||||||
|
|
||||||
calendar_grid = calendar_grid.push(date_button(
|
calendar_grid = calendar_grid.push(date_button(
|
||||||
date,
|
date,
|
||||||
is_currently_viewed_month,
|
is_currently_viewed_month,
|
||||||
is_currently_selected_day,
|
is_currently_selected_day,
|
||||||
|
is_today,
|
||||||
&this.on_select,
|
&this.on_select,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -225,9 +220,8 @@ where
|
||||||
.padding([12, 20])
|
.padding([12, 20])
|
||||||
.into(),
|
.into(),
|
||||||
calendar_grid.into(),
|
calendar_grid.into(),
|
||||||
padded_control(crate::widget::divider::horizontal::default()).into(),
|
|
||||||
])
|
])
|
||||||
.width(315)
|
.width(360)
|
||||||
.padding([8, 0]);
|
.padding([8, 0]);
|
||||||
|
|
||||||
Self::new(content_list)
|
Self::new(content_list)
|
||||||
|
|
@ -235,21 +229,24 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn date_button<Message: Clone + 'static>(
|
fn date_button<Message: Clone + 'static>(
|
||||||
date: NaiveDate,
|
date: Date,
|
||||||
is_currently_viewed_month: bool,
|
is_currently_viewed_month: bool,
|
||||||
is_currently_selected_day: bool,
|
is_currently_selected_day: bool,
|
||||||
on_select: &dyn Fn(NaiveDate) -> Message,
|
is_today: bool,
|
||||||
|
on_select: &dyn Fn(Date) -> Message,
|
||||||
) -> crate::widget::Button<'static, Message> {
|
) -> crate::widget::Button<'static, Message> {
|
||||||
let style = if is_currently_selected_day {
|
let style = if is_currently_selected_day {
|
||||||
button::ButtonClass::Suggested
|
button::ButtonClass::Suggested
|
||||||
|
} else if is_today {
|
||||||
|
button::ButtonClass::Standard
|
||||||
} else {
|
} else {
|
||||||
button::ButtonClass::Text
|
button::ButtonClass::Text
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = button::custom(text(format!("{}", date.day())).center())
|
let button = button::custom(text(format!("{}", date.day())).center())
|
||||||
.class(style)
|
.class(style)
|
||||||
.height(Length::Fixed(36.0))
|
.height(Length::Fixed(44.0))
|
||||||
.width(Length::Fixed(36.0));
|
.width(Length::Fixed(44.0));
|
||||||
|
|
||||||
if is_currently_viewed_month {
|
if is_currently_viewed_month {
|
||||||
button.on_press((on_select)(set_day(date, date.day())))
|
button.on_press((on_select)(set_day(date, date.day())))
|
||||||
|
|
@ -258,26 +255,10 @@ fn date_button<Message: Clone + 'static>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the first date that will be visible on the calender
|
/// Gets the first date that will be visible on the calendar
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_calender_first(year: i32, month: u32, from_weekday: Weekday) -> NaiveDate {
|
pub fn get_calendar_first(year: i16, month: i8, from_weekday: Weekday) -> Date {
|
||||||
let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
|
let date = Date::new(year, month, 1).expect("valid date");
|
||||||
let num_days = (date.weekday() as u32 + 7 - from_weekday as u32) % 7; // chrono::Weekday.num_days_from
|
let num_days = date.weekday().since(from_weekday);
|
||||||
date.checked_sub_days(Days::new(num_days as u64)).unwrap()
|
date.checked_sub(num_days.days()).expect("valid date")
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Refactor to use same function from applet module.
|
|
||||||
fn padded_control<'a, Message>(
|
|
||||||
content: impl Into<crate::Element<'a, Message>>,
|
|
||||||
) -> crate::widget::container::Container<'a, Message, crate::Theme, crate::Renderer> {
|
|
||||||
crate::widget::container(content)
|
|
||||||
.padding(menu_control_padding())
|
|
||||||
.width(Length::Fill)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn menu_control_padding() -> Padding {
|
|
||||||
let guard = crate::theme::THEME.lock().unwrap();
|
|
||||||
let cosmic = guard.cosmic();
|
|
||||||
[cosmic.space_xxs(), cosmic.space_m()].into()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue