iced_aw/widget/overlay/
time_picker.rs

1//! Use a time picker as an input element for picking times.
2//!
3//! *This API requires the following crate features to be activated: `time_picker`*
4
5use crate::iced_aw_font::advanced_text::{cancel, down_open, ok, up_open};
6use crate::{
7    core::clock::{
8        HOUR_RADIUS_PERCENTAGE, HOUR_RADIUS_PERCENTAGE_NO_SECONDS, MINUTE_RADIUS_PERCENTAGE,
9        MINUTE_RADIUS_PERCENTAGE_NO_SECONDS, NearestRadius, PERIOD_PERCENTAGE,
10        SECOND_RADIUS_PERCENTAGE,
11    },
12    core::{clock, overlay::Position, time::Period},
13    style::{
14        Status,
15        style_state::StyleState,
16        time_picker::{Catalog, Style},
17    },
18    time_picker::{self, Time},
19};
20use chrono::{Duration, Local, NaiveTime, Timelike};
21use iced_core::{
22    Alignment, Border, Clipboard, Color, Element, Event, Layout, Length, Overlay, Padding, Pixels,
23    Point, Rectangle, Renderer as _, Shell, Size, Text, Vector, Widget,
24    alignment::{Horizontal, Vertical},
25    event, keyboard,
26    layout::{Limits, Node},
27    mouse::{self, Cursor},
28    overlay, renderer,
29    text::Renderer as _,
30    touch,
31    widget::tree::Tree,
32};
33use iced_widget::{
34    Button, Column, Container, Renderer, Row, button,
35    canvas::{self, LineCap, Path, Stroke, Text as CanvasText},
36    container,
37    graphics::geometry::Renderer as _,
38    text::{self, Wrapping},
39};
40use std::collections::HashMap;
41
42/// The padding around the elements.
43const PADDING: Padding = Padding::new(10.0);
44/// The spacing between the elements.
45const SPACING: Pixels = Pixels(15.0);
46/// The spacing between the buttons.
47const BUTTON_SPACING: Pixels = Pixels(5.0);
48/// The percentage size of the numbers.
49const NUMBER_SIZE_PERCENTAGE: f32 = 0.15;
50/// The percentage size of the period.
51const PERIOD_SIZE_PERCENTAGE: f32 = 0.2;
52
53/// The overlay of the [`TimePicker`](crate::widget::TimePicker).
54#[allow(missing_debug_implementations)]
55pub struct TimePickerOverlay<'a, 'b, Message, Theme>
56where
57    Message: Clone,
58    Theme: Catalog + button::Catalog,
59    'b: 'a,
60{
61    /// The state of the [`TimePickerOverlay`].
62    state: &'a mut State,
63    /// The cancel button of the [`TimePickerOverlay`].
64    cancel_button: Button<'a, Message, Theme, Renderer>,
65    /// The submit button of the [`TimePickerOverlay`].
66    submit_button: Button<'a, Message, Theme, Renderer>,
67    /// The function that produces a message when the submit button of the [`TimePickerOverlay`] is pressed.
68    on_submit: &'a dyn Fn(Time) -> Message,
69    /// The position of the [`TimePickerOverlay`].
70    position: Point,
71    /// The style of the [`TimePickerOverlay`].
72    class: &'a <Theme as Catalog>::Class<'b>,
73    /// The reference to the tree holding the state of this overlay.
74    tree: &'a mut Tree,
75    viewport: Rectangle,
76}
77
78impl<'a, 'b, Message, Theme> TimePickerOverlay<'a, 'b, Message, Theme>
79where
80    Message: 'static + Clone,
81    Theme: 'a + Catalog + button::Catalog + text::Catalog + container::Catalog,
82    'b: 'a,
83{
84    /// Creates a new [`TimePickerOverlay`] on the given position.
85    pub fn new(
86        state: &'a mut time_picker::State,
87        on_cancel: Message,
88        on_submit: &'a dyn Fn(Time) -> Message,
89        position: Point,
90        class: &'a <Theme as Catalog>::Class<'b>,
91        tree: &'a mut Tree,
92        viewport: Rectangle,
93    ) -> Self {
94        let time_picker::State { overlay_state } = state;
95        let (cancel_content, cancel_font, _cancel_shaping) = cancel();
96        let (submit_content, submit_font, _submit_shaping) = ok();
97
98        TimePickerOverlay {
99            state: overlay_state,
100            cancel_button: Button::new(
101                text::Text::new(cancel_content)
102                    .font(cancel_font)
103                    .align_x(Horizontal::Center)
104                    .width(Length::Fill),
105            )
106            .width(Length::Fill)
107            .on_press(on_cancel.clone()),
108            submit_button: Button::new(
109                text::Text::new(submit_content)
110                    .font(submit_font)
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            viewport,
121        }
122    }
123
124    /// Turn this [`TimePickerOverlay`] into an overlay [`Element`](overlay::Element).
125    #[must_use]
126    pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> {
127        overlay::Element::new(Box::new(self))
128    }
129
130    /// The event handling for the clock.
131    #[allow(clippy::too_many_lines)]
132    fn on_event_clock(
133        &mut self,
134        event: &Event,
135        layout: Layout<'_>,
136        cursor: Cursor,
137    ) -> event::Status {
138        if cursor.is_over(layout.bounds()) {
139            self.state.clock_cache_needs_clearance = true;
140            self.state.clock_cache.clear();
141        } else if self.state.clock_cache_needs_clearance {
142            self.state.clock_cache.clear();
143            self.state.clock_cache_needs_clearance = false;
144        }
145
146        let clock_bounds = layout.bounds();
147        if cursor.is_over(clock_bounds) {
148            let center = clock_bounds.center();
149            let radius = clock_bounds.width.min(clock_bounds.height) * 0.5;
150
151            let period_radius = radius * PERIOD_PERCENTAGE;
152
153            let (hour_radius, minute_radius, second_radius) = if self.state.show_seconds {
154                (
155                    radius * HOUR_RADIUS_PERCENTAGE,
156                    radius * MINUTE_RADIUS_PERCENTAGE,
157                    radius * SECOND_RADIUS_PERCENTAGE,
158                )
159            } else {
160                (
161                    radius * HOUR_RADIUS_PERCENTAGE_NO_SECONDS,
162                    radius * MINUTE_RADIUS_PERCENTAGE_NO_SECONDS,
163                    f32::MAX,
164                )
165            };
166
167            let nearest_radius = crate::core::clock::nearest_radius(
168                &if self.state.show_seconds {
169                    vec![
170                        (period_radius, NearestRadius::Period),
171                        (hour_radius, NearestRadius::Hour),
172                        (minute_radius, NearestRadius::Minute),
173                        (second_radius, NearestRadius::Second),
174                    ]
175                } else {
176                    vec![
177                        (period_radius, NearestRadius::Period),
178                        (hour_radius, NearestRadius::Hour),
179                        (minute_radius, NearestRadius::Minute),
180                    ]
181                },
182                cursor.position().unwrap_or_default(),
183                center,
184            );
185
186            let clock_clicked_status = match event {
187                Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
188                | Event::Touch(touch::Event::FingerPressed { .. }) => match nearest_radius {
189                    NearestRadius::Period => {
190                        let (pm, hour) = self.state.time.hour12();
191                        let hour = if hour == 12 {
192                            if pm { 12 } else { 0 }
193                        } else {
194                            hour
195                        };
196
197                        self.state.time = self
198                            .state
199                            .time
200                            .with_hour(if pm && hour != 12 { hour } else { hour + 12 } % 24)
201                            .expect("New time with hour should be valid");
202                        event::Status::Captured
203                    }
204                    NearestRadius::Hour => {
205                        self.state.focus = Focus::DigitalHour;
206                        self.state.clock_dragged = ClockDragged::Hour;
207                        event::Status::Captured
208                    }
209                    NearestRadius::Minute => {
210                        self.state.focus = Focus::DigitalMinute;
211                        self.state.clock_dragged = ClockDragged::Minute;
212                        event::Status::Captured
213                    }
214                    NearestRadius::Second => {
215                        self.state.focus = Focus::DigitalSecond;
216                        self.state.clock_dragged = ClockDragged::Second;
217                        event::Status::Captured
218                    }
219                    NearestRadius::None => event::Status::Ignored,
220                },
221                Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
222                | Event::Touch(
223                    touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. },
224                ) => {
225                    self.state.clock_dragged = ClockDragged::None;
226                    event::Status::Captured
227                }
228                _ => event::Status::Ignored,
229            };
230
231            let clock_dragged_status = match self.state.clock_dragged {
232                ClockDragged::Hour => {
233                    let hour_points = crate::core::clock::circle_points(hour_radius, center, 12);
234                    let nearest_point = crate::core::clock::nearest_point(
235                        &hour_points,
236                        cursor.position().unwrap_or_default(),
237                    );
238
239                    let (pm, _) = self.state.time.hour12();
240
241                    self.state.time = self
242                        .state
243                        .time
244                        .with_hour((nearest_point as u32 + if pm { 12 } else { 0 }) % 24)
245                        .expect("New time with hour should be valid");
246                    event::Status::Captured
247                }
248                ClockDragged::Minute => {
249                    let minute_points =
250                        crate::core::clock::circle_points(minute_radius, center, 60);
251                    let nearest_point = crate::core::clock::nearest_point(
252                        &minute_points,
253                        cursor.position().unwrap_or_default(),
254                    );
255
256                    self.state.time = self
257                        .state
258                        .time
259                        .with_minute(nearest_point as u32)
260                        .expect("New time with minute should be valid");
261                    event::Status::Captured
262                }
263                ClockDragged::Second => {
264                    let second_points =
265                        crate::core::clock::circle_points(second_radius, center, 60);
266                    let nearest_point = crate::core::clock::nearest_point(
267                        &second_points,
268                        cursor.position().unwrap_or_default(),
269                    );
270
271                    self.state.time = self
272                        .state
273                        .time
274                        .with_second(nearest_point as u32)
275                        .expect("New time with second should be valid");
276                    event::Status::Captured
277                }
278                ClockDragged::None => event::Status::Ignored,
279            };
280
281            clock_clicked_status.merge(clock_dragged_status)
282        } else {
283            match event {
284                Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
285                | Event::Touch(
286                    touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. },
287                ) => {
288                    self.state.clock_dragged = ClockDragged::None;
289                    event::Status::Captured
290                }
291                _ => event::Status::Ignored,
292            }
293        }
294    }
295
296    /// The event handling for the digital clock.
297    #[allow(clippy::too_many_lines)]
298    fn on_event_digital_clock(
299        &mut self,
300        event: &Event,
301        layout: Layout<'_>,
302        cursor: Cursor,
303    ) -> event::Status {
304        let mut digital_clock_children = layout.children();
305
306        if !self.state.use_24h {
307            // Placeholder
308            let _ = digital_clock_children.next();
309        }
310
311        let hour_layout = digital_clock_children
312            .next()
313            .expect("widget: Layout should have a hour layout");
314        let mut hour_children = hour_layout.children();
315
316        let hour_up_arrow = hour_children
317            .next()
318            .expect("widget: Layout should have an up arrow for hours");
319        let _ = hour_children.next();
320        let hour_down_arrow = hour_children
321            .next()
322            .expect("widget: Layout should have a down arrow for hours");
323
324        let _ = digital_clock_children.next();
325
326        let minute_layout = digital_clock_children
327            .next()
328            .expect("widget: Layout should have a minute layout");
329        let mut minute_children = minute_layout.children();
330
331        let minute_up_arrow = minute_children
332            .next()
333            .expect("widget: Layout should have an up arrow for minutes");
334        let _ = minute_children.next();
335        let minute_down_arrow = minute_children
336            .next()
337            .expect("widget: Layout should have a down arrow for minutes");
338
339        let calculate_time = |time: &mut NaiveTime,
340                              up_arrow: Layout<'_>,
341                              down_arrow: Layout<'_>,
342                              duration: Duration| {
343            if cursor.is_over(up_arrow.bounds()) {
344                *time += duration;
345                event::Status::Captured
346            } else if cursor.is_over(down_arrow.bounds()) {
347                *time -= duration;
348                event::Status::Captured
349            } else {
350                event::Status::Ignored
351            }
352        };
353
354        let digital_clock_status = match event {
355            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
356            | Event::Touch(touch::Event::FingerPressed { .. }) => {
357                if cursor.is_over(hour_layout.bounds()) {
358                    self.state.focus = Focus::DigitalHour;
359
360                    calculate_time(
361                        &mut self.state.time,
362                        hour_up_arrow,
363                        hour_down_arrow,
364                        Duration::hours(1),
365                    )
366                } else if cursor.is_over(minute_layout.bounds()) {
367                    self.state.focus = Focus::DigitalMinute;
368
369                    calculate_time(
370                        &mut self.state.time,
371                        minute_up_arrow,
372                        minute_down_arrow,
373                        Duration::minutes(1),
374                    )
375                } else {
376                    event::Status::Ignored
377                }
378            }
379            _ => event::Status::Ignored,
380        };
381
382        let second_status = if self.state.show_seconds {
383            let _ = digital_clock_children.next();
384
385            let second_layout = digital_clock_children
386                .next()
387                .expect("widget: Layout should have a second layout");
388            let mut second_children = second_layout.children();
389
390            let second_up_arrow = second_children
391                .next()
392                .expect("widget: Layout should have an up arrow for seconds");
393            let _ = second_children.next();
394            let second_down_arrow = second_children
395                .next()
396                .expect("widget: Layout should have a down arrow for seconds");
397
398            match event {
399                Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
400                | Event::Touch(touch::Event::FingerPressed { .. }) => {
401                    if cursor.is_over(second_layout.bounds()) {
402                        self.state.focus = Focus::DigitalSecond;
403
404                        calculate_time(
405                            &mut self.state.time,
406                            second_up_arrow,
407                            second_down_arrow,
408                            Duration::seconds(1),
409                        )
410                    } else {
411                        event::Status::Ignored
412                    }
413                }
414                _ => event::Status::Ignored,
415            }
416        } else {
417            event::Status::Ignored
418        };
419
420        let digital_clock_status = digital_clock_status.merge(second_status);
421
422        if digital_clock_status == event::Status::Captured {
423            self.state.clock_cache.clear();
424        }
425
426        digital_clock_status
427    }
428
429    /// The event handling for the keyboard input.
430    fn on_event_keyboard(&mut self, event: &Event) -> event::Status {
431        if self.state.focus == Focus::None {
432            return event::Status::Ignored;
433        }
434
435        if let Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) = event {
436            let mut status = event::Status::Ignored;
437
438            if matches!(key, keyboard::Key::Named(keyboard::key::Named::Tab)) {
439                if self.state.keyboard_modifiers.shift() {
440                    self.state.focus = self.state.focus.previous(self.state.show_seconds);
441                } else {
442                    self.state.focus = self.state.focus.next(self.state.show_seconds);
443                }
444            } else {
445                let mut keyboard_handle =
446                    |key_code: &keyboard::Key, time: &mut NaiveTime, duration: Duration| {
447                        match key_code {
448                            keyboard::Key::Named(
449                                keyboard::key::Named::ArrowLeft | keyboard::key::Named::ArrowDown,
450                            ) => {
451                                *time -= duration;
452                                status = event::Status::Captured;
453                            }
454                            keyboard::Key::Named(
455                                keyboard::key::Named::ArrowRight | keyboard::key::Named::ArrowUp,
456                            ) => {
457                                *time += duration;
458                                status = event::Status::Captured;
459                            }
460                            _ => {}
461                        }
462                    };
463
464                match self.state.focus {
465                    Focus::DigitalHour => {
466                        keyboard_handle(key, &mut self.state.time, Duration::hours(1));
467                    }
468                    Focus::DigitalMinute => {
469                        keyboard_handle(key, &mut self.state.time, Duration::minutes(1));
470                    }
471                    Focus::DigitalSecond => {
472                        keyboard_handle(key, &mut self.state.time, Duration::seconds(1));
473                    }
474                    _ => {}
475                }
476            }
477
478            if status == event::Status::Captured {
479                self.state.clock_cache.clear();
480            }
481
482            status
483        } else if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = event {
484            self.state.keyboard_modifiers = *modifiers;
485            event::Status::Ignored
486        } else {
487            event::Status::Ignored
488        }
489    }
490}
491
492impl<'a, 'b, Message, Theme> Overlay<Message, Theme, Renderer>
493    for TimePickerOverlay<'a, 'b, Message, Theme>
494where
495    Message: 'static + Clone,
496    Theme: 'a + Catalog + button::Catalog + text::Catalog + container::Catalog,
497    'b: 'a,
498{
499    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
500        let limits = Limits::new(Size::ZERO, bounds)
501            .shrink(PADDING)
502            .width(Length::Fill)
503            .height(Length::Fill)
504            .max_width(300.0)
505            .max_height(350.0);
506
507        // Digital Clock
508        let digital_clock_limits = limits;
509        let mut digital_clock = digital_clock(self, renderer, digital_clock_limits);
510
511        // Pre-Buttons TODO: get rid of it
512        let cancel_limits = limits;
513        let cancel_button =
514            self.cancel_button
515                .layout(&mut self.tree.children[0], renderer, &cancel_limits);
516
517        let limits = limits.shrink(Size::new(
518            0.0,
519            digital_clock.bounds().height + cancel_button.bounds().height + 2.0 * SPACING.0,
520        ));
521
522        // Clock-Canvas
523        let mut clock = Row::<(), Renderer>::new()
524            .width(Length::Fill)
525            .height(Length::Fill)
526            .layout(self.tree, renderer, &limits);
527
528        let clock_bounds = clock.bounds();
529        clock = clock.move_to(Point::new(
530            clock_bounds.x + PADDING.left,
531            clock_bounds.y + PADDING.top,
532        ));
533
534        let digital_bounds = digital_clock.bounds();
535        digital_clock = digital_clock.move_to(Point::new(
536            digital_bounds.x + PADDING.left,
537            digital_bounds.y + PADDING.top + SPACING.0 + clock.bounds().height,
538        ));
539
540        // Buttons
541        let cancel_limits =
542            limits.max_width(((clock.bounds().width / 2.0) - BUTTON_SPACING.0).max(0.0));
543
544        let mut cancel_button =
545            self.cancel_button
546                .layout(&mut self.tree.children[0], renderer, &cancel_limits);
547
548        let submit_limits =
549            limits.max_width(((clock.bounds().width / 2.0) - BUTTON_SPACING.0).max(0.0));
550
551        let mut submit_button =
552            self.submit_button
553                .layout(&mut self.tree.children[1], renderer, &submit_limits);
554
555        let cancel_bounds = cancel_button.bounds();
556        cancel_button = cancel_button.move_to(Point {
557            x: cancel_bounds.x + PADDING.left,
558            y: cancel_bounds.y
559                + clock.bounds().height
560                + PADDING.top
561                + digital_clock.bounds().height
562                + 2.0 * SPACING.0,
563        });
564
565        let submit_bounds = submit_button.bounds();
566        submit_button = submit_button.move_to(Point {
567            x: submit_bounds.x + clock.bounds().width - submit_bounds.width + PADDING.left,
568            y: submit_bounds.y
569                + clock.bounds().height
570                + PADDING.top
571                + digital_clock.bounds().height
572                + 2.0 * SPACING.0,
573        });
574
575        let mut node = Node::with_children(
576            Size::new(
577                clock.bounds().width + PADDING.x(),
578                clock.bounds().height
579                    + digital_clock.bounds().height
580                    + cancel_button.bounds().height
581                    + PADDING.y()
582                    + 2.0 * SPACING.0,
583            ),
584            vec![clock, digital_clock, cancel_button, submit_button],
585        );
586
587        node.center_and_bounce(self.position, bounds);
588        node
589    }
590
591    fn update(
592        &mut self,
593        event: &Event,
594        layout: Layout<'_>,
595        cursor: Cursor,
596        renderer: &Renderer,
597        clipboard: &mut dyn Clipboard,
598        shell: &mut Shell<Message>,
599    ) {
600        let mut status = self.on_event_keyboard(event);
601        let mut children = layout.children();
602
603        // Clock canvas
604        let clock_layout = children
605            .next()
606            .expect("widget: Layout should have a clock canvas layout");
607        let clock_status = self.on_event_clock(event, clock_layout, cursor);
608
609        // ----------- Digital clock ------------------
610        let digital_clock_layout = children
611            .next()
612            .expect("widget: Layout should have a digital clock parent")
613            .children()
614            .next()
615            .expect("widget: Layout should have a digital clock layout");
616        let digital_clock_status = self.on_event_digital_clock(event, digital_clock_layout, cursor);
617
618        if digital_clock_status == event::Status::Captured
619            || clock_status == event::Status::Captured
620        {
621            status = event::Status::Captured;
622        }
623
624        // ----------- Buttons --------------
625        let cancel_button_layout = children
626            .next()
627            .expect("widget: Layout should have a cancel button layout for a TimePicker");
628
629        let mut fake_messages: Vec<Message> = Vec::new();
630
631        self.cancel_button.update(
632            &mut self.tree.children[0],
633            event,
634            cancel_button_layout,
635            cursor,
636            renderer,
637            clipboard,
638            &mut Shell::new(&mut fake_messages),
639            &layout.bounds(),
640        );
641
642        while let Some(message) = fake_messages.pop() {
643            shell.publish(message);
644            status = event::Status::Captured;
645        }
646
647        let submit_button_layout = children
648            .next()
649            .expect("widget: Layout should have a submit button layout for a TimePicker");
650
651        self.submit_button.update(
652            &mut self.tree.children[1],
653            event,
654            submit_button_layout,
655            cursor,
656            renderer,
657            clipboard,
658            &mut Shell::new(&mut fake_messages),
659            &layout.bounds(),
660        );
661
662        if !fake_messages.is_empty() {
663            let (hour, period) = if self.state.use_24h {
664                (self.state.time.hour(), Period::H24)
665            } else {
666                let (period, hour) = self.state.time.hour12();
667                (hour, if period { Period::Pm } else { Period::Am })
668            };
669
670            let time = if self.state.show_seconds {
671                Time::Hms {
672                    hour,
673                    minute: self.state.time.minute(),
674                    second: self.state.time.second(),
675                    period,
676                }
677            } else {
678                Time::Hm {
679                    hour,
680                    minute: self.state.time.minute(),
681                    period,
682                }
683            };
684
685            shell.publish((self.on_submit)(time));
686            status = event::Status::Captured;
687        }
688
689        if status == event::Status::Captured {
690            shell.capture_event();
691            shell.request_redraw();
692        }
693    }
694
695    fn mouse_interaction(
696        &self,
697        layout: Layout<'_>,
698        cursor: mouse::Cursor,
699        renderer: &Renderer,
700    ) -> mouse::Interaction {
701        let mut children = layout.children();
702        let mouse_interaction = mouse::Interaction::default();
703
704        // Clock canvas
705        let clock_layout = children
706            .next()
707            .expect("Graphics: Layout should have a clock canvas layout");
708        let clock_mouse_interaction = if cursor.is_over(clock_layout.bounds()) {
709            mouse::Interaction::Pointer
710        } else {
711            mouse::Interaction::default()
712        };
713
714        // Digital clock
715        let digital_clock_layout = children
716            .next()
717            .expect("Graphics: Layout should have a digital clock layout");
718        //let digital_clock_mouse_interaction = mouse::Interaction::default();
719        let mut digital_clock_children = digital_clock_layout
720            .children()
721            .next()
722            .expect("Graphics: Layout should have digital clock children")
723            .children();
724
725        let f = |layout: Layout<'_>| {
726            let mut children = layout.children();
727
728            let up_bounds = children
729                .next()
730                .expect("Graphics: Layout should have a up arrow bounds")
731                .bounds();
732            let _center_bounds = children.next();
733            let down_bounds = children
734                .next()
735                .expect("Graphics: Layout should have a down arrow bounds")
736                .bounds();
737
738            let mut mouse_interaction = mouse::Interaction::default();
739
740            let up_arrow_hovered = cursor.is_over(up_bounds);
741            let down_arrow_hovered = cursor.is_over(down_bounds);
742
743            if up_arrow_hovered || down_arrow_hovered {
744                mouse_interaction = mouse_interaction.max(mouse::Interaction::Pointer);
745            }
746
747            mouse_interaction
748        };
749
750        if !self.state.use_24h {
751            // Placeholder
752            let _ = digital_clock_children.next();
753        }
754
755        let hour_layout = digital_clock_children
756            .next()
757            .expect("Graphics: Layout should have a hour layout");
758        let hour_mouse_interaction = f(hour_layout);
759
760        let _hour_minute_separator = digital_clock_children.next();
761
762        let minute_layout = digital_clock_children
763            .next()
764            .expect("Graphics: Layout should have a minute layout");
765        let minute_mouse_interaction = f(minute_layout);
766
767        let second_mouse_interaction = if self.state.show_seconds {
768            let _minute_second_separator = digital_clock_children.next();
769
770            let second_layout = digital_clock_children
771                .next()
772                .expect("Graphics: Layout should have a second layout");
773            f(second_layout)
774        } else {
775            mouse::Interaction::default()
776        };
777
778        // Buttons
779        let cancel_button_layout = children
780            .next()
781            .expect("Graphics: Layout should have a cancel button layout for a TimePicker");
782
783        let cancel_mouse_interaction = self.cancel_button.mouse_interaction(
784            &self.tree.children[0],
785            cancel_button_layout,
786            cursor,
787            &self.viewport,
788            renderer,
789        );
790
791        let submit_button_layout = children
792            .next()
793            .expect("Graphics: Layout should have a submit button layout for a TimePicker");
794
795        let submit_mouse_interaction = self.submit_button.mouse_interaction(
796            &self.tree.children[1],
797            submit_button_layout,
798            cursor,
799            &self.viewport,
800            renderer,
801        );
802
803        mouse_interaction
804            .max(clock_mouse_interaction)
805            .max(hour_mouse_interaction)
806            .max(minute_mouse_interaction)
807            .max(second_mouse_interaction)
808            .max(cancel_mouse_interaction)
809            .max(submit_mouse_interaction)
810    }
811
812    fn draw(
813        &self,
814        renderer: &mut Renderer,
815        theme: &Theme,
816        style: &renderer::Style,
817        layout: Layout<'_>,
818        cursor: Cursor,
819    ) {
820        let bounds = layout.bounds();
821        let mut children = layout.children();
822
823        let mut style_sheet: HashMap<StyleState, Style> = HashMap::new();
824        let _ = style_sheet.insert(
825            StyleState::Active,
826            Catalog::style(theme, self.class, Status::Active),
827        );
828        let _ = style_sheet.insert(
829            StyleState::Selected,
830            Catalog::style(theme, self.class, Status::Selected),
831        );
832        let _ = style_sheet.insert(
833            StyleState::Hovered,
834            Catalog::style(theme, self.class, Status::Hovered),
835        );
836        let _ = style_sheet.insert(
837            StyleState::Focused,
838            Catalog::style(theme, self.class, Status::Focused),
839        );
840
841        let mut style_state = StyleState::Active;
842        if self.state.focus == Focus::Overlay {
843            style_state = style_state.max(StyleState::Focused);
844        }
845        if cursor.is_over(bounds) {
846            style_state = style_state.max(StyleState::Hovered);
847        }
848
849        // Background
850        if (bounds.width > 0.) && (bounds.height > 0.) {
851            renderer.fill_quad(
852                renderer::Quad {
853                    bounds,
854                    border: Border {
855                        radius: style_sheet[&style_state].border_radius.into(),
856                        width: style_sheet[&style_state].border_width,
857                        color: style_sheet[&style_state].border_color,
858                    },
859                    ..renderer::Quad::default()
860                },
861                style_sheet[&style_state].background,
862            );
863        }
864
865        // ----------- Clock canvas --------------------
866        let clock_layout = children
867            .next()
868            .expect("Graphics: Layout should have a clock canvas layout");
869        draw_clock(renderer, self, clock_layout, cursor, &style_sheet);
870
871        // ----------- Digital clock ------------------
872        let digital_clock_layout = children
873            .next()
874            .expect("Graphics: Layout should have a digital clock layout");
875        draw_digital_clock(renderer, self, digital_clock_layout, cursor, &style_sheet);
876
877        // ----------- Buttons ------------------------
878        let cancel_button_layout = children
879            .next()
880            .expect("Graphics: Layout should have a cancel button layout for a TimePicker");
881
882        self.cancel_button.draw(
883            &self.tree.children[0],
884            renderer,
885            theme,
886            style,
887            cancel_button_layout,
888            cursor,
889            &bounds,
890        );
891
892        let submit_button_layout = children
893            .next()
894            .expect("Graphics: Layout should have a submit button layout for a TimePicker");
895
896        self.submit_button.draw(
897            &self.tree.children[1],
898            renderer,
899            theme,
900            style,
901            submit_button_layout,
902            cursor,
903            &bounds,
904        );
905
906        // Buttons are not focusable right now...
907        let cancel_button_bounds = cancel_button_layout.bounds();
908        if (self.state.focus == Focus::Cancel)
909            && (cancel_button_bounds.width > 0.)
910            && (cancel_button_bounds.height > 0.)
911        {
912            renderer.fill_quad(
913                renderer::Quad {
914                    bounds: cancel_button_bounds,
915                    border: Border {
916                        radius: style_sheet[&StyleState::Focused].border_radius.into(),
917                        width: style_sheet[&StyleState::Focused].border_width,
918                        color: style_sheet[&StyleState::Focused].border_color,
919                    },
920                    ..renderer::Quad::default()
921                },
922                Color::TRANSPARENT,
923            );
924        }
925
926        let submit_button_bounds = submit_button_layout.bounds();
927        if (self.state.focus == Focus::Submit)
928            && (submit_button_bounds.width > 0.)
929            && (submit_button_bounds.height > 0.)
930        {
931            renderer.fill_quad(
932                renderer::Quad {
933                    bounds: submit_button_bounds,
934                    border: Border {
935                        radius: style_sheet[&StyleState::Focused].border_radius.into(),
936                        width: style_sheet[&StyleState::Focused].border_width,
937                        color: style_sheet[&StyleState::Focused].border_color,
938                    },
939                    ..renderer::Quad::default()
940                },
941                Color::TRANSPARENT,
942            );
943        }
944    }
945}
946
947/// Defines the layout of the digital clock of the time picker.
948fn digital_clock<Message, Theme>(
949    time_picker: &mut TimePickerOverlay<'_, '_, Message, Theme>,
950    renderer: &Renderer,
951    limits: Limits,
952) -> Node
953where
954    Message: 'static + Clone,
955    Theme: Catalog + button::Catalog + text::Catalog + container::Catalog,
956{
957    let arrow_size = renderer.default_size().0;
958    let font_size = 1.2 * renderer.default_size().0;
959
960    let mut digital_clock_row = Row::<Message, Theme, Renderer>::new()
961        .align_y(Alignment::Center)
962        .height(Length::Shrink)
963        .width(Length::Shrink)
964        .spacing(1);
965
966    if !time_picker.state.use_24h {
967        digital_clock_row = digital_clock_row.push(
968            Column::new() // Just a placeholder
969                .height(Length::Shrink)
970                .push(text::Text::new("AM").size(font_size)),
971        );
972    }
973
974    digital_clock_row = digital_clock_row
975        .push(
976            // Hour
977            Column::new()
978                .align_x(Alignment::Center)
979                .height(Length::Shrink)
980                .push(
981                    // Up Hour arrow
982                    Row::new()
983                        .width(Length::Fixed(arrow_size))
984                        .height(Length::Fixed(arrow_size)),
985                )
986                .push(
987                    text::Text::new(format!("{:02}", time_picker.state.time.hour()))
988                        .size(font_size),
989                )
990                .push(
991                    // Down Hour arrow
992                    Row::new()
993                        .width(Length::Fixed(arrow_size))
994                        .height(Length::Fixed(arrow_size)),
995                ),
996        )
997        .push(
998            Column::new()
999                .height(Length::Shrink)
1000                .push(text::Text::new(":").size(font_size)),
1001        )
1002        .push(
1003            Column::new()
1004                .align_x(Alignment::Center)
1005                .height(Length::Shrink)
1006                .push(
1007                    // Up Minute arrow
1008                    Row::new()
1009                        .width(Length::Fixed(arrow_size))
1010                        .height(Length::Fixed(arrow_size)),
1011                )
1012                .push(
1013                    text::Text::new(format!("{:02}", time_picker.state.time.hour()))
1014                        .size(font_size),
1015                )
1016                .push(
1017                    // Down Minute arrow
1018                    Row::new()
1019                        .width(Length::Fixed(arrow_size))
1020                        .height(Length::Fixed(arrow_size)),
1021                ),
1022        );
1023
1024    if time_picker.state.show_seconds {
1025        digital_clock_row = digital_clock_row
1026            .push(
1027                Column::new()
1028                    .height(Length::Shrink)
1029                    .push(text::Text::new(":").size(font_size)),
1030            )
1031            .push(
1032                Column::new()
1033                    .align_x(Alignment::Center)
1034                    .height(Length::Shrink)
1035                    .push(
1036                        // Up Minute arrow
1037                        Row::new()
1038                            .width(Length::Fixed(arrow_size))
1039                            .height(Length::Fixed(arrow_size)),
1040                    )
1041                    .push(
1042                        text::Text::new(format!("{:02}", time_picker.state.time.hour()))
1043                            .size(font_size),
1044                    )
1045                    .push(
1046                        // Down Minute arrow
1047                        Row::new()
1048                            .width(Length::Fixed(arrow_size))
1049                            .height(Length::Fixed(arrow_size)),
1050                    ),
1051            );
1052    }
1053
1054    if !time_picker.state.use_24h {
1055        digital_clock_row = digital_clock_row.push(
1056            Column::new()
1057                .height(Length::Shrink)
1058                .push(text::Text::new("AM").size(font_size)),
1059        );
1060    }
1061
1062    let container = Container::new(digital_clock_row)
1063        .width(Length::Fill)
1064        .height(Length::Shrink)
1065        .center_x(Length::Fill)
1066        .center_y(Length::Shrink);
1067
1068    let mut element: Element<Message, Theme, Renderer> = Element::new(container);
1069    let container_tree = if let Some(child_tree) = time_picker.tree.children.get_mut(2) {
1070        child_tree.diff(element.as_widget_mut());
1071        child_tree
1072    } else {
1073        let child_tree = Tree::new(element.as_widget());
1074        time_picker.tree.children.insert(2, child_tree);
1075        &mut time_picker.tree.children[2]
1076    };
1077
1078    element
1079        .as_widget_mut()
1080        .layout(container_tree, renderer, &limits)
1081}
1082
1083/// Draws the analog clock.
1084#[allow(clippy::too_many_lines)]
1085fn draw_clock<Message, Theme>(
1086    renderer: &mut Renderer,
1087    time_picker: &TimePickerOverlay<'_, '_, Message, Theme>,
1088    layout: Layout<'_>,
1089    cursor: Cursor,
1090    style: &HashMap<StyleState, Style>,
1091) where
1092    Message: 'static + Clone,
1093    Theme: Catalog + button::Catalog + text::Catalog,
1094{
1095    let mut clock_style_state = StyleState::Active;
1096    if cursor.is_over(layout.bounds()) {
1097        clock_style_state = clock_style_state.max(StyleState::Hovered);
1098    }
1099
1100    let geometry = time_picker
1101        .state
1102        .clock_cache
1103        .draw(renderer, layout.bounds().size(), |frame| {
1104            let center = frame.center();
1105            let radius = frame.width().min(frame.height()) * 0.5;
1106            let period = if time_picker.state.time.hour12().0 {
1107                clock::Period::PM
1108            } else {
1109                clock::Period::AM
1110            };
1111
1112            let number_size = radius * NUMBER_SIZE_PERCENTAGE;
1113            let period_size = radius * PERIOD_SIZE_PERCENTAGE;
1114
1115            let period_radius = radius * PERIOD_PERCENTAGE;
1116
1117            let (hour_radius, minute_radius, second_radius) = if time_picker.state.show_seconds {
1118                (
1119                    radius * HOUR_RADIUS_PERCENTAGE,
1120                    radius * MINUTE_RADIUS_PERCENTAGE,
1121                    radius * SECOND_RADIUS_PERCENTAGE,
1122                )
1123            } else {
1124                (
1125                    radius * HOUR_RADIUS_PERCENTAGE_NO_SECONDS,
1126                    radius * MINUTE_RADIUS_PERCENTAGE_NO_SECONDS,
1127                    f32::MAX,
1128                )
1129            };
1130
1131            let internal_cursor = cursor.position().unwrap_or_default()
1132                - Vector::new(layout.bounds().x, layout.bounds().y);
1133
1134            let nearest_radius = if cursor.is_over(layout.bounds()) {
1135                crate::core::clock::nearest_radius(
1136                    &if time_picker.state.show_seconds {
1137                        vec![
1138                            (period_radius, NearestRadius::Period),
1139                            (hour_radius, NearestRadius::Hour),
1140                            (minute_radius, NearestRadius::Minute),
1141                            (second_radius, NearestRadius::Second),
1142                        ]
1143                    } else {
1144                        vec![
1145                            (period_radius, NearestRadius::Period),
1146                            (hour_radius, NearestRadius::Hour),
1147                            (minute_radius, NearestRadius::Minute),
1148                        ]
1149                    },
1150                    internal_cursor,
1151                    center,
1152                )
1153            } else {
1154                NearestRadius::None
1155            };
1156
1157            let hour_points = crate::core::clock::circle_points(hour_radius, center, 12);
1158            let minute_points = crate::core::clock::circle_points(minute_radius, center, 60);
1159            let second_points = crate::core::clock::circle_points(second_radius, center, 60);
1160
1161            let hand_stroke = Stroke {
1162                style: canvas::Style::Solid(
1163                    style
1164                        .get(&clock_style_state)
1165                        .expect("Style Sheet not found.")
1166                        .clock_hand_color,
1167                ),
1168                width: style
1169                    .get(&clock_style_state)
1170                    .expect("Style Sheet not found.")
1171                    .clock_hand_width,
1172                line_cap: LineCap::Round,
1173                ..Stroke::default()
1174            };
1175
1176            match nearest_radius {
1177                NearestRadius::Period => {
1178                    frame.fill(
1179                        &Path::circle(center, period_size),
1180                        style
1181                            .get(&StyleState::Hovered)
1182                            .expect("Style Sheet not found.")
1183                            .clock_number_background,
1184                    );
1185                }
1186                NearestRadius::Hour => {
1187                    let nearest_point = hour_points
1188                        [crate::core::clock::nearest_point(&hour_points, internal_cursor)];
1189
1190                    frame.fill(
1191                        &Path::circle(nearest_point, 5.0),
1192                        style
1193                            .get(&StyleState::Hovered)
1194                            .expect("Style Sheet not found.")
1195                            .clock_number_background,
1196                    );
1197                }
1198                NearestRadius::Minute => {
1199                    let nearest_point = minute_points
1200                        [crate::core::clock::nearest_point(&minute_points, internal_cursor)];
1201
1202                    frame.fill(
1203                        &Path::circle(nearest_point, 5.0),
1204                        style
1205                            .get(&StyleState::Hovered)
1206                            .expect("Style Sheet not found.")
1207                            .clock_number_background,
1208                    );
1209                }
1210                NearestRadius::Second => {
1211                    let nearest_point = second_points
1212                        [crate::core::clock::nearest_point(&second_points, internal_cursor)];
1213
1214                    frame.fill(
1215                        &Path::circle(nearest_point, 5.0),
1216                        style
1217                            .get(&StyleState::Hovered)
1218                            .expect("Style Sheet not found.")
1219                            .clock_number_background,
1220                    );
1221                }
1222                NearestRadius::None => {}
1223            }
1224
1225            let period_text = CanvasText {
1226                content: format!("{period}"),
1227                position: center,
1228                color: style
1229                    .get(&clock_style_state)
1230                    .expect("Style Sheet not found.")
1231                    .clock_number_color,
1232                size: Pixels(period_size),
1233                font: renderer.default_font(),
1234                align_x: text::Alignment::Center,
1235                align_y: Vertical::Center,
1236                line_height: text::LineHeight::Relative(1.3),
1237                shaping: text::Shaping::Basic,
1238                max_width: f32::INFINITY,
1239            };
1240            frame.fill_text(period_text);
1241
1242            hour_points.iter().enumerate().for_each(|(i, p)| {
1243                let (pm, selected) = {
1244                    let (pm, _) = time_picker.state.time.hour12();
1245                    let hour = time_picker.state.time.hour();
1246                    (pm, hour % 12 == i as u32)
1247                };
1248
1249                let mut style_state = StyleState::Active;
1250                if selected {
1251                    frame.stroke(&Path::line(center, *p), hand_stroke);
1252                    frame.fill(
1253                        &Path::circle(*p, number_size * 0.8),
1254                        style
1255                            .get(&StyleState::Selected)
1256                            .expect("Style Sheet not found.")
1257                            .clock_number_background,
1258                    );
1259                    style_state = style_state.max(StyleState::Selected);
1260                }
1261
1262                let text = CanvasText {
1263                    content: format!(
1264                        "{}",
1265                        if pm && time_picker.state.use_24h {
1266                            i + 12
1267                        } else if !time_picker.state.use_24h && i == 0 {
1268                            12
1269                        } else {
1270                            i
1271                        }
1272                    ),
1273                    position: *p,
1274                    color: style
1275                        .get(&style_state)
1276                        .expect("Style Sheet not found.")
1277                        .clock_number_color,
1278                    size: Pixels(number_size),
1279                    font: renderer.default_font(),
1280                    align_x: text::Alignment::Center,
1281                    align_y: Vertical::Center,
1282                    shaping: text::Shaping::Basic,
1283                    line_height: text::LineHeight::Relative(1.3),
1284                    max_width: f32::INFINITY,
1285                };
1286
1287                frame.fill_text(text);
1288            });
1289
1290            minute_points.iter().enumerate().for_each(|(i, p)| {
1291                let selected = time_picker.state.time.minute() == i as u32;
1292
1293                let mut style_state = StyleState::Active;
1294                if selected {
1295                    frame.stroke(&Path::line(center, *p), hand_stroke);
1296                    frame.fill(
1297                        &Path::circle(*p, number_size * 0.6),
1298                        style
1299                            .get(&StyleState::Selected)
1300                            .expect("Style Sheet not found.")
1301                            .clock_number_background,
1302                    );
1303                    style_state = style_state.max(StyleState::Selected);
1304                }
1305
1306                if i % 5 == 0 {
1307                    let text = CanvasText {
1308                        content: format!("{i:02}"),
1309                        position: *p,
1310                        color: style
1311                            .get(&style_state)
1312                            .expect("Style Sheet not found.")
1313                            .clock_number_color,
1314                        size: Pixels(number_size),
1315                        font: renderer.default_font(),
1316                        align_x: text::Alignment::Center,
1317                        align_y: Vertical::Center,
1318                        shaping: text::Shaping::Basic,
1319                        line_height: text::LineHeight::Relative(1.3),
1320                        max_width: f32::INFINITY,
1321                    };
1322
1323                    frame.fill_text(text);
1324                } else {
1325                    let circle = Path::circle(*p, number_size * 0.1);
1326                    frame.fill(
1327                        &circle,
1328                        style
1329                            .get(&StyleState::Active)
1330                            .expect("Style Sheet not found.")
1331                            .clock_dots_color,
1332                    );
1333                }
1334            });
1335
1336            if time_picker.state.show_seconds {
1337                second_points.iter().enumerate().for_each(|(i, p)| {
1338                    let selected = time_picker.state.time.second() == i as u32;
1339
1340                    let mut style_state = StyleState::Active;
1341                    if selected {
1342                        frame.stroke(&Path::line(center, *p), hand_stroke);
1343                        frame.fill(
1344                            &Path::circle(*p, number_size * 0.6),
1345                            style
1346                                .get(&StyleState::Selected)
1347                                .expect("Style Sheet not found.")
1348                                .clock_number_background,
1349                        );
1350                        style_state = style_state.max(StyleState::Selected);
1351                    }
1352
1353                    if i % 10 == 0 {
1354                        let text = CanvasText {
1355                            content: format!("{i:02}"),
1356                            position: *p,
1357                            color: style
1358                                .get(&style_state)
1359                                .expect("Style Sheet not found.")
1360                                .clock_number_color,
1361                            size: Pixels(number_size),
1362                            font: renderer.default_font(),
1363                            align_x: text::Alignment::Center,
1364                            align_y: Vertical::Center,
1365                            shaping: text::Shaping::Basic,
1366                            line_height: text::LineHeight::Relative(1.3),
1367                            max_width: f32::INFINITY,
1368                        };
1369
1370                        frame.fill_text(text);
1371                    } else {
1372                        let circle = Path::circle(*p, number_size * 0.1);
1373                        frame.fill(
1374                            &circle,
1375                            style
1376                                .get(&StyleState::Active)
1377                                .expect("Style Sheet not found.")
1378                                .clock_dots_color,
1379                        );
1380                    }
1381                });
1382            }
1383        });
1384
1385    let translation = Vector::new(layout.bounds().x, layout.bounds().y);
1386    renderer.with_translation(translation, |renderer| {
1387        renderer.draw_geometry(geometry);
1388    });
1389}
1390
1391/// Draws the digital clock.
1392#[allow(clippy::too_many_lines)]
1393fn draw_digital_clock<Message, Theme>(
1394    renderer: &mut Renderer,
1395    time_picker: &TimePickerOverlay<'_, '_, Message, Theme>,
1396    layout: Layout<'_>,
1397    cursor: Cursor,
1398    style: &HashMap<StyleState, Style>,
1399) where
1400    Message: 'static + Clone,
1401    Theme: Catalog + button::Catalog + text::Catalog,
1402{
1403    //println!("layout: {:#?}", layout);
1404    let mut children = layout
1405        .children()
1406        .next()
1407        .expect("Graphics: Layout should have digital clock children")
1408        .children();
1409
1410    let f = |renderer: &mut Renderer, layout: Layout<'_>, text: String, target: Focus| {
1411        let style_state = if time_picker.state.focus == target {
1412            StyleState::Focused
1413        } else {
1414            StyleState::Active
1415        };
1416
1417        let mut children = layout.children();
1418
1419        let up_bounds = children
1420            .next()
1421            .expect("Graphics: Layout should have a up arrow bounds")
1422            .bounds();
1423        let center_bounds = children
1424            .next()
1425            .expect("Graphics: Layout should have a center bounds")
1426            .bounds();
1427        let down_bounds = children
1428            .next()
1429            .expect("Graphics: Layout should have a down arrow bounds")
1430            .bounds();
1431
1432        let up_arrow_hovered = cursor.is_over(up_bounds);
1433        let down_arrow_hovered = cursor.is_over(down_bounds);
1434
1435        // Background
1436        if style_state == StyleState::Focused {
1437            renderer.fill_quad(
1438                renderer::Quad {
1439                    bounds: layout.bounds(),
1440                    border: Border {
1441                        radius: style
1442                            .get(&style_state)
1443                            .expect("Style Sheet not found.")
1444                            .border_radius
1445                            .into(),
1446                        width: style
1447                            .get(&style_state)
1448                            .expect("Style Sheet not found.")
1449                            .border_width,
1450                        color: style
1451                            .get(&style_state)
1452                            .expect("Style Sheet not found.")
1453                            .border_color,
1454                    },
1455                    ..renderer::Quad::default()
1456                },
1457                style
1458                    .get(&style_state)
1459                    .expect("Style Sheet not found.")
1460                    .background,
1461            );
1462        }
1463
1464        let (up_content, up_font, _up_shaping) = up_open();
1465        let (down_content, down_font, _down_shaping) = down_open();
1466        // Caret up
1467        renderer.fill_text(
1468            Text {
1469                content: up_content,
1470                bounds: Size::new(up_bounds.width, up_bounds.height),
1471                size: Pixels(renderer.default_size().0 + if up_arrow_hovered { 1.0 } else { 0.0 }),
1472                font: up_font,
1473                align_x: text::Alignment::Center,
1474                align_y: Vertical::Center,
1475                line_height: text::LineHeight::Relative(1.3),
1476                shaping: text::Shaping::Basic,
1477                wrapping: Wrapping::default(),
1478            },
1479            Point::new(up_bounds.center_x(), up_bounds.center_y()),
1480            style
1481                .get(&StyleState::Active)
1482                .expect("Style Sheet not found.")
1483                .text_color,
1484            up_bounds,
1485        );
1486
1487        // Text
1488        renderer.fill_text(
1489            Text {
1490                content: text,
1491                bounds: Size::new(center_bounds.width, center_bounds.height),
1492                size: renderer.default_size(),
1493                font: renderer.default_font(),
1494                align_x: text::Alignment::Center,
1495                align_y: Vertical::Center,
1496                line_height: text::LineHeight::Relative(1.3),
1497                shaping: text::Shaping::Basic,
1498                wrapping: Wrapping::default(),
1499            },
1500            Point::new(center_bounds.center_x(), center_bounds.center_y()),
1501            style
1502                .get(&StyleState::Active)
1503                .expect("Style Sheet not found.")
1504                .text_color,
1505            center_bounds,
1506        );
1507
1508        // Down caret
1509        renderer.fill_text(
1510            Text {
1511                content: down_content,
1512                bounds: Size::new(down_bounds.width, down_bounds.height),
1513                size: Pixels(
1514                    renderer.default_size().0 + if down_arrow_hovered { 1.0 } else { 0.0 },
1515                ),
1516                font: down_font,
1517                align_x: text::Alignment::Center,
1518                align_y: Vertical::Center,
1519                line_height: text::LineHeight::Relative(1.3),
1520                shaping: text::Shaping::Basic,
1521                wrapping: Wrapping::default(),
1522            },
1523            Point::new(down_bounds.center_x(), down_bounds.center_y()),
1524            style
1525                .get(&StyleState::Active)
1526                .expect("Style Sheet not found.")
1527                .text_color,
1528            down_bounds,
1529        );
1530    };
1531
1532    if !time_picker.state.use_24h {
1533        // Placeholder
1534        let _ = children.next();
1535    }
1536
1537    // Draw hours
1538    let hour_layout = children
1539        .next()
1540        .expect("Graphics: Layout should have a hour layout");
1541    f(
1542        renderer,
1543        hour_layout,
1544        format!(
1545            "{:02}",
1546            if time_picker.state.use_24h {
1547                time_picker.state.time.hour()
1548            } else {
1549                time_picker.state.time.hour12().1
1550            }
1551        ),
1552        Focus::DigitalHour,
1553    );
1554
1555    // Draw separator between hours and minutes
1556    let hour_minute_separator = children
1557        .next()
1558        .expect("Graphics: Layout should have a hour/minute separator layout");
1559
1560    renderer.fill_text(
1561        Text {
1562            content: ":".to_owned(),
1563            bounds: Size::new(
1564                hour_minute_separator.bounds().width,
1565                hour_minute_separator.bounds().height,
1566            ),
1567            size: renderer.default_size(),
1568            font: renderer.default_font(),
1569            align_x: text::Alignment::Center,
1570            align_y: Vertical::Center,
1571            line_height: text::LineHeight::Relative(1.3),
1572            shaping: text::Shaping::Basic,
1573            wrapping: Wrapping::default(),
1574        },
1575        Point::new(
1576            hour_minute_separator.bounds().center_x(),
1577            hour_minute_separator.bounds().center_y(),
1578        ),
1579        style[&StyleState::Active].text_color,
1580        hour_minute_separator.bounds(),
1581    );
1582
1583    // Draw minutes
1584    let minute_layout = children
1585        .next()
1586        .expect("Graphics: Layout should have a minute layout");
1587    f(
1588        renderer,
1589        minute_layout,
1590        format!("{:02}", time_picker.state.time.minute()),
1591        Focus::DigitalMinute,
1592    );
1593
1594    if time_picker.state.show_seconds {
1595        // Draw separator between minutes and seconds
1596        let minute_second_separator = children
1597            .next()
1598            .expect("Graphics: Layout should have a minute/second separator layout");
1599        renderer.fill_text(
1600            Text {
1601                content: ":".to_owned(),
1602                bounds: Size::new(
1603                    minute_second_separator.bounds().width,
1604                    minute_second_separator.bounds().height,
1605                ),
1606                size: renderer.default_size(),
1607                font: renderer.default_font(),
1608                align_x: text::Alignment::Center,
1609                align_y: Vertical::Center,
1610                line_height: text::LineHeight::Relative(1.3),
1611                shaping: text::Shaping::Basic,
1612                wrapping: Wrapping::default(),
1613            },
1614            Point::new(
1615                minute_second_separator.bounds().center_x(),
1616                minute_second_separator.bounds().center_y(),
1617            ),
1618            style[&StyleState::Active].text_color,
1619            minute_second_separator.bounds(),
1620        );
1621
1622        // Draw seconds
1623        let second_layout = children
1624            .next()
1625            .expect("Graphics: Layout should have a second layout");
1626        f(
1627            renderer,
1628            second_layout,
1629            format!("{:02}", time_picker.state.time.second()),
1630            Focus::DigitalSecond,
1631        );
1632    }
1633
1634    // Draw period
1635    if !time_picker.state.use_24h {
1636        let period = children
1637            .next()
1638            .expect("Graphics: Layout should have a period layout");
1639        renderer.fill_text(
1640            Text {
1641                content: if time_picker.state.time.hour12().0 {
1642                    "PM".to_owned()
1643                } else {
1644                    "AM".to_owned()
1645                },
1646                bounds: Size::new(period.bounds().width, period.bounds().height),
1647                size: renderer.default_size(),
1648                font: renderer.default_font(),
1649                align_x: text::Alignment::Center,
1650                align_y: Vertical::Center,
1651                line_height: text::LineHeight::Relative(1.3),
1652                shaping: text::Shaping::Basic,
1653                wrapping: Wrapping::default(),
1654            },
1655            Point::new(period.bounds().center_x(), period.bounds().center_y()),
1656            style[&StyleState::Active].text_color,
1657            period.bounds(),
1658        );
1659    }
1660}
1661
1662/// The state of the [`TimePickerOverlay`].
1663#[derive(Debug)]
1664pub struct State {
1665    /// The selected time of the [`TimePickerOverlay`].
1666    pub(crate) time: NaiveTime,
1667    /// Toggle if the cache needs to be cleared.
1668    pub(crate) clock_cache_needs_clearance: bool,
1669    /// The cache of the clock of the [`TimePickerOverlay`].
1670    pub(crate) clock_cache: canvas::Cache,
1671    /// Toggle the use of the 24h clock of the [`TimePickerOverlay`].
1672    pub(crate) use_24h: bool,
1673    /// Toggle the use of the seconds of the [`TimePickerOverlay`].
1674    pub(crate) show_seconds: bool,
1675    /// The dragged clock element of the [`TimePickerOverlay`].
1676    pub(crate) clock_dragged: ClockDragged,
1677    /// The focus of the [`TimePickerOverlay`].
1678    pub(crate) focus: Focus,
1679    /// The previously pressed keyboard modifiers.
1680    pub(crate) keyboard_modifiers: keyboard::Modifiers,
1681}
1682
1683impl State {
1684    /// Creates a new State with the given time.
1685    #[must_use]
1686    pub fn new(time: Time, use_24h: bool, show_seconds: bool) -> Self {
1687        Self {
1688            use_24h,
1689            show_seconds,
1690            time: time.into(),
1691            ..Self::default()
1692        }
1693    }
1694}
1695
1696impl Default for State {
1697    fn default() -> Self {
1698        Self {
1699            time: Local::now().naive_local().time(),
1700            clock_cache_needs_clearance: false,
1701            clock_cache: canvas::Cache::new(),
1702            use_24h: false,
1703            show_seconds: false,
1704            clock_dragged: ClockDragged::None,
1705            focus: Focus::default(),
1706            keyboard_modifiers: keyboard::Modifiers::default(),
1707        }
1708    }
1709}
1710
1711/// Just a workaround to pass the button states from the tree to the overlay
1712#[allow(missing_debug_implementations)]
1713pub struct TimePickerOverlayButtons<'a, Message, Theme>
1714where
1715    Message: Clone,
1716    Theme: Catalog + button::Catalog,
1717{
1718    /// The cancel button of the [`TimePickerOverlay`].
1719    cancel_button: Element<'a, Message, Theme, Renderer>,
1720    /// The submit button of the [`TimePickerOverlay`].
1721    submit_button: Element<'a, Message, Theme, Renderer>,
1722}
1723
1724impl<'a, Message, Theme> Default for TimePickerOverlayButtons<'a, Message, Theme>
1725where
1726    Message: 'a + Clone,
1727    Theme: 'a + Catalog + button::Catalog + text::Catalog,
1728{
1729    fn default() -> Self {
1730        let (cancel_content, cancel_font, _cancel_shaping) = cancel();
1731        let (submit_content, submit_font, _submit_shaping) = ok();
1732
1733        Self {
1734            cancel_button: Button::new(
1735                text::Text::new(cancel_content)
1736                    .font(cancel_font)
1737                    .align_x(Horizontal::Center)
1738                    .width(Length::Fill),
1739            )
1740            .into(),
1741            submit_button: Button::new(
1742                text::Text::new(submit_content)
1743                    .font(submit_font)
1744                    .align_x(Horizontal::Center)
1745                    .width(Length::Fill),
1746            )
1747            .into(),
1748        }
1749    }
1750}
1751
1752#[allow(clippy::unimplemented)]
1753impl<Message, Theme> Widget<Message, Theme, Renderer>
1754    for TimePickerOverlayButtons<'_, Message, Theme>
1755where
1756    Message: Clone,
1757    Theme: Catalog + button::Catalog,
1758{
1759    fn children(&self) -> Vec<Tree> {
1760        vec![
1761            Tree::new(&self.cancel_button),
1762            Tree::new(&self.submit_button),
1763        ]
1764    }
1765
1766    fn diff(&self, tree: &mut Tree) {
1767        tree.diff_children(&[&self.cancel_button, &self.submit_button]);
1768    }
1769
1770    fn size(&self) -> Size<Length> {
1771        unimplemented!("This should never be reached!")
1772    }
1773
1774    fn layout(&mut self, _tree: &mut Tree, _renderer: &Renderer, _limits: &Limits) -> Node {
1775        unimplemented!("This should never be reached!")
1776    }
1777
1778    fn draw(
1779        &self,
1780        _state: &Tree,
1781        _renderer: &mut Renderer,
1782        _theme: &Theme,
1783        _style: &renderer::Style,
1784        _layout: Layout<'_>,
1785        _cursor: Cursor,
1786        _viewport: &Rectangle,
1787    ) {
1788        unimplemented!("This should never be reached!")
1789    }
1790}
1791
1792impl<'a, Message, Theme> From<TimePickerOverlayButtons<'a, Message, Theme>>
1793    for Element<'a, Message, Theme, Renderer>
1794where
1795    Message: 'a + Clone,
1796    Theme: 'a + Catalog + button::Catalog,
1797{
1798    fn from(overlay: TimePickerOverlayButtons<'a, Message, Theme>) -> Self {
1799        Self::new(overlay)
1800    }
1801}
1802
1803/// The state of the currently dragged watch hand.
1804#[derive(Copy, Clone, Debug)]
1805pub enum ClockDragged {
1806    /// Nothing is dragged.
1807    None,
1808
1809    /// The hour hand is dragged.
1810    Hour,
1811
1812    /// The minute hand is dragged.
1813    Minute,
1814
1815    /// The second hand is dragged.
1816    Second,
1817}
1818
1819/// An enumeration of all focusable elements of the [`TimePickerOverlay`].
1820#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1821pub enum Focus {
1822    /// Nothing is in focus.
1823    #[default]
1824    None,
1825
1826    /// The overlay itself is in focus.
1827    Overlay,
1828
1829    /// The digital hour is in focus.
1830    DigitalHour,
1831
1832    /// The digital minute is in focus.
1833    DigitalMinute,
1834
1835    /// The digital second is in focus.
1836    DigitalSecond,
1837
1838    /// The cancel button is in focus.
1839    Cancel,
1840
1841    /// The submit button is in focus.
1842    Submit,
1843}
1844
1845impl Focus {
1846    /// Gets the next focusable element.
1847    #[must_use]
1848    pub const fn next(self, show_seconds: bool) -> Self {
1849        match self {
1850            Self::Overlay => Self::DigitalHour,
1851            Self::DigitalHour => Self::DigitalMinute,
1852            Self::DigitalMinute => {
1853                if show_seconds {
1854                    Self::DigitalSecond
1855                } else {
1856                    Self::Cancel
1857                }
1858            }
1859            Self::DigitalSecond => Self::Cancel,
1860            Self::Cancel => Self::Submit,
1861            Self::Submit | Self::None => Self::Overlay,
1862        }
1863    }
1864
1865    /// Gets the previous focusable element.
1866    #[must_use]
1867    pub const fn previous(self, show_seconds: bool) -> Self {
1868        match self {
1869            Self::None => Self::None,
1870            Self::Overlay => Self::Submit,
1871            Self::DigitalHour => Self::Overlay,
1872            Self::DigitalMinute => Self::DigitalHour,
1873            Self::DigitalSecond => Self::DigitalMinute,
1874            Self::Cancel => {
1875                if show_seconds {
1876                    Self::DigitalSecond
1877                } else {
1878                    Self::DigitalMinute
1879                }
1880            }
1881            Self::Submit => Self::Cancel,
1882        }
1883    }
1884}