Skip to main content

ferroui_diagnostics/diagnostics/views/
events_page_view.rs

1//! Port of `Diagnostics/Views/EventsPageView.xaml.cs`: the class of the
2//! document `EventsPageView.xaml`.
3//!
4//! The tree of the routed events of the document is a TreeDataGrid, where upstream has a tree
5//! view (docs/porting/DEVIATIONS.md, Developer tools).
6
7use crate::diagnostics::controls::ControlHighlightAdorner;
8use crate::diagnostics::models::EventChainLink;
9use crate::diagnostics::view_models::{EventsPageViewModel, FiredEvent};
10use ferroui_base::collections::NotifyCollectionChangedEventArgs;
11use ferroui_base::data::core::ValueTypes;
12use ferroui_base::data::model::INotifyPropertyChanged;
13use ferroui_base::input::{InputElementImpl, PointerEventArgs, TappedEventArgs};
14use ferroui_base::interactivity::{IRoutedEventArgs, InteractiveImpl, RoutedEvent};
15use ferroui_base::layout::LayoutableImpl;
16use ferroui_base::metadata::{from_markup_value, into_markup_value};
17use ferroui_base::reactive::IDisposable;
18use ferroui_base::threading::{Dispatcher, DispatcherPriority};
19use ferroui_base::{
20    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, BoxedValue, FerroObjectImpl, Ref,
21    StyledElementImpl, StyledElementImplExt, Visual, VisualImpl,
22};
23use ferroui_controls::primitives::TemplatedControlImpl;
24use ferroui_controls::{ContentControlImpl, Control, ControlImpl, ListBox, UserControl};
25use ferroui_controls_treedatagrid::TreeDataGrid;
26use std::cell::RefCell;
27use std::rc::Rc;
28
29/// The view of the events page: the tree of the routed events to record,
30/// the recorded events and the chain of the selected event.
31#[repr(C)]
32pub struct EventsPageView {
33    base: UserControl,
34    events: RefCell<Option<Ref<ListBox>>>,
35    adorner: RefCell<Option<Rc<dyn IDisposable>>>,
36    // Deviation (DEVIATIONS.md, Developer tools): the TreeDataGrid of the routed events, and the
37    // handler of the changes of the selected node of the view model, which brings its row into
38    // view.
39    events_tree: RefCell<Option<Ref<TreeDataGrid>>>,
40    selected_node_changed: RefCell<Option<(Rc<EventsPageViewModel>, u64)>>,
41}
42
43ferro_class!(EventsPageView: UserControl);
44ferro_impl_classes!(
45    EventsPageView: FerroObjectImpl,
46    VisualImpl,
47    LayoutableImpl,
48    InteractiveImpl,
49    InputElementImpl,
50    ControlImpl,
51    TemplatedControlImpl,
52    ContentControlImpl
53);
54ferro_class_info!(EventsPageView {
55    new: EventsPageView::new,
56    markup: {
57        methods: [
58            fn NavigateTo(Option<BoxedValue>, Rc<dyn IRoutedEventArgs>) =>
59                |this: &Ref<EventsPageView>, sender: Option<BoxedValue>, e: Rc<dyn IRoutedEventArgs>| {
60                    if let Some(e) = e.downcast_ref::<TappedEventArgs>() {
61                        this.navigate_to(&sender, e)
62                    }
63                },
64            fn ListBoxItem_PointerEntered(Option<BoxedValue>, Rc<dyn IRoutedEventArgs>) =>
65                |this: &Ref<EventsPageView>, sender: Option<BoxedValue>, e: Rc<dyn IRoutedEventArgs>| {
66                    if let Some(e) = e.downcast_ref::<PointerEventArgs>() {
67                        this.list_box_item_pointer_entered(&sender, e)
68                    }
69                },
70            fn ListBoxItem_PointerExited(Option<BoxedValue>, Rc<dyn IRoutedEventArgs>) =>
71                |this: &Ref<EventsPageView>, sender: Option<BoxedValue>, e: Rc<dyn IRoutedEventArgs>| {
72                    if let Some(e) = e.downcast_ref::<PointerEventArgs>() {
73                        this.list_box_item_pointer_exited(&sender, e)
74                    }
75                },
76        ],
77    },
78});
79
80impl StyledElementImpl for EventsPageView {
81    fn on_data_context_changed(this: &Self) {
82        Self::parent_on_data_context_changed(this);
83
84        let selected_node_changed = this.selected_node_changed.borrow_mut().take();
85        if let Some((old_view_model, token)) = selected_node_changed {
86            old_view_model.property_changed().remove(token);
87        }
88
89        if let Some(vm) = this.view_model() {
90            // Deviation (DEVIATIONS.md, Developer tools): the row of a node the view model selects
91            // (a double tap on the name of a recorded event) is brought into view once the grid
92            // has its rows.
93            let view = this.to_ref().downgrade();
94            let token = vm.property_changed().add(Rc::new(move |property_name: &str| {
95                if property_name != "SelectedNode" {
96                    return;
97                }
98                let view = view.clone();
99                Dispatcher::ui_thread().post_local(
100                    move || {
101                        if let Some(view) = view.upgrade() {
102                            view.bring_selected_node_into_view();
103                        }
104                    },
105                    DispatcherPriority::default(),
106                );
107            }));
108            *this.selected_node_changed.borrow_mut() = Some((vm.clone(), token));
109
110            // As upstream, the handler is never removed. It reaches the view and the view model
111            // through weak references, so that the list of the view model does not keep them
112            // alive.
113            let view = this.to_ref().downgrade();
114            let view_model = Rc::downgrade(&vm);
115            let _ = vm.recorded_events().add_collection_changed(Rc::new(
116                move |_: &NotifyCollectionChangedEventArgs<'_, Rc<FiredEvent>>| {
117                    if let (Some(view), Some(view_model)) = (view.upgrade(), view_model.upgrade()) {
118                        view.on_recorded_events_changed(&view_model);
119                    }
120                },
121            ));
122        }
123    }
124}
125
126impl EventsPageView {
127    /// The URI of the document of the class.
128    pub const DOCUMENT_URI: &'static str = "ferres://FerroUI.Diagnostics/diagnostics/views/EventsPageView.xaml";
129
130    pub fn construct() -> Self {
131        Self {
132            base: UserControl::construct(),
133            events: RefCell::new(None),
134            adorner: RefCell::new(None),
135            events_tree: RefCell::new(None),
136            selected_node_changed: RefCell::new(None),
137        }
138    }
139
140    /// Creates the view and populates it from its document
141    /// (`InitializeComponent()`: the compiled markup of the document, which
142    /// the build of the crate generates).
143    ///
144    /// # Panics
145    /// Panics when the document fails to load (an exception of the
146    /// constructor in the original).
147    pub fn new() -> Ref<Self> {
148        crate::register_types();
149        let this = instantiate(Self::construct());
150        Self::initialize_component(&this);
151        *this.events.borrow_mut() = Some(this.get_control::<ListBox>("EventsList"));
152        *this.events_tree.borrow_mut() = Some(this.get_control::<TreeDataGrid>("EventsTree"));
153        this
154    }
155
156    /// Brings the row of the selected node of the tree of the routed events into view.
157    ///
158    /// Deviation (DEVIATIONS.md, Developer tools): the tree is a TreeDataGrid.
159    fn bring_selected_node_into_view(&self) {
160        let Some(vm) = self.view_model() else {
161            return;
162        };
163        let row_index = vm.selected_row_index();
164        if row_index < 0 {
165            return;
166        }
167        let events_tree = self.events_tree.borrow().clone();
168        if let Some(rows_presenter) = events_tree.and_then(|events_tree| events_tree.rows_presenter()) {
169            rows_presenter.bring_into_view(row_index, None);
170        }
171    }
172
173    /// `InitializeComponent()`.
174    fn initialize_component(this: &Ref<Self>) {
175        if let Err(error) = crate::compiled_diagnostics_views_events_page_view::populate(None, this) {
176            panic!("{}: {error}", Self::DOCUMENT_URI);
177        }
178    }
179
180    fn view_model(&self) -> Option<Rc<EventsPageViewModel>> {
181        from_markup_value::<Rc<EventsPageViewModel>>(&self.data_context())
182    }
183
184    /// Double tapping the name of an event selects its node in the tree;
185    /// double tapping an element of a chain shows it in the visual tree.
186    pub fn navigate_to(&self, sender: &Option<BoxedValue>, _e: &TappedEventArgs) {
187        let control = sender.as_ref().and_then(Control::from_boxed);
188        if let (Some(vm), Some(control)) = (self.view_model(), control) {
189            let tag = control.tag();
190            if let Some(chain_link) = from_markup_value::<Rc<EventChainLink>>(&tag) {
191                vm.request_tree_navigate_to(&chain_link);
192            } else if let Some(evt) = from_markup_value::<RoutedEvent>(&tag) {
193                vm.select_event_by_type(evt);
194            }
195        }
196    }
197
198    /// Scrolls the last recorded event into view.
199    fn on_recorded_events_changed(&self, vm: &EventsPageViewModel) {
200        let events = vm.recorded_events();
201        let Some(evt) = events.try_get(events.count().wrapping_sub(1)) else {
202            return;
203        };
204
205        let list_box = self.events.borrow().clone();
206        if let Some(list_box) = list_box {
207            Dispatcher::ui_thread().post_local(
208                move || list_box.scroll_into_view_item(&into_markup_value(evt)),
209                DispatcherPriority::default(),
210            );
211        }
212    }
213
214    /// Highlights the element of the link of the chain under the pointer.
215    pub fn list_box_item_pointer_entered(&self, sender: &Option<BoxedValue>, _e: &PointerEventArgs) {
216        let Some(vm) = self.view_model() else {
217            return;
218        };
219        let Some(control) = sender.as_ref().and_then(Control::from_boxed) else {
220            return;
221        };
222        let Some(chain_link) = from_markup_value::<Rc<EventChainLink>>(&control.data_context()) else {
223            return;
224        };
225        let Some(visual) = ValueTypes::as_object(&*chain_link.handler()).and_then(|handler| handler.cast::<Visual>())
226        else {
227            return;
228        };
229
230        if let Some(main_view) = vm.main_view() {
231            let adorner = ControlHighlightAdorner::add_visual(&visual, main_view.should_visualize_margin_padding());
232            *self.adorner.borrow_mut() = adorner;
233        }
234    }
235
236    /// Removes the highlight.
237    pub fn list_box_item_pointer_exited(&self, _sender: &Option<BoxedValue>, _e: &PointerEventArgs) {
238        let adorner = self.adorner.borrow().clone();
239        if let Some(adorner) = adorner {
240            adorner.dispose();
241        }
242    }
243}