Skip to main content

ferroui_diagnostics/diagnostics/view_models/
event_tree_node.rs

1//! Port of `Diagnostics/ViewModels/EventTreeNode.cs`.
2
3use super::{EventTreeNodeBase, EventTreeNodeKind, EventsPageViewModel, FiredEvent};
4use crate::diagnostics::models::EventChainLink;
5use crate::diagnostics::DevTools;
6use ferroui_base::interactivity::{IRoutedEventArgs, Interactive, RoutedEvent, RoutedEventArgs, RoutingStrategies};
7use ferroui_base::threading::{Dispatcher, DispatcherPriority};
8use ferroui_base::utilities::DateTime;
9use ferroui_base::{BoxedValue, FerroObject, Ref, Visual};
10use std::cell::{Cell, RefCell};
11use std::rc::{Rc, Weak};
12
13/// The most recorded events the events page keeps; the oldest are removed.
14const MAX_RECORDED_EVENTS: usize = 100;
15
16/// The node of one routed event: enabling it records the event, with the
17/// chain of the elements it visits, in the events page.
18///
19/// The part of the deriving class of a node (see [`EventTreeNodeKind`]).
20pub struct EventTreeNode {
21    event: RoutedEvent,
22    parent_view_model: Weak<EventsPageViewModel>,
23    is_registered: Cell<bool>,
24    current_event: RefCell<Option<Rc<FiredEvent>>>,
25}
26
27impl EventTreeNode {
28    /// C# `new EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsPageViewModel vm)`.
29    ///
30    /// The view model of the page is held weakly: the page holds the nodes.
31    pub fn new(
32        parent: &Rc<EventTreeNodeBase>,
33        event: RoutedEvent,
34        vm: Weak<EventsPageViewModel>,
35    ) -> Rc<EventTreeNodeBase> {
36        EventTreeNodeBase::new(Some(parent), event.name().to_string(), |_| {
37            EventTreeNodeKind::Event(EventTreeNode {
38                event,
39                parent_view_model: vm,
40                is_registered: Cell::new(false),
41                current_event: RefCell::new(None),
42            })
43        })
44    }
45
46    /// The routed event of the node.
47    pub fn event(&self) -> RoutedEvent {
48        self.event
49    }
50
51    /// The override of the setter of `IsEnabled`: starts tracking the event
52    /// when it is enabled, and updates the check box of the owner unless the
53    /// change comes from the owner.
54    pub(crate) fn set_is_enabled(this: &EventTreeNodeBase, value: Option<bool>) {
55        if this.is_enabled() != value {
56            this.base_set_is_enabled(value);
57            Self::update_tracker(this);
58            if let Some(parent) = this.parent() {
59                if this.update_parent.get() {
60                    // `try { .. } finally { .. }` upstream.
61                    parent.update_children.set(false);
62                    parent.update_checked();
63                    parent.update_children.set(true);
64                }
65            }
66        }
67    }
68
69    fn part(this: &EventTreeNodeBase) -> &EventTreeNode {
70        this.as_event_tree_node().expect("a node of an event")
71    }
72
73    fn update_tracker(this: &EventTreeNodeBase) {
74        let node = Self::part(this);
75        if this.is_enabled().unwrap_or_default() && !node.is_registered.get() {
76            let all_routes = RoutingStrategies::DIRECT | RoutingStrategies::TUNNEL | RoutingStrategies::BUBBLE;
77
78            // FIXME: This leaks event handlers.
79            // (As upstream, the handlers are never removed; they reach the node through a weak
80            // reference, so they do not keep it alive.)
81            let weak = Rc::downgrade(&this.to_rc());
82            let _ = node.event.add_class_handler_untyped(
83                FerroObject::TYPE,
84                move |sender, e| {
85                    if let Some(this) = weak.upgrade() {
86                        Self::handle_event(&this, sender, e);
87                    }
88                },
89                all_routes,
90                true,
91            );
92            let weak = Rc::downgrade(&this.to_rc());
93            let _ = node.event.route_finished().subscribe(move |e| {
94                if let Some(this) = weak.upgrade() {
95                    Self::handle_route_finished(&this, e);
96                }
97            });
98
99            node.is_registered.set(true);
100        }
101    }
102
103    /// The link of the chain of an event for an element on its route.
104    fn chain_link(handler: &Ref<Interactive>, handled: bool, route: RoutingStrategies) -> Rc<EventChainLink> {
105        // The sender as an untyped value (`object`): the handle of the object.
106        let handler: BoxedValue = Rc::new(handler.clone().upcast::<FerroObject>());
107        EventChainLink::new(handler, handled, route)
108    }
109
110    /// Runs `handler` on the UI thread: now when this is the UI thread, else
111    /// posted to it.
112    ///
113    /// Routed events, their class handlers and their route notifications
114    /// belong to the thread that raises them, which is the UI thread in an
115    /// application; the post is what the original does for any other.
116    fn run_on_ui_thread(handler: impl FnOnce() + 'static) {
117        let dispatcher = Dispatcher::ui_thread();
118        if !dispatcher.check_access() {
119            dispatcher.post_local(handler, DispatcherPriority::default());
120        } else {
121            handler();
122        }
123    }
124
125    fn handle_event(this: &Rc<EventTreeNodeBase>, sender: &Interactive, e: &dyn IRoutedEventArgs) {
126        let node = Self::part(this);
127        if !node.is_registered.get() || this.is_enabled() == Some(false) {
128            return;
129        }
130        if let Some(v) = sender.downcast_ref::<Visual>() {
131            if DevTools::does_belong_to_dev_tool(v) {
132                return;
133            }
134        }
135
136        let s = sender.to_ref();
137        let handled = e.handled();
138        let route = e.route();
139        let trigger_time = DateTime::now();
140        let args: RoutedEventArgs = e.as_routed_event_args().clone();
141        let weak = Rc::downgrade(this);
142
143        let handler = move || {
144            let Some(this) = weak.upgrade() else {
145                return;
146            };
147            let node = Self::part(&this);
148
149            let current_event = node.current_event.borrow().clone();
150            match current_event {
151                Some(current_event) if current_event.is_part_of_same_event_chain(&args) => {
152                    current_event.add_to_chain(Self::chain_link(&s, handled, route));
153                }
154                _ => {
155                    let current_event = FiredEvent::new(&args, Self::chain_link(&s, handled, route), trigger_time);
156                    *node.current_event.borrow_mut() = Some(current_event.clone());
157
158                    if let Some(parent_view_model) = node.parent_view_model.upgrade() {
159                        let recorded_events = parent_view_model.recorded_events();
160                        recorded_events.add(current_event);
161
162                        while recorded_events.count() > MAX_RECORDED_EVENTS {
163                            recorded_events.remove_at(0);
164                        }
165                    }
166                }
167            }
168        };
169
170        Self::run_on_ui_thread(handler);
171    }
172
173    fn handle_route_finished(this: &Rc<EventTreeNodeBase>, e: &dyn IRoutedEventArgs) {
174        let node = Self::part(this);
175        if !node.is_registered.get() || this.is_enabled() == Some(false) {
176            return;
177        }
178        if let Some(v) = e.source().and_then(|source| source.cast::<Visual>()) {
179            if DevTools::does_belong_to_dev_tool(&v) {
180                return;
181            }
182        }
183
184        let handled = e.handled();
185        let weak = Rc::downgrade(this);
186
187        let handler = move || {
188            let Some(this) = weak.upgrade() else {
189                return;
190            };
191            let node = Self::part(&this);
192
193            let current_event = node.current_event.borrow().clone();
194            if let Some(current_event) = current_event {
195                if handled {
196                    let event_chain = current_event.event_chain();
197                    let link_index = event_chain.count() - 1;
198                    let link = event_chain.get(link_index);
199
200                    link.set_handled(true);
201                    if current_event.handled_by().is_none() {
202                        current_event.set_handled_by(Some(link));
203                    }
204                }
205            }
206        };
207
208        Self::run_on_ui_thread(handler);
209    }
210}