Skip to main content

ferroui_controls/split_view/
split_view.rs

1use super::{SplitViewDisplayMode, SplitViewPanePlacement, SplitViewTemplateSettings};
2use crate::presenters::ContentPresenter;
3use crate::primitives::{PopupRoot, TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt};
4use crate::templates::IDataTemplate;
5use crate::{
6    ContentControl, ContentControlImpl, ContentControlImplExt, Control, ControlImpl, GridLength, GridUnitType, Panel, TopLevel,
7};
8use ferroui_base::input::{InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, PointerReleasedEventArgs};
9use ferroui_base::interactivity::{
10    CancelRoutedEventArgs, Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken,
11    RoutingStrategies,
12};
13use ferroui_base::layout::LayoutableImpl;
14use ferroui_base::media::IBrush;
15use ferroui_base::reactive::{Disposable, IDisposable};
16use ferroui_base::{
17    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, instantiate, BoxedValue,
18    DirectProperty, DirectPropertyMetadata, FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
19    FerroPropertyChangedEventArgs, Ref, StyledElement, StyledElementImpl, StyledProperty, StyledPropertyOptions,
20    Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
21};
22use std::cell::{Cell, RefCell};
23use std::rc::Rc;
24
25const PC_OPEN: &str = ":open";
26const PC_CLOSED: &str = ":closed";
27const PC_COMPACT_OVERLAY: &str = ":compactoverlay";
28const PC_COMPACT_INLINE: &str = ":compactinline";
29const PC_OVERLAY: &str = ":overlay";
30const PC_INLINE: &str = ":inline";
31const PC_LEFT: &str = ":left";
32const PC_RIGHT: &str = ":right";
33const PC_TOP: &str = ":top";
34const PC_BOTTOM: &str = ":bottom";
35const PC_LIGHT_DISMISS: &str = ":lightDismiss";
36
37/// A control with two views: a collapsible pane and an area for content.
38#[repr(C)]
39pub struct SplitView {
40    base: ContentControl,
41    pane: RefCell<Option<Ref<Panel>>>,
42    pointer_disposable: RefCell<Option<Rc<dyn IDisposable>>>,
43    template_settings: RefCell<Ref<SplitViewTemplateSettings>>,
44    last_display_mode_pseudoclass: Cell<Option<&'static str>>,
45    last_placement_pseudoclass: Cell<Option<&'static str>>,
46}
47
48ferro_class! {
49    SplitView: ContentControl, virtuals SplitViewImpl: ContentControlImpl {
50        /// Raises the `PaneOpening` event.
51        fn on_pane_opening(this, args: &CancelRoutedEventArgs);
52        /// Raises the `PaneOpened` event.
53        fn on_pane_opened(this, args: &RoutedEventArgs);
54        /// Raises the `PaneClosing` event.
55        fn on_pane_closing(this, args: &CancelRoutedEventArgs);
56        /// Raises the `PaneClosed` event.
57        fn on_pane_closed(this, args: &RoutedEventArgs);
58        /// Called when the `IsPaneOpen` property has to be coerced.
59        fn on_coerce_is_pane_open(this, value: bool) -> bool;
60    }
61}
62
63ferro_class_info!(SplitView {
64    new: SplitView::new,
65    markup: {
66        property_attributes: [Pane: [DependsOn("PaneTemplate")]],
67        attributes: [
68            TemplatePart("PART_PaneRoot", type(Ref<Panel>)),
69            PseudoClasses(":open", ":closed"),
70            PseudoClasses(":compactoverlay", ":compactinline", ":overlay", ":inline"),
71            PseudoClasses(":left", ":right", ":top", ":bottom"),
72            PseudoClasses(":lightDismiss"),
73        ],
74    },
75});
76
77ferro_impl_classes!(SplitView: StyledElementImpl, LayoutableImpl, InteractiveImpl, ControlImpl);
78
79impl FerroObjectImpl for SplitView {
80    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
81        Self::parent_on_property_changed(this, change);
82
83        if change.property() == Self::compact_pane_length_property().as_property() {
84            this.update_visual_state_for_compact_pane_length(change.get_new_value::<f64>());
85        } else if change.property() == Self::display_mode_property().as_property() {
86            this.update_visual_state_for_display_mode(change.get_new_value::<SplitViewDisplayMode>());
87        } else if change.property() == Self::is_pane_open_property().as_property() {
88            let is_pane_open = change.get_new_value::<bool>();
89            this.update_pane_state_pseudo_class(is_pane_open);
90            if is_pane_open {
91                this.on_pane_opened(&RoutedEventArgs::with_event_and_source(Self::pane_opened_event(), this.to_ref()));
92            } else {
93                this.on_pane_closed(&RoutedEventArgs::with_event_and_source(Self::pane_closed_event(), this.to_ref()));
94            }
95        } else if change.property() == Self::pane_property().as_property() {
96            let (old_value, new_value) = change.get_old_and_new_value::<Option<BoxedValue>>();
97
98            if let Some(old_child) = old_value.as_ref().and_then(Control::logical_from_boxed) {
99                StyledElement::logical_children(this).remove(&old_child);
100            }
101
102            if let Some(new_child) = new_value.as_ref().and_then(Control::logical_from_boxed) {
103                StyledElement::logical_children(this).add(new_child);
104            }
105        } else if change.property() == Self::pane_placement_property().as_property() {
106            this.update_visual_state_for_pane_placement_property(change.get_new_value::<SplitViewPanePlacement>());
107        } else if change.property() == Self::use_light_dismiss_overlay_mode_property().as_property() {
108            let mode = change.get_new_value::<bool>();
109            this.pseudo_classes().set(PC_LIGHT_DISMISS, mode);
110        }
111    }
112}
113
114impl VisualImpl for SplitView {
115    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
116        Self::parent_on_attached_to_visual_tree(this, e);
117
118        // The :left and :right style triggers contain the template so we
119        // need to do this as soon as we're attached so the template applies.
120        // The other visual states can be updated after the template applies.
121        this.update_visual_state_for_pane_placement_property(this.pane_placement());
122    }
123
124    fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
125        Self::parent_on_detached_from_visual_tree(this, e);
126        this.dispose_pointer_disposable();
127    }
128}
129
130impl InputElementImpl for SplitView {
131    fn on_key_down(this: &Self, e: &KeyEventArgs) {
132        if !e.handled() && e.key == Key::Escape && this.is_pane_open() && this.is_in_overlay_mode() {
133            this.set_current_value(Self::is_pane_open_property(), false);
134            e.set_handled(true);
135        }
136
137        Self::parent_on_key_down(this, e);
138    }
139}
140
141impl TemplatedControlImpl for SplitView {
142    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
143        Self::parent_on_apply_template(this, e);
144        let pane = e.name_scope().find_as::<Panel>("PART_PaneRoot");
145        *this.pane.borrow_mut() = pane;
146
147        this.update_visual_state_for_display_mode(this.display_mode());
148        this.update_pane_state_pseudo_class(this.is_pane_open());
149    }
150}
151
152impl ContentControlImpl for SplitView {
153    fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
154        let result = Self::parent_register_content_presenter(this, presenter);
155
156        if presenter.name().as_deref() == Some("PART_PanePresenter") {
157            return true;
158        }
159
160        result
161    }
162}
163
164impl SplitViewImpl for SplitView {
165    fn on_pane_opening(this: &Self, args: &CancelRoutedEventArgs) {
166        this.raise_event(args);
167    }
168
169    fn on_pane_opened(this: &Self, args: &RoutedEventArgs) {
170        this.invalidate_light_dismiss_subscription();
171        this.raise_event(args);
172    }
173
174    fn on_pane_closing(this: &Self, args: &CancelRoutedEventArgs) {
175        this.raise_event(args);
176    }
177
178    fn on_pane_closed(this: &Self, args: &RoutedEventArgs) {
179        this.dispose_pointer_disposable();
180        this.raise_event(args);
181    }
182
183    fn on_coerce_is_pane_open(this: &Self, value: bool) -> bool {
184        let event_args = if value {
185            let event_args = CancelRoutedEventArgs::with_event_and_source(Self::pane_opening_event(), this.to_ref());
186            this.on_pane_opening(&event_args);
187            event_args
188        } else {
189            let event_args = CancelRoutedEventArgs::with_event_and_source(Self::pane_closing_event(), this.to_ref());
190            this.on_pane_closing(&event_args);
191            event_args
192        };
193
194        if event_args.cancel() {
195            return !value;
196        }
197
198        value
199    }
200}
201
202ferro_properties! {
203    impl SplitView {
204        /// Defines the `CompactPaneLength` property.
205        pub fn compact_pane_length_property() -> StyledProperty<f64> {
206            FerroProperty::register::<SplitView, _>("CompactPaneLength", 48.0)
207        }
208
209        /// Defines the `DisplayMode` property.
210        pub fn display_mode_property() -> StyledProperty<SplitViewDisplayMode> {
211            FerroProperty::register::<SplitView, _>("DisplayMode", SplitViewDisplayMode::Overlay)
212        }
213
214        /// Defines the `IsPaneOpen` property.
215        pub fn is_pane_open_property() -> StyledProperty<bool> {
216            FerroProperty::register_with::<SplitView, _>(
217                "IsPaneOpen",
218                StyledPropertyOptions::new(false).coerce(SplitView::coerce_is_pane_open),
219            )
220        }
221
222        /// Defines the `OpenPaneLength` property.
223        pub fn open_pane_length_property() -> StyledProperty<f64> {
224            FerroProperty::register::<SplitView, _>("OpenPaneLength", 320.0)
225        }
226
227        /// Defines the `PaneBackground` property.
228        pub fn pane_background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
229            FerroProperty::register::<SplitView, _>("PaneBackground", None)
230        }
231
232        /// Defines the `PanePlacement` property.
233        pub fn pane_placement_property() -> StyledProperty<SplitViewPanePlacement> {
234            FerroProperty::register::<SplitView, _>("PanePlacement", SplitViewPanePlacement::Left)
235        }
236
237        /// Defines the `Pane` property.
238        pub fn pane_property() -> StyledProperty<Option<BoxedValue>> {
239            FerroProperty::register::<SplitView, _>("Pane", None)
240        }
241
242        /// Defines the `PaneTemplate` property.
243        pub fn pane_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
244            FerroProperty::register::<SplitView, _>("PaneTemplate", None)
245        }
246
247        /// Defines the `UseLightDismissOverlayMode` property.
248        pub fn use_light_dismiss_overlay_mode_property() -> StyledProperty<bool> {
249            FerroProperty::register::<SplitView, _>("UseLightDismissOverlayMode", false)
250        }
251
252        /// Defines the `TemplateSettings` property.
253        pub fn template_settings_property() -> DirectProperty<SplitView, Ref<SplitViewTemplateSettings>> {
254            FerroProperty::register_direct_with::<SplitView, _>(
255                "TemplateSettings",
256                |x| x.template_settings(),
257                None,
258                DirectPropertyMetadata::new(None),
259            )
260        }
261    }
262}
263
264impl SplitView {
265    ferro_routed_event!(
266        /// Defines the `PaneClosed` event.
267        pub fn pane_closed_event() -> RoutedEvent<RoutedEventArgs> {
268            RoutedEvent::register::<SplitView, _>("PaneClosed", RoutingStrategies::BUBBLE)
269        }
270    );
271
272    ferro_routed_event!(
273        /// Defines the `PaneClosing` event.
274        pub fn pane_closing_event() -> RoutedEvent<CancelRoutedEventArgs> {
275            RoutedEvent::register::<SplitView, _>("PaneClosing", RoutingStrategies::BUBBLE)
276        }
277    );
278
279    ferro_routed_event!(
280        /// Defines the `PaneOpened` event.
281        pub fn pane_opened_event() -> RoutedEvent<RoutedEventArgs> {
282            RoutedEvent::register::<SplitView, _>("PaneOpened", RoutingStrategies::BUBBLE)
283        }
284    );
285
286    ferro_routed_event!(
287        /// Defines the `PaneOpening` event.
288        pub fn pane_opening_event() -> RoutedEvent<CancelRoutedEventArgs> {
289            RoutedEvent::register::<SplitView, _>("PaneOpening", RoutingStrategies::BUBBLE)
290        }
291    );
292
293    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
294    pub fn construct() -> Self {
295        Self {
296            base: ContentControl::construct(),
297            pane: RefCell::new(None),
298            pointer_disposable: RefCell::new(None),
299            template_settings: RefCell::new(SplitViewTemplateSettings::new()),
300            last_display_mode_pseudoclass: Cell::new(None),
301            last_placement_pseudoclass: Cell::new(None),
302        }
303    }
304
305    pub fn new() -> Ref<Self> {
306        instantiate(Self::construct())
307    }
308
309    /// The length of the pane when in the compact overlay or the compact
310    /// inline mode.
311    pub fn compact_pane_length(&self) -> f64 {
312        self.get_value(Self::compact_pane_length_property())
313    }
314
315    pub fn set_compact_pane_length(&self, value: f64) {
316        self.set_value(Self::compact_pane_length_property(), value)
317    }
318
319    /// The display mode of the split view.
320    pub fn display_mode(&self) -> SplitViewDisplayMode {
321        self.get_value(Self::display_mode_property())
322    }
323
324    pub fn set_display_mode(&self, value: SplitViewDisplayMode) {
325        self.set_value(Self::display_mode_property(), value)
326    }
327
328    /// Whether the pane is open or closed.
329    pub fn is_pane_open(&self) -> bool {
330        self.get_value(Self::is_pane_open_property())
331    }
332
333    pub fn set_is_pane_open(&self, value: bool) {
334        self.set_value(Self::is_pane_open_property(), value)
335    }
336
337    /// The length of the pane when open.
338    pub fn open_pane_length(&self) -> f64 {
339        self.get_value(Self::open_pane_length_property())
340    }
341
342    pub fn set_open_pane_length(&self, value: f64) {
343        self.set_value(Self::open_pane_length_property(), value)
344    }
345
346    /// The background of the pane.
347    pub fn pane_background(&self) -> Option<Rc<dyn IBrush>> {
348        self.get_value(Self::pane_background_property())
349    }
350
351    pub fn set_pane_background(&self, value: Option<Rc<dyn IBrush>>) {
352        self.set_value(Self::pane_background_property(), value)
353    }
354
355    /// The placement of the pane.
356    pub fn pane_placement(&self) -> SplitViewPanePlacement {
357        self.get_value(Self::pane_placement_property())
358    }
359
360    pub fn set_pane_placement(&self, value: SplitViewPanePlacement) {
361        self.set_value(Self::pane_placement_property(), value)
362    }
363
364    /// The pane of the split view.
365    pub fn pane(&self) -> Option<BoxedValue> {
366        self.get_value(Self::pane_property())
367    }
368
369    pub fn set_pane(&self, value: Option<BoxedValue>) {
370        self.set_value(Self::pane_property(), value)
371    }
372
373    /// The data template used to display the pane content of the control.
374    pub fn pane_template(&self) -> Option<Rc<dyn IDataTemplate>> {
375        self.get_value(Self::pane_template_property())
376    }
377
378    pub fn set_pane_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
379        self.set_value(Self::pane_template_property(), value)
380    }
381
382    /// Whether the light dismiss overlay mode is enabled.
383    ///
384    /// When enabled, and the pane is open in the overlay or the compact
385    /// overlay mode, the contents of the split view are darkened to visually
386    /// separate the open pane and the rest of the split view.
387    pub fn use_light_dismiss_overlay_mode(&self) -> bool {
388        self.get_value(Self::use_light_dismiss_overlay_mode_property())
389    }
390
391    pub fn set_use_light_dismiss_overlay_mode(&self, value: bool) {
392        self.set_value(Self::use_light_dismiss_overlay_mode_property(), value)
393    }
394
395    /// The template settings of the split view.
396    pub fn template_settings(&self) -> Ref<SplitViewTemplateSettings> {
397        self.template_settings.borrow().clone()
398    }
399
400    /// Fired when the pane is closed.
401    pub fn pane_closed(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
402        self.add_handler(Self::pane_closed_event(), handler)
403    }
404
405    /// Fired when the pane is closing.
406    ///
407    /// The `cancel` property of the event args may be set to true to cancel
408    /// the event and keep the pane open.
409    pub fn pane_closing(
410        &self,
411        handler: impl Fn(&Interactive, &CancelRoutedEventArgs) + 'static,
412    ) -> RoutedEventHandlerToken {
413        self.add_handler(Self::pane_closing_event(), handler)
414    }
415
416    /// Fired when the pane is opened.
417    pub fn pane_opened(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
418        self.add_handler(Self::pane_opened_event(), handler)
419    }
420
421    /// Fired when the pane is opening.
422    ///
423    /// The `cancel` property of the event args may be set to true to cancel
424    /// the event and keep the pane closed.
425    pub fn pane_opening(
426        &self,
427        handler: impl Fn(&Interactive, &CancelRoutedEventArgs) + 'static,
428    ) -> RoutedEventHandlerToken {
429        self.add_handler(Self::pane_opening_event(), handler)
430    }
431
432    fn dispose_pointer_disposable(&self) {
433        let disposable = self.pointer_disposable.borrow_mut().take();
434        if let Some(disposable) = disposable {
435            disposable.dispose();
436        }
437    }
438
439    fn pointer_released_outside(&self, e: &PointerReleasedEventArgs) {
440        let Some(pane) = self.pane.borrow().clone().map(Ref::upcast::<Visual>) else {
441            return;
442        };
443        if !self.is_pane_open() {
444            return;
445        }
446
447        let mut close_pane = true;
448        let mut src = e.source().and_then(|source| source.cast::<Visual>());
449        while let Some(current) = src {
450            // Make assumption that if a popup is in the visual tree, the
451            // owning control is within the pane. This works because if the
452            // pane is triggered to close when clicked anywhere else in the
453            // window, the pane would close before the popup is opened.
454            if current.ptr_eq(&pane) || current.is::<PopupRoot>() {
455                close_pane = false;
456                break;
457            }
458
459            src = current.visual_parent();
460        }
461
462        if close_pane {
463            self.set_current_value(Self::is_pane_open_property(), false);
464            e.set_handled(true);
465        }
466    }
467
468    fn is_in_overlay_mode(&self) -> bool {
469        let display_mode = self.display_mode();
470        display_mode == SplitViewDisplayMode::CompactOverlay || display_mode == SplitViewDisplayMode::Overlay
471    }
472
473    /// The pseudoclass for the given display mode.
474    fn display_mode_pseudo_class(mode: SplitViewDisplayMode) -> &'static str {
475        match mode {
476            SplitViewDisplayMode::Inline => PC_INLINE,
477            SplitViewDisplayMode::CompactInline => PC_COMPACT_INLINE,
478            SplitViewDisplayMode::Overlay => PC_OVERLAY,
479            SplitViewDisplayMode::CompactOverlay => PC_COMPACT_OVERLAY,
480        }
481    }
482
483    /// The pseudoclass for the given pane placement.
484    fn placement_pseudo_class(placement: SplitViewPanePlacement) -> &'static str {
485        match placement {
486            SplitViewPanePlacement::Left => PC_LEFT,
487            SplitViewPanePlacement::Right => PC_RIGHT,
488            SplitViewPanePlacement::Top => PC_TOP,
489            SplitViewPanePlacement::Bottom => PC_BOTTOM,
490        }
491    }
492
493    fn update_visual_state_for_compact_pane_length(&self, new_len: f64) {
494        let display_mode = self.display_mode();
495        let template_settings = self.template_settings();
496        if display_mode == SplitViewDisplayMode::CompactInline {
497            template_settings.set_closed_pane_width(new_len);
498            template_settings.set_closed_pane_height(new_len);
499        } else if display_mode == SplitViewDisplayMode::CompactOverlay {
500            template_settings.set_closed_pane_width(new_len);
501            template_settings.set_pane_column_grid_length(GridLength::new(new_len, GridUnitType::Pixel));
502            template_settings.set_closed_pane_height(new_len);
503            template_settings.set_pane_row_grid_length(GridLength::new(new_len, GridUnitType::Pixel));
504        }
505    }
506
507    fn update_visual_state_for_display_mode(&self, new_value: SplitViewDisplayMode) {
508        if let Some(last) = self.last_display_mode_pseudoclass.get() {
509            self.pseudo_classes().remove_pseudo(last);
510        }
511
512        let pseudo_class = Self::display_mode_pseudo_class(new_value);
513        self.last_display_mode_pseudoclass.set(Some(pseudo_class));
514        self.pseudo_classes().add_pseudo(pseudo_class);
515
516        let template_settings = self.template_settings();
517
518        let (closed_pane_width, pane_column_grid_length) = match new_value {
519            SplitViewDisplayMode::Overlay => (0.0, GridLength::new(0.0, GridUnitType::Pixel)),
520            SplitViewDisplayMode::CompactOverlay => {
521                (self.compact_pane_length(), GridLength::new(self.compact_pane_length(), GridUnitType::Pixel))
522            }
523            SplitViewDisplayMode::Inline => (0.0, GridLength::new(0.0, GridUnitType::Auto)),
524            SplitViewDisplayMode::CompactInline => {
525                (self.compact_pane_length(), GridLength::new(0.0, GridUnitType::Auto))
526            }
527        };
528        template_settings.set_closed_pane_width(closed_pane_width);
529        template_settings.set_pane_column_grid_length(pane_column_grid_length);
530
531        let (closed_pane_height, pane_row_grid_length) = match new_value {
532            SplitViewDisplayMode::Overlay => (0.0, GridLength::new(0.0, GridUnitType::Pixel)),
533            SplitViewDisplayMode::CompactOverlay => {
534                (self.compact_pane_length(), GridLength::new(self.compact_pane_length(), GridUnitType::Pixel))
535            }
536            SplitViewDisplayMode::Inline => (0.0, GridLength::new(0.0, GridUnitType::Auto)),
537            SplitViewDisplayMode::CompactInline => {
538                (self.compact_pane_length(), GridLength::new(0.0, GridUnitType::Auto))
539            }
540        };
541        template_settings.set_closed_pane_height(closed_pane_height);
542        template_settings.set_pane_row_grid_length(pane_row_grid_length);
543
544        self.invalidate_light_dismiss_subscription();
545    }
546
547    fn update_visual_state_for_pane_placement_property(&self, new_value: SplitViewPanePlacement) {
548        if let Some(last) = self.last_placement_pseudoclass.get() {
549            self.pseudo_classes().remove_pseudo(last);
550        }
551
552        let pseudo_class = Self::placement_pseudo_class(new_value);
553        self.last_placement_pseudoclass.set(Some(pseudo_class));
554        self.pseudo_classes().add_pseudo(pseudo_class);
555    }
556
557    fn invalidate_light_dismiss_subscription(&self) {
558        if self.pane.borrow().is_none() {
559            return;
560        }
561
562        // If this returns false, we're not in the overlay or the compact
563        // overlay display mode and don't need the light dismiss behavior.
564        if !self.is_in_overlay_mode() {
565            self.dispose_pointer_disposable();
566            return;
567        }
568
569        if self.pointer_disposable.borrow().is_some() {
570            return;
571        }
572
573        if let Some(top_level) = TopLevel::get_top_level(Some(self)) {
574            // The handlers refer to the split view weakly: the top level
575            // must not keep the split view alive through them.
576            let weak = self.to_ref().downgrade();
577            let pointer_released = top_level.add_handler(InputElement::pointer_released_event(), {
578                let weak = weak.clone();
579                move |_, e: &PointerReleasedEventArgs| {
580                    if let Some(this) = weak.upgrade() {
581                        this.pointer_released_outside(e);
582                    }
583                }
584            });
585            let back_requested = top_level.back_requested(move |_, e| {
586                if let Some(this) = weak.upgrade() {
587                    this.top_level_back_requested(e);
588                }
589            });
590
591            let top_level = top_level.downgrade();
592            let disposable = Disposable::create(move || {
593                if let Some(top_level) = top_level.upgrade() {
594                    top_level.remove_handler(InputElement::pointer_released_event(), pointer_released);
595                    top_level.remove_handler(TopLevel::back_requested_event(), back_requested);
596                }
597            });
598            *self.pointer_disposable.borrow_mut() = Some(disposable);
599        }
600    }
601
602    fn top_level_back_requested(&self, e: &RoutedEventArgs) {
603        if !self.is_in_overlay_mode() {
604            return;
605        }
606
607        if !self.is_pane_open() {
608            return;
609        }
610
611        self.set_current_value(Self::is_pane_open_property(), false);
612        e.set_handled(true);
613    }
614
615    fn update_pane_state_pseudo_class(&self, is_pane_open: bool) {
616        if is_pane_open {
617            self.pseudo_classes().add_pseudo(PC_OPEN);
618            self.pseudo_classes().remove_pseudo(PC_CLOSED);
619        } else {
620            self.pseudo_classes().add_pseudo(PC_CLOSED);
621            self.pseudo_classes().remove_pseudo(PC_OPEN);
622        }
623    }
624
625    /// Coerces/validates the `IsPaneOpen` property value.
626    fn coerce_is_pane_open(instance: &FerroObject, value: bool) -> bool {
627        if let Some(split_view) = instance.downcast_ref::<SplitView>() {
628            return split_view.on_coerce_is_pane_open(value);
629        }
630
631        value
632    }
633}