Skip to main content

ferroui_controls/calendar/
calendar_day_button.rs

1// (c) Copyright Microsoft Corporation.
2// This source is subject to the Microsoft Public License (Ms-PL).
3// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
4// All other rights reserved.
5//
6// Ported from the Silverlight Toolkit sources as adapted by the upstream
7// project; the license text is in the `NOTICE.md` of this crate.
8
9use super::{Calendar, DateTimeHelper};
10use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl};
11use crate::{Button, ButtonImpl, ContentControl, ContentControlImpl, ControlImpl};
12use ferroui_base::input::{
13    InputElementImpl, InputElementImplExt, MouseButton, PointerPressedEventArgs, PointerReleasedEventArgs,
14};
15use ferroui_base::interactivity::InteractiveImpl;
16use ferroui_base::layout::LayoutableImpl;
17use ferroui_base::reactive::{Disposable, IDisposable};
18use ferroui_base::utilities::HandlerList;
19use ferroui_base::{
20    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
21    Ref, StyledElementImpl, VisualImpl, WeakRef,
22};
23use std::cell::{Cell, RefCell};
24use std::rc::Rc;
25
26/// Default content for the CalendarDayButton.
27const DEFAULT_CONTENT: i32 = 1;
28
29/// Represents a day on a [`Calendar`].
30#[repr(C)]
31pub struct CalendarDayButton {
32    base: Button,
33
34    is_current: Cell<bool>,
35    ignoring_mouse_over_state: Cell<bool>,
36    is_blackout: Cell<bool>,
37    is_today: Cell<bool>,
38    is_inactive: Cell<bool>,
39    is_selected: Cell<bool>,
40
41    /// The Calendar associated with this button.
42    owner: RefCell<Option<WeakRef<Calendar>>>,
43    index: Cell<i32>,
44
45    calendar_day_button_mouse_down: HandlerList<dyn Fn(&CalendarDayButton, &PointerPressedEventArgs)>,
46    calendar_day_button_mouse_up: HandlerList<dyn Fn(&CalendarDayButton, &PointerReleasedEventArgs)>,
47}
48
49ferro_class!(CalendarDayButton: Button);
50
51ferro_class_info!(CalendarDayButton {
52    new: CalendarDayButton::new,
53    markup: {
54        attributes: [PseudoClasses(":pressed", ":disabled", ":selected", ":inactive", ":today", ":blackout", ":dayfocused")],
55    },
56});
57
58ferro_impl_classes!(
59    CalendarDayButton: StyledElementImpl,
60    VisualImpl,
61    LayoutableImpl,
62    InteractiveImpl,
63    ContentControlImpl,
64    ButtonImpl
65);
66
67impl FerroObjectImpl for CalendarDayButton {
68    fn constructed(this: &Self) {
69        Self::parent_constructed(this);
70
71        let content: BoxedValue = Rc::new(DateTimeHelper::format_number(DEFAULT_CONTENT));
72        this.set_current_value(ContentControl::content_property(), Some(content));
73    }
74}
75
76impl InputElementImpl for CalendarDayButton {
77    /// Provides class handling for the pointer pressed event that occurs
78    /// when the left mouse button is pressed while the mouse pointer is
79    /// over this control.
80    ///
81    /// This method marks the event as handled and cannot be handled by
82    /// subscribing to the event through the routed event of the control.
83    /// Subscribe through [`CalendarDayButton::calendar_day_button_mouse_down`]
84    /// instead.
85    fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
86        Self::parent_on_pointer_pressed(this, e);
87
88        if e.get_current_point(Some(this)).properties.is_left_button_pressed {
89            for (_, handler) in this.calendar_day_button_mouse_down.snapshot().iter() {
90                handler(this, e);
91            }
92        }
93    }
94
95    /// Provides handling for the pointer released event that occurs when
96    /// the left mouse button is released while the mouse pointer is over
97    /// this control.
98    ///
99    /// This method marks the event as handled and cannot be handled by
100    /// subscribing to the event through the routed event of the control.
101    /// Subscribe through [`CalendarDayButton::calendar_day_button_mouse_up`]
102    /// instead.
103    fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
104        Self::parent_on_pointer_released(this, e);
105
106        if e.initial_press_mouse_button() == MouseButton::Left {
107            for (_, handler) in this.calendar_day_button_mouse_up.snapshot().iter() {
108                handler(this, e);
109            }
110        }
111    }
112}
113
114impl TemplatedControlImpl for CalendarDayButton {
115    /// Builds the visual tree for the [`CalendarDayButton`] when a new
116    /// template is applied.
117    fn on_apply_template(this: &Self, _e: &TemplateAppliedEventArgs) {
118        this.set_pseudo_classes();
119    }
120}
121
122impl ControlImpl for CalendarDayButton {
123    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
124        crate::automation::peers::CalendarDayButtonAutomationPeer::new(this).upcast()
125    }
126}
127
128impl CalendarDayButton {
129    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
130    pub fn construct() -> Self {
131        Self {
132            base: Button::construct(),
133            is_current: Cell::new(false),
134            ignoring_mouse_over_state: Cell::new(false),
135            is_blackout: Cell::new(false),
136            is_today: Cell::new(false),
137            is_inactive: Cell::new(false),
138            is_selected: Cell::new(false),
139            owner: RefCell::new(None),
140            index: Cell::new(0),
141            calendar_day_button_mouse_down: HandlerList::new(),
142            calendar_day_button_mouse_up: HandlerList::new(),
143        }
144    }
145
146    /// Initializes a new instance of the [`CalendarDayButton`] class.
147    pub fn new() -> Ref<Self> {
148        instantiate(Self::construct())
149    }
150
151    /// Gets the Calendar associated with this button.
152    pub(crate) fn owner(&self) -> Option<Ref<Calendar>> {
153        self.owner.borrow().as_ref().and_then(WeakRef::upgrade)
154    }
155
156    /// Sets the Calendar associated with this button.
157    pub(crate) fn set_owner(&self, value: Option<&Ref<Calendar>>) {
158        *self.owner.borrow_mut() = value.map(Ref::downgrade);
159    }
160
161    /// The index of the button among the children of the month view.
162    pub(crate) fn index(&self) -> i32 {
163        self.index.get()
164    }
165
166    pub(crate) fn set_index(&self, value: i32) {
167        self.index.set(value);
168    }
169
170    /// Gets a value indicating whether the button is the focused element
171    /// on the Calendar control.
172    pub(crate) fn is_current(&self) -> bool {
173        self.is_current.get()
174    }
175
176    /// Sets a value indicating whether the button is the focused element
177    /// on the Calendar control.
178    pub(crate) fn set_is_current(&self, value: bool) {
179        if self.is_current.get() != value {
180            self.is_current.set(value);
181            self.set_pseudo_classes();
182        }
183    }
184
185    /// Ensure the button is not in the MouseOver state.
186    ///
187    /// If a button is in the MouseOver state when a Popup is closed (as is
188    /// the case when you select a date in the DatePicker control), it will
189    /// continue to think it's in the mouse over state even when the Popup
190    /// opens again and it's not.  This method is used to forcibly clear the
191    /// state by changing the CommonStates state group.
192    pub(crate) fn ignore_mouse_over_state(&self) {
193        // TODO: Investigate whether this needs to be done by changing the
194        // state everytime we change any state, or if it can be done once
195        // to properly reset the control.
196
197        self.ignoring_mouse_over_state.set(false);
198
199        // If the button thinks it's in the MouseOver state (which can
200        // happen when a Popup is closed before the button can change state)
201        // we will override the state so it shows up as normal.
202        if self.is_pointer_over() {
203            self.ignoring_mouse_over_state.set(true);
204            self.set_pseudo_classes();
205        }
206    }
207
208    /// Gets a value indicating whether this is a blackout date.
209    pub(crate) fn is_blackout(&self) -> bool {
210        self.is_blackout.get()
211    }
212
213    /// Sets a value indicating whether this is a blackout date.
214    pub(crate) fn set_is_blackout(&self, value: bool) {
215        if self.is_blackout.get() != value {
216            self.is_blackout.set(value);
217            self.set_pseudo_classes();
218        }
219    }
220
221    /// Gets a value indicating whether this button represents today.
222    pub(crate) fn is_today(&self) -> bool {
223        self.is_today.get()
224    }
225
226    /// Sets a value indicating whether this button represents today.
227    pub(crate) fn set_is_today(&self, value: bool) {
228        if self.is_today.get() != value {
229            self.is_today.set(value);
230            self.set_pseudo_classes();
231        }
232    }
233
234    /// Gets a value indicating whether the button is inactive.
235    pub(crate) fn is_inactive(&self) -> bool {
236        self.is_inactive.get()
237    }
238
239    /// Sets a value indicating whether the button is inactive.
240    pub(crate) fn set_is_inactive(&self, value: bool) {
241        if self.is_inactive.get() != value {
242            self.is_inactive.set(value);
243            self.set_pseudo_classes();
244        }
245    }
246
247    /// Gets a value indicating whether the button is selected.
248    pub(crate) fn is_selected(&self) -> bool {
249        self.is_selected.get()
250    }
251
252    /// Sets a value indicating whether the button is selected.
253    pub(crate) fn set_is_selected(&self, value: bool) {
254        if self.is_selected.get() != value {
255            self.is_selected.set(value);
256            self.set_pseudo_classes();
257        }
258    }
259
260    fn set_pseudo_classes(&self) {
261        if self.ignoring_mouse_over_state.get() {
262            self.pseudo_classes().set(":pressed", self.is_pressed());
263            self.pseudo_classes().set(":disabled", !self.is_enabled());
264        }
265
266        self.pseudo_classes().set(":selected", self.is_selected());
267        self.pseudo_classes().set(":inactive", self.is_inactive());
268        self.pseudo_classes().set(":today", self.is_today());
269        self.pseudo_classes().set(":blackout", self.is_blackout());
270        self.pseudo_classes().set(":dayfocused", self.is_current() && self.is_enabled());
271    }
272
273    /// Occurs when the left mouse button is pressed (or when the tip of the
274    /// stylus touches the tablet PC) while the mouse pointer is over a UI
275    /// element. The handler receives the button that raises the event.
276    pub fn calendar_day_button_mouse_down(
277        &self,
278        handler: impl Fn(&CalendarDayButton, &PointerPressedEventArgs) + 'static,
279    ) -> Rc<dyn IDisposable> {
280        let token = self.calendar_day_button_mouse_down.add(Rc::new(handler));
281        let weak = self.to_ref().downgrade();
282        Disposable::create(move || {
283            if let Some(this) = weak.upgrade() {
284                this.calendar_day_button_mouse_down.remove(token);
285            }
286        })
287    }
288
289    /// Occurs when the left mouse button is released (or the tip of the
290    /// stylus is removed from the tablet PC) while the mouse (or the
291    /// stylus) is over a UI element (or while a UI element holds mouse
292    /// capture). The handler receives the button that raises the event.
293    pub fn calendar_day_button_mouse_up(
294        &self,
295        handler: impl Fn(&CalendarDayButton, &PointerReleasedEventArgs) + 'static,
296    ) -> Rc<dyn IDisposable> {
297        let token = self.calendar_day_button_mouse_up.add(Rc::new(handler));
298        let weak = self.to_ref().downgrade();
299        Disposable::create(move || {
300            if let Some(this) = weak.upgrade() {
301                this.calendar_day_button_mouse_up.remove(token);
302            }
303        })
304    }
305}