ferroui_diagnostics/diagnostics/view_models/
tree_page_view_model.rs1use super::{ControlDetailsViewModel, FilterViewModel, MainViewModel, TreeNode, ViewModelBase};
12use ferroui_base::collections::FerroList;
13use ferroui_base::controls::ResourceKey;
14use ferroui_base::data::model::{Event, INotifyPropertyChanged};
15use ferroui_base::metadata::MarkupAssembly;
16use ferroui_base::reactive::{Disposable, IDisposable};
17use ferroui_base::utilities::HandlerList;
18use ferroui_base::{ferro_markup_type, Ref, TypeInfo, Visual};
19use ferroui_controls::{Control, GridLength};
20use ferroui_controls_treedatagrid::models::{HierarchicalExpanderColumn, IViewColumn, TemplateColumn, TemplateColumnOptions};
21use ferroui_controls_treedatagrid::TreeDataGridSourceRef;
22use ferroui_treedatagrid_core::models::ColumnRef;
23use ferroui_treedatagrid_core::selection::TreeSelectionModelSelectionChangedEventArgsOf;
24use ferroui_treedatagrid_core::{Accessor, HierarchicalTreeDataGridSource, IndexPath, Models};
25use std::cell::{Cell, RefCell};
26use std::collections::HashSet;
27use std::rc::{Rc, Weak};
28
29pub const TREE_NODE_CELL_TEMPLATE: &str = "TreeNodeCell";
31
32ferroui_controls::ferro_markup_list!(pub TreeNodeList: Rc<TreeNode>);
34
35pub struct TreePageViewModel {
39 base: ViewModelBase,
40 this: Weak<TreePageViewModel>,
41 selected_node: RefCell<Option<Rc<TreeNode>>>,
42 details: RefCell<Option<Rc<ControlDetailsViewModel>>>,
43 pinned_properties: Rc<RefCell<HashSet<String>>>,
44 clipboard_copy_requested: Rc<HandlerList<dyn Fn(&str)>>,
45 main_view: Weak<MainViewModel>,
46 properties_filter: Rc<FilterViewModel>,
47 setters_filter: Rc<FilterViewModel>,
48 nodes: RefCell<FerroList<Rc<TreeNode>>>,
49 filter_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
50 nodes_source: Rc<HierarchicalTreeDataGridSource<TreeNode>>,
54 updating_selection: Cell<bool>,
55}
56
57impl PartialEq for TreePageViewModel {
58 fn eq(&self, other: &Self) -> bool {
59 std::ptr::eq(self, other)
60 }
61}
62
63impl INotifyPropertyChanged for TreePageViewModel {
64 fn property_changed(&self) -> &Event<str> {
65 self.base.property_changed()
66 }
67}
68
69impl TreePageViewModel {
70 pub fn new(
76 main_view: &Rc<MainViewModel>,
77 nodes: Vec<Rc<TreeNode>>,
78 pinned_properties: Rc<RefCell<HashSet<String>>>,
79 ) -> Rc<Self> {
80 Self::with_weak_main_view(Rc::downgrade(main_view), nodes, pinned_properties)
81 }
82
83 pub fn with_weak_main_view(
87 main_view: Weak<MainViewModel>,
88 nodes: Vec<Rc<TreeNode>>,
89 pinned_properties: Rc<RefCell<HashSet<String>>>,
90 ) -> Rc<Self> {
91 TreeNode::register_model();
92 let nodes = FerroList::from_items(nodes);
93 let nodes_source = HierarchicalTreeDataGridSource::new(Rc::new(nodes.clone()) as Models<TreeNode>);
94 Self::create_nodes_columns(&nodes_source);
95
96 let this = Rc::new_cyclic(|this: &Weak<TreePageViewModel>| TreePageViewModel {
97 base: ViewModelBase::new(),
98 this: this.clone(),
99 selected_node: RefCell::new(None),
100 details: RefCell::new(None),
101 pinned_properties,
102 clipboard_copy_requested: Rc::new(HandlerList::new()),
103 main_view,
104 properties_filter: FilterViewModel::new(),
105 setters_filter: FilterViewModel::new(),
106 nodes: RefCell::new(nodes),
107 filter_subscriptions: RefCell::new(Vec::new()),
108 nodes_source,
109 updating_selection: Cell::new(false),
110 });
111
112 if let Some(row_selection) = this.nodes_source.row_selection() {
115 let weak = Rc::downgrade(&this);
116 row_selection.add_selection_changed(Rc::new(
117 move |_: &TreeSelectionModelSelectionChangedEventArgsOf<TreeNode>| {
118 if let Some(this) = weak.upgrade() {
119 this.on_row_selection_changed();
120 }
121 },
122 ));
123 }
124
125 let weak = Rc::downgrade(&this);
126 let properties = this.properties_filter.refresh_filter(move || {
127 let details = weak.upgrade().and_then(|this| this.details());
128 if let Some(properties_view) = details.and_then(|details| details.properties_view()) {
129 properties_view.refresh();
130 }
131 });
132
133 let weak = Rc::downgrade(&this);
134 let setters = this.setters_filter.refresh_filter(move || {
135 if let Some(details) = weak.upgrade().and_then(|this| this.details()) {
136 details.update_style_filters();
137 }
138 });
139
140 this.filter_subscriptions.borrow_mut().extend([properties, setters]);
141 this
142 }
143
144 pub fn clipboard_copy_requested(&self, handler: impl Fn(&str) + 'static) -> Rc<dyn IDisposable> {
147 let token = self.clipboard_copy_requested.add(Rc::new(handler));
148 let handlers = self.clipboard_copy_requested.clone();
149 Disposable::create(move || {
150 handlers.remove(token);
151 })
152 }
153
154 fn raise_clipboard_copy_requested(&self, selector: &str) {
155 for (_, handler) in self.clipboard_copy_requested.snapshot().iter() {
156 handler(selector);
157 }
158 }
159
160 pub fn main_view(&self) -> Option<Rc<MainViewModel>> {
162 self.main_view.upgrade()
163 }
164
165 pub fn properties_filter(&self) -> Rc<FilterViewModel> {
166 self.properties_filter.clone()
167 }
168
169 pub fn setters_filter(&self) -> Rc<FilterViewModel> {
170 self.setters_filter.clone()
171 }
172
173 pub fn nodes(&self) -> FerroList<Rc<TreeNode>> {
175 self.nodes.borrow().clone()
176 }
177
178 pub fn set_nodes(&self, value: Vec<Rc<TreeNode>>) {
180 let nodes = FerroList::from_items(value);
181 *self.nodes.borrow_mut() = nodes.clone();
182
183 self.updating_selection.set(true);
186 self.nodes_source.set_items(Rc::new(nodes) as Models<TreeNode>);
187 let selected = self.selected_node();
188 let reselected = self.select_row(selected.as_ref());
189 self.updating_selection.set(false);
190 if !reselected {
191 self.set_selected_node(None);
192 }
193 }
194
195 fn create_nodes_columns(source: &HierarchicalTreeDataGridSource<TreeNode>) {
202 let options = TemplateColumnOptions::<TreeNode>::new();
203 options.set_can_user_resize_column(Some(false));
204 options.set_can_user_sort_column(Some(false));
205 let cell_column = TemplateColumn::from_resource_keys(
206 None,
207 ResourceKey::from(TREE_NODE_CELL_TEMPLATE),
208 None,
209 Some(GridLength::AUTO),
210 Some(Rc::new(options)),
211 );
212 source.columns().add(HierarchicalExpanderColumn::new(
213 cell_column as Rc<dyn IViewColumn<TreeNode>>,
214 |node: &Rc<TreeNode>| Some(node.children() as Models<TreeNode>),
217 None,
218 Some(
219 Accessor::member("IsExpanded", |node: &Rc<TreeNode>| node.is_expanded())
220 .with_setter(|node: &Rc<TreeNode>, value: bool| node.set_is_expanded(value)),
221 ),
222 ) as ColumnRef<TreeNode>);
223 }
224
225 pub fn nodes_source(&self) -> Rc<HierarchicalTreeDataGridSource<TreeNode>> {
227 self.nodes_source.clone()
228 }
229
230 pub fn nodes_source_ref(&self) -> TreeDataGridSourceRef {
232 TreeDataGridSourceRef::of::<TreeNode, _>(self.nodes_source.clone())
233 }
234
235 pub fn index_path_of(&self, node: &Rc<TreeNode>) -> Option<IndexPath> {
239 let mut indexes = Vec::new();
240 let mut current = node.clone();
241 loop {
242 match current.parent() {
243 Some(parent) => {
244 let index = parent.children().index_of(¤t);
245 if index < 0 {
246 return None;
247 }
248 indexes.push(index);
249 current = parent;
250 }
251 None => {
252 let index = self.nodes().index_of(¤t)?;
253 indexes.push(index as i32);
254 break;
255 }
256 }
257 }
258 indexes.reverse();
259 Some(IndexPath::from_indexes(&indexes))
260 }
261
262 pub fn selected_row_index(&self) -> i32 {
265 match self.selected_node().and_then(|node| self.index_path_of(&node)) {
266 Some(index) => self.nodes_source.get_row_index(&index, 0),
267 None => -1,
268 }
269 }
270
271 fn select_row(&self, node: Option<&Rc<TreeNode>>) -> bool {
274 let Some(selection) = self.nodes_source.row_selection() else {
275 return false;
276 };
277 match node.and_then(|node| self.index_path_of(node)) {
278 Some(index) => {
279 self.nodes_source.expand(&index.parent());
280 selection.base().set_selected_index(index);
281 true
282 }
283 None => {
284 selection.base().clear();
285 false
286 }
287 }
288 }
289
290 fn on_row_selection_changed(&self) {
292 if self.updating_selection.get() {
293 return;
294 }
295 let selected = self.nodes_source.row_selection().and_then(|selection| selection.base().selected_item());
296 self.updating_selection.set(true);
297 self.set_selected_node(selected);
298 self.updating_selection.set(false);
299 }
300
301 pub fn selected_node(&self) -> Option<Rc<TreeNode>> {
302 self.selected_node.borrow().clone()
303 }
304
305 pub fn set_selected_node(&self, value: Option<Rc<TreeNode>>) {
307 if self.base.raise_and_set_if_changed(&self.selected_node, value.clone(), "SelectedNode") {
308 let details = match (value.clone(), self.this.upgrade()) {
309 (Some(value), Some(this)) => {
310 Some(ControlDetailsViewModel::new(&this, &value.visual(), self.pinned_properties.clone()))
311 }
312 _ => None,
313 };
314 self.set_details(details);
315 if let Some(details) = self.details() {
316 details.update_properties_view(self.show_implemented_interfaces());
317 details.update_style_filters();
318 }
319
320 if !self.updating_selection.get() {
324 self.updating_selection.set(true);
325 self.select_row(value.as_ref());
326 self.updating_selection.set(false);
327 }
328 }
329 }
330
331 pub fn details(&self) -> Option<Rc<ControlDetailsViewModel>> {
333 self.details.borrow().clone()
334 }
335
336 fn set_details(&self, value: Option<Rc<ControlDetailsViewModel>>) {
337 let old_value = self.details();
338
339 let changed = match (&old_value, &value) {
341 (Some(old), Some(new)) => !Rc::ptr_eq(old, new),
342 (None, None) => false,
343 _ => true,
344 };
345 if changed {
346 *self.details.borrow_mut() = value;
347 self.base.raise_property_changed("Details");
348 if let Some(old_value) = old_value {
349 old_value.dispose();
350 }
351 }
352 }
353
354 pub fn dispose(&self) {
356 for node in self.nodes().snapshot().iter() {
357 node.dispose();
358 }
359
360 if let Some(details) = self.details() {
361 details.dispose();
362 }
363
364 self.nodes_source.dispose();
367 }
368
369 pub fn find_node(&self, control: &Ref<Control>) -> Option<Rc<TreeNode>> {
371 for node in self.nodes().snapshot().iter() {
372 let result = self.find_node_in(node, control);
373
374 if result.is_some() {
375 return result;
376 }
377 }
378
379 None
380 }
381
382 pub fn select_control(&self, control: &Ref<Control>) {
385 let mut node: Option<Rc<TreeNode>> = None;
386 let mut c: Option<Ref<Control>> = Some(control.clone());
387
388 while node.is_none() {
389 let Some(current) = c.as_ref() else { break };
390 node = self.find_node(current);
391
392 if node.is_none() {
393 c = current.get_visual_parent_of_type::<Control>();
394 }
395 }
396
397 if let Some(node) = node {
398 self.set_selected_node(Some(node.clone()));
399 self.expand_node(node.parent());
400 }
401 }
402
403 pub fn copy_selector(&self) {
405 let current_visual = self.selected_node().and_then(|node| node.visual().cast::<Visual>());
406 if let Some(current_visual) = current_visual {
407 let selector = Self::get_visual_selector(¤t_visual);
408
409 self.raise_clipboard_copy_requested(&selector);
410 }
411 }
412
413 pub fn copy_selector_from_template_parent(&self) {
416 let mut parts = Vec::new();
417
418 let mut current_visual = self.selected_node().and_then(|node| node.visual().cast::<Visual>());
419 while let Some(visual) = current_visual {
420 parts.push(Self::get_visual_selector(&visual));
421
422 current_visual = visual.templated_parent().and_then(|parent| parent.cast::<Visual>());
423 }
424
425 if !parts.is_empty() {
426 parts.reverse();
427 let selector = parts.join(" /template/ ");
428
429 self.raise_clipboard_copy_requested(&selector);
430 }
431 }
432
433 pub fn expand_recursively(&self) {
435 if let Some(selected_node) = self.selected_node() {
436 self.expand_node(Some(selected_node.clone()));
437
438 let mut stack = vec![selected_node];
439
440 while let Some(item) = stack.pop() {
441 item.set_is_expanded(true);
442 for child in item.children().to_vec() {
443 stack.push(child);
444 }
445 }
446 }
447 }
448
449 pub fn collapse_children(&self) {
451 if let Some(selected_node) = self.selected_node() {
452 let mut stack = vec![selected_node];
453
454 while let Some(item) = stack.pop() {
455 item.set_is_expanded(false);
456 for child in item.children().to_vec() {
457 stack.push(child);
458 }
459 }
460 }
461 }
462
463 pub fn capture_node_screenshot(&self) {
465 if let Some(main_view) = self.main_view() {
466 main_view.shot(None);
467 }
468 }
469
470 pub fn bring_into_view(&self) {
472 if let Some(control) = self.selected_node().and_then(|node| node.visual().cast::<Control>()) {
473 control.bring_into_view();
474 }
475 }
476
477 pub fn focus(&self) {
479 if let Some(control) = self.selected_node().and_then(|node| node.visual().cast::<Control>()) {
480 let _ = control.focus();
481 }
482 }
483
484 fn get_visual_selector(visual: &Ref<Visual>) -> String {
487 let name = visual.name().filter(|name| !name.is_empty()).map(|name| format!("#{name}")).unwrap_or_default();
488 let classes = visual.classes().snapshot();
489 let pseudo: String = classes.iter().filter(|c| c.starts_with(':')).map(String::as_str).collect();
490 let classes: String = classes.iter().filter(|c| !c.starts_with(':')).map(|c| format!(".{c}")).collect();
491 let type_ = visual.style_key();
492 format!(
493 "{{{}}}{}|{}{name}{classes}{pseudo}",
494 Self::assembly_full_name(type_),
495 type_.namespace(),
496 type_.name()
497 )
498 }
499
500 fn assembly_full_name(type_: &'static TypeInfo) -> String {
506 let module_path = type_.module_path();
507 match MarkupAssembly::of_module(module_path) {
508 Some(assembly) => assembly.name.to_string(),
509 None => module_path.split("::").next().unwrap_or_default().to_string(),
510 }
511 }
512
513 fn expand_node(&self, node: Option<Rc<TreeNode>>) {
514 if let Some(node) = node {
515 node.set_is_expanded(true);
516 self.expand_node(node.parent());
517 }
518 }
519
520 fn find_node_in(&self, node: &Rc<TreeNode>, control: &Ref<Control>) -> Option<Rc<TreeNode>> {
521 if node.visual().ptr_eq(control) {
522 return Some(node.clone());
523 } else {
524 for child in node.children().to_vec() {
525 let result = self.find_node_in(&child, control);
526
527 if result.is_some() {
528 return result;
529 }
530 }
531 }
532
533 None
534 }
535
536 pub fn update_properties_view(&self) {
540 if let Some(details) = self.details() {
541 details.update_properties_view(self.show_implemented_interfaces());
542 }
543 }
544
545 fn show_implemented_interfaces(&self) -> bool {
547 self.main_view().is_none_or(|main_view| main_view.show_implemented_interfaces())
548 }
549}
550
551ferro_markup_type!(class TreePageViewModel {
552 this: Rc<TreePageViewModel>,
553 handles: [TreePageViewModel, Rc<TreePageViewModel>, Option<Rc<TreePageViewModel>>],
554 properties: [
555 MainView: Option<Rc<MainViewModel>> { get: TreePageViewModel::main_view },
556 PropertiesFilter: Rc<FilterViewModel> { get: TreePageViewModel::properties_filter },
557 SettersFilter: Rc<FilterViewModel> { get: TreePageViewModel::setters_filter },
558 Nodes: FerroList<Rc<TreeNode>> { get: TreePageViewModel::nodes },
559 NodesSource: TreeDataGridSourceRef { get: TreePageViewModel::nodes_source_ref },
561 SelectedNode: Option<Rc<TreeNode>> {
562 get: TreePageViewModel::selected_node,
563 set: TreePageViewModel::set_selected_node
564 },
565 Details: Option<Rc<ControlDetailsViewModel>> { get: TreePageViewModel::details },
566 ],
567 methods: [
568 fn CopySelector() => TreePageViewModel::copy_selector,
569 fn CopySelectorFromTemplateParent() => TreePageViewModel::copy_selector_from_template_parent,
570 fn ExpandRecursively() => TreePageViewModel::expand_recursively,
571 fn CollapseChildren() => TreePageViewModel::collapse_children,
572 fn CaptureNodeScreenshot() => TreePageViewModel::capture_node_screenshot,
573 fn BringIntoView() => TreePageViewModel::bring_into_view,
574 fn Focus() => TreePageViewModel::focus,
575 ],
576 notify_property_changed: TreePageViewModel,
577});
578
579pub fn register_tree_value_types() {
584 use ferroui_base::data::core::ValueTypes;
585 use ferroui_controls::ItemsSource;
586
587 ValueTypes::register_reference::<TreeNode>();
588 ValueTypes::register_reference::<super::TreeNodeCollection>();
589 ValueTypes::register_reference::<TreePageViewModel>();
590 ValueTypes::register_reference::<FilterViewModel>();
591 ItemsSource::register_binding_conversion_with::<Rc<super::TreeNodeCollection>>(|children| {
594 ItemsSource::new(children.clone())
595 });
596 ItemsSource::register_binding_conversion_with::<super::TreeNodeCollection>(|children| {
597 ItemsSource::new(children.to_rc())
598 });
599}