Skip to main content

ferroui_base/interactivity/
event_route.rs

1use super::{IRoutedEventArgs, Interactive, RoutedEvent, RoutingStrategies};
2use crate::Ref;
3use std::cell::RefCell;
4use std::rc::Rc;
5
6/// An event handler as stored on a route: invoked with the element the
7/// handler is attached to and the args being routed.
8pub type RouteHandler = Rc<dyn Fn(&Interactive, &dyn IRoutedEventArgs)>;
9
10struct RouteItem {
11    target: Ref<Interactive>,
12    handler: Option<RouteHandler>,
13    routes: RoutingStrategies,
14    handled_events_too: bool,
15}
16
17/// The number of route lists kept for reuse. Raises nest (a handler raising
18/// another event), so more than one list can be in use at a time.
19const POOL_SIZE: usize = 8;
20
21/// The initial capacity of a route list.
22const INITIAL_CAPACITY: usize = 16;
23
24crate::ferro_thread_local! {
25    static POOL: RefCell<Vec<Vec<RouteItem>>> = const { RefCell::new(Vec::new()) };
26}
27
28fn rent() -> Vec<RouteItem> {
29    POOL.with(|pool| pool.borrow_mut().pop()).unwrap_or_else(|| Vec::with_capacity(INITIAL_CAPACITY))
30}
31
32fn give_back(mut route: Vec<RouteItem>) {
33    // Dropping the items releases element references and can therefore run
34    // arbitrary drop code: do it before touching the pool.
35    route.clear();
36    POOL.with(|pool| {
37        let mut pool = pool.borrow_mut();
38        if pool.len() < POOL_SIZE {
39            pool.push(route);
40        }
41    });
42}
43
44/// Holds the route for a routed event and supports raising an event on that
45/// route.
46///
47/// The list of handlers is taken from a per-thread pool and returned to it
48/// when the route is dropped (or [disposed](Self::dispose)), so raising an
49/// event does not allocate once the pool is warm.
50pub struct EventRoute {
51    event: RoutedEvent,
52    route: Option<Vec<RouteItem>>,
53}
54
55impl EventRoute {
56    /// Creates a route for a routed event.
57    pub fn new<T: ?Sized>(e: &RoutedEvent<T>) -> Self {
58        Self { event: e.as_routed_event(), route: None }
59    }
60
61    /// Whether the route has any handlers.
62    pub fn has_handlers(&self) -> bool {
63        self.route.as_ref().is_some_and(|route| !route.is_empty())
64    }
65
66    /// Adds a handler to the route.
67    pub fn add(
68        &mut self,
69        target: &Ref<Interactive>,
70        handler: RouteHandler,
71        routes: RoutingStrategies,
72        handled_events_too: bool,
73    ) {
74        self.route.get_or_insert_with(rent).push(RouteItem {
75            target: target.clone(),
76            handler: Some(handler),
77            routes,
78            handled_events_too,
79        });
80    }
81
82    /// Adds a class handler to the route: a marker that makes the class
83    /// handlers of the event run for `target`.
84    pub fn add_class_handler(&mut self, target: &Ref<Interactive>) {
85        self.route.get_or_insert_with(rent).push(RouteItem {
86            target: target.clone(),
87            handler: None,
88            routes: RoutingStrategies::empty(),
89            handled_events_too: false,
90        });
91    }
92
93    /// Raises an event along the route.
94    pub fn raise_event(&self, source: &Ref<Interactive>, e: &dyn IRoutedEventArgs) {
95        e.set_source(source);
96
97        let strategies = self.event.routing_strategies();
98
99        if strategies == RoutingStrategies::DIRECT {
100            e.set_route(RoutingStrategies::DIRECT);
101            self.raise_event_impl(e);
102            self.event.invoke_route_finished(e);
103        } else {
104            if strategies.contains(RoutingStrategies::TUNNEL) {
105                e.set_route(RoutingStrategies::TUNNEL);
106                self.raise_event_impl(e);
107                self.event.invoke_route_finished(e);
108            }
109
110            if strategies.contains(RoutingStrategies::BUBBLE) {
111                e.set_route(RoutingStrategies::BUBBLE);
112                self.raise_event_impl(e);
113                self.event.invoke_route_finished(e);
114            }
115        }
116    }
117
118    /// Releases the route's handler list back to the pool.
119    pub fn dispose(&mut self) {
120        if let Some(route) = self.route.take() {
121            give_back(route);
122        }
123    }
124
125    fn raise_event_impl(&self, e: &dyn IRoutedEventArgs) {
126        let Some(route) = &self.route else { return };
127
128        let route_strategy = e.route();
129        let count = route.len();
130        let mut last_target: Option<&Ref<Interactive>> = None;
131
132        for step in 0..count {
133            let index = if route_strategy == RoutingStrategies::TUNNEL { count - 1 - step } else { step };
134            let entry = &route[index];
135
136            // If we've got to a new control then call any `raised` listeners
137            // (the class handlers).
138            if last_target != Some(&entry.target) {
139                self.event.invoke_raised(&entry.target, e);
140
141                // If this is a direct event and we've already raised events
142                // then we're finished.
143                if e.route() == RoutingStrategies::DIRECT && last_target.is_some() {
144                    return;
145                }
146
147                last_target = Some(&entry.target);
148            }
149
150            // Raise the event handler.
151            if let Some(handler) = &entry.handler {
152                if entry.routes.contains(e.route()) && (!e.handled() || entry.handled_events_too) {
153                    handler(&entry.target, e);
154                }
155            }
156        }
157    }
158}
159
160impl Drop for EventRoute {
161    fn drop(&mut self) {
162        self.dispose();
163    }
164}