1use crate::i_clickable_control::register_clickable_control;
2use crate::i_command_source::register_command_source;
3use crate::metadata::PseudoClassesAttribute;
4use crate::platform::{FeedbackAction, FeedbackType, PlatformFeedback, PlatformFeedbackExtensions};
5use crate::primitives::{
6 FlyoutBase, PopupFlyoutBase, TemplateAppliedEventArgs, TemplatedControlImpl,
7 TemplatedControlImplExt,
8};
9use crate::{ContentControl, ContentControlImpl, ControlImpl, HotKeyManager};
10use ferroui_base::data::{BindingError, BindingValueType};
11use ferroui_base::input::{
12 AccessKeyEventArgs, AccessKeyHandler, AccessKeyPressedEventArgs, FocusChangedEventArgs, IClickableControl,
13 ICommand, ICommandSource, InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyGesture, MouseButton, PointerCaptureLostEventArgs,
14 PointerPressedEventArgs, PointerReleasedEventArgs,
15};
16use ferroui_base::interactivity::{
17 IRoutedEventArgs, Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken,
18 RoutingStrategies,
19};
20use ferroui_base::layout::LayoutableImpl;
21use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
22use ferroui_base::utilities::WeakEvents;
23use ferroui_base::reactive::{Disposable, IDisposable};
24use ferroui_base::{
25 ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, BoxedValue, DirectProperty,
26 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, Ref, StyledElementImpl,
27 StyledElementImplExt, StyledProperty, StyledPropertyOptions, Visual, VisualImpl, VisualImplExt,
28 VisualTreeAttachmentEventArgs,
29};
30use std::cell::{Cell, RefCell};
31use std::rc::Rc;
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub enum ClickMode {
36 #[default]
38 Release,
39
40 Press,
42}
43
44const PC_PRESSED: &str = ":pressed";
45const PC_FLYOUT_OPEN: &str = ":flyout-open";
46
47#[repr(C)]
49pub struct Button {
50 base: ContentControl,
51 can_execute_change_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
52 is_subscribed_to_command: Cell<bool>,
53 default_key_down_handlers: RefCell<Vec<RoutedEventHandlerToken>>,
54 cancel_key_down_handlers: RefCell<Vec<RoutedEventHandlerToken>>,
55 command_can_execute: Cell<bool>,
56 hotkey: Cell<Option<KeyGesture>>,
57 is_flyout_open: Cell<bool>,
58 is_pressed: Cell<bool>,
59 flyout_events: RefCell<Option<(Rc<dyn IDisposable>, Rc<dyn IDisposable>)>>,
61}
62
63ferro_class! {
64 Button: ContentControl, virtuals ButtonImpl: ContentControlImpl {
65 fn on_click(this);
67 fn open_flyout(this);
69 fn close_flyout(this);
71 fn on_flyout_opened(this);
73 fn on_flyout_closed(this);
75 }
76}
77ferroui_base::ferro_class_info!(Button { new: Button::new });
78
79ferro_impl_classes!(Button: LayoutableImpl, InteractiveImpl, ContentControlImpl);
80
81impl ControlImpl for Button {
82 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
83 crate::automation::peers::ButtonAutomationPeer::new(this).upcast()
84 }
85}
86
87impl FerroObjectImpl for Button {
88 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
89 Self::parent_on_property_changed(this, change);
90
91 let property = change.property();
92
93 if property == Self::command_property().as_property() {
94 let (old_value, new_value) = change.get_old_and_new_value::<Option<Rc<dyn ICommand>>>();
95 if this.is_subscribed_to_command.get() {
96 if old_value.is_some() {
97 this.unsubscribe_can_execute_changed();
98 }
99
100 if let Some(new_command) = &new_value {
101 this.subscribe_can_execute_changed(new_command);
102 }
103 }
104 this.can_execute_changed(new_value.as_ref(), this.command_parameter().as_ref());
105 } else if property == Self::command_parameter_property().as_property() {
106 let new_value = change.get_new_value::<Option<BoxedValue>>();
107 this.can_execute_changed(this.command().as_ref(), new_value.as_ref());
108 } else if property == Self::is_cancel_property().as_property() {
109 let is_cancel = change.get_new_value::<bool>();
110
111 if let Some(input_root) = this.visual_root().and_then(|root| root.downcast::<InputElement>().ok()) {
112 if is_cancel {
113 this.listen_for_cancel(&input_root);
114 } else {
115 this.stop_listening_for_cancel(&input_root);
116 }
117 }
118 } else if property == Self::is_default_property().as_property() {
119 let is_default = change.get_new_value::<bool>();
120
121 if let Some(input_root) = this.visual_root().and_then(|root| root.downcast::<InputElement>().ok()) {
122 if is_default {
123 this.listen_for_default(&input_root);
124 } else {
125 this.stop_listening_for_default(&input_root);
126 }
127 }
128 } else if property == Self::is_pressed_property().as_property() {
129 this.update_pseudo_classes();
130 } else if property == Self::flyout_property().as_property() {
131 let (old_flyout, new_flyout) = change.get_old_and_new_value::<Option<Ref<FlyoutBase>>>();
132
133 if let Some(old_flyout) = &old_flyout {
136 if old_flyout.is_open() {
137 old_flyout.hide();
138 }
139
140 if let Some(old_flyout) = old_flyout.downcast_ref::<PopupFlyoutBase>() {
141 old_flyout.set_default_placement_target(None);
142 }
143 }
144
145 this.unregister_flyout_events(old_flyout.as_ref());
147
148 this.register_flyout_events(new_flyout.as_ref());
149 if let Some(new_flyout) = new_flyout.as_ref().and_then(|flyout| flyout.downcast_ref::<PopupFlyoutBase>()) {
150 new_flyout.set_default_placement_target(Some(this));
151 }
152 this.update_pseudo_classes();
153 } else if property == InputElement::is_effectively_enabled_property().as_property()
154 && !change.get_new_value::<bool>()
155 {
156 this.set_is_pressed(false);
157 }
158 }
159
160 fn update_data_validation(
161 this: &Self,
162 property: &'static FerroProperty,
163 state: BindingValueType,
164 error: Option<&BindingError>,
165 ) {
166 Self::parent_update_data_validation(this, property, state, error);
167
168 if property == Self::command_property().as_property()
169 && state == BindingValueType::BINDING_ERROR
170 && this.command_can_execute.get()
171 {
172 this.command_can_execute.set(false);
173 this.update_is_effectively_enabled();
174 }
175 }
176}
177
178impl StyledElementImpl for Button {
179 fn on_attached_to_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
180 if let Some(hotkey) = this.hotkey.get() {
183 this.set_current_value(Self::hot_key_property(), Some(hotkey));
184 }
185
186 Self::parent_on_attached_to_logical_tree(this, e);
187
188 let (command, parameter) = (this.command(), this.command_parameter());
189 if let Some(command) = &command {
190 this.subscribe_can_execute_changed(command);
191 this.can_execute_changed(Some(command), parameter.as_ref());
192 }
193
194 this.is_subscribed_to_command.set(true);
195 }
196
197 fn on_detached_from_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
198 if let Some(hotkey) = this.hot_key() {
201 this.hotkey.set(Some(hotkey));
202 this.set_current_value(Self::hot_key_property(), None);
203 }
204
205 Self::parent_on_detached_from_logical_tree(this, e);
206
207 if this.command().is_some() {
208 this.unsubscribe_can_execute_changed();
209 }
210
211 this.is_subscribed_to_command.set(false);
212 }
213}
214
215impl VisualImpl for Button {
216 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
217 Self::parent_on_attached_to_visual_tree(this, e);
218
219 if this.is_default() {
220 if let Some(input_element) = e.root_visual().cast::<InputElement>() {
221 this.listen_for_default(&input_element);
222 }
223 }
224 if this.is_cancel() {
225 if let Some(input_element) = e.root_visual().cast::<InputElement>() {
226 this.listen_for_cancel(&input_element);
227 }
228 }
229 }
230
231 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
232 Self::parent_on_detached_from_visual_tree(this, e);
233
234 if this.is_default() {
235 if let Some(input_element) = e.root_visual().cast::<InputElement>() {
236 this.stop_listening_for_default(&input_element);
237 }
238 }
239 if this.is_cancel() {
240 if let Some(input_element) = e.root_visual().cast::<InputElement>() {
241 this.stop_listening_for_cancel(&input_element);
242 }
243 }
244 }
245}
246
247impl InputElementImpl for Button {
248 fn is_enabled_core(this: &Self) -> bool {
249 Self::parent_is_enabled_core(this) && this.command_can_execute.get()
250 }
251
252 fn on_access_key(this: &Self, e: &dyn IRoutedEventArgs) {
253 if e.downcast_ref::<AccessKeyEventArgs>().is_some_and(AccessKeyEventArgs::is_multiple) {
254 Self::parent_on_access_key(this, e);
255 } else {
256 this.on_click();
257 }
258 }
259
260 fn on_key_down(this: &Self, e: &KeyEventArgs) {
261 match e.key {
262 Key::Enter => {
263 this.on_click();
264 e.set_handled(true);
265 }
266 Key::Space => {
267 if this.is_focused() {
270 if this.click_mode() == ClickMode::Press {
271 this.on_click();
272 }
273
274 this.set_is_pressed(true);
275 e.set_handled(true);
276 }
277 }
278 Key::Escape if this.flyout().is_some() => {
279 this.close_flyout();
281 }
282 _ => {}
283 }
284
285 Self::parent_on_key_down(this, e);
286 }
287
288 fn on_key_up(this: &Self, e: &KeyEventArgs) {
289 if e.key == Key::Space && this.is_focused() {
292 if this.click_mode() == ClickMode::Release {
293 this.on_click();
294 }
295 this.set_is_pressed(false);
296 e.set_handled(true);
297 }
298
299 Self::parent_on_key_up(this, e);
300 }
301
302 fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
303 Self::parent_on_pointer_pressed(this, e);
304
305 if e.get_current_point(Some(this)).properties.is_left_button_pressed {
306 if this.is_flyout_open.get() && this.is_effectively_enabled() {
307 e.set_handled(true);
311 this.on_click();
312 } else {
313 this.set_is_pressed(true);
314 e.set_handled(true);
315
316 if this.click_mode() == ClickMode::Press {
317 this.on_click();
318 }
319 }
320 }
321 }
322
323 fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
324 Self::parent_on_pointer_released(this, e);
325
326 if this.is_pressed() && e.initial_press_mouse_button() == MouseButton::Left {
327 this.set_is_pressed(false);
328 e.set_handled(true);
329
330 if this.click_mode() == ClickMode::Release {
331 let this_visual: &Visual = this;
332 let is_over = this.get_visuals_at(e.get_position(Some(this))).iter().any(|c| {
333 std::ptr::eq::<Visual>(&**c, this_visual) || this.is_visual_ancestor_of(c)
334 });
335
336 if is_over {
337 this.on_click();
338 }
339 }
340 }
341 }
342
343 fn on_pointer_capture_lost(this: &Self, e: &PointerCaptureLostEventArgs) {
344 Self::parent_on_pointer_capture_lost(this, e);
345
346 this.set_is_pressed(false);
347 }
348
349 fn on_lost_focus(this: &Self, e: &FocusChangedEventArgs) {
350 Self::parent_on_lost_focus(this, e);
351
352 this.set_is_pressed(false);
353 }
354}
355
356impl TemplatedControlImpl for Button {
357 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
358 Self::parent_on_apply_template(this, e);
359
360 let flyout = this.flyout();
361 this.unregister_flyout_events(flyout.as_ref());
362 this.register_flyout_events(flyout.as_ref());
363 this.update_pseudo_classes();
364 }
365}
366
367impl ButtonImpl for Button {
368 fn on_click(this: &Self) {
369 if this.is_effectively_enabled() {
370 if this.is_flyout_open.get() {
371 this.close_flyout();
372 } else {
373 this.open_flyout();
374 }
375
376 this.perform_feedback(FeedbackAction::click());
377
378 let e = RoutedEventArgs::with_event(Self::click_event());
379 this.raise_event(&e);
380
381 let (command, parameter) = (this.command(), this.command_parameter());
382 if let Some(command) = command {
383 if !e.handled() && command.can_execute(parameter.as_ref()) {
384 command.execute(parameter.as_ref());
385 e.set_handled(true);
386 }
387 }
388 }
389 }
390
391 fn open_flyout(this: &Self) {
392 if let Some(flyout) = this.flyout() {
393 flyout.show_at(this);
394 }
395 }
396
397 fn close_flyout(this: &Self) {
398 if let Some(flyout) = this.flyout() {
399 flyout.hide();
400 }
401 }
402
403 fn on_flyout_opened(_this: &Self) {
404 }
406
407 fn on_flyout_closed(_this: &Self) {
408 }
410}
411
412struct ButtonHandle(Ref<Button>);
414
415impl ICommandSource for ButtonHandle {
416 fn command(&self) -> Option<Rc<dyn ICommand>> {
417 self.0.command()
418 }
419
420 fn command_parameter(&self) -> Option<BoxedValue> {
421 self.0.command_parameter()
422 }
423
424 fn can_execute_changed(&self) {
425 self.0.can_execute_changed_handler();
426 }
427
428 fn is_effectively_enabled(&self) -> bool {
429 self.0.is_effectively_enabled()
430 }
431}
432
433impl IClickableControl for ButtonHandle {
434 fn click(&self, handler: Rc<dyn Fn(&RoutedEventArgs)>) -> Rc<dyn IDisposable> {
435 let token = self.0.click(move |_, e| handler(e));
436 let button = self.0.downgrade();
437 Disposable::create(move || {
438 if let Some(button) = button.upgrade() {
439 button.remove_handler(Button::click_event(), token);
440 }
441 })
442 }
443
444 fn raise_click(&self) {
445 self.0.on_click();
446 }
447
448 fn is_effectively_enabled(&self) -> bool {
449 self.0.is_effectively_enabled()
450 }
451}
452
453impl Button {
454 pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[PC_FLYOUT_OPEN, PC_PRESSED]);
456}
457
458ferroui_base::ferro_properties! { impl Button, also [
459 Button::is_pressed_property,
460 Button::flyout_property,
461] {
462 ferro_property!(
463 pub fn click_mode_property() -> StyledProperty<ClickMode> {
465 FerroProperty::register::<Button, _>("ClickMode", ClickMode::Release)
466 }
467 );
468
469 ferro_property!(
470 pub fn command_property() -> StyledProperty<Option<Rc<dyn ICommand>>> {
472 FerroProperty::register_with::<Button, _>(
473 "Command",
474 StyledPropertyOptions::new(None).enable_data_validation(true),
475 )
476 }
477 );
478
479 ferro_property!(
480 pub fn hot_key_property() -> StyledProperty<Option<KeyGesture>> {
482 HotKeyManager::hot_key_property().add_owner::<Button>()
483 }
484 );
485
486 ferro_property!(
487 pub fn command_parameter_property() -> StyledProperty<Option<BoxedValue>> {
489 FerroProperty::register::<Button, _>("CommandParameter", None)
490 }
491 );
492
493 ferro_property!(
494 pub fn is_default_property() -> StyledProperty<bool> {
496 FerroProperty::register::<Button, _>("IsDefault", false)
497 }
498 );
499
500 ferro_property!(
501 pub fn is_cancel_property() -> StyledProperty<bool> {
503 FerroProperty::register::<Button, _>("IsCancel", false)
504 }
505 );
506} }
507
508impl Button {
509 ferro_routed_event!(
510 pub fn click_event() -> RoutedEvent<RoutedEventArgs> {
512 RoutedEvent::register::<Button, _>("Click", RoutingStrategies::BUBBLE)
513 }
514 );
515
516 ferro_property!(for Button;
517 pub fn is_pressed_property() -> DirectProperty<Button, bool> {
519 FerroProperty::register_direct::<Button, bool>("IsPressed", |b| b.is_pressed(), None, false)
520 }
521 );
522
523 ferro_property!(for Button;
524 pub fn flyout_property() -> StyledProperty<Option<Ref<FlyoutBase>>> {
526 FerroProperty::register::<Button, _>("Flyout", None)
527 }
528 );
529
530 fn static_constructor() {
531 register_command_source::<Button>(|button| Rc::new(ButtonHandle(button)));
532 register_clickable_control::<Button>(|button| Rc::new(ButtonHandle(button)));
533
534 InputElement::focusable_property().override_default_value::<Button>(true);
535 PlatformFeedback::feedback_type_property().override_default_value::<Button>(FeedbackType::Auto);
536 AccessKeyHandler::access_key_pressed_event().add_class_handler::<Button>(Self::on_access_key_pressed);
537 }
538
539 pub fn construct() -> Self {
541 Self {
542 base: ContentControl::construct(),
543 can_execute_change_subscription: RefCell::new(None),
544 is_subscribed_to_command: Cell::new(false),
545 default_key_down_handlers: RefCell::new(Vec::new()),
546 cancel_key_down_handlers: RefCell::new(Vec::new()),
547 command_can_execute: Cell::new(true),
548 hotkey: Cell::new(None),
549 is_flyout_open: Cell::new(false),
550 is_pressed: Cell::new(false),
551 flyout_events: RefCell::new(None),
552 }
553 }
554
555 pub fn new() -> Ref<Self> {
557 instantiate(Self::construct())
558 }
559
560 pub fn click(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
562 self.add_handler(Self::click_event(), handler)
563 }
564
565 pub fn click_mode(&self) -> ClickMode {
567 self.get_value(Self::click_mode_property())
568 }
569
570 pub fn set_click_mode(&self, value: ClickMode) {
571 self.set_value(Self::click_mode_property(), value)
572 }
573
574 pub fn command(&self) -> Option<Rc<dyn ICommand>> {
576 self.get_value(Self::command_property())
577 }
578
579 pub fn set_command(&self, value: Option<Rc<dyn ICommand>>) {
580 self.set_value(Self::command_property(), value)
581 }
582
583 pub fn hot_key(&self) -> Option<KeyGesture> {
585 self.get_value(Self::hot_key_property())
586 }
587
588 pub fn set_hot_key(&self, value: Option<KeyGesture>) {
589 self.set_value(Self::hot_key_property(), value)
590 }
591
592 pub fn command_parameter(&self) -> Option<BoxedValue> {
594 self.get_value(Self::command_parameter_property())
595 }
596
597 pub fn set_command_parameter(&self, value: Option<BoxedValue>) {
598 self.set_value(Self::command_parameter_property(), value)
599 }
600
601 pub fn is_default(&self) -> bool {
604 self.get_value(Self::is_default_property())
605 }
606
607 pub fn set_is_default(&self, value: bool) {
608 self.set_value(Self::is_default_property(), value)
609 }
610
611 pub fn is_cancel(&self) -> bool {
614 self.get_value(Self::is_cancel_property())
615 }
616
617 pub fn set_is_cancel(&self, value: bool) {
618 self.set_value(Self::is_cancel_property(), value)
619 }
620
621 #[inline]
623 pub fn is_pressed(&self) -> bool {
624 self.is_pressed.get()
625 }
626
627 fn set_is_pressed(&self, value: bool) {
628 self.set_and_raise_cell(Self::is_pressed_property(), &self.is_pressed, value);
629 }
630
631 pub fn flyout(&self) -> Option<Ref<FlyoutBase>> {
633 self.get_value(Self::flyout_property())
634 }
635
636 pub fn set_flyout(&self, value: impl Into<Nullable<FlyoutBase>>) {
637 self.set_value(Self::flyout_property(), value.into().0)
638 }
639
640 #[allow(dead_code)]
642 pub(crate) fn perform_click(&self) {
643 self.on_click();
644 }
645
646 fn on_access_key_pressed(sender: &Button, e: &AccessKeyPressedEventArgs) {
647 if e.handled() || e.target().is_some() {
648 return;
649 }
650 e.set_target(Some(sender.to_ref().upcast()));
651 e.set_handled(true);
652 }
653
654 fn subscribe_can_execute_changed(&self, command: &Rc<dyn ICommand>) {
655 let subscription = WeakEvents::subscribe_command_can_execute_changed(command, &self.to_ref(), |this| {
658 this.can_execute_changed_handler()
659 });
660 let old = self.can_execute_change_subscription.replace(Some(subscription));
661 if let Some(old) = old {
662 old.dispose();
663 }
664 }
665
666 fn unsubscribe_can_execute_changed(&self) {
667 let subscription = self.can_execute_change_subscription.take();
668 if let Some(subscription) = subscription {
669 subscription.dispose();
670 }
671 }
672
673 fn can_execute_changed_handler(&self) {
675 self.can_execute_changed(self.command().as_ref(), self.command_parameter().as_ref());
676 }
677
678 fn can_execute_changed(&self, command: Option<&Rc<dyn ICommand>>, parameter: Option<&BoxedValue>) {
679 if !self.is_attached_to_logical_tree() {
680 return;
681 }
682
683 let can_execute = command.is_none_or(|command| command.can_execute(parameter));
684
685 if can_execute != self.command_can_execute.get() {
686 self.command_can_execute.set(can_execute);
687 self.update_is_effectively_enabled();
688 }
689 }
690
691 fn listen_for_default(&self, root: &InputElement) {
693 let weak = self.to_ref().downgrade();
694 let token = root.add_handler(InputElement::key_down_event(), move |_, e| {
695 if let Some(this) = weak.upgrade() {
696 this.root_default_key_down(e);
697 }
698 });
699 self.default_key_down_handlers.borrow_mut().push(token);
700 }
701
702 fn listen_for_cancel(&self, root: &InputElement) {
704 let weak = self.to_ref().downgrade();
705 let token = root.add_handler(InputElement::key_down_event(), move |_, e| {
706 if let Some(this) = weak.upgrade() {
707 this.root_cancel_key_down(e);
708 }
709 });
710 self.cancel_key_down_handlers.borrow_mut().push(token);
711 }
712
713 fn stop_listening_for_default(&self, root: &InputElement) {
716 let token = self.default_key_down_handlers.borrow_mut().pop();
717 if let Some(token) = token {
718 root.remove_handler(InputElement::key_down_event(), token);
719 }
720 }
721
722 fn stop_listening_for_cancel(&self, root: &InputElement) {
725 let token = self.cancel_key_down_handlers.borrow_mut().pop();
726 if let Some(token) = token {
727 root.remove_handler(InputElement::key_down_event(), token);
728 }
729 }
730
731 fn root_default_key_down(&self, e: &KeyEventArgs) {
734 if e.key == Key::Enter && self.is_effectively_visible() && self.is_effectively_enabled() {
735 self.on_click();
736 e.set_handled(true);
737 }
738 }
739
740 fn root_cancel_key_down(&self, e: &KeyEventArgs) {
743 if e.key == Key::Escape && self.is_effectively_visible() && self.is_effectively_enabled() {
744 self.on_click();
745 e.set_handled(true);
746 }
747 }
748
749 fn register_flyout_events(&self, flyout: Option<&Ref<FlyoutBase>>) {
751 if let Some(flyout) = flyout {
752 let weak = self.to_ref().downgrade();
753 let weak_flyout = flyout.downgrade();
754 let opened = flyout.opened({
755 let (weak, weak_flyout) = (weak.clone(), weak_flyout.clone());
756 move || {
757 if let (Some(this), Some(flyout)) = (weak.upgrade(), weak_flyout.upgrade()) {
758 this.flyout_opened(&flyout);
759 }
760 }
761 });
762 let closed = flyout.closed(move || {
763 if let (Some(this), Some(flyout)) = (weak.upgrade(), weak_flyout.upgrade()) {
764 this.flyout_closed(&flyout);
765 }
766 });
767 *self.flyout_events.borrow_mut() = Some((opened, closed));
768 }
769 }
770
771 fn unregister_flyout_events(&self, flyout: Option<&Ref<FlyoutBase>>) {
773 if flyout.is_some() {
774 let flyout_events = self.flyout_events.take();
775 if let Some((opened, closed)) = flyout_events {
776 opened.dispose();
777 closed.dispose();
778 }
779 }
780 }
781
782 fn is_flyout_target(&self, flyout: &FlyoutBase) -> bool {
784 let this: Ref<crate::Control> = self.to_ref().upcast();
785 flyout.target() == Some(this)
786 }
787
788 fn flyout_opened(&self, flyout: &FlyoutBase) {
790 if self.is_flyout_target(flyout) {
799 self.is_flyout_open.set(true);
800 self.update_pseudo_classes();
801
802 self.on_flyout_opened();
803 }
804 }
805
806 fn flyout_closed(&self, flyout: &FlyoutBase) {
808 if self.is_flyout_target(flyout) {
810 self.is_flyout_open.set(false);
811 self.update_pseudo_classes();
812
813 self.on_flyout_closed();
814 }
815 }
816
817 fn update_pseudo_classes(&self) {
820 self.pseudo_classes().set(PC_FLYOUT_OPEN, self.is_flyout_open.get());
821 self.pseudo_classes().set(PC_PRESSED, self.is_pressed());
822 }
823}