iced_aw/widget/
date_picker.rs

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