1use 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#[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
70type ParseResult = Result<Option<DateTime>, Rc<dyn Error>>;
73
74fn utf16_length(text: &str) -> i32 {
77 text.encode_utf16().count() as i32
78}
79
80fn 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#[repr(C)]
104pub struct CalendarDatePicker {
105 base: TemplatedControl,
106
107 calendar: RefCell<Option<Ref<Calendar>>>,
108 calendar_subscriptions: RefCell<Option<Rc<dyn IDisposable>>>,
110 default_text: RefCell<String>,
111 drop_down_button: RefCell<Option<Ref<Button>>>,
112 drop_down_button_click_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
114 pop_up: RefCell<Option<Ref<Popup>>>,
115 pop_up_closed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
117 text_box: RefCell<Option<Ref<TextBox>>>,
118 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 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 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 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 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 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 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 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 else if property == Self::selected_date_format_property().as_property() {
253 this.on_date_format_changed();
254 }
255 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 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 } 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 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 pub fn display_date_property() -> StyledProperty<DateTime> {
599 FerroProperty::register::<CalendarDatePicker, _>("DisplayDate", DateTime::MIN_VALUE)
600 }
601
602 pub fn display_date_start_property() -> StyledProperty<Option<DateTime>> {
604 FerroProperty::register::<CalendarDatePicker, _>("DisplayDateStart", None)
605 }
606
607 pub fn display_date_end_property() -> StyledProperty<Option<DateTime>> {
609 FerroProperty::register::<CalendarDatePicker, _>("DisplayDateEnd", None)
610 }
611
612 pub fn first_day_of_week_property() -> StyledProperty<DayOfWeek> {
614 FerroProperty::register::<CalendarDatePicker, _>("FirstDayOfWeek", DayOfWeek::Sunday)
615 }
616
617 pub fn is_drop_down_open_property() -> StyledProperty<bool> {
619 FerroProperty::register::<CalendarDatePicker, _>("IsDropDownOpen", false)
620 }
621
622 pub fn is_today_highlighted_property() -> StyledProperty<bool> {
624 FerroProperty::register::<CalendarDatePicker, _>("IsTodayHighlighted", false)
625 }
626
627 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 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 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 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 pub fn text_property() -> StyledProperty<Option<String>> {
662 FerroProperty::register::<CalendarDatePicker, _>("Text", None)
663 }
664
665 pub fn placeholder_text_property() -> StyledProperty<Option<String>> {
667 TextBox::placeholder_text_property().add_owner::<CalendarDatePicker>()
668 }
669
670 pub fn use_floating_placeholder_property() -> StyledProperty<bool> {
672 TextBox::use_floating_placeholder_property().add_owner::<CalendarDatePicker>()
673 }
674
675 pub fn placeholder_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
677 TextBox::placeholder_foreground_property().add_owner::<CalendarDatePicker>()
678 }
679
680 pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
682 ContentControl::horizontal_content_alignment_property().add_owner::<CalendarDatePicker>()
683 }
684
685 pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
687 ContentControl::vertical_content_alignment_property().add_owner::<CalendarDatePicker>()
688 }
689
690 pub fn is_week_number_visible_property() -> StyledProperty<bool> {
692 Calendar::is_week_number_visible_property().add_owner::<CalendarDatePicker>()
693 }
694
695 pub fn week_number_rule_property() -> StyledProperty<CalendarWeekRule> {
697 Calendar::week_number_rule_property().add_owner::<CalendarDatePicker>()
698 }
699 }
700}
701
702impl CalendarDatePicker {
703 #[deprecated(note = "Use placeholder_text_property instead.")]
705 pub fn watermark_property() -> &'static StyledProperty<Option<String>> {
706 Self::placeholder_text_property()
707 }
708
709 #[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 #[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 InputElement::is_tab_stop_property().override_default_value::<CalendarDatePicker>(false);
726 }
727
728 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 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 pub fn blackout_dates(&self) -> Option<CalendarBlackoutDatesCollection> {
777 self.blackout_dates.borrow().clone()
778 }
779
780 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 #[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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 fn parse_text(&self, text: &str) -> Option<DateTime> {
1248 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 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 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 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 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 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 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)] fn is_valid_date_format_string(format_string: &String) -> bool {
1466 !format_string.trim().is_empty()
1467 }
1468
1469 pub fn clear(&self) {
1471 self.set_current_value(Self::selected_date_property(), None);
1472 }
1473}