First version of calendar app

- Monthly View
- prev/next month buttons
- year hard-coded to 2022

Signed-off-by: Fabrizio Iannetti <fabrizio.iannetti@gmail.com>
This commit is contained in:
Fabrizio Iannetti 2022-08-02 08:04:48 +02:00
commit 23a3102df0
7 changed files with 2656 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

11
.project Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>calendar</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>

2205
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "calendar"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#iced = "0.4.2"
#iced_native = "0.5.1"
iced = { path = "../iced" }
iced_native = { path = "../iced/native" }

0
README.adoc Normal file
View File

303
src/calendar.rs Normal file
View File

@ -0,0 +1,303 @@
// 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)
}
}

124
src/main.rs Normal file
View File

@ -0,0 +1,124 @@
//! Simple calendar applications.
mod calendar;
use calendar::{CalendarMonthView, CalendarParams };
use iced::{
Alignment, Column, Row,
Container, Element, Length, Sandbox, Settings,
Button, Text,
};
use iced::button;
use iced::theme;
pub fn main() -> iced::Result {
CalendarApp::run(Settings::default())
}
#[derive(Default)]
struct CalendarApp {
month: i32,
controls: Controls,
}
#[derive(Debug, Clone, Copy)]
enum Message {
NextMonth,
PrevMonth,
}
#[derive(Default)]
struct Controls {
prev_button: button::State,
next_button: button::State,
}
impl Controls {
fn view(&mut self, month_name: &str) -> Element<Message> {
Row::new()
.align_items(Alignment::Center)
.padding(5)
.spacing(10)
.push(
Button::new(&mut self.prev_button, Text::new("<<"))
.on_press(Message::PrevMonth)
.style(theme::Button::Secondary),
)
.push(
Text::new(month_name)
.width(Length::Shrink)
.size(50),
)
.push(
Button::new(&mut self.next_button, Text::new(">>"))
.on_press(Message::NextMonth)
.style(theme::Button::Secondary),
)
.into()
}
}
impl Sandbox for CalendarApp {
type Message = Message;
fn new() -> Self {
CalendarApp {
month: 7,
..CalendarApp::default()
}
}
fn title(&self) -> String {
String::from("Calendar")
}
fn update(&mut self, message: Message) {
match message {
Message::PrevMonth => {
self.month = (self.month + 12 - 1) % 12;
}
Message::NextMonth => {
self.month = (self.month + 12 + 1) % 12;
}
}
println!("month={}", self.month);
}
fn view(&mut self) -> Element<Message> {
const MONTH_NAMES: [&str;12] = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"Novemeber",
"December",
];
let content = Column::new()
.align_items(Alignment::Fill)
.push(self.controls.view(MONTH_NAMES[self.month as usize]))
.push(CalendarMonthView::new(&CalendarParams::new(), self.month))
;
// let area = Row::new()
// .align_items(Alignment::Fill)
// .push(Column::new()
// .align_items(Alignment::Start)
// .push(CalendarMonthView::new(6))
// .push(CalendarMonthView::new(6))
// )
// .push(content)
// ;
Container::new(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}