ferroui_diagnostics/diagnostics/view_models/
events_page_view_model.rs1use super::event_tree_node_base::sync_visible_nodes;
11use super::{EventOwnerTreeNode, EventTreeNodeBase, FilterViewModel, FiredEvent, MainViewModel, ViewModelBase};
12use crate::diagnostics::models::EventChainLink;
13use ferroui_base::collections::FerroList;
14use ferroui_base::controls::ResourceKey;
15use ferroui_base::data::core::ValueTypes;
16use ferroui_base::data::model::{Event, INotifyPropertyChanged};
17use ferroui_base::ferro_markup_type;
18use ferroui_base::input::InputElement;
19use ferroui_base::interactivity::{RoutedEvent, RoutedEventRegistry};
20use ferroui_base::TypeInfo;
21use ferroui_controls::{Button, Control, GridLength, GridUnitType};
22use ferroui_controls_treedatagrid::models::{HierarchicalExpanderColumn, IViewColumn, TemplateColumn, TemplateColumnOptions};
23use ferroui_controls_treedatagrid::TreeDataGridSourceRef;
24use ferroui_treedatagrid_core::models::ColumnRef;
25use ferroui_treedatagrid_core::selection::TreeSelectionModelSelectionChangedEventArgsOf;
26use ferroui_treedatagrid_core::{Accessor, HierarchicalTreeDataGridSource, IndexPath, Models};
27use std::cell::{Cell, RefCell};
28use std::collections::HashSet;
29use std::rc::{Rc, Weak};
30
31pub const EVENT_TREE_NODE_CELL_TEMPLATE: &str = "EventTreeNodeCell";
34
35fn default_events() -> HashSet<RoutedEvent> {
38 HashSet::from([
39 Button::click_event().as_routed_event(),
40 InputElement::key_down_event().as_routed_event(),
41 InputElement::key_up_event().as_routed_event(),
42 InputElement::text_input_event().as_routed_event(),
43 InputElement::pointer_released_event().as_routed_event(),
44 InputElement::pointer_pressed_event().as_routed_event(),
45 ])
46}
47
48pub struct EventsPageViewModel {
52 base: ViewModelBase,
53 main_view_model: Weak<MainViewModel>,
54 selected_event: RefCell<Option<Rc<FiredEvent>>>,
55 selected_node: RefCell<Option<Rc<EventTreeNodeBase>>>,
56 nodes: FerroList<Rc<EventTreeNodeBase>>,
57 recorded_events: FerroList<Rc<FiredEvent>>,
58 events_filter: Rc<FilterViewModel>,
59 visible_nodes: Rc<FerroList<Rc<EventTreeNodeBase>>>,
63 nodes_source: Rc<HierarchicalTreeDataGridSource<EventTreeNodeBase>>,
64 updating_selection: Cell<bool>,
65}
66
67impl PartialEq for EventsPageViewModel {
69 fn eq(&self, other: &Self) -> bool {
70 std::ptr::eq(self, other)
71 }
72}
73
74impl INotifyPropertyChanged for EventsPageViewModel {
75 fn property_changed(&self) -> &Event<str> {
76 self.base.property_changed()
77 }
78}
79
80impl EventsPageViewModel {
81 pub fn new(main_view_model: Weak<MainViewModel>) -> Rc<Self> {
87 let _ = default_events();
91 EventTreeNodeBase::register_model();
92 let this = Rc::new_cyclic(|this: &Weak<EventsPageViewModel>| {
93 let mut groups: Vec<(&'static TypeInfo, Vec<RoutedEvent>)> = Vec::new();
95 for e in RoutedEventRegistry::instance().get_all_registered() {
96 let owner_type = e.owner_type();
97 match groups.iter_mut().find(|(key, _)| std::ptr::eq(*key, owner_type)) {
98 Some((_, events)) => events.push(e),
99 None => groups.push((owner_type, vec![e])),
100 }
101 }
102 groups.sort_by(|(x, _), (y, _)| x.name().cmp(y.name()));
104
105 let nodes =
106 FerroList::from_items(groups.into_iter().map(|(key, events)| EventOwnerTreeNode::new(key, events, this)));
107 let visible_nodes = Rc::new(FerroList::from_items(nodes.to_vec()));
108 let nodes_source =
109 HierarchicalTreeDataGridSource::new(visible_nodes.clone() as Models<EventTreeNodeBase>);
110 Self::create_nodes_columns(&nodes_source);
111
112 Self {
113 base: ViewModelBase::new(),
114 main_view_model,
115 selected_event: RefCell::new(None),
116 selected_node: RefCell::new(None),
117 nodes,
118 recorded_events: FerroList::new(),
119 events_filter: FilterViewModel::new(),
120 visible_nodes,
121 nodes_source,
122 updating_selection: Cell::new(false),
123 }
124 });
125
126 if let Some(row_selection) = this.nodes_source.row_selection() {
129 let weak = Rc::downgrade(&this);
130 row_selection.add_selection_changed(Rc::new(
131 move |_: &TreeSelectionModelSelectionChangedEventArgsOf<EventTreeNodeBase>| {
132 if let Some(this) = weak.upgrade() {
133 this.on_row_selection_changed();
134 }
135 },
136 ));
137 }
138
139 let weak = Rc::downgrade(&this);
140 let _ = this.events_filter.refresh_filter(move || {
142 if let Some(this) = weak.upgrade() {
143 this.update_event_filters();
144 }
145 });
146
147 this.enable_default();
148 this
149 }
150
151 pub fn name(&self) -> String {
153 "Events".to_string()
154 }
155
156 pub fn nodes(&self) -> FerroList<Rc<EventTreeNodeBase>> {
159 self.nodes.clone()
160 }
161
162 pub fn recorded_events(&self) -> FerroList<Rc<FiredEvent>> {
164 self.recorded_events.clone()
165 }
166
167 pub fn selected_event(&self) -> Option<Rc<FiredEvent>> {
168 self.selected_event.borrow().clone()
169 }
170
171 pub fn set_selected_event(&self, value: Option<Rc<FiredEvent>>) {
172 self.base.raise_and_set_if_changed(&self.selected_event, value, "SelectedEvent");
173 }
174
175 pub fn selected_node(&self) -> Option<Rc<EventTreeNodeBase>> {
176 self.selected_node.borrow().clone()
177 }
178
179 pub fn set_selected_node(&self, value: Option<Rc<EventTreeNodeBase>>) {
180 if self.base.raise_and_set_if_changed(&self.selected_node, value.clone(), "SelectedNode") {
181 if !self.updating_selection.get() {
184 self.updating_selection.set(true);
185 self.select_row(value.as_ref());
186 self.updating_selection.set(false);
187 }
188 }
189 }
190
191 fn create_nodes_columns(source: &HierarchicalTreeDataGridSource<EventTreeNodeBase>) {
199 let options = TemplateColumnOptions::<EventTreeNodeBase>::new();
200 options.set_can_user_resize_column(Some(false));
201 options.set_can_user_sort_column(Some(false));
202 let cell_column = TemplateColumn::from_resource_keys(
203 None,
204 ResourceKey::from(EVENT_TREE_NODE_CELL_TEMPLATE),
205 None,
206 Some(GridLength::new(1.0, GridUnitType::Star)),
207 Some(Rc::new(options)),
208 );
209 source.columns().add(HierarchicalExpanderColumn::new(
210 cell_column as Rc<dyn IViewColumn<EventTreeNodeBase>>,
211 |node: &Rc<EventTreeNodeBase>| {
212 node.visible_children().map(|children| children as Models<EventTreeNodeBase>)
213 },
214 None,
215 Some(
216 Accessor::member("IsExpanded", |node: &Rc<EventTreeNodeBase>| node.is_expanded())
217 .with_setter(|node: &Rc<EventTreeNodeBase>, value: bool| node.set_is_expanded(value)),
218 ),
219 ) as ColumnRef<EventTreeNodeBase>);
220 }
221
222 pub fn nodes_source(&self) -> Rc<HierarchicalTreeDataGridSource<EventTreeNodeBase>> {
224 self.nodes_source.clone()
225 }
226
227 pub fn nodes_source_ref(&self) -> TreeDataGridSourceRef {
229 TreeDataGridSourceRef::of::<EventTreeNodeBase, _>(self.nodes_source.clone())
230 }
231
232 pub fn visible_nodes(&self) -> Rc<FerroList<Rc<EventTreeNodeBase>>> {
234 self.visible_nodes.clone()
235 }
236
237 pub fn index_path_of(&self, node: &Rc<EventTreeNodeBase>) -> Option<IndexPath> {
239 match node.parent() {
240 Some(parent) => {
241 let parent_index = self.visible_nodes.index_of(&parent)?;
242 let index = parent.visible_children()?.index_of(node)?;
243 Some(IndexPath::new(parent_index as i32).append(index as i32))
244 }
245 None => self.visible_nodes.index_of(node).map(|index| IndexPath::new(index as i32)),
246 }
247 }
248
249 pub fn selected_row_index(&self) -> i32 {
252 match self.selected_node().and_then(|node| self.index_path_of(&node)) {
253 Some(index) => self.nodes_source.get_row_index(&index, 0),
254 None => -1,
255 }
256 }
257
258 fn select_row(&self, node: Option<&Rc<EventTreeNodeBase>>) -> bool {
261 let Some(selection) = self.nodes_source.row_selection() else {
262 return false;
263 };
264 match node.and_then(|node| self.index_path_of(node)) {
265 Some(index) => {
266 self.nodes_source.expand(&index.parent());
267 selection.base().set_selected_index(index);
268 true
269 }
270 None => {
271 selection.base().clear();
272 false
273 }
274 }
275 }
276
277 fn on_row_selection_changed(&self) {
279 if self.updating_selection.get() {
280 return;
281 }
282 let selected = self.nodes_source.row_selection().and_then(|selection| selection.base().selected_item());
283 self.updating_selection.set(true);
284 self.set_selected_node(selected);
285 self.updating_selection.set(false);
286 }
287
288 pub fn events_filter(&self) -> Rc<FilterViewModel> {
290 self.events_filter.clone()
291 }
292
293 pub fn clear(&self) {
295 self.recorded_events.clear();
296 }
297
298 pub fn disable_all(&self) {
300 self.evaluate_node_enabled(&|_: &EventTreeNodeBase| false);
301 }
302
303 pub fn enable_default(&self) {
305 let default_events = default_events();
306 self.evaluate_node_enabled(&|node: &EventTreeNodeBase| {
307 let event = node.as_event_tree_node().expect("a node of an event").event();
308 default_events.contains(&event)
309 });
310 }
311
312 pub fn request_tree_navigate_to(&self, nav_target: &EventChainLink) {
315 if let Some(control) = ValueTypes::as_object(&*nav_target.handler()).and_then(|handler| handler.cast::<Control>())
316 {
317 if let Some(main_view_model) = self.main_view() {
318 main_view_model.request_tree_navigate_to(&control, true);
319 }
320 }
321 }
322
323 pub fn select_event_by_type(&self, evt: RoutedEvent) {
325 fn find_node(node: &Rc<EventTreeNodeBase>, event_type: RoutedEvent) -> Option<Rc<EventTreeNodeBase>> {
326 if node.as_event_tree_node().is_some_and(|event_node| event_node.event() == event_type) {
327 return Some(node.clone());
328 }
329
330 if let Some(children) = node.children() {
331 for child in children.to_vec() {
332 let result = find_node(&child, event_type);
333
334 if result.is_some() {
335 return result;
336 }
337 }
338 }
339
340 None
341 }
342
343 for node in self.nodes.to_vec() {
344 let result = find_node(&node, evt);
345
346 if let Some(result) = result.filter(|result| result.is_visible()) {
347 self.set_selected_node(Some(result));
348
349 break;
350 }
351 }
352 }
353
354 fn evaluate_node_enabled(&self, eval: &dyn Fn(&EventTreeNodeBase) -> bool) {
356 fn process_node(node: &Rc<EventTreeNodeBase>, eval: &dyn Fn(&EventTreeNodeBase) -> bool) {
357 if node.as_event_tree_node().is_some() {
358 node.set_is_enabled(Some(eval(&**node)));
359 }
360
361 if let Some(children) = node.children() {
362 for child_node in children.to_vec() {
363 process_node(&child_node, eval);
364 }
365 }
366 }
367
368 for node in self.nodes.to_vec() {
369 process_node(&node, eval);
370 }
371 }
372
373 fn update_event_filters(&self) {
376 fn filter_node(filter: &FilterViewModel, node: &Rc<EventTreeNodeBase>, is_parent_visible: bool) -> bool {
377 let matches_filter = filter.filter(node.text());
378 let mut has_visible_child = false;
379
380 if let Some(children) = node.children() {
381 for child_node in children.to_vec() {
382 has_visible_child |= filter_node(filter, &child_node, matches_filter);
383 }
384 }
385
386 node.set_is_visible(has_visible_child || matches_filter || is_parent_visible);
387
388 node.is_visible()
389 }
390
391 for node in self.nodes.to_vec() {
392 filter_node(&self.events_filter, &node, false);
393 }
394
395 self.updating_selection.set(true);
399 for node in self.nodes.to_vec() {
400 node.update_visible_children();
401 }
402 sync_visible_nodes(&self.nodes.to_vec(), &self.visible_nodes);
403 let selected = self.selected_node();
404 self.select_row(selected.as_ref());
405 self.updating_selection.set(false);
406 }
407
408 pub fn main_view(&self) -> Option<Rc<MainViewModel>> {
410 self.main_view_model.upgrade()
411 }
412}
413
414ferro_markup_type!(class EventsPageViewModel {
415 this: Rc<EventsPageViewModel>,
416 handles: [EventsPageViewModel, Rc<EventsPageViewModel>, Option<Rc<EventsPageViewModel>>],
417 base: ViewModelBase,
418 properties: [
419 Name: String { get: EventsPageViewModel::name },
420 Nodes: FerroList<Rc<EventTreeNodeBase>> { get: EventsPageViewModel::nodes },
421 RecordedEvents: FerroList<Rc<FiredEvent>> { get: EventsPageViewModel::recorded_events },
422 SelectedEvent: Option<Rc<FiredEvent>> {
423 get: EventsPageViewModel::selected_event,
424 set: EventsPageViewModel::set_selected_event
425 },
426 SelectedNode: Option<Rc<EventTreeNodeBase>> {
427 get: EventsPageViewModel::selected_node,
428 set: EventsPageViewModel::set_selected_node
429 },
430 EventsFilter: Rc<FilterViewModel> { get: EventsPageViewModel::events_filter },
431 NodesSource: TreeDataGridSourceRef { get: EventsPageViewModel::nodes_source_ref },
433 MainView: Option<Rc<MainViewModel>> { get: EventsPageViewModel::main_view },
434 ],
435 methods: [
436 fn Clear() => EventsPageViewModel::clear,
437 fn DisableAll() => EventsPageViewModel::disable_all,
438 fn EnableDefault() => EventsPageViewModel::enable_default,
439 fn RequestTreeNavigateTo(Rc<EventChainLink>) =>
440 (|this: &Rc<EventsPageViewModel>, nav_target: Rc<EventChainLink>| this.request_tree_navigate_to(&nav_target)),
441 fn SelectEventByType(RoutedEvent) => EventsPageViewModel::select_event_by_type,
442 ],
443 notify_property_changed: EventsPageViewModel,
444});