1use crate::presenters::ScrollContentPresenter;
2use crate::primitives::{register_logical_scrollable, ILogicalScrollable};
3use crate::utils::TimeUtils;
4use crate::{Control, ControlImpl, Controls, ListBoxItem, Panel, PanelImpl};
5use ferroui_base::animation::TimeSpan;
6use ferroui_base::input::{
7 IScrollable, InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, NavigationDirection, ScrollGestureEndedEventArgs,
8 TappedEventArgs,
9};
10use ferroui_base::interactivity::{InteractiveImpl, RoutedEventHandlerToken, RoutingStrategies};
11use ferroui_base::layout::{Layoutable, LayoutableImpl, LayoutableImplExt, VerticalAlignment};
12use ferroui_base::media::{Brushes, IBrush};
13use ferroui_base::reactive::{Disposable, IDisposable};
14use ferroui_base::utilities::{DateTime, HandlerList};
15use ferroui_base::{
16 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, AnyValue, BoxedValue,
17 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Rect, Ref, Size, StyledElementImpl, StyledProperty,
18 Vector, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs, WeakRef,
19};
20use std::cell::{Cell, RefCell};
21use std::rc::Rc;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
25#[repr(i32)]
26pub enum DateTimePickerPanelType {
27 #[default]
28 Year = 0,
29 Month = 1,
30 Day = 2,
31 Hour = 3,
32 Minute = 4,
33 TimePeriod = 5,
35 Second = 6,
36}
37
38impl DateTimePickerPanelType {
39 pub const fn name(self) -> &'static str {
41 match self {
42 DateTimePickerPanelType::Year => "Year",
43 DateTimePickerPanelType::Month => "Month",
44 DateTimePickerPanelType::Day => "Day",
45 DateTimePickerPanelType::Hour => "Hour",
46 DateTimePickerPanelType::Minute => "Minute",
47 DateTimePickerPanelType::TimePeriod => "TimePeriod",
48 DateTimePickerPanelType::Second => "Second",
49 }
50 }
51
52 const fn item_class(self) -> &'static str {
54 match self {
55 DateTimePickerPanelType::Year => "YearItem",
56 DateTimePickerPanelType::Month => "MonthItem",
57 DateTimePickerPanelType::Day => "DayItem",
58 DateTimePickerPanelType::Hour => "HourItem",
59 DateTimePickerPanelType::Minute => "MinuteItem",
60 DateTimePickerPanelType::TimePeriod => "TimePeriodItem",
61 DateTimePickerPanelType::Second => "SecondItem",
62 }
63 }
64}
65
66#[repr(C)]
69pub struct DateTimePickerPanel {
70 base: Panel,
71
72 minimum_value: Cell<i32>,
74 maximum_value: Cell<i32>,
75 selected_value: Cell<i32>,
76 increment: Cell<i32>,
77
78 selected_index: Cell<i32>,
80 total_items: Cell<i32>,
81 num_items_above_below_selected: Cell<i32>,
82 range: Cell<i32>,
83 extent_one: Cell<f64>,
84 extent: Cell<Size>,
85 offset: Cell<Vector>,
86 has_init: Cell<bool>,
87 suppress_update_offset: Cell<bool>,
88 parent_scroller: RefCell<Option<WeakRef<ScrollContentPresenter>>>,
89 scroll_gesture_ended_handler: Cell<Option<RoutedEventHandlerToken>>,
92
93 format_date: Cell<DateTime>,
94 scroll_invalidated: Rc<HandlerList<dyn Fn()>>,
95 selection_changed: HandlerList<dyn Fn()>,
96}
97
98ferro_class!(DateTimePickerPanel: Panel);
99
100ferro_class_info!(DateTimePickerPanel {
101 new: DateTimePickerPanel::new,
102 markup: {
103 properties: [
104 MinimumValue: i32 { get: DateTimePickerPanel::minimum_value, set: DateTimePickerPanel::set_minimum_value },
105 MaximumValue: i32 { get: DateTimePickerPanel::maximum_value, set: DateTimePickerPanel::set_maximum_value },
106 SelectedValue: i32 { get: DateTimePickerPanel::selected_value, set: DateTimePickerPanel::set_selected_value },
107 Increment: i32 { get: DateTimePickerPanel::increment, set: DateTimePickerPanel::set_increment },
108 Offset: Vector { get: DateTimePickerPanel::offset, set: DateTimePickerPanel::set_offset },
109 CanHorizontallyScroll: bool {
110 get: DateTimePickerPanel::can_horizontally_scroll,
111 set: DateTimePickerPanel::set_can_horizontally_scroll
112 },
113 CanVerticallyScroll: bool {
114 get: DateTimePickerPanel::can_vertically_scroll,
115 set: DateTimePickerPanel::set_can_vertically_scroll
116 },
117 IsLogicalScrollEnabled: bool { get: DateTimePickerPanel::is_logical_scroll_enabled },
118 ScrollSize: Size { get: DateTimePickerPanel::scroll_size },
119 PageScrollSize: Size { get: DateTimePickerPanel::page_scroll_size },
120 Extent: Size { get: DateTimePickerPanel::extent },
121 Viewport: Size { get: DateTimePickerPanel::viewport },
122 ],
123 },
124});
125
126ferro_impl_classes!(DateTimePickerPanel: StyledElementImpl, InteractiveImpl, ControlImpl, PanelImpl);
127
128impl FerroObjectImpl for DateTimePickerPanel {
129 fn constructed(this: &Self) {
130 Self::parent_constructed(this);
131
132 let weak = this.to_ref().downgrade();
133 this.add_handler_with(
134 InputElement::tapped_event(),
135 move |_, e: &TappedEventArgs| {
136 if let Some(this) = weak.upgrade() {
137 this.on_item_tapped(e);
138 }
139 },
140 RoutingStrategies::BUBBLE,
141 false,
142 );
143 }
144
145 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
146 Self::parent_on_property_changed(this, change);
147
148 if change.property() == Self::item_height_property().as_property() {
149 let item_height = this.item_height();
150 for child in this.children().snapshot().iter() {
151 child.set_height(item_height);
152 }
153
154 this.update_helper_info();
155 this.has_init.set(false);
156 }
157 }
158}
159
160impl VisualImpl for DateTimePickerPanel {
161 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
162 Self::parent_on_attached_to_visual_tree(this, e);
163
164 let parent_scroller = this.get_visual_parent().and_then(|parent| parent.cast::<ScrollContentPresenter>());
165 if let Some(parent_scroller) = &parent_scroller {
166 let weak = this.to_ref().downgrade();
167 let token = parent_scroller.add_handler(
168 InputElement::scroll_gesture_ended_event(),
169 move |_, e: &ScrollGestureEndedEventArgs| {
170 if let Some(this) = weak.upgrade() {
171 this.on_scroll_gesture_ended(e);
172 }
173 },
174 );
175 this.scroll_gesture_ended_handler.set(Some(token));
176 }
177 *this.parent_scroller.borrow_mut() = parent_scroller.as_ref().map(Ref::downgrade);
178 }
179
180 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
181 Self::parent_on_detached_from_visual_tree(this, e);
182
183 let parent_scroller = this.parent_scroller.take().and_then(|weak| weak.upgrade());
184 let token = this.scroll_gesture_ended_handler.take();
185 if let (Some(parent_scroller), Some(token)) = (parent_scroller, token) {
186 parent_scroller.remove_handler(InputElement::scroll_gesture_ended_event(), token);
187 }
188 }
189}
190
191impl LayoutableImpl for DateTimePickerPanel {
192 fn measure_override(this: &Self, available_size: Size) -> Size {
193 if available_size.width.is_infinite() || available_size.height.is_infinite() {
194 panic!("Panel must have finite height");
195 }
196
197 if !this.has_init.get() {
198 this.update_helper_info();
199 }
200
201 let item_height = this.item_height();
202 let init_y = (available_size.height / 2.0) - (item_height / 2.0);
203 this.num_items_above_below_selected.set((init_y / item_height).ceil() as i32 + 1);
204
205 let children = this.children();
206
207 this.create_or_destroy_items(&children);
208
209 for child in children.snapshot().iter() {
210 child.measure(available_size);
211 }
212
213 if !this.has_init.get() {
214 this.update_items();
215 this.raise_scroll_invalidated();
216 this.has_init.set(true);
217 }
218
219 available_size
220 }
221
222 fn arrange_override(this: &Self, final_size: Size) -> Size {
223 let children = this.children();
224 if children.count() == 0 {
225 return Self::parent_arrange_override(this, final_size);
226 }
227
228 let item_hgt = this.item_height();
229 let offset_y = this.offset.get().y;
230 let mut init_y = (final_size.height / 2.0) - (item_hgt / 2.0);
231
232 if this.should_loop() {
233 let extent_one = this.extent_one.get();
234 let current_set = (offset_y / extent_one).trunc();
235 init_y += (extent_one * current_set)
236 + f64::from(this.selected_index.get() - this.num_items_above_below_selected.get()) * item_hgt;
237
238 for child in children.snapshot().iter() {
239 let rc = Rect::new(0.0, init_y - offset_y, final_size.width, item_hgt);
240 child.arrange(rc);
241 init_y += item_hgt;
242 }
243 } else {
244 let first = f64::from((this.selected_index.get() - this.num_items_above_below_selected.get()).max(0));
245 for child in children.snapshot().iter() {
246 let rc = Rect::new(0.0, (init_y + first * item_hgt) - offset_y, final_size.width, item_hgt);
247 child.arrange(rc);
248 init_y += item_hgt;
249 }
250 }
251
252 final_size
253 }
254}
255
256impl InputElementImpl for DateTimePickerPanel {
257 fn on_key_down(this: &Self, e: &KeyEventArgs) {
258 match e.key {
259 Key::Up => {
260 this.scroll_up(1);
261 e.set_handled(true);
262 }
263 Key::Down => {
264 this.scroll_down(1);
265 e.set_handled(true);
266 }
267 Key::PageUp => {
268 this.scroll_up(4);
269 e.set_handled(true);
270 }
271 Key::PageDown => {
272 this.scroll_down(4);
273 e.set_handled(true);
274 }
275 _ => {}
276 }
277 Self::parent_on_key_down(this, e);
278 }
279}
280
281impl IScrollable for DateTimePickerPanel {
282 fn extent(&self) -> Size {
283 DateTimePickerPanel::extent(self)
284 }
285
286 fn offset(&self) -> Vector {
287 DateTimePickerPanel::offset(self)
288 }
289
290 fn set_offset(&self, value: Vector) {
291 DateTimePickerPanel::set_offset(self, value)
292 }
293
294 fn viewport(&self) -> Size {
295 DateTimePickerPanel::viewport(self)
296 }
297
298 fn can_horizontally_scroll(&self) -> bool {
299 DateTimePickerPanel::can_horizontally_scroll(self)
300 }
301
302 fn can_vertically_scroll(&self) -> bool {
303 DateTimePickerPanel::can_vertically_scroll(self)
304 }
305}
306
307impl ILogicalScrollable for DateTimePickerPanel {
308 fn set_can_horizontally_scroll(&self, value: bool) {
309 DateTimePickerPanel::set_can_horizontally_scroll(self, value)
310 }
311
312 fn set_can_vertically_scroll(&self, value: bool) {
313 DateTimePickerPanel::set_can_vertically_scroll(self, value)
314 }
315
316 fn is_logical_scroll_enabled(&self) -> bool {
317 DateTimePickerPanel::is_logical_scroll_enabled(self)
318 }
319
320 fn scroll_size(&self) -> Size {
321 DateTimePickerPanel::scroll_size(self)
322 }
323
324 fn page_scroll_size(&self) -> Size {
325 DateTimePickerPanel::page_scroll_size(self)
326 }
327
328 fn scroll_invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
329 let token = self.scroll_invalidated.add(handler);
330 let handlers = Rc::downgrade(&self.scroll_invalidated);
331 Disposable::create(move || {
332 if let Some(handlers) = handlers.upgrade() {
333 handlers.remove(token);
334 }
335 })
336 }
337
338 fn bring_into_view(&self, target: &Ref<Control>, target_rect: Rect) -> bool {
339 DateTimePickerPanel::bring_into_view(self, target, target_rect)
340 }
341
342 fn get_control_in_direction(
343 &self,
344 direction: NavigationDirection,
345 from: Option<&Ref<Control>>,
346 ) -> Option<Ref<Control>> {
347 DateTimePickerPanel::get_control_in_direction(self, direction, from)
348 }
349
350 fn raise_scroll_invalidated(&self) {
351 DateTimePickerPanel::raise_scroll_invalidated(self)
352 }
353}
354
355ferro_properties! {
356 impl DateTimePickerPanel {
357 pub fn item_height_property() -> StyledProperty<f64> {
359 FerroProperty::register::<DateTimePickerPanel, _>("ItemHeight", 40.0)
360 }
361
362 pub fn panel_type_property() -> StyledProperty<DateTimePickerPanelType> {
364 FerroProperty::register::<DateTimePickerPanel, _>("PanelType", DateTimePickerPanelType::Year)
365 }
366
367 pub fn item_format_property() -> StyledProperty<String> {
369 FerroProperty::register::<DateTimePickerPanel, _>("ItemFormat", "yyyy".to_string())
370 }
371
372 pub fn should_loop_property() -> StyledProperty<bool> {
374 FerroProperty::register::<DateTimePickerPanel, _>("ShouldLoop", false)
375 }
376 }
377}
378
379impl DateTimePickerPanel {
380 fn static_constructor() {
381 InputElement::focusable_property().override_default_value::<DateTimePickerPanel>(true);
382 Panel::background_property()
383 .override_default_value::<DateTimePickerPanel>(Some(Brushes::transparent() as Rc<dyn IBrush>));
384 Layoutable::affects_measure::<DateTimePickerPanel>(&[Self::item_height_property().as_property()]);
385 register_logical_scrollable::<DateTimePickerPanel>();
386 }
387
388 pub fn construct() -> Self {
390 Self {
391 base: Panel::construct(),
392 minimum_value: Cell::new(1),
393 maximum_value: Cell::new(2),
394 selected_value: Cell::new(1),
395 increment: Cell::new(1),
396 selected_index: Cell::new(0),
397 total_items: Cell::new(0),
398 num_items_above_below_selected: Cell::new(0),
399 range: Cell::new(0),
400 extent_one: Cell::new(0.0),
401 extent: Cell::new(Size::default()),
402 offset: Cell::new(Vector::default()),
403 has_init: Cell::new(false),
404 suppress_update_offset: Cell::new(false),
405 parent_scroller: RefCell::new(None),
406 scroll_gesture_ended_handler: Cell::new(None),
407 format_date: Cell::new(DateTime::now()),
408 scroll_invalidated: Rc::new(HandlerList::new()),
409 selection_changed: HandlerList::new(),
410 }
411 }
412
413 pub fn new() -> Ref<Self> {
414 instantiate(Self::construct())
415 }
416
417 pub fn panel_type(&self) -> DateTimePickerPanelType {
419 self.get_value(Self::panel_type_property())
420 }
421
422 pub fn set_panel_type(&self, value: DateTimePickerPanelType) {
423 self.set_value(Self::panel_type_property(), value)
424 }
425
426 pub fn item_height(&self) -> f64 {
428 self.get_value(Self::item_height_property())
429 }
430
431 pub fn set_item_height(&self, value: f64) {
432 self.set_value(Self::item_height_property(), value)
433 }
434
435 pub fn item_format(&self) -> String {
438 self.get_value(Self::item_format_property())
439 }
440
441 pub fn set_item_format(&self, value: &str) {
442 self.set_value(Self::item_format_property(), value.to_string())
443 }
444
445 pub fn should_loop(&self) -> bool {
447 self.get_value(Self::should_loop_property())
448 }
449
450 pub fn set_should_loop(&self, value: bool) {
451 self.set_value(Self::should_loop_property(), value)
452 }
453
454 pub fn minimum_value(&self) -> i32 {
456 self.minimum_value.get()
457 }
458
459 pub fn set_minimum_value(&self, value: i32) {
461 if value > self.maximum_value() {
462 panic!("Minimum cannot be greater than Maximum");
463 }
464 self.minimum_value.set(value);
465 self.update_helper_info();
466 let sel = self.coerce_selected(self.selected_value());
467 if sel != self.selected_value() {
468 self.set_selected_value(sel);
469 }
470 self.update_items();
471 self.invalidate_arrange();
472 self.raise_scroll_invalidated();
473 }
474
475 pub fn maximum_value(&self) -> i32 {
477 self.maximum_value.get()
478 }
479
480 pub fn set_maximum_value(&self, value: i32) {
482 if value < self.minimum_value() {
483 panic!("Maximum cannot be less than Minimum");
484 }
485 self.maximum_value.set(value);
486 self.update_helper_info();
487 let sel = self.coerce_selected(self.selected_value());
488 if sel != self.selected_value() {
489 self.set_selected_value(sel);
490 }
491 self.update_items();
492 self.invalidate_arrange();
493 self.raise_scroll_invalidated();
494 }
495
496 pub fn selected_value(&self) -> i32 {
498 self.selected_value.get()
499 }
500
501 pub fn set_selected_value(&self, value: i32) {
504 if value > self.maximum_value() || value < self.minimum_value() {
505 panic!("Specified argument was out of the range of valid values. (Parameter 'value')");
506 }
507
508 let sel = self.coerce_selected(value);
509 self.selected_value.set(sel);
510 self.selected_index.set((value - self.minimum_value()) / self.increment());
511
512 if !self.should_loop() {
513 self.create_or_destroy_items(&self.children());
514 }
515
516 if !self.suppress_update_offset.get() {
517 let item_height = self.item_height();
518 let selected_index = f64::from(self.selected_index.get());
519 self.offset.set(Vector::new(
520 0.0,
521 if self.should_loop() {
522 selected_index * item_height + (self.extent_one.get() * 50.0)
523 } else {
524 selected_index * item_height
525 },
526 ));
527 }
528
529 self.update_items();
530 self.invalidate_arrange();
531 self.raise_scroll_invalidated();
532
533 for (_, handler) in self.selection_changed.snapshot().iter() {
534 handler();
535 }
536 }
537
538 pub fn increment(&self) -> i32 {
540 self.increment.get()
541 }
542
543 pub fn set_increment(&self, value: i32) {
546 if value <= 0 || value > self.range.get() {
547 panic!("Specified argument was out of the range of valid values. (Parameter 'value')");
548 }
549 self.increment.set(value);
550 self.update_helper_info();
551 let sel = self.coerce_selected(self.selected_value());
552 if sel != self.selected_value() {
553 self.set_selected_value(sel);
554 }
555 self.update_items();
556 self.invalidate_arrange();
557 self.raise_scroll_invalidated();
558 }
559
560 pub(crate) fn format_date(&self) -> DateTime {
564 self.format_date.get()
565 }
566
567 pub(crate) fn set_format_date(&self, value: DateTime) {
568 self.format_date.set(value)
569 }
570
571 pub fn offset(&self) -> Vector {
573 self.offset.get()
574 }
575
576 pub fn set_offset(&self, value: Vector) {
579 let old = self.offset.get();
580 self.offset.set(value);
581 let dy = value.y - old.y;
582 let children = self.children();
583
584 if dy > 0.0 {
585 let snapshot = children.snapshot();
587 let mut num_counts_to_move = 0;
588 for child in snapshot.iter() {
589 if child.bounds().bottom() - dy < 0.0 {
590 num_counts_to_move += 1;
591 } else {
592 break;
593 }
594 }
595 self.move_children(&children, 0, num_counts_to_move, snapshot.len().checked_sub(1));
596
597 let scroll_height = self.extent.get().height - self.viewport().height;
598 if self.should_loop() && value.y >= scroll_height - self.extent_one.get() {
599 self.offset.set(Vector::new(0.0, value.y - (self.extent_one.get() * 50.0)));
600 }
601 } else if dy < 0.0 {
602 let snapshot = children.snapshot();
604 let bounds_height = self.bounds().height;
605 let mut num_counts_to_move = 0;
606 for child in snapshot.iter().rev() {
607 if child.bounds().y - dy > bounds_height {
608 num_counts_to_move += 1;
609 } else {
610 break;
611 }
612 }
613 self.move_children(&children, snapshot.len() - num_counts_to_move, num_counts_to_move, Some(0));
614 if self.should_loop() && value.y < self.extent_one.get() {
615 self.offset.set(Vector::new(0.0, value.y + (self.extent_one.get() * 50.0)));
616 }
617 }
618
619 let new_sel = (self.offset.get().y / self.item_height()) % f64::from(self.total_items.get());
621 self.suppress_update_offset.set(true);
622 self.set_selected_value(new_sel as i32 * self.increment() + self.minimum_value());
623 self.suppress_update_offset.set(false);
624 }
625
626 fn move_children(&self, children: &Controls, old_index: usize, count: usize, new_index: Option<usize>) {
633 match new_index {
634 None => panic!(
635 "Index was out of range. Must be non-negative and less than or equal to the size of the collection. (Parameter 'index')"
636 ),
637 Some(new_index) if count != 0 && old_index != new_index => {
638 children.move_range(old_index, count, new_index);
639 }
640 _ => self.invalidate_measure_on_children_changed(),
641 }
642 }
643
644 pub fn can_horizontally_scroll(&self) -> bool {
645 false
646 }
647
648 pub fn set_can_horizontally_scroll(&self, _value: bool) {}
649
650 pub fn can_vertically_scroll(&self) -> bool {
651 true
652 }
653
654 pub fn set_can_vertically_scroll(&self, _value: bool) {}
655
656 pub fn is_logical_scroll_enabled(&self) -> bool {
657 true
658 }
659
660 pub fn scroll_size(&self) -> Size {
661 Size::new(0.0, self.item_height())
662 }
663
664 pub fn page_scroll_size(&self) -> Size {
665 Size::new(0.0, self.item_height() * 4.0)
666 }
667
668 pub fn extent(&self) -> Size {
669 self.extent.get()
670 }
671
672 pub fn viewport(&self) -> Size {
673 self.bounds().size()
674 }
675
676 pub fn selection_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
679 let token = self.selection_changed.add(Rc::new(handler));
680 let weak = self.to_ref().downgrade();
681 Disposable::create(move || {
682 if let Some(this) = weak.upgrade() {
683 this.selection_changed.remove(token);
684 }
685 })
686 }
687
688 pub fn refresh_items(&self) {
690 self.update_items();
691 }
692
693 pub fn scroll_up(&self, num_items: i32) {
695 let new_y = (self.offset.get().y - (f64::from(num_items) * self.item_height())).max(0.0);
696 self.set_offset(Vector::new(0.0, new_y));
697 }
698
699 pub fn scroll_down(&self, num_items: i32) {
701 let scroll_height = (self.extent.get().height - self.item_height()).max(0.0);
702 let new_y = (self.offset.get().y + (f64::from(num_items) * self.item_height())).min(scroll_height);
703 self.set_offset(Vector::new(0.0, new_y));
704 }
705
706 fn update_helper_info(&self) {
708 self.range.set(self.maximum_value.get() - self.minimum_value.get() + 1);
709 self.total_items.set((f64::from(self.range.get()) / f64::from(self.increment.get())).ceil() as i32);
710
711 let item_hgt = self.item_height();
712 let should_loop = self.should_loop();
713 let total_items = f64::from(self.total_items.get());
714 self.extent.set(Size::new(
716 0.0,
717 if should_loop { total_items * item_hgt * 100.0 } else { total_items * item_hgt },
718 ));
719
720 self.extent_one.set(total_items * item_hgt);
722 let selected_index = f64::from(self.selected_index.get());
723 self.offset.set(Vector::new(
724 0.0,
725 if should_loop {
726 self.extent_one.get() * 50.0 + selected_index * item_hgt
727 } else {
728 selected_index * item_hgt
729 },
730 ));
731 }
732
733 fn create_or_destroy_items(&self, children: &Controls) {
735 let num_items_above_below_selected = self.num_items_above_below_selected.get();
736 let mut total_items_in_viewport = num_items_above_below_selected * 2 + 1;
737
738 if !self.should_loop() {
739 let selected_index = self.selected_index.get();
740 let total_items = self.total_items.get();
741
742 let mut num_item_above_select = num_items_above_below_selected;
743 if selected_index - num_items_above_below_selected < 0 {
744 num_item_above_select = selected_index;
745 }
746 let mut num_item_below_select = num_items_above_below_selected;
747 if selected_index + num_items_above_below_selected >= total_items {
748 num_item_below_select = total_items - selected_index - 1;
749 }
750
751 total_items_in_viewport = num_item_below_select + num_item_above_select + 1;
752 }
753
754 while (children.count() as i64) < i64::from(total_items_in_viewport) {
755 let item = ListBoxItem::new();
756 item.set_height(self.item_height());
757 item.classes().add(self.panel_type().item_class());
758 item.set_vertical_content_alignment(VerticalAlignment::Center);
759 item.set_focusable(false);
760 children.add(item);
761 }
762 if (children.count() as i64) > i64::from(total_items_in_viewport) {
763 let total_items_in_viewport = usize::try_from(total_items_in_viewport)
766 .unwrap_or_else(|_| panic!("Index was out of range. Must be non-negative."));
767 let num_to_remove = children.count() - total_items_in_viewport;
768 children.remove_range(children.count() - num_to_remove, num_to_remove);
769 }
770 }
771
772 fn update_items(&self) {
775 let children = self.children().snapshot();
776 let min = self.minimum_value();
777 let panel_type = self.panel_type();
778 let selected = self.selected_value();
779 let max = self.maximum_value();
780 let increment = self.increment();
781
782 let mut first;
783 if self.should_loop() {
784 let total_items = self.total_items.get();
785 first = (self.selected_index.get() - self.num_items_above_below_selected.get()) % total_items;
786 first = if first < 0 { min + (first + total_items) * increment } else { min + first * increment };
787 } else {
788 first = min + (self.selected_index.get() - self.num_items_above_below_selected.get()).max(0) * increment;
789 }
790
791 for child in children.iter() {
792 let item = child.cast::<ListBoxItem>().expect("the children of the panel are list box items");
793 item.set_content(Some(Rc::new(self.format_content(first, panel_type)) as BoxedValue));
794 item.set_tag(Some(Rc::new(first) as BoxedValue));
795 item.set_is_selected(first == selected);
796 first += increment;
797 if first > max {
798 first = min;
799 }
800 }
801 }
802
803 fn format_content(&self, value: i32, panel_type: DateTimePickerPanelType) -> String {
804 match panel_type {
805 DateTimePickerPanelType::Year => DateTime::new(value, 1, 1).to_string_with(&self.item_format()),
806 DateTimePickerPanelType::Month => {
807 DateTime::new(self.format_date().year(), value, 1).to_string_with(&self.item_format())
808 }
809 DateTimePickerPanelType::Day => {
810 let format_date = self.format_date();
811 DateTime::new(format_date.year(), format_date.month(), value).to_string_with(&self.item_format())
812 }
813 DateTimePickerPanelType::Hour => TimeSpan::from_hms(value, 0, 0).to_string_format(&self.item_format()),
814 DateTimePickerPanelType::Minute => TimeSpan::from_hms(0, value, 0).to_string_format(&self.item_format()),
815 DateTimePickerPanelType::Second => TimeSpan::from_hms(0, 0, value).to_string_format(&self.item_format()),
816 DateTimePickerPanelType::TimePeriod => {
817 if value == self.minimum_value() {
818 TimeUtils::get_am_designator()
819 } else {
820 TimeUtils::get_pm_designator()
821 }
822 }
823 }
824 }
825
826 fn coerce_selected(&self, new_value: i32) -> i32 {
829 let minimum = self.minimum_value();
830 let maximum = self.maximum_value();
831 if new_value < minimum {
832 return minimum;
833 }
834 if new_value > maximum {
835 return maximum;
836 }
837
838 let increment = self.increment();
839 if new_value % increment != 0 {
840 let count = i64::from(maximum) + 1;
845 if count < 0 {
846 panic!("Specified argument was out of the range of valid values. (Parameter 'count')");
847 }
848 let distance = |candidate: i64| (candidate - i64::from(new_value)).abs();
849 let mut nearest: Option<(i32, i64)> = None;
850 let candidates = (i64::from(minimum)..i64::from(minimum) + count).filter(|i| i % i64::from(increment) == 0);
851 for (index, candidate) in candidates.enumerate() {
852 nearest = match nearest {
853 Some((_, current)) if distance(current) <= distance(candidate) => nearest,
854 _ => Some((index as i32, candidate)),
855 };
856 }
857 let (index, _) = nearest.unwrap_or_else(|| panic!("Sequence contains no elements"));
858 return index * increment;
859 }
860 new_value
861 }
862
863 fn on_item_tapped(&self, e: &TappedEventArgs) {
864 let Some(source) = e.source().and_then(|source| source.cast::<Visual>()) else {
865 return;
866 };
867 let Some(list_box_item) = Self::get_item_from_source(source) else {
868 return;
869 };
870 let Some(tag) = list_box_item.tag() else {
871 return;
872 };
873 let tag: &dyn AnyValue = &*tag;
874 if let Some(tag) = tag.downcast_ref::<i32>() {
875 self.set_selected_value(*tag);
876 e.set_handled(true);
877 }
878 }
879
880 fn get_item_from_source(src: Ref<Visual>) -> Option<Ref<ListBoxItem>> {
882 let mut item = Some(src);
883 while let Some(current) = item {
884 if let Some(list_box_item) = current.cast::<ListBoxItem>() {
885 return Some(list_box_item);
886 }
887 item = current.visual_parent();
888 }
889 None
890 }
891
892 pub fn bring_into_view(&self, _target: &Ref<Control>, _target_rect: Rect) -> bool {
893 false
894 }
895
896 pub fn get_control_in_direction(
897 &self,
898 _direction: NavigationDirection,
899 _from: Option<&Ref<Control>>,
900 ) -> Option<Ref<Control>> {
901 None
902 }
903
904 pub fn raise_scroll_invalidated(&self) {
906 for (_, handler) in self.scroll_invalidated.snapshot().iter() {
907 handler();
908 }
909 }
910
911 fn on_scroll_gesture_ended(&self, _e: &ScrollGestureEndedEventArgs) {
912 let offset = self.offset.get();
913 let item_height = self.item_height();
914 let snap_y = (offset.y / item_height).round_ties_even() * item_height;
915
916 if snap_y != offset.y {
917 self.set_offset(Vector::new(offset.x, snap_y));
918 }
919 }
920}