1use 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
33const PADDING: Padding = Padding::new(10.0);
35const SPACING: Pixels = Pixels(15.0);
37const DAY_CELL_PADDING: Padding = Padding::new(7.0);
39const BUTTON_SPACING: Pixels = Pixels(5.0);
41
42#[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 state: &'a mut State,
52 cancel_button: Button<'a, Message, Theme, Renderer>,
54 submit_button: Button<'a, Message, Theme, Renderer>,
56 on_submit: &'a dyn Fn(Date) -> Message,
58 position: Point,
60 class: &'a <Theme as crate::style::date_picker::Catalog>::Class<'b>,
62 tree: &'a mut Tree,
64 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 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 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), on_submit,
117 position,
118 class,
119 tree,
120 font_size,
121 viewport,
122 }
123 }
124
125 #[must_use]
127 pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> {
128 overlay::Element::new(Box::new(self))
129 }
130
131 fn date_as_string(&self) -> String {
133 crate::core::date::date_as_string(self.state.date)
134 }
135
136 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 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 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 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 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 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 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 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 Text::new("September").size(font_size).width(Length::Shrink),
453 )
454 .push(
455 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 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 Text::new("9999").size(font_size).width(Length::Shrink),
483 )
484 .push(
485 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 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 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 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 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 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 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 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 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 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 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 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 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#[derive(Debug)]
928pub struct State {
929 pub(crate) date: NaiveDate,
931 pub(crate) focus: Focus,
933 pub(crate) keyboard_modifiers: keyboard::Modifiers,
935 pub(crate) day_mouse_over: Option<(usize, usize)>,
937}
938
939impl State {
940 #[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#[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 cancel_button: Element<'a, Message, Theme, Renderer>,
970 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#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1064pub enum Focus {
1065 #[default]
1067 None,
1068
1069 Overlay,
1071
1072 Month,
1074
1075 Year,
1077
1078 Day,
1080
1081 Cancel,
1083
1084 Submit,
1086}
1087
1088impl Focus {
1089 #[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 #[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
1117fn month_year(
1119 renderer: &mut Renderer,
1120 layout: Layout<'_>,
1121 date: &str,
1122 cursor: Point,
1123 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 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 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 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 f(month_layout, month, Focus::Month, font_size);
1263
1264 f(year_layout, year, Focus::Year, font_size);
1266}
1267
1268#[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
1297fn 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)]
1331fn 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}"), 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}