ferroui_diagnostics/diagnostics/views/
events_page_view.rs1use 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#[repr(C)]
32pub struct EventsPageView {
33 base: UserControl,
34 events: RefCell<Option<Ref<ListBox>>>,
35 adorner: RefCell<Option<Rc<dyn IDisposable>>>,
36 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 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 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 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 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 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 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 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 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 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 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}