Skip to main content

ferroui_controls/date_time_pickers/
date_picker_presenter.rs

1use super::date_picker::index_of_ignore_case;
2use super::{
3    DatePicker, DateTimePickerPanel, DateTimePickerPanelType, PickerPresenterBase, PickerPresenterBaseImpl,
4};
5use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt};
6use crate::{Button, ColumnDefinition, Control, ControlImpl, Grid, GridUnitType, Panel, SpinDirection};
7use ferroui_base::input::{
8    FocusManager, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyModifiers, KeyboardNavigation, KeyboardNavigationHandler,
9    KeyboardNavigationMode, NavigationDirection, NavigationMethod,
10};
11use ferroui_base::interactivity::InteractiveImpl;
12use ferroui_base::layout::LayoutableImpl;
13use ferroui_base::utilities::{CultureInfo, DateTimeOffset, GregorianCalendar};
14use ferroui_base::{
15    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt,
16    FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty, StyledPropertyOptions,
17    VisualImpl,
18};
19use std::cell::{Cell, RefCell};
20
21const PICKER_CONTAINER_NAME: &str = "PART_PickerContainer";
22const ACCEPT_BUTTON_NAME: &str = "PART_AcceptButton";
23const DISMISS_BUTTON_NAME: &str = "PART_DismissButton";
24const FIRST_SPACER_NAME: &str = "PART_FirstSpacer";
25const SECOND_SPACER_NAME: &str = "PART_SecondSpacer";
26const MONTH_HOST_NAME: &str = "PART_MonthHost";
27const YEAR_HOST_NAME: &str = "PART_YearHost";
28const DAY_HOST_NAME: &str = "PART_DayHost";
29const MONTH_SELECTOR_NAME: &str = "PART_MonthSelector";
30const YEAR_SELECTOR_NAME: &str = "PART_YearSelector";
31const DAY_SELECTOR_NAME: &str = "PART_DaySelector";
32const MONTH_UP_BUTTON_NAME: &str = "PART_MonthUpButton";
33const DAY_UP_BUTTON_NAME: &str = "PART_DayUpButton";
34const YEAR_UP_BUTTON_NAME: &str = "PART_YearUpButton";
35const MONTH_DOWN_BUTTON_NAME: &str = "PART_MonthDownButton";
36const DAY_DOWN_BUTTON_NAME: &str = "PART_DayDownButton";
37const YEAR_DOWN_BUTTON_NAME: &str = "PART_YearDownButton";
38
39/// The parts of the control template.
40#[derive(Clone)]
41#[allow(dead_code)]
42struct TemplateItems {
43    picker_container: Ref<Grid>,
44    accept_button: Ref<Button>,
45    dismiss_button: Option<Ref<Button>>,
46    first_spacer: Option<Ref<Control>>,
47    second_spacer: Option<Ref<Control>>,
48    month_host: Ref<Panel>,
49    year_host: Ref<Panel>,
50    day_host: Ref<Panel>,
51    month_selector: Ref<DateTimePickerPanel>,
52    year_selector: Ref<DateTimePickerPanel>,
53    day_selector: Ref<DateTimePickerPanel>,
54    month_up_button: Option<Ref<Button>>,
55    day_up_button: Option<Ref<Button>>,
56    year_up_button: Option<Ref<Button>>,
57    month_down_button: Option<Ref<Button>>,
58    day_down_button: Option<Ref<Button>>,
59    year_down_button: Option<Ref<Button>>,
60}
61
62/// Defines the presenter used for selecting a date for a [`DatePicker`].
63#[repr(C)]
64pub struct DatePickerPresenter {
65    base: PickerPresenterBase,
66    template_items: RefCell<Option<TemplateItems>>,
67    sync_date: Cell<DateTimeOffset>,
68    calendar: GregorianCalendar,
69    suppress_update_selection: Cell<bool>,
70}
71
72ferro_class!(DatePickerPresenter: PickerPresenterBase);
73
74ferro_class_info!(DatePickerPresenter {
75    new: DatePickerPresenter::new,
76    markup: {
77        attributes: [
78            TemplatePart("PART_AcceptButton", type(Ref<Button>), IsRequired = true),
79            TemplatePart("PART_DayDownButton", type(Ref<Button>)),
80            TemplatePart("PART_DayHost", type(Ref<Panel>), IsRequired = true),
81            TemplatePart("PART_DaySelector", type(Ref<DateTimePickerPanel>), IsRequired = true),
82            TemplatePart("PART_DayUpButton", type(Ref<Button>)),
83            TemplatePart("PART_DismissButton", type(Ref<Button>)),
84            TemplatePart("PART_FirstSpacer", type(Ref<Control>)),
85            TemplatePart("PART_MonthDownButton", type(Ref<Button>)),
86            TemplatePart("PART_MonthHost", type(Ref<Panel>), IsRequired = true),
87            TemplatePart("PART_MonthSelector", type(Ref<DateTimePickerPanel>), IsRequired = true),
88            TemplatePart("PART_MonthUpButton", type(Ref<Button>)),
89            TemplatePart("PART_PickerContainer", type(Ref<Grid>), IsRequired = true),
90            TemplatePart("PART_SecondSpacer", type(Ref<Control>)),
91            TemplatePart("PART_YearDownButton", type(Ref<Button>)),
92            TemplatePart("PART_YearHost", type(Ref<Panel>), IsRequired = true),
93            TemplatePart("PART_YearSelector", type(Ref<DateTimePickerPanel>), IsRequired = true),
94            TemplatePart("PART_YearUpButton", type(Ref<Button>)),
95        ],
96    },
97});
98
99ferro_impl_classes!(
100    DatePickerPresenter: StyledElementImpl,
101    VisualImpl,
102    LayoutableImpl,
103    InteractiveImpl,
104    ControlImpl,
105    PickerPresenterBaseImpl
106);
107
108impl FerroObjectImpl for DatePickerPresenter {
109    fn constructed(this: &Self) {
110        Self::parent_constructed(this);
111
112        let now = DateTimeOffset::now();
113        this.set_current_value(
114            Self::min_year_property(),
115            DateTimeOffset::new(now.year() - 100, 1, 1, 0, 0, 0, now.offset()),
116        );
117        this.set_current_value(
118            Self::max_year_property(),
119            DateTimeOffset::new(now.year() + 100, 12, 31, 0, 0, 0, now.offset()),
120        );
121        this.set_current_value(Self::date_property(), now);
122    }
123
124    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
125        Self::parent_on_property_changed(this, change);
126
127        let property = change.property();
128        if property == Self::date_property().as_property() {
129            this.on_date_changed(change.get_new_value::<DateTimeOffset>());
130        } else if property == Self::max_year_property().as_property()
131            || property == Self::min_year_property().as_property()
132        {
133            this.on_date_range_changed();
134        } else if property == Self::month_format_property().as_property()
135            || property == Self::year_format_property().as_property()
136            || property == Self::day_format_property().as_property()
137        {
138            this.init_picker();
139        }
140    }
141}
142
143impl TemplatedControlImpl for DatePickerPresenter {
144    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
145        Self::parent_on_apply_template(this, e);
146
147        let name_scope = e.name_scope();
148        let weak_this = this.to_ref().downgrade();
149        let selector_button = |name: &str, type_: DateTimePickerPanelType, direction: SpinDirection| {
150            let button = name_scope.find_as::<Button>(name)?;
151            let weak = weak_this.clone();
152            button.click(move |_, _| {
153                if let Some(this) = weak.upgrade() {
154                    this.on_selector_button_click(type_, direction);
155                }
156            });
157            Some(button)
158        };
159
160        let items = TemplateItems {
161            // These are requirements, so panic if not found
162            picker_container: name_scope.get_as::<Grid>(PICKER_CONTAINER_NAME),
163            month_host: name_scope.get_as::<Panel>(MONTH_HOST_NAME),
164            day_host: name_scope.get_as::<Panel>(DAY_HOST_NAME),
165            year_host: name_scope.get_as::<Panel>(YEAR_HOST_NAME),
166
167            month_selector: name_scope.get_as::<DateTimePickerPanel>(MONTH_SELECTOR_NAME),
168            day_selector: name_scope.get_as::<DateTimePickerPanel>(DAY_SELECTOR_NAME),
169            year_selector: name_scope.get_as::<DateTimePickerPanel>(YEAR_SELECTOR_NAME),
170
171            accept_button: name_scope.get_as::<Button>(ACCEPT_BUTTON_NAME),
172
173            month_up_button: selector_button(MONTH_UP_BUTTON_NAME, DateTimePickerPanelType::Month, SpinDirection::Decrease),
174            month_down_button: selector_button(
175                MONTH_DOWN_BUTTON_NAME,
176                DateTimePickerPanelType::Month,
177                SpinDirection::Increase,
178            ),
179            day_up_button: selector_button(DAY_UP_BUTTON_NAME, DateTimePickerPanelType::Day, SpinDirection::Decrease),
180            day_down_button: selector_button(DAY_DOWN_BUTTON_NAME, DateTimePickerPanelType::Day, SpinDirection::Increase),
181            year_up_button: selector_button(YEAR_UP_BUTTON_NAME, DateTimePickerPanelType::Year, SpinDirection::Decrease),
182            year_down_button: selector_button(
183                YEAR_DOWN_BUTTON_NAME,
184                DateTimePickerPanelType::Year,
185                SpinDirection::Increase,
186            ),
187
188            dismiss_button: name_scope.find_as::<Button>(DISMISS_BUTTON_NAME),
189            first_spacer: name_scope.find_as::<Control>(FIRST_SPACER_NAME),
190            second_spacer: name_scope.find_as::<Control>(SECOND_SPACER_NAME),
191        };
192        *this.template_items.borrow_mut() = Some(items.clone());
193
194        let weak = weak_this.clone();
195        items.accept_button.click(move |_, _| {
196            if let Some(this) = weak.upgrade() {
197                this.on_accept_button_clicked();
198            }
199        });
200        let weak = weak_this.clone();
201        items.month_selector.selection_changed(move || {
202            if let Some(this) = weak.upgrade() {
203                this.on_month_changed();
204            }
205        });
206        let weak = weak_this.clone();
207        items.day_selector.selection_changed(move || {
208            if let Some(this) = weak.upgrade() {
209                this.on_day_changed();
210            }
211        });
212        let weak = weak_this.clone();
213        items.year_selector.selection_changed(move || {
214            if let Some(this) = weak.upgrade() {
215                this.on_year_changed();
216            }
217        });
218
219        if let Some(dismiss_button) = &items.dismiss_button {
220            let weak = weak_this.clone();
221            dismiss_button.click(move |_, _| {
222                if let Some(this) = weak.upgrade() {
223                    this.on_dismiss_button_clicked();
224                }
225            });
226        }
227
228        this.init_picker();
229    }
230}
231
232impl InputElementImpl for DatePickerPresenter {
233    fn on_key_down(this: &Self, e: &KeyEventArgs) {
234        match e.key {
235            Key::Escape => {
236                this.on_dismiss();
237                e.set_handled(true);
238            }
239            Key::Tab => {
240                let focus_manager = FocusManager::get_focus_manager(this);
241                if let Some(focus) = focus_manager.and_then(|focus_manager| focus_manager.get_focused_element()) {
242                    let next_focus = KeyboardNavigationHandler::get_next(&focus, NavigationDirection::Next);
243                    if let Some(next_focus) = next_focus {
244                        next_focus.focus_with(NavigationMethod::Tab, KeyModifiers::NONE);
245                    }
246                    e.set_handled(true);
247                }
248            }
249            Key::Enter => {
250                this.set_current_value(Self::date_property(), this.sync_date.get());
251                this.on_confirmed();
252                e.set_handled(true);
253            }
254            _ => {}
255        }
256        Self::parent_on_key_down(this, e);
257    }
258}
259
260ferro_properties! {
261    impl DatePickerPresenter {
262        /// Defines the `Date` property.
263        pub fn date_property() -> StyledProperty<DateTimeOffset> {
264            FerroProperty::register_with::<DatePickerPresenter, _>(
265                "Date",
266                StyledPropertyOptions::new(DateTimeOffset::default()).coerce(DatePickerPresenter::coerce_date),
267            )
268        }
269
270        /// Defines the `DayFormat` property.
271        pub fn day_format_property() -> StyledProperty<String> {
272            DatePicker::day_format_property().add_owner::<DatePickerPresenter>()
273        }
274
275        /// Defines the `DayVisible` property.
276        pub fn day_visible_property() -> StyledProperty<bool> {
277            DatePicker::day_visible_property().add_owner::<DatePickerPresenter>()
278        }
279
280        /// Defines the `MaxYear` property.
281        pub fn max_year_property() -> StyledProperty<DateTimeOffset> {
282            DatePicker::max_year_property().add_owner::<DatePickerPresenter>()
283        }
284
285        /// Defines the `MinYear` property.
286        pub fn min_year_property() -> StyledProperty<DateTimeOffset> {
287            DatePicker::min_year_property().add_owner::<DatePickerPresenter>()
288        }
289
290        /// Defines the `MonthFormat` property.
291        pub fn month_format_property() -> StyledProperty<String> {
292            DatePicker::month_format_property().add_owner::<DatePickerPresenter>()
293        }
294
295        /// Defines the `MonthVisible` property.
296        pub fn month_visible_property() -> StyledProperty<bool> {
297            DatePicker::month_visible_property().add_owner::<DatePickerPresenter>()
298        }
299
300        /// Defines the `YearFormat` property.
301        pub fn year_format_property() -> StyledProperty<String> {
302            DatePicker::year_format_property().add_owner::<DatePickerPresenter>()
303        }
304
305        /// Defines the `YearVisible` property.
306        pub fn year_visible_property() -> StyledProperty<bool> {
307            DatePicker::year_visible_property().add_owner::<DatePickerPresenter>()
308        }
309    }
310}
311
312impl DatePickerPresenter {
313    fn static_constructor() {
314        KeyboardNavigation::tab_navigation_property()
315            .override_default_value::<DatePickerPresenter>(KeyboardNavigationMode::Cycle);
316    }
317
318    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
319    pub fn construct() -> Self {
320        Self {
321            base: PickerPresenterBase::construct(),
322            template_items: RefCell::new(None),
323            sync_date: Cell::new(DateTimeOffset::default()),
324            calendar: GregorianCalendar::new(),
325            suppress_update_selection: Cell::new(false),
326        }
327    }
328
329    pub fn new() -> Ref<Self> {
330        instantiate(Self::construct())
331    }
332
333    fn coerce_date(sender: &FerroObject, value: DateTimeOffset) -> DateTimeOffset {
334        let max = sender.get_value(Self::max_year_property());
335        if value > max {
336            return max;
337        }
338        let min = sender.get_value(Self::min_year_property());
339        if value < min {
340            return min;
341        }
342
343        value
344    }
345
346    fn on_date_range_changed(&self) {
347        self.coerce_value(Self::date_property().as_property());
348    }
349
350    /// The current date for the picker.
351    pub fn date(&self) -> DateTimeOffset {
352        self.get_value(Self::date_property())
353    }
354
355    pub fn set_date(&self, value: DateTimeOffset) {
356        self.set_value(Self::date_property(), value)
357    }
358
359    fn on_date_changed(&self, new_value: DateTimeOffset) {
360        self.sync_date.set(new_value);
361        self.init_picker();
362    }
363
364    /// The day format.
365    pub fn day_format(&self) -> String {
366        self.get_value(Self::day_format_property())
367    }
368
369    pub fn set_day_format(&self, value: &str) {
370        self.set_value(Self::day_format_property(), value.to_string())
371    }
372
373    /// Whether the day selector is visible.
374    pub fn day_visible(&self) -> bool {
375        self.get_value(Self::day_visible_property())
376    }
377
378    pub fn set_day_visible(&self, value: bool) {
379        self.set_value(Self::day_visible_property(), value)
380    }
381
382    /// The maximum pickable year.
383    pub fn max_year(&self) -> DateTimeOffset {
384        self.get_value(Self::max_year_property())
385    }
386
387    pub fn set_max_year(&self, value: DateTimeOffset) {
388        self.set_value(Self::max_year_property(), value)
389    }
390
391    /// The minimum pickable year.
392    pub fn min_year(&self) -> DateTimeOffset {
393        self.get_value(Self::min_year_property())
394    }
395
396    pub fn set_min_year(&self, value: DateTimeOffset) {
397        self.set_value(Self::min_year_property(), value)
398    }
399
400    /// The month format.
401    pub fn month_format(&self) -> String {
402        self.get_value(Self::month_format_property())
403    }
404
405    pub fn set_month_format(&self, value: &str) {
406        self.set_value(Self::month_format_property(), value.to_string())
407    }
408
409    /// Whether the month selector is visible.
410    pub fn month_visible(&self) -> bool {
411        self.get_value(Self::month_visible_property())
412    }
413
414    pub fn set_month_visible(&self, value: bool) {
415        self.set_value(Self::month_visible_property(), value)
416    }
417
418    /// The year format.
419    pub fn year_format(&self) -> String {
420        self.get_value(Self::year_format_property())
421    }
422
423    pub fn set_year_format(&self, value: &str) {
424        self.set_value(Self::year_format_property(), value.to_string())
425    }
426
427    /// Whether the year selector is visible.
428    pub fn year_visible(&self) -> bool {
429        self.get_value(Self::year_visible_property())
430    }
431
432    pub fn set_year_visible(&self, value: bool) {
433        self.set_value(Self::year_visible_property(), value)
434    }
435
436    fn items(&self) -> Option<TemplateItems> {
437        self.template_items.borrow().clone()
438    }
439
440    /// Initializes the picker selectors.
441    fn init_picker(&self) {
442        // The template must have been applied before we can init here...
443        let Some(items) = self.items() else {
444            return;
445        };
446
447        self.suppress_update_selection.set(true);
448
449        items.month_selector.set_maximum_value(12);
450        items.month_selector.set_minimum_value(1);
451        items.month_selector.set_item_format(&self.month_format());
452
453        items.day_selector.set_item_format(&self.day_format());
454
455        items.year_selector.set_maximum_value(self.max_year().year());
456        items.year_selector.set_minimum_value(self.min_year().year());
457        items.year_selector.set_item_format(&self.year_format());
458
459        self.set_grid(&items);
460
461        // Date should've been set when we reach this point
462        let dt = self.date();
463        if self.day_visible() {
464            items.day_selector.set_format_date(dt.date());
465            let max_days = self.calendar.get_days_in_month(dt.year(), dt.month());
466            items.day_selector.set_maximum_value(max_days);
467            items.day_selector.set_minimum_value(1);
468            items.day_selector.set_selected_value(dt.day());
469        }
470
471        if self.month_visible() {
472            items.month_selector.set_selected_value(dt.month());
473            items.month_selector.set_format_date(dt.date());
474        }
475
476        if self.year_visible() {
477            items.year_selector.set_selected_value(dt.year());
478            items.year_selector.set_format_date(dt.date());
479        }
480
481        self.suppress_update_selection.set(false);
482
483        self.set_initial_focus(&items);
484    }
485
486    fn set_grid(&self, items: &TemplateItems) {
487        let date_time_format = CultureInfo::current_culture().date_time_format();
488        let fmt = date_time_format.short_date_pattern();
489        let mut columns = [
490            (&items.month_host, if self.month_visible() { index_of_ignore_case(fmt, 'm') } else { -1 }),
491            (&items.year_host, if self.year_visible() { index_of_ignore_case(fmt, 'y') } else { -1 }),
492            (&items.day_host, if self.day_visible() { index_of_ignore_case(fmt, 'd') } else { -1 }),
493        ];
494
495        columns.sort_by_key(|column| column.1);
496        items.picker_container.column_definitions().clear();
497
498        let mut column_index = 0;
499
500        for (host, index) in columns {
501            host.set_is_visible(index != -1);
502
503            if index != -1 {
504                if column_index > 0 {
505                    items.picker_container.column_definitions().add(ColumnDefinition::with_value(0.0, GridUnitType::Auto));
506                }
507
508                items.picker_container.column_definitions().add(ColumnDefinition::with_value(
509                    if host.ptr_eq(&items.month_host) { 138.0 } else { 78.0 },
510                    GridUnitType::Star,
511                ));
512
513                if host.parent().is_none() {
514                    items.picker_container.children().add(host.clone());
515                }
516
517                Grid::set_column(host, column_index * 2);
518                column_index += 1;
519            }
520        }
521
522        fn configure_spacer(spacer: &Option<Ref<Control>>, visible: bool, column: i32) {
523            let Some(spacer) = spacer else {
524                return;
525            };
526            // The conditional is used to make sure grid cells will be validated
527            Grid::set_column(spacer, if visible { column } else { 0 });
528            spacer.set_is_visible(visible);
529        }
530
531        configure_spacer(&items.first_spacer, column_index > 1, 1);
532        configure_spacer(&items.second_spacer, column_index > 2, 3);
533    }
534
535    fn set_initial_focus(&self, items: &TemplateItems) {
536        let candidates = [
537            (self.month_visible(), &items.month_host, &items.month_selector),
538            (self.day_visible(), &items.day_host, &items.day_selector),
539            (self.year_visible(), &items.year_host, &items.year_selector),
540        ];
541
542        let mut leftmost: Option<&Ref<DateTimePickerPanel>> = None;
543        let mut min_col = i32::MAX;
544
545        for (visible, host, selector) in candidates {
546            if !visible {
547                continue;
548            }
549
550            let col = Grid::get_column(host);
551            if col < min_col {
552                min_col = col;
553                leftmost = Some(selector);
554            }
555        }
556
557        if let Some(leftmost) = leftmost {
558            leftmost.focus_with(NavigationMethod::Pointer, KeyModifiers::NONE);
559        }
560    }
561
562    fn on_dismiss_button_clicked(&self) {
563        self.on_dismiss();
564    }
565
566    fn on_accept_button_clicked(&self) {
567        self.set_current_value(Self::date_property(), self.sync_date.get());
568        self.on_confirmed();
569    }
570
571    fn on_selector_button_click(&self, type_: DateTimePickerPanelType, direction: SpinDirection) {
572        let items = self.items();
573        let target = match type_ {
574            DateTimePickerPanelType::Month => items.map(|items| items.month_selector),
575            DateTimePickerPanelType::Day => items.map(|items| items.day_selector),
576            DateTimePickerPanelType::Year => items.map(|items| items.year_selector),
577            _ => panic!("The method or operation is not implemented."),
578        };
579
580        let Some(target) = target else {
581            return;
582        };
583
584        match direction {
585            SpinDirection::Increase => target.scroll_down(1),
586            SpinDirection::Decrease => target.scroll_up(1),
587        }
588    }
589
590    fn on_year_changed(&self) {
591        if self.suppress_update_selection.get() {
592            return;
593        }
594        let Some(items) = self.items() else {
595            return;
596        };
597
598        let sync_date = self.sync_date.get();
599        let max_days = self.calendar.get_days_in_month(items.year_selector.selected_value(), sync_date.month());
600        let new_date = DateTimeOffset::new(
601            items.year_selector.selected_value(),
602            sync_date.month(),
603            if sync_date.day() > max_days { max_days } else { sync_date.day() },
604            0,
605            0,
606            0,
607            sync_date.offset(),
608        );
609
610        self.sync_date.set(new_date);
611
612        // We don't need to update the days if not displaying day, not february
613        if !self.day_visible() || new_date.month() != 2 {
614            return;
615        }
616
617        self.suppress_update_selection.set(true);
618
619        items.day_selector.set_format_date(new_date.date());
620
621        if items.day_selector.maximum_value() != max_days {
622            items.day_selector.set_maximum_value(max_days);
623        } else {
624            items.day_selector.refresh_items();
625        }
626
627        self.suppress_update_selection.set(false);
628    }
629
630    fn on_day_changed(&self) {
631        if self.suppress_update_selection.get() {
632            return;
633        }
634        let Some(items) = self.items() else {
635            return;
636        };
637        let sync_date = self.sync_date.get();
638        self.sync_date.set(DateTimeOffset::new(
639            sync_date.year(),
640            sync_date.month(),
641            items.day_selector.selected_value(),
642            0,
643            0,
644            0,
645            sync_date.offset(),
646        ));
647    }
648
649    fn on_month_changed(&self) {
650        if self.suppress_update_selection.get() {
651            return;
652        }
653        let Some(items) = self.items() else {
654            return;
655        };
656
657        let sync_date = self.sync_date.get();
658        let max_days = self.calendar.get_days_in_month(sync_date.year(), items.month_selector.selected_value());
659        let new_date = DateTimeOffset::new(
660            sync_date.year(),
661            items.month_selector.selected_value(),
662            if sync_date.day() > max_days { max_days } else { sync_date.day() },
663            0,
664            0,
665            0,
666            sync_date.offset(),
667        );
668
669        if !self.day_visible() {
670            self.sync_date.set(new_date);
671            return;
672        }
673
674        self.suppress_update_selection.set(true);
675
676        items.day_selector.set_format_date(new_date.date());
677        self.sync_date.set(new_date);
678
679        if items.day_selector.maximum_value() != max_days {
680            items.day_selector.set_maximum_value(max_days);
681        } else {
682            items.day_selector.refresh_items();
683        }
684
685        self.suppress_update_selection.set(false);
686    }
687
688    pub(crate) fn get_offset_for_popup(&self) -> f64 {
689        let Some(items) = self.items() else {
690            return 0.0;
691        };
692
693        let accept_dismiss_button_height = items.accept_button.bounds().height;
694        -(self.max_height() - accept_dismiss_button_height) / 2.0 - (items.month_selector.item_height() / 2.0)
695    }
696}