1use 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
42const PADDING: Padding = Padding::new(10.0);
44const SPACING: Pixels = Pixels(15.0);
46const BUTTON_SPACING: Pixels = Pixels(5.0);
48const NUMBER_SIZE_PERCENTAGE: f32 = 0.15;
50const PERIOD_SIZE_PERCENTAGE: f32 = 0.2;
52
53#[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 state: &'a mut State,
63 cancel_button: Button<'a, Message, Theme, Renderer>,
65 submit_button: Button<'a, Message, Theme, Renderer>,
67 on_submit: &'a dyn Fn(Time) -> Message,
69 position: Point,
71 class: &'a <Theme as Catalog>::Class<'b>,
73 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 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), on_submit,
117 position,
118 class,
119 tree,
120 viewport,
121 }
122 }
123
124 #[must_use]
126 pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> {
127 overlay::Element::new(Box::new(self))
128 }
129
130 #[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 #[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 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 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 let digital_clock_limits = limits;
509 let mut digital_clock = digital_clock(self, renderer, digital_clock_limits);
510
511 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 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 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 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 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 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 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 let digital_clock_layout = children
716 .next()
717 .expect("Graphics: Layout should have a digital clock layout");
718 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 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 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 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 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 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 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 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
947fn 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() .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 Column::new()
978 .align_x(Alignment::Center)
979 .height(Length::Shrink)
980 .push(
981 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 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 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 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 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 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#[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#[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 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 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 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 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 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 let _ = children.next();
1535 }
1536
1537 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 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 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 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 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 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#[derive(Debug)]
1664pub struct State {
1665 pub(crate) time: NaiveTime,
1667 pub(crate) clock_cache_needs_clearance: bool,
1669 pub(crate) clock_cache: canvas::Cache,
1671 pub(crate) use_24h: bool,
1673 pub(crate) show_seconds: bool,
1675 pub(crate) clock_dragged: ClockDragged,
1677 pub(crate) focus: Focus,
1679 pub(crate) keyboard_modifiers: keyboard::Modifiers,
1681}
1682
1683impl State {
1684 #[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#[allow(missing_debug_implementations)]
1713pub struct TimePickerOverlayButtons<'a, Message, Theme>
1714where
1715 Message: Clone,
1716 Theme: Catalog + button::Catalog,
1717{
1718 cancel_button: Element<'a, Message, Theme, Renderer>,
1720 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#[derive(Copy, Clone, Debug)]
1805pub enum ClockDragged {
1806 None,
1808
1809 Hour,
1811
1812 Minute,
1814
1815 Second,
1817}
1818
1819#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1821pub enum Focus {
1822 #[default]
1824 None,
1825
1826 Overlay,
1828
1829 DigitalHour,
1831
1832 DigitalMinute,
1834
1835 DigitalSecond,
1837
1838 Cancel,
1840
1841 Submit,
1843}
1844
1845impl Focus {
1846 #[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 #[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}