iced_aw/widget/
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 super::overlay::time_picker::{self, TimePickerOverlay, TimePickerOverlayButtons};
6
7use chrono::Local;
8use iced_core::{
9    Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
10    layout::{Limits, Node},
11    mouse::{self, Cursor},
12    overlay, renderer,
13    widget::tree::{self, Tag, Tree},
14};
15use iced_widget::{Renderer, button, container, text};
16
17pub use crate::{
18    core::time::{Period, Time},
19    style::{
20        Status, StyleFn,
21        time_picker::{Catalog, Style},
22    },
23};
24
25//TODO: Remove ignore when Null is updated. Temp fix for Test runs
26/// An input element for picking times.
27///
28/// # Example
29/// ```ignore
30/// # use iced_aw::{TimePicker, time_picker};
31/// # use iced_widget::{button, Button, Text};
32/// #
33/// #[derive(Clone, Debug)]
34/// enum Message {
35///     Open,
36///     Cancel,
37///     Submit(time_picker::Time),
38/// }
39///
40/// let time_picker = TimePicker::new(
41///     true,
42///     time_picker::Time::now_hms(true),
43///     Button::new(Text::new("Pick time"))
44///         .on_press(Message::Open),
45///     Message::Cancel,
46///     Message::Submit,
47/// );
48/// ```
49#[allow(missing_debug_implementations)]
50pub struct TimePicker<'a, Message, Theme>
51where
52    Message: Clone,
53    Theme: Catalog + button::Catalog,
54{
55    /// Show the picker.
56    show_picker: bool,
57    /// The time to show.
58    time: Time,
59    /// The underlying element.
60    underlay: Element<'a, Message, Theme, Renderer>,
61    /// The message that is send if the cancel button of the [`TimePickerOverlay`] is pressed.
62    on_cancel: Message,
63    /// The function that produces a message when the submit button of the [`TimePickerOverlay`] is pressed.
64    on_submit: Box<dyn Fn(Time) -> Message>,
65    /// The style of the [`TimePickerOverlay`].
66    class: <Theme as Catalog>::Class<'a>,
67    /// The buttons of the overlay.
68    overlay_state: Element<'a, Message, Theme, Renderer>,
69    /// Toggle the use of the 24h clock of the [`TimePickerOverlay`].
70    use_24h: bool,
71    /// Toggle the use of the seconds of the [`TimePickerOverlay`].
72    show_seconds: bool,
73}
74
75impl<'a, Message, Theme> TimePicker<'a, Message, Theme>
76where
77    Message: 'a + Clone,
78    Theme: 'a + Catalog + button::Catalog + text::Catalog,
79{
80    /// Creates a new [`TimePicker`] wrapping around the given underlay.
81    ///
82    /// It expects:
83    ///     * if the overlay of the time picker is visible.
84    ///     * the initial time to show.
85    ///     * the underlay [`Element`] on which this [`TimePicker`]
86    ///         will be wrapped around.
87    ///     * a message that will be send when the cancel button of the [`TimePicker`] is pressed.
88    ///     * a function that will be called when the submit button of the [`TimePicker`]
89    ///         is pressed, which takes the picked [`Time`] value.
90    pub fn new<U, F>(
91        show_picker: bool,
92        time: impl Into<Time>,
93        underlay: U,
94        on_cancel: Message,
95        on_submit: F,
96    ) -> Self
97    where
98        U: Into<Element<'a, Message, Theme, Renderer>>,
99        F: 'static + Fn(Time) -> Message,
100    {
101        Self {
102            show_picker,
103            time: time.into(),
104            underlay: underlay.into(),
105            on_cancel,
106            on_submit: Box::new(on_submit),
107            class: <Theme as Catalog>::default(),
108            overlay_state: TimePickerOverlayButtons::default().into(),
109            use_24h: false,
110            show_seconds: false,
111        }
112    }
113
114    /// Enables the picker to also pick seconds.
115    #[must_use]
116    pub fn show_seconds(mut self) -> Self {
117        self.show_seconds = true;
118        self
119    }
120
121    /// Sets the style of the [`TimePicker`].
122    #[must_use]
123    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
124    where
125        <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme, Style>>,
126    {
127        self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into();
128        self
129    }
130
131    /// Use 24 hour format instead of AM/PM.
132    #[must_use]
133    pub fn use_24h(mut self) -> Self {
134        self.use_24h = true;
135        self
136    }
137
138    /// Sets the class of the input of the [`TimePicker`].
139    #[must_use]
140    pub fn class(mut self, class: impl Into<<Theme as Catalog>::Class<'a>>) -> Self {
141        self.class = class.into();
142        self
143    }
144}
145
146/// The state of the [`TimePicker`] / [`TimePickerOverlay`].
147#[derive(Debug)]
148pub struct State {
149    /// The state of the overlay.
150    pub(crate) overlay_state: time_picker::State,
151}
152
153impl State {
154    /// Creates a new [`State`] with the current time.
155    #[must_use]
156    pub fn now() -> Self {
157        Self {
158            overlay_state: time_picker::State::default(),
159        }
160    }
161
162    /// Creates a new [`State`] with the given time.
163    #[must_use]
164    pub fn new(time: Time, use_24h: bool, show_seconds: bool) -> Self {
165        Self {
166            overlay_state: time_picker::State::new(time, use_24h, show_seconds),
167        }
168    }
169
170    /// Resets the time of the state to the current time.
171    pub fn reset(&mut self) {
172        self.overlay_state.clock_cache.clear();
173        self.overlay_state.time = Local::now().naive_local().time();
174    }
175}
176
177impl<Message, Theme> Widget<Message, Theme, Renderer> for TimePicker<'_, Message, Theme>
178where
179    Message: 'static + Clone,
180    Theme: Catalog + button::Catalog + text::Catalog + container::Catalog,
181{
182    fn tag(&self) -> Tag {
183        Tag::of::<State>()
184    }
185
186    fn state(&self) -> tree::State {
187        tree::State::new(State::new(self.time, self.use_24h, self.show_seconds))
188    }
189
190    fn children(&self) -> Vec<Tree> {
191        vec![Tree::new(&self.underlay), Tree::new(&self.overlay_state)]
192    }
193
194    fn diff(&self, tree: &mut Tree) {
195        tree.diff_children(&[&self.underlay, &self.overlay_state]);
196    }
197
198    fn size(&self) -> Size<Length> {
199        self.underlay.as_widget().size()
200    }
201
202    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
203        self.underlay
204            .as_widget_mut()
205            .layout(&mut tree.children[0], renderer, limits)
206    }
207
208    fn update(
209        &mut self,
210        state: &mut Tree,
211        event: &Event,
212        layout: Layout<'_>,
213        cursor: Cursor,
214        renderer: &Renderer,
215        clipboard: &mut dyn Clipboard,
216        shell: &mut Shell<'_, Message>,
217        viewport: &Rectangle,
218    ) {
219        self.underlay.as_widget_mut().update(
220            &mut state.children[0],
221            event,
222            layout,
223            cursor,
224            renderer,
225            clipboard,
226            shell,
227            viewport,
228        );
229    }
230
231    fn mouse_interaction(
232        &self,
233        state: &Tree,
234        layout: Layout<'_>,
235        cursor: Cursor,
236        viewport: &Rectangle,
237        renderer: &Renderer,
238    ) -> mouse::Interaction {
239        self.underlay.as_widget().mouse_interaction(
240            &state.children[0],
241            layout,
242            cursor,
243            viewport,
244            renderer,
245        )
246    }
247
248    fn draw(
249        &self,
250        tree: &Tree,
251        renderer: &mut Renderer,
252        theme: &Theme,
253        style: &renderer::Style,
254        layout: Layout<'_>,
255        cursor: Cursor,
256        viewport: &Rectangle,
257    ) {
258        self.underlay.as_widget().draw(
259            &tree.children[0],
260            renderer,
261            theme,
262            style,
263            layout,
264            cursor,
265            viewport,
266        );
267    }
268
269    fn overlay<'b>(
270        &'b mut self,
271        tree: &'b mut Tree,
272        layout: Layout<'b>,
273        renderer: &Renderer,
274        viewport: &Rectangle,
275        translation: Vector,
276    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
277        let picker_state: &mut State = tree.state.downcast_mut();
278
279        if !self.show_picker {
280            return self.underlay.as_widget_mut().overlay(
281                &mut tree.children[0],
282                layout,
283                renderer,
284                viewport,
285                translation,
286            );
287        }
288
289        let bounds = layout.bounds();
290        let position = Point::new(bounds.center_x(), bounds.center_y());
291
292        Some(
293            TimePickerOverlay::new(
294                picker_state,
295                self.on_cancel.clone(),
296                &self.on_submit,
297                position,
298                &self.class,
299                &mut tree.children[1],
300                *viewport,
301            )
302            .overlay(),
303        )
304    }
305}
306
307impl<'a, Message, Theme> From<TimePicker<'a, Message, Theme>>
308    for Element<'a, Message, Theme, Renderer>
309where
310    Message: 'static + Clone,
311    Theme: 'a + Catalog + button::Catalog + text::Catalog + container::Catalog,
312{
313    fn from(time_picker: TimePicker<'a, Message, Theme>) -> Self {
314        Element::new(time_picker)
315    }
316}