use crate::iced_aw_font::advanced_text::{cancel, left_open, ok, right_open};
use crate::{
core::{
date::{Date, IsInMonth},
overlay::Position,
},
date_picker,
style::{Status, date_picker::Style, style_state::StyleState},
};
use chrono::{Datelike, Local, NaiveDate};
use iced_core::{
Alignment, Border, Clipboard, Color, Element, Event, Layout, Length, Overlay, Padding, Pixels,
Point, Rectangle, Renderer as _, Shadow, Shell, Size, Widget,
alignment::{Horizontal, Vertical},
event, keyboard,
layout::{Limits, Node},
mouse::{self, Cursor},
overlay, renderer,
text::Renderer as _,
touch,
widget::{self, tree::Tree},
};
use iced_widget::{
Button, Column, Container, Renderer, Row, Text,
text::{self, Wrapping},
};
use std::collections::HashMap;
const PADDING: Padding = Padding::new(10.0);
const SPACING: Pixels = Pixels(15.0);
const DAY_CELL_PADDING: Padding = Padding::new(7.0);
const BUTTON_SPACING: Pixels = Pixels(5.0);
#[allow(missing_debug_implementations)]
pub struct DatePickerOverlay<'a, 'b, Message, Theme>
where
Message: Clone,
Theme: crate::style::date_picker::Catalog + iced_widget::button::Catalog,
'b: 'a,
{
state: &'a mut State,
cancel_button: Button<'a, Message, Theme, Renderer>,
submit_button: Button<'a, Message, Theme, Renderer>,
on_submit: &'a dyn Fn(Date) -> Message,
position: Point,
class: &'a <Theme as crate::style::date_picker::Catalog>::Class<'b>,
tree: &'a mut Tree,
font_size: Pixels,
viewport: Rectangle,
}
impl<'a, 'b, Message, Theme> DatePickerOverlay<'a, 'b, Message, Theme>
where
Message: 'static + Clone,
Theme: 'a
+ crate::style::date_picker::Catalog
+ iced_widget::button::Catalog
+ iced_widget::text::Catalog
+ iced_widget::container::Catalog,
'b: 'a,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
state: &'a mut date_picker::State,
on_cancel: Message,
on_submit: &'a dyn Fn(Date) -> Message,
position: Point,
class: &'a <Theme as crate::style::date_picker::Catalog>::Class<'b>,
tree: &'a mut Tree,
font_size: Pixels,
viewport: Rectangle,
) -> Self {
let date_picker::State { overlay_state } = state;
let (cancel_content, cancel_font, _cancel_shaping) = cancel();
let (submit_content, submit_font, _submit_shaping) = ok();
DatePickerOverlay {
state: overlay_state,
cancel_button: Button::new(
text::Text::new(cancel_content)
.font(cancel_font)
.size(font_size)
.align_x(Horizontal::Center)
.width(Length::Fill),
)
.width(Length::Fill)
.on_press(on_cancel.clone()),
submit_button: Button::new(
text::Text::new(submit_content)
.font(submit_font)
.size(font_size)
.align_x(Horizontal::Center)
.width(Length::Fill),
)
.width(Length::Fill)
.on_press(on_cancel), on_submit,
position,
class,
tree,
font_size,
viewport,
}
}
#[must_use]
pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> {
overlay::Element::new(Box::new(self))
}
fn date_as_string(&self) -> String {
crate::core::date::date_as_string(self.state.date)
}
fn on_event_month_year(
&mut self,
event: &Event,
layout: Layout<'_>,
cursor: Cursor,
shell: &mut Shell<Message>,
) {
let mut children = layout.children();
let month_layout = children
.next()
.expect("widget: Layout should have a month layout");
let mut month_children = month_layout.children();
let left_bounds = month_children
.next()
.expect("widget: Layout should have a left month arrow layout")
.bounds();
let _center_bounds = month_children
.next()
.expect("widget: Layout should have a center month layout")
.bounds();
let right_bounds = month_children
.next()
.expect("widget: Layout should have a right month arrow layout")
.bounds();
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if cursor.is_over(month_layout.bounds()) {
self.state.focus = Focus::Month;
shell.request_redraw();
}
if cursor.is_over(left_bounds) {
self.state.date = crate::core::date::pred_month(self.state.date);
shell.capture_event();
shell.request_redraw();
} else if cursor.is_over(right_bounds) {
self.state.date = crate::core::date::succ_month(self.state.date);
shell.capture_event();
shell.request_redraw();
}
}
Event::Mouse(mouse::Event::CursorMoved { position })
if month_layout.bounds().contains(*position)
|| left_bounds.contains(*position)
|| right_bounds.contains(*position) =>
{
self.state.day_mouse_over = None;
shell.request_redraw();
}
_ => {}
}
let year_layout = children
.next()
.expect("widget: Layout should have a year layout");
let mut year_children = year_layout.children();
let left_bounds = year_children
.next()
.expect("widget: Layout should have a left year arrow layout")
.bounds();
let _center_bounds = year_children
.next()
.expect("widget: Layout should have a center year layout")
.bounds();
let right_bounds = year_children
.next()
.expect("widget: Layout should have a right year arrow layout")
.bounds();
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if cursor.is_over(year_layout.bounds()) {
self.state.focus = Focus::Year;
shell.request_redraw();
}
if cursor.is_over(left_bounds) {
self.state.date = crate::core::date::pred_year(self.state.date);
shell.capture_event();
shell.request_redraw();
} else if cursor.is_over(right_bounds) {
self.state.date = crate::core::date::succ_year(self.state.date);
shell.capture_event();
shell.request_redraw();
}
}
Event::Mouse(mouse::Event::CursorMoved { position })
if year_layout.bounds().contains(*position)
|| left_bounds.contains(*position)
|| right_bounds.contains(*position) =>
{
self.state.day_mouse_over = None;
shell.request_redraw();
}
_ => {}
}
}
fn on_event_days(
&mut self,
event: &Event,
layout: Layout<'_>,
cursor: Cursor,
shell: &mut Shell<Message>,
) {
let mut children = layout.children();
let _day_labels_layout = children
.next()
.expect("widget: Layout should have a day label layout");
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if cursor.is_over(layout.bounds()) {
self.state.focus = Focus::Day;
shell.request_redraw();
}
'outer: for (y, row) in children.enumerate() {
for (x, label) in row.children().enumerate() {
let bounds = label.bounds();
if cursor.is_over(bounds) {
let (day, is_in_month) = crate::core::date::position_to_day(
x,
y,
self.state.date.year(),
self.state.date.month(),
);
self.state.date = match is_in_month {
IsInMonth::Previous => {
crate::core::date::pred_month(self.state.date)
.with_day(day as u32)
.expect("Previous month with day should be valid")
}
IsInMonth::Same => self
.state
.date
.with_day(day as u32)
.expect("Same month with day should be valid"),
IsInMonth::Next => crate::core::date::succ_month(self.state.date)
.with_day(day as u32)
.expect("Succeeding month with day should be valid"),
};
shell.capture_event();
shell.request_redraw();
break 'outer;
}
}
}
}
Event::Mouse(mouse::Event::CursorMoved { position }) => {
if cursor.is_over(layout.bounds()) {
self.state.focus = Focus::Day;
shell.request_redraw();
}
self.state.day_mouse_over = None;
'outer: for (y, row) in children.enumerate() {
for (x, label) in row.children().enumerate() {
let bounds = label.bounds();
if bounds.contains(*position) {
self.state.day_mouse_over = Some((y, x));
shell.capture_event();
shell.request_redraw();
break 'outer;
}
}
}
}
_ => {}
}
}
fn on_event_keyboard(&mut self, event: &Event) -> event::Status {
if self.state.focus == Focus::None {
return event::Status::Ignored;
}
if let Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) = event {
let mut status = event::Status::Ignored;
match key.as_ref() {
keyboard::Key::Named(keyboard::key::Named::Tab) => {
if self.state.keyboard_modifiers.shift() {
self.state.focus = self.state.focus.previous();
} else {
self.state.focus = self.state.focus.next();
}
}
keyboard::Key::Named(k) => match self.state.focus {
Focus::Month => match k {
keyboard::key::Named::ArrowLeft => {
self.state.date = crate::core::date::pred_month(self.state.date);
status = event::Status::Captured;
}
keyboard::key::Named::ArrowRight => {
self.state.date = crate::core::date::succ_month(self.state.date);
status = event::Status::Captured;
}
_ => {}
},
Focus::Year => match k {
keyboard::key::Named::ArrowLeft => {
self.state.date = crate::core::date::pred_year(self.state.date);
status = event::Status::Captured;
}
keyboard::key::Named::ArrowRight => {
self.state.date = crate::core::date::succ_year(self.state.date);
status = event::Status::Captured;
}
_ => {}
},
Focus::Day => match k {
keyboard::key::Named::ArrowLeft => {
self.state.date = crate::core::date::pred_day(self.state.date);
status = event::Status::Captured;
}
keyboard::key::Named::ArrowRight => {
self.state.date = crate::core::date::succ_day(self.state.date);
status = event::Status::Captured;
}
keyboard::key::Named::ArrowUp => {
self.state.date = crate::core::date::pred_week(self.state.date);
status = event::Status::Captured;
}
keyboard::key::Named::ArrowDown => {
self.state.date = crate::core::date::succ_week(self.state.date);
status = event::Status::Captured;
}
_ => {}
},
_ => {}
},
_ => {}
}
status
} else if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = event {
self.state.keyboard_modifiers = *modifiers;
event::Status::Ignored
} else {
event::Status::Ignored
}
}
}
impl<'a, 'b, Message, Theme> Overlay<Message, Theme, Renderer>
for DatePickerOverlay<'a, 'b, Message, Theme>
where
Message: 'static + Clone,
Theme: 'a
+ crate::style::date_picker::Catalog
+ iced_widget::button::Catalog
+ iced_widget::text::Catalog
+ iced_widget::container::Catalog,
'b: 'a,
{
#[allow(clippy::too_many_lines)]
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
let limits = Limits::new(Size::ZERO, bounds)
.shrink(PADDING)
.width(Length::Shrink)
.height(Length::Shrink);
let cancel_limits = limits;
let cancel_button =
self.cancel_button
.layout(&mut self.tree.children[0], renderer, &cancel_limits);
let limits = limits.shrink(Size::new(0.0, cancel_button.bounds().height + SPACING.0));
let font_size = self.font_size;
let (left_content, left_font, _left_shaping) = left_open();
let (right_content, right_font, _right_shaping) = right_open();
let month_year = Row::<Message, Theme, Renderer>::new()
.width(Length::Shrink)
.spacing(SPACING)
.push(
Row::new()
.width(Length::Shrink)
.spacing(SPACING)
.align_y(Alignment::Center)
.push(
Container::new(
Text::new(&left_content)
.size(font_size.0 + 1.0)
.font(left_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
)
.push(
Text::new("September").size(font_size).width(Length::Shrink),
)
.push(
Container::new(
Text::new(&right_content)
.size(font_size.0 + 1.0)
.font(right_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
),
)
.push(
Row::new()
.width(Length::Shrink)
.spacing(SPACING)
.align_y(Alignment::Center)
.push(
Container::new(
Text::new(&left_content)
.size(font_size.0 + 1.0)
.font(left_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
)
.push(
Text::new("9999").size(font_size).width(Length::Shrink),
)
.push(
Container::new(
Text::new(&right_content)
.size(font_size.0 + 1.0)
.font(right_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
),
);
let days = Container::<Message, Theme, Renderer>::new((0..7).fold(
Column::new().width(Length::Shrink).height(Length::Shrink),
|column, _y| {
column.push(
(0..7).fold(
Row::new()
.height(Length::Shrink)
.width(Length::Shrink)
.spacing(SPACING),
|row, _x| {
row.push(
Container::new(Row::new().push(Text::new("31").size(font_size)))
.width(Length::Shrink)
.height(Length::Shrink)
.padding(DAY_CELL_PADDING),
)
},
),
)
},
))
.width(Length::Shrink)
.height(Length::Shrink)
.center_y(Length::Shrink);
let col = Column::<Message, Theme, Renderer>::new()
.spacing(SPACING)
.align_x(Alignment::Center)
.push(month_year)
.push(days);
let mut element: Element<Message, Theme, Renderer> = Element::new(col);
let col_tree = if let Some(child_tree) = self.tree.children.get_mut(2) {
child_tree.diff(element.as_widget_mut());
child_tree
} else {
let child_tree = Tree::new(element.as_widget());
self.tree.children.insert(2, child_tree);
&mut self.tree.children[2]
};
let mut col = element.as_widget_mut().layout(col_tree, renderer, &limits);
let col_bounds = col.bounds();
col = col.move_to(Point::new(
col_bounds.x + PADDING.left,
col_bounds.y + PADDING.top,
));
let cancel_limits =
limits.max_width(((col.bounds().width / 2.0) - BUTTON_SPACING.0).max(0.0));
let mut cancel_button =
self.cancel_button
.layout(&mut self.tree.children[0], renderer, &cancel_limits);
let submit_limits =
limits.max_width(((col.bounds().width / 2.0) - BUTTON_SPACING.0).max(0.0));
let mut submit_button =
self.submit_button
.layout(&mut self.tree.children[1], renderer, &submit_limits);
let cancel_bounds = cancel_button.bounds();
cancel_button = cancel_button.move_to(Point {
x: cancel_bounds.x + PADDING.left,
y: cancel_bounds.y + col.bounds().height + PADDING.top + SPACING.0,
});
let submit_bounds = submit_button.bounds();
submit_button = submit_button.move_to(Point {
x: submit_bounds.x + col.bounds().width - submit_bounds.width + PADDING.left,
y: submit_bounds.y + col.bounds().height + PADDING.top + SPACING.0,
});
let mut node = Node::with_children(
Size::new(
col.bounds().width + PADDING.x(),
col.bounds().height + cancel_button.bounds().height + PADDING.y() + SPACING.0,
),
vec![col, cancel_button, submit_button],
);
node.center_and_bounce(self.position, bounds);
node
}
fn update(
&mut self,
event: &Event,
layout: Layout<'_>,
cursor: Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<Message>,
) {
if event::Status::Captured == self.on_event_keyboard(event) {
shell.capture_event();
shell.request_redraw();
return;
}
let mut children = layout.children();
let mut date_children = children
.next()
.expect("widget: Layout should have date children")
.children();
let month_year_layout = date_children
.next()
.expect("widget: Layout should have a month/year layout");
self.on_event_month_year(event, month_year_layout, cursor, shell);
let days_layout = date_children
.next()
.expect("widget: Layout should have a days table parent")
.children()
.next()
.expect("widget: Layout should have a days table layout");
self.on_event_days(event, days_layout, cursor, shell);
let cancel_button_layout = children
.next()
.expect("widget: Layout should have a cancel button layout for a DatePicker");
if cursor.is_over(cancel_button_layout.bounds()) {
self.state.day_mouse_over = None;
}
self.cancel_button.update(
&mut self.tree.children[0],
event,
cancel_button_layout,
cursor,
renderer,
clipboard,
shell,
&layout.bounds(),
);
let submit_button_layout = children
.next()
.expect("widget: Layout should have a submit button layout for a DatePicker");
if cursor.is_over(submit_button_layout.bounds()) {
self.state.day_mouse_over = None;
}
let mut fake_messages: Vec<Message> = Vec::new();
self.submit_button.update(
&mut self.tree.children[1],
event,
submit_button_layout,
cursor,
renderer,
clipboard,
&mut Shell::new(&mut fake_messages),
&layout.bounds(),
);
if !fake_messages.is_empty() {
shell.publish((self.on_submit)(self.state.date.into()));
shell.capture_event();
shell.request_redraw();
}
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
) -> mouse::Interaction {
let mouse_interaction = mouse::Interaction::default();
let mut children = layout.children();
let mut date_children = children
.next()
.expect("Graphics: Layout should have a date layout")
.children();
let month_year_layout = date_children
.next()
.expect("Graphics: Layout should have a month/year layout");
let mut month_year_children = month_year_layout.children();
let month_layout = month_year_children
.next()
.expect("Graphics: Layout should have a month layout");
let year_layout = month_year_children
.next()
.expect("Graphics: Layout should have a year layout");
let f = |layout: Layout<'_>| {
let mut children = layout.children();
let left_bounds = children
.next()
.expect("Graphics: Layout should have a left arrow layout")
.bounds();
let _center = children.next();
let right_bounds = children
.next()
.expect("Graphics: Layout should have a right arrow layout")
.bounds();
let left_arrow_hovered = cursor.is_over(left_bounds);
let right_arrow_hovered = cursor.is_over(right_bounds);
if left_arrow_hovered || right_arrow_hovered {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
};
let month_mouse_interaction = f(month_layout);
let year_mouse_interaction = f(year_layout);
let days_layout = date_children
.next()
.expect("Graphics: Layout should have a days layout parent")
.children()
.next()
.expect("Graphics: Layout should have a days layout");
let mut days_children = days_layout.children();
let _day_labels_layout = days_children.next();
let mut table_mouse_interaction = mouse::Interaction::default();
for row in days_children {
for label in row.children() {
let bounds = label.bounds();
let mouse_over = cursor.is_over(bounds);
if mouse_over {
table_mouse_interaction =
table_mouse_interaction.max(mouse::Interaction::Pointer);
}
}
}
let cancel_button_layout = children
.next()
.expect("Graphics: Layout should have a cancel button layout for a DatePicker");
let cancel_button_mouse_interaction = self.cancel_button.mouse_interaction(
&self.tree.children[0],
cancel_button_layout,
cursor,
&self.viewport,
renderer,
);
let submit_button_layout = children
.next()
.expect("Graphics: Layout should have a submit button layout for a DatePicker");
let submit_button_mouse_interaction = self.submit_button.mouse_interaction(
&self.tree.children[1],
submit_button_layout,
cursor,
&self.viewport,
renderer,
);
mouse_interaction
.max(month_mouse_interaction)
.max(year_mouse_interaction)
.max(table_mouse_interaction)
.max(cancel_button_mouse_interaction)
.max(submit_button_mouse_interaction)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: Cursor,
) {
let bounds = layout.bounds();
let mut children = layout.children();
let mut date_children = children
.next()
.expect("Graphics: Layout should have a date layout")
.children();
let cursor_position = cursor.position().unwrap_or_default();
let mut style_sheet: HashMap<StyleState, Style> = HashMap::new();
let _ = style_sheet.insert(
StyleState::Active,
crate::style::date_picker::Catalog::style(theme, self.class, Status::Active),
);
let _ = style_sheet.insert(
StyleState::Selected,
crate::style::date_picker::Catalog::style(theme, self.class, Status::Selected),
);
let _ = style_sheet.insert(
StyleState::Hovered,
crate::style::date_picker::Catalog::style(theme, self.class, Status::Hovered),
);
let _ = style_sheet.insert(
StyleState::Focused,
crate::style::date_picker::Catalog::style(theme, self.class, Status::Focused),
);
let style_state = if self.state.focus == Focus::Overlay {
StyleState::Focused
} else if cursor.is_over(bounds) {
StyleState::Hovered
} else {
StyleState::Active
};
if (bounds.width > 0.) && (bounds.height > 0.) {
renderer.fill_quad(
renderer::Quad {
bounds,
border: Border {
radius: style_sheet[&style_state].border_radius.into(),
width: style_sheet[&style_state].border_width,
color: style_sheet[&style_state].border_color,
},
..renderer::Quad::default()
},
style_sheet[&style_state].background,
);
}
let month_year_layout = date_children
.next()
.expect("Graphics: Layout should have a month/year layout");
month_year(
renderer,
month_year_layout,
&self.date_as_string(),
cursor_position,
&style_sheet,
self.state.focus,
self.font_size,
);
let days_layout = date_children
.next()
.expect("Graphics: Layout should have a days layout parent")
.children()
.next()
.expect("Graphics: Layout should have a days layout");
days(
renderer,
days_layout,
self.state.date,
&style_sheet,
self.state.focus,
self.font_size,
self.state.day_mouse_over,
);
let cancel_button_layout = children
.next()
.expect("Graphics: Layout should have a cancel button layout for a DatePicker");
self.cancel_button.draw(
&self.tree.children[0],
renderer,
theme,
style,
cancel_button_layout,
cursor,
&bounds,
);
let submit_button_layout = children
.next()
.expect("Graphics: Layout should have a submit button layout for a DatePicker");
self.submit_button.draw(
&self.tree.children[1],
renderer,
theme,
style,
submit_button_layout,
cursor,
&bounds,
);
let border = Border {
radius: style_sheet[&StyleState::Focused].border_radius.into(),
width: style_sheet[&StyleState::Focused].border_width,
color: style_sheet[&StyleState::Focused].border_color,
};
if self.state.focus == Focus::Cancel {
renderer.fill_quad(
renderer::Quad {
bounds: cancel_button_layout.bounds(),
border,
..renderer::Quad::default()
},
Color::TRANSPARENT,
);
}
if self.state.focus == Focus::Submit {
renderer.fill_quad(
renderer::Quad {
bounds: submit_button_layout.bounds(),
border,
..renderer::Quad::default()
},
Color::TRANSPARENT,
);
}
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation,
) {
let mut children = layout.children();
if let Some(col_layout) = children.next() {
if let Some(col_tree) = self.tree.children.get_mut(2) {
let (left_content, left_font, _left_shaping) = left_open();
let (right_content, right_font, _right_shaping) = right_open();
let font_size = self.font_size;
let date_string = crate::core::date::date_as_string(self.state.date);
let (year, month) = date_string.split_once(' ').unwrap_or(("", ""));
let month_year = Row::<Message, Theme, Renderer>::new()
.width(Length::Shrink)
.spacing(SPACING)
.push(
Row::new()
.width(Length::Shrink)
.spacing(SPACING)
.align_y(Alignment::Center)
.push(
Container::new(
Text::new(&left_content)
.size(font_size.0 + 1.0)
.font(left_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
)
.push(Text::new(month).size(font_size).width(Length::Shrink))
.push(
Container::new(
Text::new(&right_content)
.size(font_size.0 + 1.0)
.font(right_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
),
)
.push(
Row::new()
.width(Length::Shrink)
.spacing(SPACING)
.align_y(Alignment::Center)
.push(
Container::new(
Text::new(&left_content)
.size(font_size.0 + 1.0)
.font(left_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
)
.push(Text::new(year).size(font_size).width(Length::Shrink))
.push(
Container::new(
Text::new(&right_content)
.size(font_size.0 + 1.0)
.font(right_font),
)
.height(Length::Shrink)
.width(Length::Shrink),
),
);
let days =
Container::<Message, Theme, Renderer>::new((0..7).fold(
Column::new().width(Length::Shrink).height(Length::Shrink),
|column, y| {
column.push(
(0..7).fold(
Row::new()
.height(Length::Shrink)
.width(Length::Shrink)
.spacing(SPACING),
|row, x| {
let (day, _) = crate::core::date::position_to_day(
x,
y,
self.state.date.year(),
self.state.date.month(),
);
row.push(
Container::new(Row::new().push(
Text::new(format!("{day:02}")).size(font_size),
))
.width(Length::Shrink)
.height(Length::Shrink)
.padding(DAY_CELL_PADDING),
)
},
),
)
},
))
.width(Length::Shrink)
.height(Length::Shrink)
.center_y(Length::Shrink);
let col = Column::<Message, Theme, Renderer>::new()
.spacing(SPACING)
.align_x(Alignment::Center)
.push(month_year)
.push(days);
let mut element: Element<Message, Theme, Renderer> = Element::new(col);
element
.as_widget_mut()
.operate(col_tree, col_layout, renderer, operation);
}
}
if let Some(cancel_layout) = children.next() {
Widget::operate(
&mut self.cancel_button,
&mut self.tree.children[0],
cancel_layout,
renderer,
operation,
);
}
if let Some(submit_layout) = children.next() {
Widget::operate(
&mut self.submit_button,
&mut self.tree.children[1],
submit_layout,
renderer,
operation,
);
}
}
}
#[derive(Debug)]
pub struct State {
pub(crate) date: NaiveDate,
pub(crate) focus: Focus,
pub(crate) keyboard_modifiers: keyboard::Modifiers,
pub(crate) day_mouse_over: Option<(usize, usize)>,
}
impl State {
#[must_use]
pub fn new(date: NaiveDate) -> Self {
Self {
date,
..Self::default()
}
}
}
impl Default for State {
fn default() -> Self {
Self {
date: Local::now().naive_local().date(),
focus: Focus::default(),
keyboard_modifiers: keyboard::Modifiers::default(),
day_mouse_over: None,
}
}
}
#[allow(missing_debug_implementations)]
pub struct DatePickerOverlayButtons<'a, Message, Theme>
where
Message: Clone,
Theme: crate::style::date_picker::Catalog + iced_widget::button::Catalog,
{
cancel_button: Element<'a, Message, Theme, Renderer>,
submit_button: Element<'a, Message, Theme, Renderer>,
}
impl<'a, Message, Theme> Default for DatePickerOverlayButtons<'a, Message, Theme>
where
Message: 'a + Clone,
Theme: 'a
+ crate::style::date_picker::Catalog
+ iced_widget::button::Catalog
+ iced_widget::text::Catalog
+ iced_widget::container::Catalog,
{
fn default() -> Self {
let (cancel_content, cancel_font, _cancel_shaping) = cancel();
let (submit_content, submit_font, _submit_shaping) = ok();
Self {
cancel_button: Button::new(
text::Text::new(cancel_content)
.font(cancel_font)
.align_x(Horizontal::Center)
.width(Length::Fill),
)
.into(),
submit_button: Button::new(
text::Text::new(submit_content)
.font(submit_font)
.align_x(Horizontal::Center)
.width(Length::Fill),
)
.into(),
}
}
}
#[allow(clippy::unimplemented)]
impl<Message, Theme> Widget<Message, Theme, Renderer>
for DatePickerOverlayButtons<'_, Message, Theme>
where
Message: Clone,
Theme: crate::style::date_picker::Catalog
+ iced_widget::button::Catalog
+ iced_widget::container::Catalog,
{
fn children(&self) -> Vec<Tree> {
vec![
Tree::new(&self.cancel_button),
Tree::new(&self.submit_button),
]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[&self.cancel_button, &self.submit_button]);
}
fn size(&self) -> Size<Length> {
unimplemented!("This should never be reached!")
}
fn layout(&mut self, _tree: &mut Tree, _renderer: &Renderer, _limits: &Limits) -> Node {
unimplemented!("This should never be reached!")
}
fn draw(
&self,
_state: &Tree,
_renderer: &mut Renderer,
_theme: &Theme,
_style: &renderer::Style,
_layout: Layout<'_>,
_cursor: Cursor,
_viewport: &Rectangle,
) {
unimplemented!("This should never be reached!")
}
}
impl<'a, Message, Theme> From<DatePickerOverlayButtons<'a, Message, Theme>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: 'a
+ crate::style::date_picker::Catalog
+ iced_widget::button::Catalog
+ iced_widget::container::Catalog,
{
fn from(overlay: DatePickerOverlayButtons<'a, Message, Theme>) -> Self {
Self::new(overlay)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum Focus {
#[default]
None,
Overlay,
Month,
Year,
Day,
Cancel,
Submit,
}
impl Focus {
#[must_use]
pub const fn next(self) -> Self {
match self {
Self::Overlay => Self::Month,
Self::Month => Self::Year,
Self::Year => Self::Day,
Self::Day => Self::Cancel,
Self::Cancel => Self::Submit,
Self::Submit | Self::None => Self::Overlay,
}
}
#[must_use]
pub const fn previous(self) -> Self {
match self {
Self::None => Self::None,
Self::Overlay => Self::Submit,
Self::Month => Self::Overlay,
Self::Year => Self::Month,
Self::Day => Self::Year,
Self::Cancel => Self::Day,
Self::Submit => Self::Cancel,
}
}
}
fn month_year(
renderer: &mut Renderer,
layout: Layout<'_>,
date: &str,
cursor: Point,
style: &HashMap<StyleState, Style>,
focus: Focus,
font_size: Pixels,
) {
let mut children = layout.children();
let month_layout = children
.next()
.expect("Graphics: Layout should have a month layout");
let year_layout = children
.next()
.expect("Graphics: Layout should have a year layout");
let mut f = |layout: Layout<'_>, text: &str, target: Focus, font_size: Pixels| {
let style_state = if focus == target {
StyleState::Focused
} else {
StyleState::Active
};
let mut children = layout.children();
let left_bounds = children
.next()
.expect("Graphics: Layout should have a left arrow layout")
.bounds();
let center_bounds = children
.next()
.expect("Graphics: Layout should have a center layout")
.bounds();
let right_bounds = children
.next()
.expect("Graphics: Layout should have a right arrow layout")
.bounds();
let left_arrow_hovered = left_bounds.contains(cursor);
let right_arrow_hovered = right_bounds.contains(cursor);
let bounds = layout.bounds();
if (style_state == StyleState::Focused) && (bounds.width > 0.) && (bounds.height > 0.) {
renderer.fill_quad(
renderer::Quad {
bounds,
border: Border {
radius: style
.get(&style_state)
.expect("Style Sheet not found.")
.border_radius
.into(),
width: style
.get(&style_state)
.expect("Style Sheet not found.")
.border_width,
color: style
.get(&style_state)
.expect("Style Sheet not found.")
.border_color,
},
shadow: Shadow::default(),
snap: false,
},
style
.get(&style_state)
.expect("Style Sheet not found.")
.background,
);
}
let (left_content, left_font, _left_shaping) = left_open();
let (right_content, right_font, _right_shaping) = right_open();
renderer.fill_text(
iced_core::Text {
content: left_content,
bounds: Size::new(left_bounds.width, left_bounds.height),
size: Pixels(font_size.0 + if left_arrow_hovered { 1.0 } else { 0.0 }),
font: left_font,
align_x: text::Alignment::Center,
align_y: Vertical::Center,
line_height: text::LineHeight::Relative(1.3),
shaping: text::Shaping::Advanced,
wrapping: Wrapping::default(),
},
Point::new(left_bounds.center_x(), left_bounds.center_y()),
style
.get(&style_state)
.expect("Style Sheet not found.")
.text_color,
left_bounds,
);
renderer.fill_text(
iced_core::Text {
content: text.to_owned(),
bounds: Size::new(center_bounds.width, center_bounds.height),
size: font_size,
font: renderer.default_font(),
line_height: text::LineHeight::Relative(1.3),
shaping: text::Shaping::Basic,
wrapping: Wrapping::default(),
align_x: text::Alignment::Center,
align_y: Vertical::Center,
},
Point::new(center_bounds.center_x(), center_bounds.center_y()),
style
.get(&style_state)
.expect("Style Sheet not found.")
.text_color,
center_bounds,
);
renderer.fill_text(
iced_core::Text {
content: right_content,
bounds: Size::new(right_bounds.width, right_bounds.height),
size: Pixels(font_size.0 + if right_arrow_hovered { 1.0 } else { 0.0 }),
font: right_font,
align_x: text::Alignment::Center,
align_y: Vertical::Center,
line_height: text::LineHeight::Relative(1.3),
shaping: text::Shaping::Advanced,
wrapping: Wrapping::default(),
},
Point::new(right_bounds.center_x(), right_bounds.center_y()),
style
.get(&style_state)
.expect("Style Sheet not found.")
.text_color,
right_bounds,
);
};
let (year, month) = date.split_once(' ').expect("Date must contain space");
f(month_layout, month, Focus::Month, font_size);
f(year_layout, year, Focus::Year, font_size);
}
#[allow(clippy::too_many_arguments)]
fn days(
renderer: &mut Renderer,
layout: Layout<'_>,
date: chrono::NaiveDate,
style: &HashMap<StyleState, Style>,
focus: Focus,
font_size: Pixels,
mouse_over: Option<(usize, usize)>,
) {
let mut children = layout.children();
let day_labels_layout = children
.next()
.expect("Graphics: Layout should have a day labels layout");
day_labels(renderer, day_labels_layout, style, focus, font_size);
day_table(
renderer,
&mut children,
date,
style,
focus,
font_size,
mouse_over,
);
}
fn day_labels(
renderer: &mut Renderer,
layout: Layout<'_>,
style: &HashMap<StyleState, Style>,
_focus: Focus,
font_size: Pixels,
) {
for (i, label) in layout.children().enumerate() {
let bounds = label.bounds();
renderer.fill_text(
iced_core::Text {
content: crate::core::date::WEEKDAY_LABELS[i].clone(),
bounds: Size::new(bounds.width, bounds.height),
size: font_size,
font: renderer.default_font(),
align_x: text::Alignment::Center,
align_y: Vertical::Center,
line_height: text::LineHeight::Relative(1.3),
shaping: text::Shaping::Basic,
wrapping: Wrapping::default(),
},
Point::new(bounds.center_x(), bounds.center_y()),
style
.get(&StyleState::Active)
.expect("Style Sheet not found.")
.text_color,
bounds,
);
}
}
#[allow(clippy::too_many_arguments)]
fn day_table(
renderer: &mut Renderer,
children: &mut dyn Iterator<Item = Layout<'_>>,
date: chrono::NaiveDate,
style: &HashMap<StyleState, Style>,
focus: Focus,
font_size: Pixels,
day_mouse_over: Option<(usize, usize)>,
) {
for (y, row) in children.enumerate() {
for (x, label) in row.children().enumerate() {
let bounds = label.bounds();
let (number, is_in_month) =
crate::core::date::position_to_day(x, y, date.year(), date.month());
let mouse_over = if let Some((day_y, day_x)) = day_mouse_over {
day_y == y && day_x == x
} else {
false
};
let selected = date.day() == number as u32 && is_in_month == IsInMonth::Same;
let style_state = if selected {
StyleState::Selected
} else if mouse_over {
StyleState::Hovered
} else {
StyleState::Active
};
renderer.fill_quad(
renderer::Quad {
bounds,
border: Border {
radius: (bounds.height / 2.0).into(),
width: 0.0,
color: Color::TRANSPARENT,
},
shadow: Shadow::default(),
snap: false,
},
style
.get(&style_state)
.expect("Style Sheet not found.")
.day_background,
);
if focus == Focus::Day && selected {
renderer.fill_quad(
renderer::Quad {
bounds,
border: Border {
radius: style
.get(&StyleState::Focused)
.expect("Style Sheet not found.")
.border_radius
.into(),
width: style
.get(&StyleState::Focused)
.expect("Style Sheet not found.")
.border_width,
color: style
.get(&StyleState::Focused)
.expect("Style Sheet not found.")
.border_color,
},
shadow: Shadow::default(),
snap: false,
},
Color::TRANSPARENT,
);
}
renderer.fill_text(
iced_core::Text {
content: format!("{number:02}"), bounds: Size::new(bounds.width, bounds.height),
size: font_size,
font: renderer.default_font(),
align_x: text::Alignment::Center,
align_y: Vertical::Center,
line_height: text::LineHeight::Relative(1.3),
shaping: text::Shaping::Basic,
wrapping: Wrapping::default(),
},
Point::new(bounds.center_x(), bounds.center_y()),
if is_in_month == IsInMonth::Same {
style
.get(&style_state)
.expect("Style Sheet not found.")
.text_color
} else {
style
.get(&style_state)
.expect("Style Sheet not found.")
.text_attenuated_color
},
bounds,
);
}
}
}