Skip to main content

ferroui_diagnostics/diagnostics/view_models/
fired_event.rs

1//! Port of `Diagnostics/ViewModels/FiredEvent.cs`.
2
3use super::ViewModelBase;
4use crate::diagnostics::models::EventChainLink;
5use ferroui_base::collections::FerroList;
6use ferroui_base::data::model::{Event, INotifyPropertyChanged};
7use ferroui_base::ferro_markup_type;
8use ferroui_base::interactivity::{RoutedEvent, RoutedEventArgs, RoutingStrategies};
9use ferroui_base::utilities::DateTime;
10use std::cell::RefCell;
11use std::rc::Rc;
12
13/// `Environment.NewLine`.
14const NEW_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" };
15
16/// The text of routing strategies as the text of a flags enumeration of
17/// .NET: the names of the set flags, by value, separated by `, `.
18pub(crate) fn routing_strategies_text(strategies: RoutingStrategies) -> String {
19    let names: Vec<&str> = [
20        (RoutingStrategies::DIRECT, "Direct"),
21        (RoutingStrategies::TUNNEL, "Tunnel"),
22        (RoutingStrategies::BUBBLE, "Bubble"),
23    ]
24    .into_iter()
25    .filter(|(flag, _)| strategies.contains(*flag))
26    .map(|(_, name)| name)
27    .collect();
28
29    if names.is_empty() {
30        strategies.bits().to_string()
31    } else {
32        names.join(", ")
33    }
34}
35
36/// `a == b` of two references to links that may be null: reference
37/// equality, as the class of the original.
38fn same_link(a: &Option<Rc<EventChainLink>>, b: &Option<Rc<EventChainLink>>) -> bool {
39    match (a, b) {
40        (Some(a), Some(b)) => Rc::ptr_eq(a, b),
41        (None, None) => true,
42        _ => false,
43    }
44}
45
46/// A routed event recorded by the events page: the event, when it was
47/// raised, and the chain of the elements it visited.
48pub struct FiredEvent {
49    base: ViewModelBase,
50    event_args: RoutedEventArgs,
51    original_event: Option<RoutedEvent>,
52    handled_by: RefCell<Option<Rc<EventChainLink>>>,
53    trigger_time: DateTime,
54    event_chain: FerroList<Rc<EventChainLink>>,
55    originator: Rc<EventChainLink>,
56}
57
58/// Reference equality, as the class of the original.
59impl PartialEq for FiredEvent {
60    fn eq(&self, other: &Self) -> bool {
61        std::ptr::eq(self, other)
62    }
63}
64
65impl INotifyPropertyChanged for FiredEvent {
66    fn property_changed(&self) -> &Event<str> {
67        self.base.property_changed()
68    }
69}
70
71impl FiredEvent {
72    /// C# `new FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator, DateTime triggerTime)`:
73    /// the event of `event_args`, with `originator` as the first link of its
74    /// chain.
75    pub fn new(event_args: &RoutedEventArgs, originator: Rc<EventChainLink>, trigger_time: DateTime) -> Rc<Self> {
76        let this = Rc::new(Self {
77            base: ViewModelBase::new(),
78            event_args: event_args.clone(),
79            original_event: event_args.routed_event(),
80            handled_by: RefCell::new(None),
81            trigger_time,
82            event_chain: FerroList::new(),
83            originator: originator.clone(),
84        });
85        this.add_to_chain(originator);
86        this
87    }
88
89    /// Whether `e` are the args of the same raise of the same event.
90    pub fn is_part_of_same_event_chain(&self, e: &RoutedEventArgs) -> bool {
91        // Note, the framework might reuse RoutedEventArgs for different events to avoid extra allocations.
92        // Like, PointerEntered and PointerExited will use the same instance of RoutedEventArgs.
93        *e == self.event_args && e.routed_event() == self.original_event
94    }
95
96    pub fn trigger_time(&self) -> DateTime {
97        self.trigger_time
98    }
99
100    /// The routed event.
101    pub fn event(&self) -> RoutedEvent {
102        self.original_event.expect("the args of a raised event name the routed event")
103    }
104
105    /// Whether an element on the route handled the event.
106    pub fn is_handled(&self) -> bool {
107        self.handled_by().is_some_and(|handled_by| handled_by.handled())
108    }
109
110    /// The elements the event visited, in order.
111    pub fn event_chain(&self) -> FerroList<Rc<EventChainLink>> {
112        self.event_chain.clone()
113    }
114
115    /// The description of the event: its name, its source, its routing
116    /// strategies and what handled it.
117    pub fn display_text(&self) -> String {
118        let event = self.event();
119        if self.is_handled() {
120            let handled_by = self.handled_by().expect("a handled event has the link that handled it");
121            return format!("{} on {};", event.name(), self.originator.handler_name())
122                + NEW_LINE
123                + &format!(
124                    "strategies: {}; handled by: {}",
125                    routing_strategies_text(event.routing_strategies()),
126                    handled_by.handler_name()
127                );
128        }
129
130        format!(
131            "{} on {}; strategies: {}",
132            event.name(),
133            self.originator.handler_name(),
134            routing_strategies_text(event.routing_strategies())
135        )
136    }
137
138    /// The first link of the chain: the source of the event.
139    pub fn originator(&self) -> Rc<EventChainLink> {
140        self.originator.clone()
141    }
142
143    /// The link of the element that handled the event.
144    pub fn handled_by(&self) -> Option<Rc<EventChainLink>> {
145        self.handled_by.borrow().clone()
146    }
147
148    pub fn set_handled_by(&self, value: Option<Rc<EventChainLink>>) {
149        let changed = !same_link(&self.handled_by.borrow(), &value);
150        if changed {
151            *self.handled_by.borrow_mut() = value;
152            self.base.raise_property_changed("HandledBy");
153            self.base.raise_property_changed("IsHandled");
154            self.base.raise_property_changed("DisplayText");
155        }
156    }
157
158    /// Adds the link of the next element the event visited; a link whose
159    /// route differs from the route of the previous link begins a new route.
160    pub fn add_to_chain(&self, link: Rc<EventChainLink>) {
161        if self.event_chain.count() > 0 {
162            let prev_link = self.event_chain.get(self.event_chain.count() - 1);
163
164            if prev_link.route() != link.route() {
165                link.set_begins_new_route(true);
166            }
167        }
168
169        self.event_chain.add(link.clone());
170
171        if self.handled_by().is_none() && link.handled() {
172            self.set_handled_by(Some(link));
173        }
174    }
175}
176
177// The list of the links of the chain of an event, which the events page shows.
178ferroui_controls::ferro_markup_list!(pub EventChainLinkList: Rc<EventChainLink>);
179
180// The list of the recorded events, which the events page shows.
181ferroui_controls::ferro_markup_list!(pub FiredEventList: Rc<FiredEvent>);
182
183ferro_markup_type!(class FiredEvent {
184    this: Rc<FiredEvent>,
185    handles: [FiredEvent, Rc<FiredEvent>, Option<Rc<FiredEvent>>],
186    base: ViewModelBase,
187    properties: [
188        TriggerTime: DateTime { get: FiredEvent::trigger_time },
189        Event: RoutedEvent { get: FiredEvent::event },
190        IsHandled: bool { get: FiredEvent::is_handled },
191        EventChain: FerroList<Rc<EventChainLink>> { get: FiredEvent::event_chain },
192        DisplayText: String { get: FiredEvent::display_text },
193        Originator: Rc<EventChainLink> { get: FiredEvent::originator },
194        HandledBy: Option<Rc<EventChainLink>> { get: FiredEvent::handled_by, set: FiredEvent::set_handled_by },
195    ],
196    notify_property_changed: FiredEvent,
197});