iced_aw/widget/overlay/
date_picker.rs

1//! Use a date picker as an input element for picking dates.
2//!
3//! *This API requires the following crate features to be activated: `date_picker`*
4
5use crate::iced_aw_font::advanced_text::{cancel, left_open, ok, right_open};
6use crate::{
7    core::{
8        date::{Date, IsInMonth},
9        overlay::Position,
10    },
11    date_picker,
12    style::{Status, date_picker::Style, style_state::StyleState},
13};
14use chrono::{Datelike, Local, NaiveDate};
15use iced_core::{
16    Alignment, Border, Clipboard, Color, Element, Event, Layout, Length, Overlay, Padding, Pixels,
17    Point, Rectangle, Renderer as _, Shadow, Shell, Size, Widget,
18    alignment::{Horizontal, Vertical},
19    event, keyboard,
20    layout::{Limits, Node},
21    mouse::{self, Cursor},
22    overlay, renderer,
23    text::Renderer as _,
24    touch,
25    widget::tree::Tree,
26};
27use iced_widget::{
28    Button, Column, Container, Renderer, Row, Text,
29    text::{self, Wrapping},
30};
31use std::collections::HashMap;
32
33/// The padding around the elements.
34const PADDING: Padding = Padding::new(10.0);
35/// The spacing between the elements.
36const SPACING: Pixels = Pixels(15.0);
37/// The padding of the day cells.
38const DAY_CELL_PADDING: Padding = Padding::new(7.0);
39/// The spacing between the buttons.
40const BUTTON_SPACING: Pixels = Pixels(5.0);
41
42/// The overlay of the [`DatePicker`](crate::widget::DatePicker).
43#[allow(missing_debug_implementations)]
44pub struct DatePickerOverlay<'a, 'b, Message, Theme>
45where
46    Message: Clone,
47    Theme: crate::style::date_picker::Catalog + iced_widget::button::Catalog,
48    'b: 'a,
49{
50    /// The state of the [`DatePickerOverlay`].
51    state: &'a mut State,
52    /// The cancel button of the [`DatePickerOverlay`].
53    cancel_button: Button<'a, Message, Theme, Renderer>,
54    /// The submit button of the [`DatePickerOverlay`].
55    submit_button: Button<'a, Message, Theme, Renderer>,
56    /// The function that produces a message when the submit button of the [`DatePickerOverlay`] is pressed.
57    on_submit: &'a dyn Fn(Date) -> Message,
58    /// The position of the [`DatePickerOverlay`].
59    position: Point,
60    /// The style of the [`DatePickerOverlay`].
61    class: &'a <Theme as crate::style::date_picker::Catalog>::Class<'b>,
62    /// The reference to the tree holding the state of this overlay.
63    tree: &'a mut Tree,
64    /// The font size of text and icons in the [`DatePickerOverlay`]
65    font_size: Pixels,
66    viewport: Rectangle,
67}
68
69impl<'a, 'b, Message, Theme> DatePickerOverlay<'a, 'b, Message, Theme>
70where
71    Message: 'static + Clone,
72    Theme: 'a
73        + crate::style::date_picker::Catalog
74        + iced_widget::button::Catalog
75        + iced_widget::text::Catalog
76        + iced_widget::container::Catalog,
77    'b: 'a,
78{
79    #[allow(clippy::too_many_arguments)]
80    /// Creates a new [`DatePickerOverlay`] on the given position.
81    pub fn new(
82        state: &'a mut date_picker::State,
83        on_cancel: Message,
84        on_submit: &'a dyn Fn(Date) -> Message,
85        position: Point,
86        class: &'a <Theme as crate::style::date_picker::Catalog>::Class<'b>,
87        tree: &'a mut Tree,
88        //button_style: impl Clone +  Into<<Renderer as button::Renderer>::Style>, // clone not satisfied
89        font_size: Pixels,
90        viewport: Rectangle,
91    ) -> Self {
92        let date_picker::State { overlay_state } = state;
93
94        let (cancel_content, cancel_font, _cancel_shaping) = cancel();
95        let (submit_content, submit_font, _submit_shaping) = ok();
96        DatePickerOverlay {
97            state: overlay_state,
98            cancel_button: Button::new(
99                text::Text::new(cancel_content)
100                    .font(cancel_font)
101                    .size(font_size)
102                    .align_x(Horizontal::Center)
103                    .width(Length::Fill),
104            )
105            .width(Length::Fill)
106            .on_press(on_cancel.clone()),
107            submit_button: Button::new(
108                text::Text::new(submit_content)
109                    .font(submit_font)
110                    .size(font_size)
111                    .align_x(Horizontal::Center)
112                    .width(Length::Fill),
113            )
114            .width(Length::Fill)
115            .on_press(on_cancel), // Sending a fake message
116            on_submit,
117            position,
118            class,
119            tree,
120            font_size,
121            viewport,
122        }
123    }
124
125    /// Turn this [`DatePickerOverlay`] into an overlay [`Element`](overlay::Element).
126    #[must_use]
127    pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> {
128        overlay::Element::new(Box::new(self))
129    }
130
131    /// String representation of the current month and year.
132    fn date_as_string(&self) -> String {
133        crate::core::date::date_as_string(self.state.date)
134    }
135
136    /// The event handling for the month / year bar.
137    fn on_event_month_year(
138        &mut self,
139        event: &Event,
140        layout: Layout<'_>,
141        cursor: Cursor,
142        shell: &mut Shell<Message>,
143    ) {
144        let mut children = layout.children();
145
146        // ----------- Month ----------------------
147        let month_layout = children
148            .next()
149            .expect("widget: Layout should have a month layout");
150        let mut month_children = month_layout.children();
151
152        let left_bounds = month_children
153            .next()
154            .expect("widget: Layout should have a left month arrow layout")
155            .bounds();
156        let _center_bounds = month_children
157            .next()
158            .expect("widget: Layout should have a center month layout")
159            .bounds();
160        let right_bounds = month_children
161            .next()
162            .expect("widget: Layout should have a right month arrow layout")
163            .bounds();
164
165        match event {
166            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
167            | Event::Touch(touch::Event::FingerPressed { .. }) => {
168                if cursor.is_over(month_layout.bounds()) {
169                    self.state.focus = Focus::Month;
170                    shell.request_redraw();
171                }
172
173                if cursor.is_over(left_bounds) {
174                    self.state.date = crate::core::date::pred_month(self.state.date);
175                    shell.capture_event();
176                    shell.request_redraw();
177                } else if cursor.is_over(right_bounds) {
178                    self.state.date = crate::core::date::succ_month(self.state.date);
179                    shell.capture_event();
180                    shell.request_redraw();
181                }
182            }
183            Event::Mouse(mouse::Event::CursorMoved { position }) => {
184                if month_layout.bounds().contains(*position)
185                    || left_bounds.contains(*position)
186                    || right_bounds.contains(*position)
187                {
188                    self.state.day_mouse_over = None;
189                    shell.request_redraw();
190                }
191            }
192            _ => {}
193        }
194
195        // ----------- Year -----------------------
196        let year_layout = children
197            .next()
198            .expect("widget: Layout should have a year layout");
199        let mut year_children = year_layout.children();
200
201        let left_bounds = year_children
202            .next()
203            .expect("widget: Layout should have a left year arrow layout")
204            .bounds();
205        let _center_bounds = year_children
206            .next()
207            .expect("widget: Layout should have a center year layout")
208            .bounds();
209        let right_bounds = year_children
210            .next()
211            .expect("widget: Layout should have a right year arrow layout")
212            .bounds();
213
214        match event {
215            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
216            | Event::Touch(touch::Event::FingerPressed { .. }) => {
217                if cursor.is_over(year_layout.bounds()) {
218                    self.state.focus = Focus::Year;
219                    shell.request_redraw();
220                }
221
222                if cursor.is_over(left_bounds) {
223                    self.state.date = crate::core::date::pred_year(self.state.date);
224                    shell.capture_event();
225                    shell.request_redraw();
226                } else if cursor.is_over(right_bounds) {
227                    self.state.date = crate::core::date::succ_year(self.state.date);
228                    shell.capture_event();
229                    shell.request_redraw();
230                }
231            }
232            Event::Mouse(mouse::Event::CursorMoved { position }) => {
233                if year_layout.bounds().contains(*position)
234                    || left_bounds.contains(*position)
235                    || right_bounds.contains(*position)
236                {
237                    self.state.day_mouse_over = None;
238                    shell.request_redraw();
239                }
240            }
241            _ => {}
242        }
243    }
244
245    /// The event handling for the calendar days.
246    fn on_event_days(
247        &mut self,
248        event: &Event,
249        layout: Layout<'_>,
250        cursor: Cursor,
251        shell: &mut Shell<Message>,
252    ) {
253        let mut children = layout.children();
254
255        let _day_labels_layout = children
256            .next()
257            .expect("widget: Layout should have a day label layout");
258
259        match event {
260            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
261            | Event::Touch(touch::Event::FingerPressed { .. }) => {
262                if cursor.is_over(layout.bounds()) {
263                    self.state.focus = Focus::Day;
264                    shell.request_redraw();
265                }
266
267                'outer: for (y, row) in children.enumerate() {
268                    for (x, label) in row.children().enumerate() {
269                        let bounds = label.bounds();
270                        if cursor.is_over(bounds) {
271                            let (day, is_in_month) = crate::core::date::position_to_day(
272                                x,
273                                y,
274                                self.state.date.year(),
275                                self.state.date.month(),
276                            );
277
278                            self.state.date = match is_in_month {
279                                IsInMonth::Previous => {
280                                    crate::core::date::pred_month(self.state.date)
281                                        .with_day(day as u32)
282                                        .expect("Previous month with day should be valid")
283                                }
284                                IsInMonth::Same => self
285                                    .state
286                                    .date
287                                    .with_day(day as u32)
288                                    .expect("Same month with day should be valid"),
289                                IsInMonth::Next => crate::core::date::succ_month(self.state.date)
290                                    .with_day(day as u32)
291                                    .expect("Succeeding month with day should be valid"),
292                            };
293
294                            shell.capture_event();
295                            shell.request_redraw();
296                            break 'outer;
297                        }
298                    }
299                }
300            }
301            Event::Mouse(mouse::Event::CursorMoved { position }) => {
302                if cursor.is_over(layout.bounds()) {
303                    self.state.focus = Focus::Day;
304                    shell.request_redraw();
305                }
306
307                self.state.day_mouse_over = None;
308
309                'outer: for (y, row) in children.enumerate() {
310                    for (x, label) in row.children().enumerate() {
311                        let bounds = label.bounds();
312
313                        if bounds.contains(*position) {
314                            self.state.day_mouse_over = Some((y, x));
315                            shell.capture_event();
316                            shell.request_redraw();
317                            break 'outer;
318                        }
319                    }
320                }
321            }
322            _ => {}
323        }
324    }
325
326    /// The event handling for the keyboard input.
327    fn on_event_keyboard(&mut self, event: &Event) -> event::Status {
328        if self.state.focus == Focus::None {
329            return event::Status::Ignored;
330        }
331
332        if let Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) = event {
333            let mut status = event::Status::Ignored;
334
335            match key.as_ref() {
336                keyboard::Key::Named(keyboard::key::Named::Tab) => {
337                    if self.state.keyboard_modifiers.shift() {
338                        self.state.focus = self.state.focus.previous();
339                    } else {
340                        self.state.focus = self.state.focus.next();
341                    }
342                }
343                keyboard::Key::Named(k) => match self.state.focus {
344                    Focus::Month => match k {
345                        keyboard::key::Named::ArrowLeft => {
346                            self.state.date = crate::core::date::pred_month(self.state.date);
347                            status = event::Status::Captured;
348                        }
349                        keyboard::key::Named::ArrowRight => {
350                            self.state.date = crate::core::date::succ_month(self.state.date);
351                            status = event::Status::Captured;
352                        }
353                        _ => {}
354                    },
355                    Focus::Year => match k {
356                        keyboard::key::Named::ArrowLeft => {
357                            self.state.date = crate::core::date::pred_year(self.state.date);
358                            status = event::Status::Captured;
359                        }
360                        keyboard::key::Named::ArrowRight => {
361                            self.state.date = crate::core::date::succ_year(self.state.date);
362                            status = event::Status::Captured;
363                        }
364                        _ => {}
365                    },
366                    Focus::Day => match k {
367                        keyboard::key::Named::ArrowLeft => {
368                            self.state.date = crate::core::date::pred_day(self.state.date);
369                            status = event::Status::Captured;
370                        }
371                        keyboard::key::Named::ArrowRight => {
372                            self.state.date = crate::core::date::succ_day(self.state.date);
373                            status = event::Status::Captured;
374                        }
375                        keyboard::key::Named::ArrowUp => {
376                            self.state.date = crate::core::date::pred_week(self.state.date);
377                            status = event::Status::Captured;
378                        }
379                        keyboard::key::Named::ArrowDown => {
380                            self.state.date = crate::core::date::succ_week(self.state.date);
381                            status = event::Status::Captured;
382                        }
383                        _ => {}
384                    },
385                    _ => {}
386                },
387                _ => {}
388            }
389
390            status
391        } else if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = event {
392            self.state.keyboard_modifiers = *modifiers;
393            event::Status::Ignored
394        } else {
395            event::Status::Ignored
396        }
397    }
398}
399
400impl<'a, 'b, Message, Theme> Overlay<Message, Theme, Renderer>
401    for DatePickerOverlay<'a, 'b, Message, Theme>
402where
403    Message: 'static + Clone,
404    Theme: 'a
405        + crate::style::date_picker::Catalog
406        + iced_widget::button::Catalog
407        + iced_widget::text::Catalog
408        + iced_widget::container::Catalog,
409    'b: 'a,
410{
411    #[allow(clippy::too_many_lines)]
412    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
413        let limits = Limits::new(Size::ZERO, bounds)
414            .shrink(PADDING)
415            .width(Length::Shrink)
416            .height(Length::Shrink);
417
418        // Pre-Buttons TODO: get rid of it
419        let cancel_limits = limits;
420        let cancel_button =
421            self.cancel_button
422                .layout(&mut self.tree.children[0], renderer, &cancel_limits);
423
424        let limits = limits.shrink(Size::new(0.0, cancel_button.bounds().height + SPACING.0));
425
426        // Month/Year
427        let font_size = self.font_size;
428
429        let (left_content, left_font, _left_shaping) = left_open();
430        let (right_content, right_font, _right_shaping) = right_open();
431
432        let month_year = Row::<Message, Theme, Renderer>::new()
433            .width(Length::Shrink)
434            .spacing(SPACING)
435            .push(
436                Row::new()
437                    .width(Length::Shrink)
438                    .spacing(SPACING)
439                    .align_y(Alignment::Center)
440                    .push(
441                        // Left Month arrow
442                        Container::new(
443                            Text::new(&left_content)
444                                .size(font_size.0 + 1.0)
445                                .font(left_font),
446                        )
447                        .height(Length::Shrink)
448                        .width(Length::Shrink),
449                    )
450                    .push(
451                        // Month
452                        Text::new("September").size(font_size).width(Length::Shrink),
453                    )
454                    .push(
455                        // Right Month arrow
456                        Container::new(
457                            Text::new(&right_content)
458                                .size(font_size.0 + 1.0)
459                                .font(right_font),
460                        )
461                        .height(Length::Shrink)
462                        .width(Length::Shrink),
463                    ),
464            )
465            .push(
466                Row::new()
467                    .width(Length::Shrink)
468                    .spacing(SPACING)
469                    .align_y(Alignment::Center)
470                    .push(
471                        // Left Year arrow
472                        Container::new(
473                            Text::new(&left_content)
474                                .size(font_size.0 + 1.0)
475                                .font(left_font),
476                        )
477                        .height(Length::Shrink)
478                        .width(Length::Shrink),
479                    )
480                    .push(
481                        // Year
482                        Text::new("9999").size(font_size).width(Length::Shrink),
483                    )
484                    .push(
485                        // Right Year arrow
486                        Container::new(
487                            Text::new(&right_content)
488                                .size(font_size.0 + 1.0)
489                                .font(right_font),
490                        )
491                        .height(Length::Shrink)
492                        .width(Length::Shrink),
493                    ),
494            );
495
496        let days = Container::<Message, Theme, Renderer>::new((0..7).fold(
497            Column::new().width(Length::Shrink).height(Length::Shrink),
498            |column, _y| {
499                column.push(
500                    (0..7).fold(
501                        Row::new()
502                            .height(Length::Shrink)
503                            .width(Length::Shrink)
504                            .spacing(SPACING),
505                        |row, _x| {
506                            row.push(
507                                Container::new(Row::new().push(Text::new("31").size(font_size)))
508                                    .width(Length::Shrink)
509                                    .height(Length::Shrink)
510                                    .padding(DAY_CELL_PADDING),
511                            )
512                        },
513                    ),
514                )
515            },
516        ))
517        .width(Length::Shrink)
518        .height(Length::Shrink)
519        .center_y(Length::Shrink);
520
521        let col = Column::<Message, Theme, Renderer>::new()
522            .spacing(SPACING)
523            .align_x(Alignment::Center)
524            .push(month_year)
525            .push(days);
526
527        let mut element: Element<Message, Theme, Renderer> = Element::new(col);
528        let col_tree = if let Some(child_tree) = self.tree.children.get_mut(2) {
529            child_tree.diff(element.as_widget_mut());
530            child_tree
531        } else {
532            let child_tree = Tree::new(element.as_widget());
533            self.tree.children.insert(2, child_tree);
534            &mut self.tree.children[2]
535        };
536
537        let mut col = element.as_widget_mut().layout(col_tree, renderer, &limits);
538        let col_bounds = col.bounds();
539        col = col.move_to(Point::new(
540            col_bounds.x + PADDING.left,
541            col_bounds.y + PADDING.top,
542        ));
543
544        // Buttons
545        let cancel_limits =
546            limits.max_width(((col.bounds().width / 2.0) - BUTTON_SPACING.0).max(0.0));
547
548        let mut cancel_button =
549            self.cancel_button
550                .layout(&mut self.tree.children[0], renderer, &cancel_limits);
551
552        let submit_limits =
553            limits.max_width(((col.bounds().width / 2.0) - BUTTON_SPACING.0).max(0.0));
554
555        let mut submit_button =
556            self.submit_button
557                .layout(&mut self.tree.children[1], renderer, &submit_limits);
558
559        let cancel_bounds = cancel_button.bounds();
560        cancel_button = cancel_button.move_to(Point {
561            x: cancel_bounds.x + PADDING.left,
562            y: cancel_bounds.y + col.bounds().height + PADDING.top + SPACING.0,
563        });
564
565        let submit_bounds = submit_button.bounds();
566        submit_button = submit_button.move_to(Point {
567            x: submit_bounds.x + col.bounds().width - submit_bounds.width + PADDING.left,
568            y: submit_bounds.y + col.bounds().height + PADDING.top + SPACING.0,
569        });
570
571        let mut node = Node::with_children(
572            Size::new(
573                col.bounds().width + PADDING.x(),
574                col.bounds().height + cancel_button.bounds().height + PADDING.y() + SPACING.0,
575            ),
576            vec![col, cancel_button, submit_button],
577        );
578        node.center_and_bounce(self.position, bounds);
579        node
580    }
581
582    fn update(
583        &mut self,
584        event: &Event,
585        layout: Layout<'_>,
586        cursor: Cursor,
587        renderer: &Renderer,
588        clipboard: &mut dyn Clipboard,
589        shell: &mut Shell<Message>,
590    ) {
591        if event::Status::Captured == self.on_event_keyboard(event) {
592            shell.capture_event();
593            shell.request_redraw();
594            return;
595        }
596
597        let mut children = layout.children();
598
599        let mut date_children = children
600            .next()
601            .expect("widget: Layout should have date children")
602            .children();
603
604        // ----------- Year/Month----------------------
605        let month_year_layout = date_children
606            .next()
607            .expect("widget: Layout should have a month/year layout");
608        self.on_event_month_year(event, month_year_layout, cursor, shell);
609
610        // ----------- Days ----------------------
611        let days_layout = date_children
612            .next()
613            .expect("widget: Layout should have a days table parent")
614            .children()
615            .next()
616            .expect("widget: Layout should have a days table layout");
617        self.on_event_days(event, days_layout, cursor, shell);
618
619        // ----------- Buttons ------------------------
620        let cancel_button_layout = children
621            .next()
622            .expect("widget: Layout should have a cancel button layout for a DatePicker");
623
624        if cursor.is_over(cancel_button_layout.bounds()) {
625            self.state.day_mouse_over = None;
626        }
627
628        self.cancel_button.update(
629            &mut self.tree.children[0],
630            event,
631            cancel_button_layout,
632            cursor,
633            renderer,
634            clipboard,
635            shell,
636            &layout.bounds(),
637        );
638
639        let submit_button_layout = children
640            .next()
641            .expect("widget: Layout should have a submit button layout for a DatePicker");
642
643        if cursor.is_over(submit_button_layout.bounds()) {
644            self.state.day_mouse_over = None;
645        }
646
647        let mut fake_messages: Vec<Message> = Vec::new();
648
649        self.submit_button.update(
650            &mut self.tree.children[1],
651            event,
652            submit_button_layout,
653            cursor,
654            renderer,
655            clipboard,
656            &mut Shell::new(&mut fake_messages),
657            &layout.bounds(),
658        );
659
660        if !fake_messages.is_empty() {
661            shell.publish((self.on_submit)(self.state.date.into()));
662            shell.capture_event();
663            shell.request_redraw();
664        }
665    }
666
667    fn mouse_interaction(
668        &self,
669        layout: Layout<'_>,
670        cursor: mouse::Cursor,
671        renderer: &Renderer,
672    ) -> mouse::Interaction {
673        let mouse_interaction = mouse::Interaction::default();
674
675        let mut children = layout.children();
676        let mut date_children = children
677            .next()
678            .expect("Graphics: Layout should have a date layout")
679            .children();
680
681        // Month and year mouse interaction
682        let month_year_layout = date_children
683            .next()
684            .expect("Graphics: Layout should have a month/year layout");
685        let mut month_year_children = month_year_layout.children();
686        let month_layout = month_year_children
687            .next()
688            .expect("Graphics: Layout should have a month layout");
689        let year_layout = month_year_children
690            .next()
691            .expect("Graphics: Layout should have a year layout");
692
693        let f = |layout: Layout<'_>| {
694            let mut children = layout.children();
695
696            let left_bounds = children
697                .next()
698                .expect("Graphics: Layout should have a left arrow layout")
699                .bounds();
700            let _center = children.next();
701            let right_bounds = children
702                .next()
703                .expect("Graphics: Layout should have a right arrow layout")
704                .bounds();
705
706            let left_arrow_hovered = cursor.is_over(left_bounds);
707            let right_arrow_hovered = cursor.is_over(right_bounds);
708
709            if left_arrow_hovered || right_arrow_hovered {
710                mouse::Interaction::Pointer
711            } else {
712                mouse::Interaction::default()
713            }
714        };
715
716        let month_mouse_interaction = f(month_layout);
717        let year_mouse_interaction = f(year_layout);
718
719        // Days
720        let days_layout = date_children
721            .next()
722            .expect("Graphics: Layout should have a days layout parent")
723            .children()
724            .next()
725            .expect("Graphics: Layout should have a days layout");
726        let mut days_children = days_layout.children();
727        let _day_labels_layout = days_children.next();
728
729        let mut table_mouse_interaction = mouse::Interaction::default();
730
731        for row in days_children {
732            for label in row.children() {
733                let bounds = label.bounds();
734
735                let mouse_over = cursor.is_over(bounds);
736                if mouse_over {
737                    table_mouse_interaction =
738                        table_mouse_interaction.max(mouse::Interaction::Pointer);
739                }
740            }
741        }
742
743        // Buttons
744        let cancel_button_layout = children
745            .next()
746            .expect("Graphics: Layout should have a cancel button layout for a DatePicker");
747
748        let cancel_button_mouse_interaction = self.cancel_button.mouse_interaction(
749            &self.tree.children[0],
750            cancel_button_layout,
751            cursor,
752            &self.viewport,
753            renderer,
754        );
755
756        let submit_button_layout = children
757            .next()
758            .expect("Graphics: Layout should have a submit button layout for a DatePicker");
759
760        let submit_button_mouse_interaction = self.submit_button.mouse_interaction(
761            &self.tree.children[1],
762            submit_button_layout,
763            cursor,
764            &self.viewport,
765            renderer,
766        );
767
768        mouse_interaction
769            .max(month_mouse_interaction)
770            .max(year_mouse_interaction)
771            .max(table_mouse_interaction)
772            .max(cancel_button_mouse_interaction)
773            .max(submit_button_mouse_interaction)
774    }
775
776    fn draw(
777        &self,
778        renderer: &mut Renderer,
779        theme: &Theme,
780        style: &renderer::Style,
781        layout: Layout<'_>,
782        cursor: Cursor,
783    ) {
784        let bounds = layout.bounds();
785        let mut children = layout.children();
786        let mut date_children = children
787            .next()
788            .expect("Graphics: Layout should have a date layout")
789            .children();
790        let cursor_position = cursor.position().unwrap_or_default();
791        let mut style_sheet: HashMap<StyleState, Style> = HashMap::new();
792        let _ = style_sheet.insert(
793            StyleState::Active,
794            crate::style::date_picker::Catalog::style(theme, self.class, Status::Active),
795        );
796        let _ = style_sheet.insert(
797            StyleState::Selected,
798            crate::style::date_picker::Catalog::style(theme, self.class, Status::Selected),
799        );
800        let _ = style_sheet.insert(
801            StyleState::Hovered,
802            crate::style::date_picker::Catalog::style(theme, self.class, Status::Hovered),
803        );
804        let _ = style_sheet.insert(
805            StyleState::Focused,
806            crate::style::date_picker::Catalog::style(theme, self.class, Status::Focused),
807        );
808
809        let style_state = if self.state.focus == Focus::Overlay {
810            StyleState::Focused
811        } else if cursor.is_over(bounds) {
812            StyleState::Hovered
813        } else {
814            StyleState::Active
815        };
816
817        // Background
818        if (bounds.width > 0.) && (bounds.height > 0.) {
819            renderer.fill_quad(
820                renderer::Quad {
821                    bounds,
822                    border: Border {
823                        radius: style_sheet[&style_state].border_radius.into(),
824                        width: style_sheet[&style_state].border_width,
825                        color: style_sheet[&style_state].border_color,
826                    },
827                    ..renderer::Quad::default()
828                },
829                style_sheet[&style_state].background,
830            );
831        }
832
833        // ----------- Year/Month----------------------
834        let month_year_layout = date_children
835            .next()
836            .expect("Graphics: Layout should have a month/year layout");
837
838        month_year(
839            renderer,
840            month_year_layout,
841            &self.date_as_string(),
842            cursor_position,
843            &style_sheet,
844            self.state.focus,
845            self.font_size,
846        );
847
848        // ----------- Days ---------------------------
849        let days_layout = date_children
850            .next()
851            .expect("Graphics: Layout should have a days layout parent")
852            .children()
853            .next()
854            .expect("Graphics: Layout should have a days layout");
855
856        days(
857            renderer,
858            days_layout,
859            self.state.date,
860            &style_sheet,
861            self.state.focus,
862            self.font_size,
863            self.state.day_mouse_over,
864        );
865
866        // ----------- Buttons ------------------------
867        let cancel_button_layout = children
868            .next()
869            .expect("Graphics: Layout should have a cancel button layout for a DatePicker");
870
871        self.cancel_button.draw(
872            &self.tree.children[0],
873            renderer,
874            theme,
875            style,
876            cancel_button_layout,
877            cursor,
878            &bounds,
879        );
880
881        let submit_button_layout = children
882            .next()
883            .expect("Graphics: Layout should have a submit button layout for a DatePicker");
884
885        self.submit_button.draw(
886            &self.tree.children[1],
887            renderer,
888            theme,
889            style,
890            submit_button_layout,
891            cursor,
892            &bounds,
893        );
894
895        let border = Border {
896            radius: style_sheet[&StyleState::Focused].border_radius.into(),
897            width: style_sheet[&StyleState::Focused].border_width,
898            color: style_sheet[&StyleState::Focused].border_color,
899        };
900
901        // Buttons are not focusable right now...
902        if self.state.focus == Focus::Cancel {
903            renderer.fill_quad(
904                renderer::Quad {
905                    bounds: cancel_button_layout.bounds(),
906                    border,
907                    ..renderer::Quad::default()
908                },
909                Color::TRANSPARENT,
910            );
911        }
912
913        if self.state.focus == Focus::Submit {
914            renderer.fill_quad(
915                renderer::Quad {
916                    bounds: submit_button_layout.bounds(),
917                    border,
918                    ..renderer::Quad::default()
919                },
920                Color::TRANSPARENT,
921            );
922        }
923    }
924}
925
926/// The state of the [`DatePickerOverlay`].
927#[derive(Debug)]
928pub struct State {
929    /// The selected date of the [`DatePickerOverlay`].
930    pub(crate) date: NaiveDate,
931    /// The focus of the [`DatePickerOverlay`].
932    pub(crate) focus: Focus,
933    /// The previously pressed keyboard modifiers.
934    pub(crate) keyboard_modifiers: keyboard::Modifiers,
935    /// ID of the day the mouse is over.
936    pub(crate) day_mouse_over: Option<(usize, usize)>,
937}
938
939impl State {
940    /// Creates a new State with the given date.
941    #[must_use]
942    pub fn new(date: NaiveDate) -> Self {
943        Self {
944            date,
945            ..Self::default()
946        }
947    }
948}
949
950impl Default for State {
951    fn default() -> Self {
952        Self {
953            date: Local::now().naive_local().date(),
954            focus: Focus::default(),
955            keyboard_modifiers: keyboard::Modifiers::default(),
956            day_mouse_over: None,
957        }
958    }
959}
960
961/// Just a workaround to pass the button states from the tree to the overlay
962#[allow(missing_debug_implementations)]
963pub struct DatePickerOverlayButtons<'a, Message, Theme>
964where
965    Message: Clone,
966    Theme: crate::style::date_picker::Catalog + iced_widget::button::Catalog,
967{
968    /// The cancel button of the [`DatePickerOverlay`].
969    cancel_button: Element<'a, Message, Theme, Renderer>,
970    /// The submit button of the [`DatePickerOverlay`].
971    submit_button: Element<'a, Message, Theme, Renderer>,
972}
973
974impl<'a, Message, Theme> Default for DatePickerOverlayButtons<'a, Message, Theme>
975where
976    Message: 'a + Clone,
977    Theme: 'a
978        + crate::style::date_picker::Catalog
979        + iced_widget::button::Catalog
980        + iced_widget::text::Catalog
981        + iced_widget::container::Catalog,
982{
983    fn default() -> Self {
984        let (cancel_content, cancel_font, _cancel_shaping) = cancel();
985        let (submit_content, submit_font, _submit_shaping) = ok();
986
987        Self {
988            cancel_button: Button::new(
989                text::Text::new(cancel_content)
990                    .font(cancel_font)
991                    .align_x(Horizontal::Center)
992                    .width(Length::Fill),
993            )
994            .into(),
995            submit_button: Button::new(
996                text::Text::new(submit_content)
997                    .font(submit_font)
998                    .align_x(Horizontal::Center)
999                    .width(Length::Fill),
1000            )
1001            .into(),
1002        }
1003    }
1004}
1005
1006#[allow(clippy::unimplemented)]
1007impl<Message, Theme> Widget<Message, Theme, Renderer>
1008    for DatePickerOverlayButtons<'_, Message, Theme>
1009where
1010    Message: Clone,
1011    Theme: crate::style::date_picker::Catalog
1012        + iced_widget::button::Catalog
1013        + iced_widget::container::Catalog,
1014{
1015    fn children(&self) -> Vec<Tree> {
1016        vec![
1017            Tree::new(&self.cancel_button),
1018            Tree::new(&self.submit_button),
1019        ]
1020    }
1021
1022    fn diff(&self, tree: &mut Tree) {
1023        tree.diff_children(&[&self.cancel_button, &self.submit_button]);
1024    }
1025
1026    fn size(&self) -> Size<Length> {
1027        unimplemented!("This should never be reached!")
1028    }
1029
1030    fn layout(&mut self, _tree: &mut Tree, _renderer: &Renderer, _limits: &Limits) -> Node {
1031        unimplemented!("This should never be reached!")
1032    }
1033
1034    fn draw(
1035        &self,
1036        _state: &Tree,
1037        _renderer: &mut Renderer,
1038        _theme: &Theme,
1039        _style: &renderer::Style,
1040        _layout: Layout<'_>,
1041        _cursor: Cursor,
1042        _viewport: &Rectangle,
1043    ) {
1044        unimplemented!("This should never be reached!")
1045    }
1046}
1047
1048impl<'a, Message, Theme> From<DatePickerOverlayButtons<'a, Message, Theme>>
1049    for Element<'a, Message, Theme, Renderer>
1050where
1051    Message: 'a + Clone,
1052    Theme: 'a
1053        + crate::style::date_picker::Catalog
1054        + iced_widget::button::Catalog
1055        + iced_widget::container::Catalog,
1056{
1057    fn from(overlay: DatePickerOverlayButtons<'a, Message, Theme>) -> Self {
1058        Self::new(overlay)
1059    }
1060}
1061
1062/// An enumeration of all focusable elements of the [`DatePickerOverlay`].
1063#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1064pub enum Focus {
1065    /// Nothing is in focus.
1066    #[default]
1067    None,
1068
1069    /// The overlay itself is in focus.
1070    Overlay,
1071
1072    /// The month area is in focus.
1073    Month,
1074
1075    /// The year area is in focus.
1076    Year,
1077
1078    /// The day area is in focus.
1079    Day,
1080
1081    /// The cancel button is in focus.
1082    Cancel,
1083
1084    /// The submit button is in focus.
1085    Submit,
1086}
1087
1088impl Focus {
1089    /// Gets the next focusable element.
1090    #[must_use]
1091    pub const fn next(self) -> Self {
1092        match self {
1093            Self::Overlay => Self::Month,
1094            Self::Month => Self::Year,
1095            Self::Year => Self::Day,
1096            Self::Day => Self::Cancel,
1097            Self::Cancel => Self::Submit,
1098            Self::Submit | Self::None => Self::Overlay,
1099        }
1100    }
1101
1102    /// Gets the previous focusable element.
1103    #[must_use]
1104    pub const fn previous(self) -> Self {
1105        match self {
1106            Self::None => Self::None,
1107            Self::Overlay => Self::Submit,
1108            Self::Month => Self::Overlay,
1109            Self::Year => Self::Month,
1110            Self::Day => Self::Year,
1111            Self::Cancel => Self::Day,
1112            Self::Submit => Self::Cancel,
1113        }
1114    }
1115}
1116
1117/// Draws the month/year row
1118fn month_year(
1119    renderer: &mut Renderer,
1120    layout: Layout<'_>,
1121    date: &str,
1122    cursor: Point,
1123    //style: &Style,
1124    style: &HashMap<StyleState, Style>,
1125    focus: Focus,
1126    font_size: Pixels,
1127) {
1128    let mut children = layout.children();
1129
1130    let month_layout = children
1131        .next()
1132        .expect("Graphics: Layout should have a month layout");
1133    let year_layout = children
1134        .next()
1135        .expect("Graphics: Layout should have a year layout");
1136
1137    let mut f = |layout: Layout<'_>, text: &str, target: Focus, font_size: Pixels| {
1138        let style_state = if focus == target {
1139            StyleState::Focused
1140        } else {
1141            StyleState::Active
1142        };
1143
1144        let mut children = layout.children();
1145
1146        let left_bounds = children
1147            .next()
1148            .expect("Graphics: Layout should have a left arrow layout")
1149            .bounds();
1150        let center_bounds = children
1151            .next()
1152            .expect("Graphics: Layout should have a center layout")
1153            .bounds();
1154        let right_bounds = children
1155            .next()
1156            .expect("Graphics: Layout should have a right arrow layout")
1157            .bounds();
1158
1159        let left_arrow_hovered = left_bounds.contains(cursor);
1160        let right_arrow_hovered = right_bounds.contains(cursor);
1161
1162        let bounds = layout.bounds();
1163        if (style_state == StyleState::Focused) && (bounds.width > 0.) && (bounds.height > 0.) {
1164            renderer.fill_quad(
1165                renderer::Quad {
1166                    bounds,
1167                    border: Border {
1168                        radius: style
1169                            .get(&style_state)
1170                            .expect("Style Sheet not found.")
1171                            .border_radius
1172                            .into(),
1173                        width: style
1174                            .get(&style_state)
1175                            .expect("Style Sheet not found.")
1176                            .border_width,
1177                        color: style
1178                            .get(&style_state)
1179                            .expect("Style Sheet not found.")
1180                            .border_color,
1181                    },
1182                    shadow: Shadow::default(),
1183                    snap: false,
1184                },
1185                style
1186                    .get(&style_state)
1187                    .expect("Style Sheet not found.")
1188                    .background,
1189            );
1190        }
1191
1192        let (left_content, left_font, _left_shaping) = left_open();
1193        let (right_content, right_font, _right_shaping) = right_open();
1194
1195        // Left caret
1196        renderer.fill_text(
1197            iced_core::Text {
1198                content: left_content,
1199                bounds: Size::new(left_bounds.width, left_bounds.height),
1200                size: Pixels(font_size.0 + if left_arrow_hovered { 1.0 } else { 0.0 }),
1201                font: left_font,
1202                align_x: text::Alignment::Center,
1203                align_y: Vertical::Center,
1204                line_height: text::LineHeight::Relative(1.3),
1205                shaping: text::Shaping::Advanced,
1206                wrapping: Wrapping::default(),
1207            },
1208            Point::new(left_bounds.center_x(), left_bounds.center_y()),
1209            style
1210                .get(&style_state)
1211                .expect("Style Sheet not found.")
1212                .text_color,
1213            left_bounds,
1214        );
1215
1216        // Text
1217        renderer.fill_text(
1218            iced_core::Text {
1219                content: text.to_owned(),
1220                bounds: Size::new(center_bounds.width, center_bounds.height),
1221                size: font_size,
1222                font: renderer.default_font(),
1223                line_height: text::LineHeight::Relative(1.3),
1224                shaping: text::Shaping::Basic,
1225                wrapping: Wrapping::default(),
1226                align_x: text::Alignment::Center,
1227                align_y: Vertical::Center,
1228            },
1229            Point::new(center_bounds.center_x(), center_bounds.center_y()),
1230            style
1231                .get(&style_state)
1232                .expect("Style Sheet not found.")
1233                .text_color,
1234            center_bounds,
1235        );
1236
1237        // Right caret
1238        renderer.fill_text(
1239            iced_core::Text {
1240                content: right_content,
1241                bounds: Size::new(right_bounds.width, right_bounds.height),
1242                size: Pixels(font_size.0 + if right_arrow_hovered { 1.0 } else { 0.0 }),
1243                font: right_font,
1244                align_x: text::Alignment::Center,
1245                align_y: Vertical::Center,
1246                line_height: text::LineHeight::Relative(1.3),
1247                shaping: text::Shaping::Advanced,
1248                wrapping: Wrapping::default(),
1249            },
1250            Point::new(right_bounds.center_x(), right_bounds.center_y()),
1251            style
1252                .get(&style_state)
1253                .expect("Style Sheet not found.")
1254                .text_color,
1255            right_bounds,
1256        );
1257    };
1258
1259    let (year, month) = date.split_once(' ').expect("Date must contain space");
1260
1261    // Draw month
1262    f(month_layout, month, Focus::Month, font_size);
1263
1264    // Draw year
1265    f(year_layout, year, Focus::Year, font_size);
1266}
1267
1268/// Draws the days
1269#[allow(clippy::too_many_arguments)]
1270fn days(
1271    renderer: &mut Renderer,
1272    layout: Layout<'_>,
1273    date: chrono::NaiveDate,
1274    style: &HashMap<StyleState, Style>,
1275    focus: Focus,
1276    font_size: Pixels,
1277    mouse_over: Option<(usize, usize)>,
1278) {
1279    let mut children = layout.children();
1280
1281    let day_labels_layout = children
1282        .next()
1283        .expect("Graphics: Layout should have a day labels layout");
1284    day_labels(renderer, day_labels_layout, style, focus, font_size);
1285
1286    day_table(
1287        renderer,
1288        &mut children,
1289        date,
1290        style,
1291        focus,
1292        font_size,
1293        mouse_over,
1294    );
1295}
1296
1297/// Draws the day labels
1298fn day_labels(
1299    renderer: &mut Renderer,
1300    layout: Layout<'_>,
1301    style: &HashMap<StyleState, Style>,
1302    _focus: Focus,
1303    font_size: Pixels,
1304) {
1305    for (i, label) in layout.children().enumerate() {
1306        let bounds = label.bounds();
1307
1308        renderer.fill_text(
1309            iced_core::Text {
1310                content: crate::core::date::WEEKDAY_LABELS[i].clone(),
1311                bounds: Size::new(bounds.width, bounds.height),
1312                size: font_size,
1313                font: renderer.default_font(),
1314                align_x: text::Alignment::Center,
1315                align_y: Vertical::Center,
1316                line_height: text::LineHeight::Relative(1.3),
1317                shaping: text::Shaping::Basic,
1318                wrapping: Wrapping::default(),
1319            },
1320            Point::new(bounds.center_x(), bounds.center_y()),
1321            style
1322                .get(&StyleState::Active)
1323                .expect("Style Sheet not found.")
1324                .text_color,
1325            bounds,
1326        );
1327    }
1328}
1329
1330#[allow(clippy::too_many_arguments)]
1331/// Draws the day table
1332fn day_table(
1333    renderer: &mut Renderer,
1334    children: &mut dyn Iterator<Item = Layout<'_>>,
1335    date: chrono::NaiveDate,
1336    style: &HashMap<StyleState, Style>,
1337    focus: Focus,
1338    font_size: Pixels,
1339    day_mouse_over: Option<(usize, usize)>,
1340) {
1341    for (y, row) in children.enumerate() {
1342        for (x, label) in row.children().enumerate() {
1343            let bounds = label.bounds();
1344            let (number, is_in_month) =
1345                crate::core::date::position_to_day(x, y, date.year(), date.month());
1346
1347            let mouse_over = if let Some((day_y, day_x)) = day_mouse_over {
1348                day_y == y && day_x == x
1349            } else {
1350                false
1351            };
1352
1353            let selected = date.day() == number as u32 && is_in_month == IsInMonth::Same;
1354
1355            let style_state = if selected {
1356                StyleState::Selected
1357            } else if mouse_over {
1358                StyleState::Hovered
1359            } else {
1360                StyleState::Active
1361            };
1362
1363            renderer.fill_quad(
1364                renderer::Quad {
1365                    bounds,
1366                    border: Border {
1367                        radius: (bounds.height / 2.0).into(),
1368                        width: 0.0,
1369                        color: Color::TRANSPARENT,
1370                    },
1371                    shadow: Shadow::default(),
1372                    snap: false,
1373                },
1374                style
1375                    .get(&style_state)
1376                    .expect("Style Sheet not found.")
1377                    .day_background,
1378            );
1379
1380            if focus == Focus::Day && selected {
1381                renderer.fill_quad(
1382                    renderer::Quad {
1383                        bounds,
1384                        border: Border {
1385                            radius: style
1386                                .get(&StyleState::Focused)
1387                                .expect("Style Sheet not found.")
1388                                .border_radius
1389                                .into(),
1390                            width: style
1391                                .get(&StyleState::Focused)
1392                                .expect("Style Sheet not found.")
1393                                .border_width,
1394                            color: style
1395                                .get(&StyleState::Focused)
1396                                .expect("Style Sheet not found.")
1397                                .border_color,
1398                        },
1399                        shadow: Shadow::default(),
1400                        snap: false,
1401                    },
1402                    Color::TRANSPARENT,
1403                );
1404            }
1405
1406            renderer.fill_text(
1407                iced_core::Text {
1408                    content: format!("{number:02}"), // Todo: is there some way of static format as this has a fixed size?
1409                    bounds: Size::new(bounds.width, bounds.height),
1410                    size: font_size,
1411                    font: renderer.default_font(),
1412                    align_x: text::Alignment::Center,
1413                    align_y: Vertical::Center,
1414                    line_height: text::LineHeight::Relative(1.3),
1415                    shaping: text::Shaping::Basic,
1416                    wrapping: Wrapping::default(),
1417                },
1418                Point::new(bounds.center_x(), bounds.center_y()),
1419                if is_in_month == IsInMonth::Same {
1420                    style
1421                        .get(&style_state)
1422                        .expect("Style Sheet not found.")
1423                        .text_color
1424                } else {
1425                    style
1426                        .get(&style_state)
1427                        .expect("Style Sheet not found.")
1428                        .text_attenuated_color
1429                },
1430                bounds,
1431            );
1432        }
1433    }
1434}