Skip to main content

ferroui_diagnostics/diagnostics/view_models/
event_tree_node_base.rs

1//! Port of `Diagnostics/ViewModels/EventTreeNodeBase.cs`.
2
3use super::{EventOwnerTreeNode, EventTreeNode, ViewModelBase};
4use ferroui_base::collections::FerroList;
5use ferroui_base::data::model::{Event, INotifyPropertyChanged, ModelTypes};
6use ferroui_base::ferro_markup_type;
7use std::cell::{Cell, OnceCell, RefCell};
8use std::rc::{Rc, Weak};
9
10/// What the deriving class of a node adds to it: the owner of a group of
11/// routed events ([`EventOwnerTreeNode`]) or one routed event
12/// ([`EventTreeNode`]).
13///
14/// Upstream the node is an abstract class with these two deriving classes,
15/// whose `IsEnabled` overrides the virtual property of the base. In the port
16/// a node is one shared type, [`EventTreeNodeBase`], that carries the part of
17/// its deriving class: the tree of the events page holds one model type
18/// (its cell template is typed with the base class), and the type tests of
19/// the original (`node is EventTreeNode eventNode`) are the hooks
20/// [`EventTreeNodeBase::as_event_tree_node`] and
21/// [`EventTreeNodeBase::as_event_owner_tree_node`].
22pub enum EventTreeNodeKind {
23    /// A node of an [`EventOwnerTreeNode`].
24    Owner(EventOwnerTreeNode),
25    /// A node of an [`EventTreeNode`].
26    Event(EventTreeNode),
27}
28
29/// A node of the tree of routed events of the events page: the owner of a
30/// group of events or one event, with the check box that enables the
31/// recording of the events below it.
32pub struct EventTreeNodeBase {
33    base: ViewModelBase,
34    this: Weak<EventTreeNodeBase>,
35    pub(crate) update_children: Cell<bool>,
36    pub(crate) update_parent: Cell<bool>,
37    is_expanded: Cell<bool>,
38    is_enabled: Cell<Option<bool>>,
39    is_visible: Cell<bool>,
40    parent: Option<Weak<EventTreeNodeBase>>,
41    text: String,
42    children: RefCell<Option<FerroList<Rc<EventTreeNodeBase>>>>,
43    // Deviation (DEVIATIONS.md, Developer tools): the children the TreeDataGrid of the events
44    // page shows (the visible ones, in their order), made the first time they are asked for.
45    visible_children: OnceCell<Rc<FerroList<Rc<EventTreeNodeBase>>>>,
46    kind: EventTreeNodeKind,
47}
48
49/// Reference equality, as the class of the original.
50impl PartialEq for EventTreeNodeBase {
51    fn eq(&self, other: &Self) -> bool {
52        std::ptr::eq(self, other)
53    }
54}
55
56impl INotifyPropertyChanged for EventTreeNodeBase {
57    fn property_changed(&self) -> &Event<str> {
58        self.base.property_changed()
59    }
60}
61
62impl EventTreeNodeBase {
63    /// C# `protected EventTreeNodeBase(EventTreeNodeBase? parent, string text)`, with the part
64    /// of the deriving class, which `kind` creates from the weak reference to the new node.
65    pub(crate) fn new(
66        parent: Option<&Rc<EventTreeNodeBase>>,
67        text: String,
68        kind: impl FnOnce(&Weak<EventTreeNodeBase>) -> EventTreeNodeKind,
69    ) -> Rc<Self> {
70        let this = Rc::new_cyclic(|this| Self {
71            base: ViewModelBase::new(),
72            this: this.clone(),
73            update_children: Cell::new(true),
74            update_parent: Cell::new(true),
75            is_expanded: Cell::new(false),
76            is_enabled: Cell::new(Some(false)),
77            is_visible: Cell::new(false),
78            parent: parent.map(Rc::downgrade),
79            text,
80            children: RefCell::new(None),
81            visible_children: OnceCell::new(),
82            kind: kind(this),
83        });
84        this.set_is_visible(true);
85        this
86    }
87
88    /// The shared handle of this node.
89    pub(crate) fn to_rc(&self) -> Rc<EventTreeNodeBase> {
90        self.this.upgrade().expect("a node is reached through its shared handle")
91    }
92
93    /// The part of the deriving class.
94    pub fn kind(&self) -> &EventTreeNodeKind {
95        &self.kind
96    }
97
98    /// The node as an [`EventTreeNode`] (C# `node as EventTreeNode`).
99    pub fn as_event_tree_node(&self) -> Option<&EventTreeNode> {
100        match &self.kind {
101            EventTreeNodeKind::Event(node) => Some(node),
102            EventTreeNodeKind::Owner(_) => None,
103        }
104    }
105
106    /// The node as an [`EventOwnerTreeNode`] (C# `node as EventOwnerTreeNode`).
107    pub fn as_event_owner_tree_node(&self) -> Option<&EventOwnerTreeNode> {
108        match &self.kind {
109            EventTreeNodeKind::Owner(node) => Some(node),
110            EventTreeNodeKind::Event(_) => None,
111        }
112    }
113
114    /// The child nodes; `None` for a node without children.
115    pub fn children(&self) -> Option<FerroList<Rc<EventTreeNodeBase>>> {
116        self.children.borrow().clone()
117    }
118
119    /// The visible children, in their order: the child rows of the node in the TreeDataGrid of the
120    /// events page, which follow the filter of the page (`sync_visible_nodes`); `None` for a node
121    /// without children. The same list every time, so that the rows of the children are kept
122    /// while the node notifies other changes.
123    ///
124    /// Deviation (DEVIATIONS.md, Developer tools): upstream's tree view hides the items of the
125    /// nodes that are not visible; the rows of a TreeDataGrid are the nodes of its source.
126    pub fn visible_children(&self) -> Option<Rc<FerroList<Rc<EventTreeNodeBase>>>> {
127        let children = self.children()?;
128        let visible = self.visible_children.get_or_init(|| {
129            Rc::new(FerroList::from_items(children.to_vec().into_iter().filter(|child| child.is_visible())))
130        });
131        Some(visible.clone())
132    }
133
134    /// Updates the visible children (once they were asked for) after a change of the visibility
135    /// of the children.
136    pub(crate) fn update_visible_children(&self) {
137        if let (Some(children), Some(visible)) = (self.children(), self.visible_children.get()) {
138            sync_visible_nodes(&children.to_vec(), visible);
139        }
140    }
141
142    /// States what the TreeDataGrid of the events page observes of the nodes: their property
143    /// change notifications (the expansion of a node). Once per thread.
144    pub(crate) fn register_model() {
145        if !ModelTypes::is_registered(std::any::TypeId::of::<EventTreeNodeBase>()) {
146            ModelTypes::register::<EventTreeNodeBase>(|builder| builder.notify_property_changed());
147        }
148    }
149
150    /// The protected setter of `Children`.
151    pub(crate) fn set_children(&self, value: Option<FerroList<Rc<EventTreeNodeBase>>>) {
152        *self.children.borrow_mut() = value;
153    }
154
155    pub fn is_expanded(&self) -> bool {
156        self.is_expanded.get()
157    }
158
159    pub fn set_is_expanded(&self, value: bool) {
160        self.base.raise_and_set_if_changed_cell(&self.is_expanded, value, "IsExpanded");
161    }
162
163    /// Whether the events of the node are recorded: `None` when some of the
164    /// events below the node are and some are not.
165    pub fn is_enabled(&self) -> Option<bool> {
166        self.is_enabled.get()
167    }
168
169    /// The virtual setter of `IsEnabled`: the override of the deriving class
170    /// ([`EventOwnerTreeNode`] or [`EventTreeNode`]).
171    pub fn set_is_enabled(&self, value: Option<bool>) {
172        match &self.kind {
173            EventTreeNodeKind::Owner(_) => EventOwnerTreeNode::set_is_enabled(self, value),
174            EventTreeNodeKind::Event(_) => EventTreeNode::set_is_enabled(self, value),
175        }
176    }
177
178    /// The base implementation of the setter of `IsEnabled` (C# `base.IsEnabled = value`).
179    pub(crate) fn base_set_is_enabled(&self, value: Option<bool>) {
180        self.base.raise_and_set_if_changed_cell(&self.is_enabled, value, "IsEnabled");
181    }
182
183    pub fn is_visible(&self) -> bool {
184        self.is_visible.get()
185    }
186
187    pub fn set_is_visible(&self, value: bool) {
188        self.base.raise_and_set_if_changed_cell(&self.is_visible, value, "IsVisible");
189    }
190
191    pub fn parent(&self) -> Option<Rc<EventTreeNodeBase>> {
192        self.parent.as_ref().and_then(Weak::upgrade)
193    }
194
195    pub fn text(&self) -> &str {
196        &self.text
197    }
198
199    /// Sets `IsEnabled` from the children: their common value, or `None`
200    /// when they differ.
201    pub(crate) fn update_checked(&self) {
202        let get_value = || -> Option<bool> {
203            let Some(children) = self.children() else {
204                return Some(false);
205            };
206
207            let mut value = Some(false);
208
209            for i in 0..children.count() {
210                if i == 0 {
211                    value = children.get(i).is_enabled();
212                    continue;
213                }
214
215                if value != children.get(i).is_enabled() {
216                    value = None;
217                    break;
218                }
219            }
220
221            value
222        };
223
224        self.set_is_enabled(get_value());
225    }
226}
227
228/// Makes `visible` the nodes of `all` that are visible, in the order of `all`: inserts the nodes
229/// that became visible and removes the ones that were hidden, so that the rows of the others are
230/// kept. `visible` holds nodes of `all` only, in their order.
231///
232/// Deviation (DEVIATIONS.md, Developer tools): the rows of the TreeDataGrid of the events page.
233pub(crate) fn sync_visible_nodes(all: &[Rc<EventTreeNodeBase>], visible: &FerroList<Rc<EventTreeNodeBase>>) {
234    let mut index = 0;
235    for node in all {
236        let shown = index < visible.count() && Rc::ptr_eq(&visible.get(index), node);
237        if node.is_visible() {
238            if !shown {
239                visible.insert(index, node.clone());
240            }
241            index += 1;
242        } else if shown {
243            visible.remove_at(index);
244        }
245    }
246}
247
248// The list of the child nodes (the items of a tree view upstream; the TreeDataGrid of the events
249// page shows the visible children, `visible_children`).
250ferroui_controls::ferro_markup_list!(pub EventTreeNodeList: Rc<EventTreeNodeBase>);
251
252ferro_markup_type!(class EventTreeNodeBase {
253    this: Rc<EventTreeNodeBase>,
254    handles: [EventTreeNodeBase, Rc<EventTreeNodeBase>, Option<Rc<EventTreeNodeBase>>],
255    base: ViewModelBase,
256    properties: [
257        Children: Option<FerroList<Rc<EventTreeNodeBase>>> { get: EventTreeNodeBase::children },
258        IsExpanded: bool { get: EventTreeNodeBase::is_expanded, set: EventTreeNodeBase::set_is_expanded },
259        IsEnabled: Option<bool> { get: EventTreeNodeBase::is_enabled, set: EventTreeNodeBase::set_is_enabled },
260        IsVisible: bool { get: EventTreeNodeBase::is_visible, set: EventTreeNodeBase::set_is_visible },
261        Parent: Option<Rc<EventTreeNodeBase>> { get: EventTreeNodeBase::parent },
262        Text: String { get: |this: &Rc<EventTreeNodeBase>| this.text().to_string() },
263    ],
264    notify_property_changed: EventTreeNodeBase,
265});