Skip to main content

ferroui_controls/calendar_date_picker/
calendar_date_picker.rs

1// (c) Copyright Microsoft Corporation.
2// This source is subject to the Microsoft Public License (Ms-PL).
3// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
4// All other rights reserved.
5//
6// Ported from the Silverlight Toolkit sources as adapted by the upstream
7// project; the license text is in the `NOTICE.md` of this crate.
8//
9// This file holds both parts of the upstream partial class: the control and
10// its properties.
11
12use super::{CalendarDatePickerDateValidationErrorEventArgs, CalendarDatePickerFormat};
13use crate::calendar::{
14    Calendar, CalendarBlackoutDatesCollection, CalendarDateChangedEventArgs, CalendarMode, CalendarSelectionMode,
15    DateTimeHelper,
16};
17use crate::primitives::{
18    Popup, SelectingItemsControl, TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl,
19};
20use crate::{Button, ContentControl, ControlImpl, SelectionChangedEventArgs, TextBox};
21use ferroui_base::data::converters::IValueConverter;
22use ferroui_base::data::core::ValueType;
23use ferroui_base::data::BindingMode;
24use ferroui_base::input::navigation::XYFocusHelpers;
25use ferroui_base::input::{
26    FocusChangedEventArgs, InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyModifiers,
27    MouseButton, NavigationMethod, PointerCaptureLostEventArgs, PointerPressedEventArgs, PointerReleasedEventArgs,
28    PointerWheelEventArgs,
29};
30use ferroui_base::interactivity::{Interactive, InteractiveImpl, RoutingStrategies};
31use ferroui_base::layout::{HorizontalAlignment, LayoutableImpl, VerticalAlignment};
32use ferroui_base::media::IBrush;
33use ferroui_base::reactive::{CompositeDisposable, Disposable, IDisposable, ObservableExt};
34use ferroui_base::threading::Dispatcher;
35use ferroui_base::utilities::{CalendarWeekRule, DateTime, DayOfWeek, FormatError, HandlerList};
36use ferroui_base::{
37    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, BoxedValue, FerroObject,
38    FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref,
39    StyledElementImpl, StyledProperty, StyledPropertyOptions, Visual, VisualImpl,
40};
41use std::cell::{Cell, RefCell};
42use std::error::Error;
43use std::fmt;
44use std::rc::Rc;
45
46const PC_PRESSED: &str = ":pressed";
47const PC_FLYOUT_OPEN: &str = ":flyout-open";
48
49const ELEMENT_TEXT_BOX: &str = "PART_TextBox";
50const ELEMENT_BUTTON: &str = "PART_Button";
51const ELEMENT_POPUP: &str = "PART_Popup";
52const ELEMENT_CALENDAR: &str = "PART_Calendar";
53
54/// The error of a date that cannot be selected: what the reference reports
55/// with an exception for an argument that is out of range.
56#[derive(Debug)]
57struct ArgumentOutOfRangeError {
58    message: &'static str,
59    parameter: &'static str,
60}
61
62impl fmt::Display for ArgumentOutOfRangeError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{} (Parameter '{}')", self.message, self.parameter)
65    }
66}
67
68impl Error for ArgumentOutOfRangeError {}
69
70/// The outcome of the conversion of a text to a date: the date (or none),
71/// or the error of a text that is not in a correct format.
72type ParseResult = Result<Option<DateTime>, Rc<dyn Error>>;
73
74/// The length of a text in UTF-16 code units, the unit of the positions of
75/// a text box.
76fn utf16_length(text: &str) -> i32 {
77    text.encode_utf16().count() as i32
78}
79
80/// Formats a composite format string that has no arguments: doubled braces
81/// are the braces themselves, and any other brace makes the string invalid
82/// (the reference formats the text of a date this way, so such a text
83/// panics as the reference throws).
84fn format_without_arguments(format: &str) -> Result<String, FormatError> {
85    let mut result = String::with_capacity(format.len());
86    let mut chars = format.chars().peekable();
87
88    while let Some(c) = chars.next() {
89        if c == '{' || c == '}' {
90            if chars.peek() == Some(&c) {
91                chars.next();
92            } else {
93                return Err(FormatError::new("Input string was not in a correct format."));
94            }
95        }
96        result.push(c);
97    }
98
99    Ok(result)
100}
101
102/// A control to allow the user to select a date.
103#[repr(C)]
104pub struct CalendarDatePicker {
105    base: TemplatedControl,
106
107    calendar: RefCell<Option<Ref<Calendar>>>,
108    /// The subscriptions to the events of the calendar.
109    calendar_subscriptions: RefCell<Option<Rc<dyn IDisposable>>>,
110    default_text: RefCell<String>,
111    drop_down_button: RefCell<Option<Ref<Button>>>,
112    /// The subscription to the click event of the drop-down button.
113    drop_down_button_click_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
114    pop_up: RefCell<Option<Ref<Popup>>>,
115    /// The subscription to the closed event of the popup.
116    pop_up_closed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
117    text_box: RefCell<Option<Ref<TextBox>>>,
118    /// The subscriptions to the key down and got focus events of the text
119    /// box.
120    text_box_subscriptions: RefCell<Option<Rc<dyn IDisposable>>>,
121    text_box_text_changed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
122    button_pointer_pressed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
123
124    on_open_selected_date: Cell<Option<DateTime>>,
125    setting_selected_date: Cell<bool>,
126
127    suspend_text_change_handler: Cell<bool>,
128    is_popup_closing: Cell<bool>,
129    ignore_button_click: Cell<bool>,
130    is_flyout_open: Cell<bool>,
131    is_pressed: Cell<bool>,
132
133    blackout_dates: RefCell<Option<CalendarBlackoutDatesCollection>>,
134
135    calendar_closed: HandlerList<dyn Fn()>,
136    calendar_opened: HandlerList<dyn Fn()>,
137    date_validation_error: HandlerList<dyn Fn(&CalendarDatePickerDateValidationErrorEventArgs)>,
138    selected_date_changed: HandlerList<dyn Fn(&SelectionChangedEventArgs)>,
139}
140
141ferro_class! {
142    CalendarDatePicker: TemplatedControl, virtuals CalendarDatePickerImpl: TemplatedControlImpl {
143        /// Raises the `DateValidationError` event.
144        fn on_date_validation_error(this, e: &CalendarDatePickerDateValidationErrorEventArgs);
145    }
146}
147
148ferro_class_info!(CalendarDatePicker {
149    new: CalendarDatePicker::new,
150    markup: {
151        properties: [
152            BlackoutDates: Option<CalendarBlackoutDatesCollection> { get: CalendarDatePicker::blackout_dates },
153        ],
154        attributes: [
155            TemplatePart("PART_Button", type(Ref<Button>)),
156            TemplatePart("PART_Calendar", type(Ref<Calendar>)),
157            TemplatePart("PART_Popup", type(Ref<Popup>)),
158            TemplatePart("PART_TextBox", type(Ref<TextBox>)),
159            PseudoClasses(":flyout-open", ":pressed"),
160        ],
161    },
162});
163
164ferro_impl_classes!(CalendarDatePicker: StyledElementImpl, VisualImpl, LayoutableImpl, InteractiveImpl);
165
166impl FerroObjectImpl for CalendarDatePicker {
167    /// Initializes a new instance of the [`CalendarDatePicker`] class.
168    fn constructed(this: &Self) {
169        Self::parent_constructed(this);
170
171        this.set_current_value(
172            Self::first_day_of_week_property(),
173            DateTimeHelper::get_current_date_format().first_day_of_week(),
174        );
175        this.default_text.borrow_mut().clear();
176        this.set_current_value(Self::display_date_property(), DateTime::today());
177    }
178
179    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
180        let property = change.property();
181
182        // CustomDateFormatString
183        if property == Self::custom_date_format_string_property().as_property() {
184            if this.selected_date_format() == CalendarDatePickerFormat::Custom {
185                this.on_date_format_changed();
186            }
187        }
188        // IsDropDownOpen
189        else if property == Self::is_drop_down_open_property().as_property() {
190            let (old_value, new_value) = change.get_old_and_new_value::<bool>();
191
192            let pop_up = this.pop_up.borrow().clone();
193            if let Some(pop_up) = pop_up.filter(|pop_up| pop_up.child().is_some()) {
194                if new_value != old_value {
195                    let calendar = this.calendar().expect("the date picker has a calendar");
196                    if calendar.display_mode() != CalendarMode::Month {
197                        calendar.set_display_mode(CalendarMode::Month);
198                    }
199
200                    if new_value {
201                        this.open_drop_down();
202                    } else {
203                        pop_up.set_is_open(false);
204                        this.is_flyout_open.set(pop_up.is_open());
205                        this.is_pressed.set(false);
206
207                        this.update_pseudo_classes();
208                        this.on_calendar_closed();
209                    }
210                }
211            }
212        }
213        // SelectedDate
214        else if property == Self::selected_date_property().as_property() {
215            let (removed_date, added_date) = change.get_old_and_new_value::<Option<DateTime>>();
216
217            if let Some(day) = this.selected_date() {
218                // When the SelectedDateProperty change is done from
219                // OnTextPropertyChanged method, two-way binding breaks if
220                // BeginInvoke is not used:
221                let picker = this.to_ref();
222                let _ = Dispatcher::ui_thread().invoke_async_local(move || {
223                    picker.setting_selected_date.set(true);
224                    picker.set_current_value(Self::text_property(), picker.date_time_to_string(day));
225                    picker.setting_selected_date.set(false);
226                    picker.on_date_selected(added_date, removed_date);
227                });
228
229                // When DatePickerDisplayDateFlag is TRUE, the SelectedDate
230                // change is coming from the Calendar UI itself, so, we
231                // shouldn't change the DisplayDate since it will automatically
232                // be changed by the Calendar
233                let calendar = this.calendar();
234                let display_date = this.display_date();
235                if (day.month() != display_date.month() || day.year() != display_date.year())
236                    && !calendar.as_ref().is_some_and(|calendar| calendar.calendar_date_picker_display_date_flag())
237                {
238                    this.set_current_value(Self::display_date_property(), day);
239                }
240
241                if let Some(calendar) = calendar {
242                    calendar.set_calendar_date_picker_display_date_flag(false);
243                }
244            } else {
245                this.setting_selected_date.set(true);
246                this.set_water_mark_text();
247                this.setting_selected_date.set(false);
248                this.on_date_selected(added_date, removed_date);
249            }
250        }
251        // SelectedDateFormat
252        else if property == Self::selected_date_format_property().as_property() {
253            this.on_date_format_changed();
254        }
255        // Text
256        else if property == Self::text_property().as_property() {
257            let new_value = change.get_new_value::<Option<String>>();
258
259            if !this.suspend_text_change_handler.get() {
260                if let Some(new_value) = new_value {
261                    let text_box = this.text_box();
262                    match text_box {
263                        Some(text_box) => text_box.set_text(Some(&new_value)),
264                        None => *this.default_text.borrow_mut() = new_value,
265                    }
266
267                    if !this.setting_selected_date.get() {
268                        this.set_selected_date_from_text();
269                    }
270                } else if !this.setting_selected_date.get() {
271                    this.setting_selected_date.set(true);
272                    this.set_current_value(Self::selected_date_property(), None);
273                    this.setting_selected_date.set(false);
274                }
275            } else {
276                this.set_water_mark_text();
277            }
278        }
279
280        Self::parent_on_property_changed(this, change);
281    }
282}
283
284impl InputElementImpl for CalendarDatePicker {
285    fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
286        Self::parent_on_pointer_pressed(this, e);
287
288        if e.get_current_point(Some(this)).properties.is_left_button_pressed {
289            e.set_handled(true);
290
291            this.ignore_button_click.set(this.is_popup_closing.get());
292
293            this.is_pressed.set(true);
294            this.update_pseudo_classes();
295        }
296    }
297
298    fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
299        Self::parent_on_pointer_released(this, e);
300
301        if this.is_pressed.get() && e.initial_press_mouse_button() == MouseButton::Left {
302            e.set_handled(true);
303
304            if !this.ignore_button_click.get() {
305                this.toggle_pop_up();
306            } else {
307                this.ignore_button_click.set(false);
308            }
309
310            this.is_pressed.set(false);
311            this.update_pseudo_classes();
312        }
313    }
314
315    fn on_pointer_capture_lost(this: &Self, e: &PointerCaptureLostEventArgs) {
316        Self::parent_on_pointer_capture_lost(this, e);
317
318        this.is_pressed.set(false);
319        this.update_pseudo_classes();
320    }
321
322    fn on_pointer_wheel_changed(this: &Self, e: &PointerWheelEventArgs) {
323        Self::parent_on_pointer_wheel_changed(this, e);
324
325        if !e.handled() {
326            if let (Some(selected_date), Some(calendar)) = (this.selected_date(), this.calendar()) {
327                let new_date = DateTimeHelper::add_days(selected_date, if e.delta().y > 0.0 { -1 } else { 1 });
328                if let Some(new_date) = new_date {
329                    if Calendar::is_valid_date_selection(&calendar, Some(new_date)) {
330                        this.set_current_value(Self::selected_date_property(), Some(new_date));
331                        e.set_handled(true);
332                    }
333                }
334            }
335        }
336    }
337
338    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
339        Self::parent_on_got_focus(this, e);
340
341        let text_box = this.text_box();
342        if let Some(text_box) = text_box {
343            // Only keyboard focus on a closed picker goes to the text box, because that closes the drop-down.
344            if this.is_enabled()
345                && e.source().is_some_and(|source| source.ptr_eq(&this.to_ref()))
346                && e.navigation_method != NavigationMethod::Pointer
347                && !this.is_drop_down_open()
348            {
349                text_box.focus_with(e.navigation_method, KeyModifiers::NONE);
350
351                if e.navigation_method == NavigationMethod::Tab {
352                    let text = text_box.text();
353                    if let Some(text) = text.filter(|text| !text.is_empty()) {
354                        text_box.set_selection_start(0);
355                        text_box.set_selection_end(utf16_length(&text));
356                    }
357                }
358            }
359        }
360    }
361
362    fn on_lost_focus(this: &Self, e: &FocusChangedEventArgs) {
363        Self::parent_on_lost_focus(this, e);
364
365        this.is_pressed.set(false);
366        this.update_pseudo_classes();
367
368        this.set_selected_date_from_text();
369    }
370
371    fn on_key_up(this: &Self, e: &KeyEventArgs) {
372        let key = e.key;
373
374        if (key == Key::Space || key == Key::Enter) && this.is_effectively_enabled() {
375            // Since the TextBox is used for direct date entry,
376            // it isn't supported to open the popup/flyout using these keys.
377            // Other controls open the popup/flyout here.
378        } else if key == Key::Down
379            && e.key_modifiers.contains(KeyModifiers::ALT)
380            && this.is_effectively_enabled()
381            && !XYFocusHelpers::is_allowed_xy_navigation_mode(this, Some(e.key_device_type))
382        {
383            // It is only possible to open the popup using these keys.
384            // This is important as the down key is handled by calendar.
385            // If down also closed the popup, the date would move 1 week
386            // and then close the popup. This isn't user friendly at all.
387            // (calendar doesn't mark as handled either).
388            // The Escape key will still close the popup.
389            if !this.is_drop_down_open() {
390                e.set_handled(true);
391
392                if !this.ignore_button_click.get() {
393                    this.toggle_pop_up();
394                } else {
395                    this.ignore_button_click.set(false);
396                }
397
398                this.update_pseudo_classes();
399            }
400        }
401
402        Self::parent_on_key_up(this, e);
403    }
404}
405
406impl TemplatedControlImpl for CalendarDatePicker {
407    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
408        if let Some(subscriptions) = this.calendar_subscriptions.take() {
409            subscriptions.dispose();
410        }
411        let calendar = e.name_scope().find_as::<Calendar>(ELEMENT_CALENDAR);
412        *this.calendar.borrow_mut() = calendar.clone();
413        if let Some(calendar) = &calendar {
414            calendar.set_selection_mode(CalendarSelectionMode::SingleDate);
415
416            let subscriptions = CompositeDisposable::new();
417            let weak = this.to_ref().downgrade();
418            subscriptions.add(calendar.day_button_mouse_up(move |_| {
419                if let Some(this) = weak.upgrade() {
420                    this.calendar_day_button_mouse_up();
421                }
422            }));
423            let weak = this.to_ref().downgrade();
424            subscriptions.add(calendar.display_date_changed(move |e| {
425                if let Some(this) = weak.upgrade() {
426                    this.calendar_display_date_changed(e);
427                }
428            }));
429            let weak = this.to_ref().downgrade();
430            subscriptions.add(calendar.selected_dates_changed(move |e| {
431                if let Some(this) = weak.upgrade() {
432                    this.calendar_selected_dates_changed(e);
433                }
434            }));
435            subscriptions.add(calendar.add_disposable_handler(InputElement::pointer_released_event(), |_, e| {
436                Self::calendar_pointer_released(e);
437            }));
438            let weak = this.to_ref().downgrade();
439            subscriptions.add(calendar.add_disposable_handler(InputElement::key_down_event(), move |sender, e| {
440                if let Some(this) = weak.upgrade() {
441                    this.calendar_key_down(sender, e);
442                }
443            }));
444            *this.calendar_subscriptions.borrow_mut() = Some(Rc::new(subscriptions));
445
446            let current_blackout_days = this.blackout_dates();
447            let blackout_dates = calendar.blackout_dates();
448            *this.blackout_dates.borrow_mut() = Some(blackout_dates.clone());
449            if let Some(current_blackout_days) = current_blackout_days {
450                for range in current_blackout_days.to_vec() {
451                    blackout_dates.add(range);
452                }
453            }
454        }
455
456        let old_pop_up = this.pop_up.take();
457        if let Some(old_pop_up) = old_pop_up {
458            old_pop_up.set_child(None);
459            if let Some(subscription) = this.pop_up_closed_subscription.take() {
460                subscription.dispose();
461            }
462        }
463        let pop_up = e.name_scope().find_as::<Popup>(ELEMENT_POPUP);
464        *this.pop_up.borrow_mut() = pop_up.clone();
465        if let Some(pop_up) = &pop_up {
466            let weak = this.to_ref().downgrade();
467            let closed = pop_up.closed(move || {
468                if let Some(this) = weak.upgrade() {
469                    this.pop_up_closed();
470                }
471            });
472            *this.pop_up_closed_subscription.borrow_mut() = Some(closed);
473
474            if this.is_drop_down_open() {
475                this.open_drop_down();
476            }
477        }
478
479        if this.drop_down_button.take().is_some() {
480            if let Some(subscription) = this.drop_down_button_click_subscription.take() {
481                subscription.dispose();
482            }
483            if let Some(subscription) = this.button_pointer_pressed_subscription.take() {
484                subscription.dispose();
485            }
486        }
487        let drop_down_button = e.name_scope().find_as::<Button>(ELEMENT_BUTTON);
488        *this.drop_down_button.borrow_mut() = drop_down_button.clone();
489        if let Some(drop_down_button) = &drop_down_button {
490            let weak = this.to_ref().downgrade();
491            let click = drop_down_button.add_disposable_handler(Button::click_event(), move |_, _| {
492                if let Some(this) = weak.upgrade() {
493                    this.drop_down_button_click();
494                }
495            });
496            *this.drop_down_button_click_subscription.borrow_mut() = Some(click);
497
498            let routes = RoutingStrategies::DIRECT | RoutingStrategies::BUBBLE;
499            let weak = this.to_ref().downgrade();
500            let pressed = drop_down_button.add_disposable_handler_with(
501                InputElement::pointer_pressed_event(),
502                move |_, e| {
503                    if let Some(this) = weak.upgrade() {
504                        this.drop_down_button_pointer_pressed(e);
505                    }
506                },
507                routes,
508                true,
509            );
510            let weak = this.to_ref().downgrade();
511            let released = drop_down_button.add_disposable_handler_with(
512                InputElement::pointer_released_event(),
513                move |_, _| {
514                    if let Some(this) = weak.upgrade() {
515                        this.drop_down_button_pointer_released();
516                    }
517                },
518                routes,
519                true,
520            );
521            *this.button_pointer_pressed_subscription.borrow_mut() =
522                Some(Rc::new(CompositeDisposable::from_disposables([pressed, released])));
523        }
524
525        if this.text_box.take().is_some() {
526            if let Some(subscriptions) = this.text_box_subscriptions.take() {
527                subscriptions.dispose();
528            }
529            if let Some(subscription) = this.text_box_text_changed_subscription.take() {
530                subscription.dispose();
531            }
532        }
533        let text_box = e.name_scope().find_as::<TextBox>(ELEMENT_TEXT_BOX);
534        *this.text_box.borrow_mut() = text_box.clone();
535
536        if this.selected_date().is_none() {
537            this.set_water_mark_text();
538        }
539
540        if let Some(text_box) = &text_box {
541            let subscriptions = CompositeDisposable::new();
542            let weak = this.to_ref().downgrade();
543            subscriptions.add(text_box.add_disposable_handler(InputElement::key_down_event(), move |_, e| {
544                if let Some(this) = weak.upgrade() {
545                    this.text_box_key_down(e);
546                }
547            }));
548            let weak = this.to_ref().downgrade();
549            subscriptions.add(text_box.add_disposable_handler(InputElement::got_focus_event(), move |_, _| {
550                if let Some(this) = weak.upgrade() {
551                    this.text_box_got_focus();
552                }
553            }));
554            *this.text_box_subscriptions.borrow_mut() = Some(Rc::new(subscriptions));
555
556            let weak = this.to_ref().downgrade();
557            let object: &FerroObject = text_box;
558            let subscription =
559                FerroObjectExtensions::get_observable(object, TextBox::text_property()).subscribe_fn(move |_| {
560                    if let Some(this) = weak.upgrade() {
561                        this.text_box_text_changed();
562                    }
563                });
564            *this.text_box_text_changed_subscription.borrow_mut() = Some(subscription);
565
566            if let Some(selected_date) = this.selected_date() {
567                text_box.set_text(this.date_time_to_string(selected_date).as_deref());
568            } else {
569                let default_text = this.default_text.borrow().clone();
570                if !default_text.is_empty() {
571                    text_box.set_text(Some(&default_text));
572                    this.set_selected_date_from_text();
573                }
574            }
575        }
576
577        this.update_pseudo_classes();
578    }
579}
580
581impl ControlImpl for CalendarDatePicker {
582    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
583        crate::automation::peers::CalendarDatePickerAutomationPeer::new(this).upcast()
584    }
585}
586
587impl CalendarDatePickerImpl for CalendarDatePicker {
588    fn on_date_validation_error(this: &Self, e: &CalendarDatePickerDateValidationErrorEventArgs) {
589        for (_, handler) in this.date_validation_error.snapshot().iter() {
590            handler(e);
591        }
592    }
593}
594
595ferro_properties! {
596    impl CalendarDatePicker {
597        /// Defines the `DisplayDate` property.
598        pub fn display_date_property() -> StyledProperty<DateTime> {
599            FerroProperty::register::<CalendarDatePicker, _>("DisplayDate", DateTime::MIN_VALUE)
600        }
601
602        /// Defines the `DisplayDateStart` property.
603        pub fn display_date_start_property() -> StyledProperty<Option<DateTime>> {
604            FerroProperty::register::<CalendarDatePicker, _>("DisplayDateStart", None)
605        }
606
607        /// Defines the `DisplayDateEnd` property.
608        pub fn display_date_end_property() -> StyledProperty<Option<DateTime>> {
609            FerroProperty::register::<CalendarDatePicker, _>("DisplayDateEnd", None)
610        }
611
612        /// Defines the `FirstDayOfWeek` property.
613        pub fn first_day_of_week_property() -> StyledProperty<DayOfWeek> {
614            FerroProperty::register::<CalendarDatePicker, _>("FirstDayOfWeek", DayOfWeek::Sunday)
615        }
616
617        /// Defines the `IsDropDownOpen` property.
618        pub fn is_drop_down_open_property() -> StyledProperty<bool> {
619            FerroProperty::register::<CalendarDatePicker, _>("IsDropDownOpen", false)
620        }
621
622        /// Defines the `IsTodayHighlighted` property.
623        pub fn is_today_highlighted_property() -> StyledProperty<bool> {
624            FerroProperty::register::<CalendarDatePicker, _>("IsTodayHighlighted", false)
625        }
626
627        /// Defines the `SelectedDate` property.
628        pub fn selected_date_property() -> StyledProperty<Option<DateTime>> {
629            FerroProperty::register_with::<CalendarDatePicker, _>(
630                "SelectedDate",
631                StyledPropertyOptions::new(None).enable_data_validation(true).default_binding_mode(BindingMode::TwoWay),
632            )
633        }
634
635        /// Defines the `SelectedDateFormat` property.
636        pub fn selected_date_format_property() -> StyledProperty<CalendarDatePickerFormat> {
637            FerroProperty::register_with::<CalendarDatePicker, _>(
638                "SelectedDateFormat",
639                StyledPropertyOptions::new(CalendarDatePickerFormat::Short)
640                    .validate(CalendarDatePicker::is_valid_selected_date_format),
641            )
642        }
643
644        /// Defines the `CustomDateFormatString` property.
645        pub fn custom_date_format_string_property() -> StyledProperty<String> {
646            FerroProperty::register_with::<CalendarDatePicker, _>(
647                "CustomDateFormatString",
648                StyledPropertyOptions::new("d".to_string()).validate(CalendarDatePicker::is_valid_date_format_string),
649            )
650        }
651
652        /// Defines the `TextConverter` property.
653        pub fn text_converter_property() -> StyledProperty<Option<Rc<dyn IValueConverter>>> {
654            FerroProperty::register_with::<CalendarDatePicker, _>(
655                "TextConverter",
656                StyledPropertyOptions::new(None).default_binding_mode(BindingMode::OneWay),
657            )
658        }
659
660        /// Defines the `Text` property.
661        pub fn text_property() -> StyledProperty<Option<String>> {
662            FerroProperty::register::<CalendarDatePicker, _>("Text", None)
663        }
664
665        /// Defines the `PlaceholderText` property.
666        pub fn placeholder_text_property() -> StyledProperty<Option<String>> {
667            TextBox::placeholder_text_property().add_owner::<CalendarDatePicker>()
668        }
669
670        /// Defines the `UseFloatingPlaceholder` property.
671        pub fn use_floating_placeholder_property() -> StyledProperty<bool> {
672            TextBox::use_floating_placeholder_property().add_owner::<CalendarDatePicker>()
673        }
674
675        /// Defines the `PlaceholderForeground` property.
676        pub fn placeholder_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
677            TextBox::placeholder_foreground_property().add_owner::<CalendarDatePicker>()
678        }
679
680        /// Defines the `HorizontalContentAlignment` property.
681        pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
682            ContentControl::horizontal_content_alignment_property().add_owner::<CalendarDatePicker>()
683        }
684
685        /// Defines the `VerticalContentAlignment` property.
686        pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
687            ContentControl::vertical_content_alignment_property().add_owner::<CalendarDatePicker>()
688        }
689
690        /// Defines the `IsWeekNumberVisible` property.
691        pub fn is_week_number_visible_property() -> StyledProperty<bool> {
692            Calendar::is_week_number_visible_property().add_owner::<CalendarDatePicker>()
693        }
694
695        /// Defines the `WeekNumberRule` property.
696        pub fn week_number_rule_property() -> StyledProperty<CalendarWeekRule> {
697            Calendar::week_number_rule_property().add_owner::<CalendarDatePicker>()
698        }
699    }
700}
701
702impl CalendarDatePicker {
703    /// Defines the `Watermark` property.
704    #[deprecated(note = "Use placeholder_text_property instead.")]
705    pub fn watermark_property() -> &'static StyledProperty<Option<String>> {
706        Self::placeholder_text_property()
707    }
708
709    /// Defines the `UseFloatingWatermark` property.
710    #[deprecated(note = "Use use_floating_placeholder_property instead.")]
711    pub fn use_floating_watermark_property() -> &'static StyledProperty<bool> {
712        Self::use_floating_placeholder_property()
713    }
714
715    /// Defines the `WatermarkForeground` property.
716    #[deprecated(note = "Use placeholder_foreground_property instead.")]
717    pub fn watermark_foreground_property() -> &'static StyledProperty<Option<Rc<dyn IBrush>>> {
718        Self::placeholder_foreground_property()
719    }
720
721    fn static_constructor() {
722        InputElement::focusable_property().override_default_value::<CalendarDatePicker>(true);
723
724        // The picker forwards focus to its text box, so as a tab stop it would trap Shift+Tab.
725        InputElement::is_tab_stop_property().override_default_value::<CalendarDatePicker>(false);
726    }
727
728    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
729    pub fn construct() -> Self {
730        Self {
731            base: TemplatedControl::construct(),
732            calendar: RefCell::new(None),
733            calendar_subscriptions: RefCell::new(None),
734            default_text: RefCell::new(String::new()),
735            drop_down_button: RefCell::new(None),
736            drop_down_button_click_subscription: RefCell::new(None),
737            pop_up: RefCell::new(None),
738            pop_up_closed_subscription: RefCell::new(None),
739            text_box: RefCell::new(None),
740            text_box_subscriptions: RefCell::new(None),
741            text_box_text_changed_subscription: RefCell::new(None),
742            button_pointer_pressed_subscription: RefCell::new(None),
743            on_open_selected_date: Cell::new(None),
744            setting_selected_date: Cell::new(false),
745            suspend_text_change_handler: Cell::new(false),
746            is_popup_closing: Cell::new(false),
747            ignore_button_click: Cell::new(false),
748            is_flyout_open: Cell::new(false),
749            is_pressed: Cell::new(false),
750            blackout_dates: RefCell::new(None),
751            calendar_closed: HandlerList::new(),
752            calendar_opened: HandlerList::new(),
753            date_validation_error: HandlerList::new(),
754            selected_date_changed: HandlerList::new(),
755        }
756    }
757
758    /// Initializes a new instance of the [`CalendarDatePicker`] class.
759    pub fn new() -> Ref<Self> {
760        instantiate(Self::construct())
761    }
762
763    fn calendar(&self) -> Option<Ref<Calendar>> {
764        self.calendar.borrow().clone()
765    }
766
767    fn text_box(&self) -> Option<Ref<TextBox>> {
768        self.text_box.borrow().clone()
769    }
770
771    // --- properties -----------------------------------------------------------
772
773    /// Gets a collection of dates that are marked as not selectable: the
774    /// collection of the calendar of the template, `None` before a
775    /// template with a calendar is applied.
776    pub fn blackout_dates(&self) -> Option<CalendarBlackoutDatesCollection> {
777        self.blackout_dates.borrow().clone()
778    }
779
780    /// Gets or sets the date to display.
781    pub fn display_date(&self) -> DateTime {
782        self.get_value(Self::display_date_property())
783    }
784
785    pub fn set_display_date(&self, value: DateTime) {
786        self.set_value(Self::display_date_property(), value)
787    }
788
789    /// Gets or sets the first date to be displayed.
790    pub fn display_date_start(&self) -> Option<DateTime> {
791        self.get_value(Self::display_date_start_property())
792    }
793
794    pub fn set_display_date_start(&self, value: Option<DateTime>) {
795        self.set_value(Self::display_date_start_property(), value)
796    }
797
798    /// Gets or sets the last date to be displayed.
799    pub fn display_date_end(&self) -> Option<DateTime> {
800        self.get_value(Self::display_date_end_property())
801    }
802
803    pub fn set_display_date_end(&self, value: Option<DateTime>) {
804        self.set_value(Self::display_date_end_property(), value)
805    }
806
807    /// Gets or sets the day that is considered the beginning of the week.
808    pub fn first_day_of_week(&self) -> DayOfWeek {
809        self.get_value(Self::first_day_of_week_property())
810    }
811
812    pub fn set_first_day_of_week(&self, value: DayOfWeek) {
813        self.set_value(Self::first_day_of_week_property(), value)
814    }
815
816    /// Gets or sets a value indicating whether the drop-down calendar is
817    /// open or closed.
818    pub fn is_drop_down_open(&self) -> bool {
819        self.get_value(Self::is_drop_down_open_property())
820    }
821
822    pub fn set_is_drop_down_open(&self, value: bool) {
823        self.set_value(Self::is_drop_down_open_property(), value)
824    }
825
826    /// Gets or sets a value indicating whether the current date will be
827    /// highlighted.
828    pub fn is_today_highlighted(&self) -> bool {
829        self.get_value(Self::is_today_highlighted_property())
830    }
831
832    pub fn set_is_today_highlighted(&self, value: bool) {
833        self.set_value(Self::is_today_highlighted_property(), value)
834    }
835
836    /// Gets or sets the currently selected date. The default is `None`.
837    ///
838    /// Setting it panics when the date is in the blackout dates.
839    pub fn selected_date(&self) -> Option<DateTime> {
840        self.get_value(Self::selected_date_property())
841    }
842
843    pub fn set_selected_date(&self, value: Option<DateTime>) {
844        self.set_value(Self::selected_date_property(), value)
845    }
846
847    /// Gets or sets the format that is used to display the selected date.
848    pub fn selected_date_format(&self) -> CalendarDatePickerFormat {
849        self.get_value(Self::selected_date_format_property())
850    }
851
852    pub fn set_selected_date_format(&self, value: CalendarDatePickerFormat) {
853        self.set_value(Self::selected_date_format_property(), value)
854    }
855
856    /// Gets or sets the format string of the custom date format.
857    pub fn custom_date_format_string(&self) -> String {
858        self.get_value(Self::custom_date_format_string_property())
859    }
860
861    pub fn set_custom_date_format_string(&self, value: &str) {
862        self.set_value(Self::custom_date_format_string_property(), value.to_string())
863    }
864
865    /// Gets or sets the converter between a date and its text. With a
866    /// converter the date format and the custom format string are not
867    /// used.
868    pub fn text_converter(&self) -> Option<Rc<dyn IValueConverter>> {
869        self.get_value(Self::text_converter_property())
870    }
871
872    pub fn set_text_converter(&self, value: Option<Rc<dyn IValueConverter>>) {
873        self.set_value(Self::text_converter_property(), value)
874    }
875
876    /// Gets or sets the text that is displayed by the date picker.
877    ///
878    /// Setting a text that is not in an acceptable format raises the
879    /// `DateValidationError` event (and panics when a handler asks for the
880    /// exception to be thrown).
881    pub fn text(&self) -> Option<String> {
882        self.get_value(Self::text_property())
883    }
884
885    pub fn set_text(&self, value: Option<&str>) {
886        self.set_value(Self::text_property(), value.map(str::to_owned))
887    }
888
889    /// Gets or sets the placeholder text.
890    pub fn placeholder_text(&self) -> Option<String> {
891        self.get_value(Self::placeholder_text_property())
892    }
893
894    pub fn set_placeholder_text(&self, value: Option<&str>) {
895        self.set_value(Self::placeholder_text_property(), value.map(str::to_owned))
896    }
897
898    /// Gets or sets the placeholder text.
899    #[deprecated(note = "Use placeholder_text instead.")]
900    pub fn watermark(&self) -> Option<String> {
901        self.placeholder_text()
902    }
903
904    #[deprecated(note = "Use set_placeholder_text instead.")]
905    pub fn set_watermark(&self, value: Option<&str>) {
906        self.set_placeholder_text(value)
907    }
908
909    /// Gets or sets a value indicating whether the placeholder floats
910    /// above the text.
911    pub fn use_floating_placeholder(&self) -> bool {
912        self.get_value(Self::use_floating_placeholder_property())
913    }
914
915    pub fn set_use_floating_placeholder(&self, value: bool) {
916        self.set_value(Self::use_floating_placeholder_property(), value)
917    }
918
919    /// Gets or sets a value indicating whether the placeholder floats
920    /// above the text.
921    #[deprecated(note = "Use use_floating_placeholder instead.")]
922    pub fn use_floating_watermark(&self) -> bool {
923        self.use_floating_placeholder()
924    }
925
926    #[deprecated(note = "Use set_use_floating_placeholder instead.")]
927    pub fn set_use_floating_watermark(&self, value: bool) {
928        self.set_use_floating_placeholder(value)
929    }
930
931    /// Gets or sets the brush used for the foreground color of the
932    /// placeholder text.
933    pub fn placeholder_foreground(&self) -> Option<Rc<dyn IBrush>> {
934        self.get_value(Self::placeholder_foreground_property())
935    }
936
937    pub fn set_placeholder_foreground(&self, value: Option<Rc<dyn IBrush>>) {
938        self.set_value(Self::placeholder_foreground_property(), value)
939    }
940
941    /// Gets or sets the brush used for the foreground color of the
942    /// placeholder text.
943    #[deprecated(note = "Use placeholder_foreground instead.")]
944    pub fn watermark_foreground(&self) -> Option<Rc<dyn IBrush>> {
945        self.placeholder_foreground()
946    }
947
948    #[deprecated(note = "Use set_placeholder_foreground instead.")]
949    pub fn set_watermark_foreground(&self, value: Option<Rc<dyn IBrush>>) {
950        self.set_placeholder_foreground(value)
951    }
952
953    /// Gets or sets the horizontal alignment of the content within the
954    /// control.
955    pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
956        self.get_value(Self::horizontal_content_alignment_property())
957    }
958
959    pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
960        self.set_value(Self::horizontal_content_alignment_property(), value)
961    }
962
963    /// Gets or sets the vertical alignment of the content within the
964    /// control.
965    pub fn vertical_content_alignment(&self) -> VerticalAlignment {
966        self.get_value(Self::vertical_content_alignment_property())
967    }
968
969    pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
970        self.set_value(Self::vertical_content_alignment_property(), value)
971    }
972
973    /// Gets or sets a value indicating whether week numbers are shown in
974    /// the calendar popup.
975    pub fn is_week_number_visible(&self) -> bool {
976        self.get_value(Self::is_week_number_visible_property())
977    }
978
979    pub fn set_is_week_number_visible(&self, value: bool) {
980        self.set_value(Self::is_week_number_visible_property(), value)
981    }
982
983    /// Gets or sets the rule used to determine the first week of the year
984    /// for week number display. The default is taken from the culture that
985    /// was current when the calendar class was initialised.
986    pub fn week_number_rule(&self) -> CalendarWeekRule {
987        self.get_value(Self::week_number_rule_property())
988    }
989
990    pub fn set_week_number_rule(&self, value: CalendarWeekRule) {
991        self.set_value(Self::week_number_rule_property(), value)
992    }
993
994    // --- events ---------------------------------------------------------------
995
996    fn subscribe<F: ?Sized + 'static>(
997        &self,
998        list: fn(&CalendarDatePicker) -> &HandlerList<F>,
999        handler: Rc<F>,
1000    ) -> Rc<dyn IDisposable> {
1001        let token = list(self).add(handler);
1002        let weak = self.to_ref().downgrade();
1003        Disposable::create(move || {
1004            if let Some(this) = weak.upgrade() {
1005                list(&this).remove(token);
1006            }
1007        })
1008    }
1009
1010    /// Occurs when the drop-down [`Calendar`] is closed.
1011    pub fn calendar_closed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
1012        self.subscribe::<dyn Fn()>(|picker| &picker.calendar_closed, Rc::new(handler))
1013    }
1014
1015    /// Occurs when the drop-down [`Calendar`] is opened.
1016    pub fn calendar_opened(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
1017        self.subscribe::<dyn Fn()>(|picker| &picker.calendar_opened, Rc::new(handler))
1018    }
1019
1020    /// Occurs when the text is assigned a value that cannot be interpreted
1021    /// as a date.
1022    pub fn date_validation_error(
1023        &self,
1024        handler: impl Fn(&CalendarDatePickerDateValidationErrorEventArgs) + 'static,
1025    ) -> Rc<dyn IDisposable> {
1026        self.subscribe::<dyn Fn(&CalendarDatePickerDateValidationErrorEventArgs)>(
1027            |picker| &picker.date_validation_error,
1028            Rc::new(handler),
1029        )
1030    }
1031
1032    /// Occurs when the selected date is changed.
1033    pub fn selected_date_changed(
1034        &self,
1035        handler: impl Fn(&SelectionChangedEventArgs) + 'static,
1036    ) -> Rc<dyn IDisposable> {
1037        self.subscribe::<dyn Fn(&SelectionChangedEventArgs)>(|picker| &picker.selected_date_changed, Rc::new(handler))
1038    }
1039
1040    // --- the control ------------------------------------------------------------
1041
1042    /// Updates the visual state of the control by applying the latest
1043    /// pseudo classes.
1044    pub fn update_pseudo_classes(&self) {
1045        self.pseudo_classes().set(PC_FLYOUT_OPEN, self.is_flyout_open.get());
1046        self.pseudo_classes().set(PC_PRESSED, self.is_pressed.get());
1047    }
1048
1049    fn on_date_format_changed(&self) {
1050        if let Some(text_box) = self.text_box() {
1051            if let Some(selected_date) = self.selected_date() {
1052                self.set_current_value(Self::text_property(), self.date_time_to_string(selected_date));
1053            } else {
1054                let text = text_box.text().unwrap_or_default();
1055                if text.is_empty() {
1056                    self.set_water_mark_text();
1057                } else if let Some(date) = self.parse_text(&text) {
1058                    let s = self.date_time_to_string(date);
1059                    self.set_current_value(Self::text_property(), s);
1060                }
1061            }
1062        }
1063    }
1064
1065    fn on_date_selected(&self, added_date: Option<DateTime>, removed_date: Option<DateTime>) {
1066        let handlers = self.selected_date_changed.snapshot();
1067        if !handlers.is_empty() {
1068            let boxed = |date: Option<DateTime>| -> Vec<Option<BoxedValue>> {
1069                date.map(|date| Some(Rc::new(date) as BoxedValue)).into_iter().collect()
1070            };
1071
1072            let e = SelectionChangedEventArgs::new(
1073                Some(SelectingItemsControl::selection_changed_event()),
1074                boxed(removed_date),
1075                boxed(added_date),
1076            );
1077            for (_, handler) in handlers.iter() {
1078                handler(&e);
1079            }
1080        }
1081    }
1082
1083    fn on_calendar_closed(&self) {
1084        for (_, handler) in self.calendar_closed.snapshot().iter() {
1085            handler();
1086        }
1087    }
1088
1089    fn on_calendar_opened(&self) {
1090        for (_, handler) in self.calendar_opened.snapshot().iter() {
1091            handler();
1092        }
1093    }
1094
1095    fn calendar_day_button_mouse_up(&self) {
1096        self.focus();
1097        self.set_current_value(Self::is_drop_down_open_property(), false);
1098    }
1099
1100    fn calendar_display_date_changed(&self, e: &CalendarDateChangedEventArgs) {
1101        if e.added_date() != Some(self.display_date()) {
1102            self.set_value(Self::display_date_property(), e.added_date().expect("Nullable object must have a value."));
1103        }
1104    }
1105
1106    fn calendar_selected_dates_changed(&self, e: &SelectionChangedEventArgs) {
1107        debug_assert!(e.added_items().len() < 2, "There should be less than 2 AddedItems!");
1108
1109        let added_date = e.added_items().first().map(|item| {
1110            item.as_ref()
1111                .and_then(|item| item.downcast_ref::<DateTime>().copied())
1112                .expect("the added item of the calendar is a date")
1113        });
1114        let selected_date = self.selected_date();
1115
1116        match (added_date, selected_date) {
1117            (Some(added_date), Some(selected_date)) if DateTime::compare(added_date, selected_date) != 0 => {
1118                self.set_current_value(Self::selected_date_property(), Some(added_date));
1119            }
1120            (None, _) => {
1121                self.set_current_value(Self::selected_date_property(), None);
1122            }
1123            (Some(added_date), None) => {
1124                self.set_current_value(Self::selected_date_property(), Some(added_date));
1125            }
1126            _ => {}
1127        }
1128    }
1129
1130    fn calendar_pointer_released(e: &PointerReleasedEventArgs) {
1131        if e.initial_press_mouse_button() == MouseButton::Left {
1132            e.set_handled(true);
1133        }
1134    }
1135
1136    fn calendar_key_down(&self, sender: &Interactive, e: &KeyEventArgs) {
1137        if !e.handled()
1138            && sender.to_ref().cast::<Calendar>().is_some_and(|calendar| calendar.display_mode() == CalendarMode::Month)
1139            && (e.key == Key::Enter || e.key == Key::Space || e.key == Key::Escape)
1140        {
1141            self.focus();
1142            self.set_current_value(Self::is_drop_down_open_property(), false);
1143
1144            if e.key == Key::Escape {
1145                self.set_current_value(Self::selected_date_property(), self.on_open_selected_date.get());
1146            }
1147        }
1148    }
1149
1150    fn text_box_got_focus(&self) {
1151        self.set_current_value(Self::is_drop_down_open_property(), false);
1152    }
1153
1154    fn text_box_key_down(&self, e: &KeyEventArgs) {
1155        if !e.handled() {
1156            e.set_handled(self.process_date_picker_key(e));
1157        }
1158    }
1159
1160    fn text_box_text_changed(&self) {
1161        if let Some(text_box) = self.text_box() {
1162            self.suspend_text_change_handler.set(true);
1163            self.set_current_value(Self::text_property(), text_box.text());
1164            self.suspend_text_change_handler.set(false);
1165        }
1166    }
1167
1168    fn drop_down_button_pointer_pressed(&self, e: &PointerPressedEventArgs) {
1169        let drop_down_button = self.drop_down_button.borrow().clone();
1170        let drop_down_button_visual: Option<&Visual> = match &drop_down_button {
1171            Some(button) => Some(button),
1172            None => None,
1173        };
1174        if self.is_flyout_open.get()
1175            && drop_down_button.as_ref().is_some_and(|button| button.is_effectively_enabled())
1176            && e.get_current_point(drop_down_button_visual).properties.is_left_button_pressed
1177        {
1178            // When a flyout is open with OverlayDismissEventPassThrough enabled and the drop-down button
1179            // is pressed, close the flyout
1180            self.ignore_button_click.set(true);
1181            e.set_handled(true);
1182            self.toggle_pop_up();
1183        } else {
1184            self.ignore_button_click.set(self.is_popup_closing.get());
1185
1186            self.is_pressed.set(true);
1187            self.update_pseudo_classes();
1188        }
1189    }
1190
1191    fn drop_down_button_pointer_released(&self) {
1192        self.is_pressed.set(false);
1193        self.update_pseudo_classes();
1194    }
1195
1196    fn drop_down_button_click(&self) {
1197        if !self.ignore_button_click.get() {
1198            self.toggle_pop_up();
1199        } else {
1200            self.ignore_button_click.set(false);
1201        }
1202    }
1203
1204    fn pop_up_closed(&self) {
1205        self.set_current_value(Self::is_drop_down_open_property(), false);
1206
1207        if !self.is_popup_closing.get() {
1208            self.is_popup_closing.set(true);
1209            let picker = self.to_ref();
1210            let _ = Dispatcher::ui_thread().invoke_async_local(move || picker.is_popup_closing.set(false));
1211        }
1212    }
1213
1214    fn toggle_pop_up(&self) {
1215        if self.is_drop_down_open() {
1216            self.focus();
1217            self.set_current_value(Self::is_drop_down_open_property(), false);
1218        } else {
1219            self.set_selected_date_from_text();
1220            self.set_current_value(Self::is_drop_down_open_property(), true);
1221            self.calendar().expect("the date picker has a calendar").focus();
1222        }
1223    }
1224
1225    fn open_drop_down(&self) {
1226        if let Some(calendar) = self.calendar() {
1227            calendar.focus();
1228
1229            // Open the PopUp
1230            self.on_open_selected_date.set(self.selected_date());
1231            let pop_up = self.pop_up.borrow().clone().expect("the date picker has a popup");
1232            pop_up.set_is_open(true);
1233            self.is_flyout_open.set(pop_up.is_open());
1234
1235            self.update_pseudo_classes();
1236            calendar.reset_states();
1237            self.on_calendar_opened();
1238        }
1239    }
1240
1241    /// Input text is parsed in the correct format and changed into a
1242    /// DateTime object.  If the text can not be parsed TextParseError Event
1243    /// is thrown.
1244    ///
1245    /// Returns `None` if the text can not be parsed, or the date that is
1246    /// represented by the text.
1247    fn parse_text(&self, text: &str) -> Option<DateTime> {
1248        // The parse result keeps the error apart in order to be able to
1249        // pass it to the DateValidationError event
1250        match self.convert_text(text) {
1251            Ok(new_selected_date) => {
1252                let is_valid = match (self.calendar(), new_selected_date) {
1253                    (_, None) => true,
1254                    (Some(calendar), new_selected_date) => Calendar::is_valid_date_selection(&calendar, new_selected_date),
1255                    (None, Some(_)) => panic!("Object reference not set to an instance of an object."),
1256                };
1257
1258                if is_valid {
1259                    return new_selected_date;
1260                }
1261
1262                let date_validation_error = CalendarDatePickerDateValidationErrorEventArgs::new(
1263                    Rc::new(ArgumentOutOfRangeError { message: "SelectedDate value is not valid.", parameter: "text" }),
1264                    text,
1265                );
1266                self.on_date_validation_error(&date_validation_error);
1267
1268                if date_validation_error.throw_exception() {
1269                    panic!("{}", date_validation_error.exception());
1270                }
1271            }
1272            Err(error) => {
1273                let text_parse_error = CalendarDatePickerDateValidationErrorEventArgs::new(error, text);
1274                self.on_date_validation_error(&text_parse_error);
1275
1276                if text_parse_error.throw_exception() {
1277                    panic!("{}", text_parse_error.exception());
1278                }
1279            }
1280        }
1281
1282        None
1283    }
1284
1285    /// Converts a text to a date with the text converter, or else with the
1286    /// date format of the control and the conventions of the current
1287    /// culture.
1288    fn convert_text(&self, text: &str) -> ParseResult {
1289        if let Some(text_converter) = self.text_converter() {
1290            let boxed: BoxedValue = Rc::new(text.to_string());
1291            return match text_converter.convert_back(Some(&boxed), ValueType::of::<Option<DateTime>>(), None, &ferroui_base::utilities::CultureInfo::current_culture()) {
1292                Ok(value) => Ok(value.and_then(|value| {
1293                    value
1294                        .downcast_ref::<DateTime>()
1295                        .copied()
1296                        .or_else(|| value.downcast_ref::<Option<DateTime>>().copied().flatten())
1297                })),
1298                // A text that is not in a correct format is reported as the
1299                // parse error of the control; any other error is not
1300                // handled, as in the reference.
1301                Err(error) => match error.inner().downcast_ref::<FormatError>() {
1302                    Some(format_error) => Err(Rc::new(format_error.clone())),
1303                    None => panic!("{error}"),
1304                },
1305            };
1306        }
1307
1308        let date_format = DateTimeHelper::get_current_date_format();
1309        let custom_date_format_string = self.custom_date_format_string();
1310        let parsed = if self.selected_date_format() == CalendarDatePickerFormat::Custom
1311            && !custom_date_format_string.is_empty()
1312        {
1313            DateTime::parse_exact(text, &custom_date_format_string, &date_format)
1314        } else {
1315            DateTime::parse(text, &date_format)
1316        };
1317
1318        match parsed {
1319            Ok(date) => Ok(Some(date)),
1320            Err(error) => Err(Rc::new(error)),
1321        }
1322    }
1323
1324    fn date_time_to_string(&self, d: DateTime) -> Option<String> {
1325        if let Some(text_converter) = self.text_converter() {
1326            let boxed: BoxedValue = Rc::new(d);
1327            return match text_converter.convert(Some(&boxed), ValueType::of::<String>(), None, &ferroui_base::utilities::CultureInfo::current_culture()) {
1328                Ok(value) => value.and_then(|value| {
1329                    value
1330                        .downcast_ref::<String>()
1331                        .cloned()
1332                        .or_else(|| value.downcast_ref::<Option<String>>().cloned().flatten())
1333                }),
1334                Err(error) => panic!("{error}"),
1335            };
1336        }
1337
1338        let date_format = DateTimeHelper::get_current_date_format();
1339        let text = match self.selected_date_format() {
1340            CalendarDatePickerFormat::Short => d.to_string_format(date_format.short_date_pattern(), &date_format),
1341            CalendarDatePickerFormat::Long => d.to_string_format(date_format.long_date_pattern(), &date_format),
1342            CalendarDatePickerFormat::Custom => d.to_string_format(&self.custom_date_format_string(), &date_format),
1343        };
1344
1345        match format_without_arguments(&text) {
1346            Ok(text) => Some(text),
1347            Err(error) => panic!("{error}"),
1348        }
1349    }
1350
1351    fn process_date_picker_key(&self, e: &KeyEventArgs) -> bool {
1352        match e.key {
1353            Key::Enter => {
1354                self.set_selected_date_from_text();
1355                true
1356            }
1357            Key::Down => {
1358                if e.key_modifiers.contains(KeyModifiers::CONTROL) {
1359                    self.toggle_pop_up();
1360                    return true;
1361                }
1362                false
1363            }
1364            _ => false,
1365        }
1366    }
1367
1368    /// Sets the selected date from the text of the text box (or, without a
1369    /// text box, from the text that was set before the template was
1370    /// applied): the `SetSelectedDate` of the reference.
1371    fn set_selected_date_from_text(&self) {
1372        if let Some(text_box) = self.text_box() {
1373            let s = text_box.text().unwrap_or_default();
1374            if !s.is_empty() {
1375                if let Some(selected_date) = self.selected_date() {
1376                    // If the string value of the SelectedDate and the
1377                    // TextBox string value are equal, we do not parse the
1378                    // string again if we do an extra parse, we lose data in
1379                    // M/d/yy format.
1380                    // ex: SelectedDate = DateTime(1008,12,19) but when
1381                    // "12/19/08" is parsed it is interpreted as
1382                    // DateTime(2008,12,19)
1383                    let selected_date = self.date_time_to_string(selected_date);
1384                    if selected_date.as_deref() == Some(s.as_str()) {
1385                        return;
1386                    }
1387                }
1388                let d = self.set_text_box_value(&s);
1389
1390                if self.selected_date() != d {
1391                    self.set_current_value(Self::selected_date_property(), d);
1392                }
1393            } else if self.selected_date().is_some() {
1394                self.set_current_value(Self::selected_date_property(), None);
1395            }
1396        } else {
1397            let default_text = self.default_text.borrow().clone();
1398            let d = self.set_text_box_value(&default_text);
1399
1400            if self.selected_date() != d {
1401                self.set_current_value(Self::selected_date_property(), d);
1402            }
1403        }
1404    }
1405
1406    fn set_text_box_value(&self, s: &str) -> Option<DateTime> {
1407        if s.is_empty() {
1408            self.set_value(Self::text_property(), Some(s.to_string()));
1409            return self.selected_date();
1410        }
1411
1412        match self.parse_text(s) {
1413            Some(d) => {
1414                // make sure displayed text is reformatted to correct date format
1415                let new_text = self.date_time_to_string(d);
1416                self.set_value(Self::text_property(), new_text);
1417                Some(d)
1418            }
1419            None => {
1420                // If parse error: TextBox should have the latest valid
1421                // SelectedDate value:
1422                match self.selected_date() {
1423                    Some(selected_date) => {
1424                        let new_text = self.date_time_to_string(selected_date);
1425                        self.set_value(Self::text_property(), new_text);
1426                        Some(selected_date)
1427                    }
1428                    None => {
1429                        self.set_water_mark_text();
1430                        None
1431                    }
1432                }
1433            }
1434        }
1435    }
1436
1437    fn set_water_mark_text(&self) {
1438        if let Some(text_box) = self.text_box() {
1439            self.set_current_value(Self::text_property(), Some(String::new()));
1440
1441            if self.placeholder_text().is_none_or(|text| text.is_empty()) && !self.use_floating_placeholder() {
1442                let date_format = DateTimeHelper::get_current_date_format();
1443                self.default_text.borrow_mut().clear();
1444
1445                let placeholder_text = match self.selected_date_format() {
1446                    CalendarDatePickerFormat::Custom => format!("<{}>", self.custom_date_format_string()),
1447                    CalendarDatePickerFormat::Long => format!("<{}>", date_format.long_date_pattern()),
1448                    CalendarDatePickerFormat::Short => format!("<{}>", date_format.short_date_pattern()),
1449                };
1450                text_box.set_placeholder_text(Some(&placeholder_text));
1451            } else {
1452                text_box.clear_value(TextBox::placeholder_text_property());
1453            }
1454        }
1455    }
1456
1457    fn is_valid_selected_date_format(value: &CalendarDatePickerFormat) -> bool {
1458        matches!(
1459            value,
1460            CalendarDatePickerFormat::Long | CalendarDatePickerFormat::Short | CalendarDatePickerFormat::Custom
1461        )
1462    }
1463
1464    #[allow(clippy::ptr_arg)] // the signature of a property validator
1465    fn is_valid_date_format_string(format_string: &String) -> bool {
1466        !format_string.trim().is_empty()
1467    }
1468
1469    /// Clear the date picker.
1470    pub fn clear(&self) {
1471        self.set_current_value(Self::selected_date_property(), None);
1472    }
1473}