Skip to main content

ferroui_linuxframebuffer/
raw_event_grouping.rs

1//! The input queue of a backend that handles input asynchronously (the
2//! port of `Shared/RawEventGrouping.cs`, a source file the reference
3//! compiles into every backend that uses it).
4//!
5//! While queueing, it groups Move and TouchUpdate events so that the
6//! intermediate points of a move can be provided.
7
8use ferroui_base::input::raw::{
9    IRawInputEventArgs, RawPointerEventArgs, RawPointerEventType, RawPointerPoint, RawTouchEventArgs,
10};
11use ferroui_base::input::IntermediatePoints;
12use ferroui_base::platform::IManagedDispatcherInputProvider;
13use ferroui_base::threading::{Dispatcher, DispatcherPriority};
14use std::cell::{Cell, LazyCell, RefCell};
15use std::collections::{HashMap, VecDeque};
16use std::rc::{Rc, Weak};
17use std::sync::Arc;
18
19/// A raw input event.
20pub type RawEvent = Rc<dyn IRawInputEventArgs>;
21/// What handles a queued event.
22pub type RawEventHandler = Rc<dyn Fn(RawEvent)>;
23
24/// Where a grouper queues its events.
25pub trait IRawEventGrouperDispatchQueue {
26    fn add(&self, args: RawEvent, handler: RawEventHandler);
27}
28
29/// A queue that is emptied by whoever owns it (the event loop of the
30/// platform).
31#[derive(Default)]
32pub struct ManualRawEventGrouperDispatchQueue {
33    input_queue: RefCell<VecDeque<(RawEvent, RawEventHandler)>>,
34}
35
36impl ManualRawEventGrouperDispatchQueue {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    pub fn has_jobs(&self) -> bool {
42        !self.input_queue.borrow().is_empty()
43    }
44
45    pub fn dispatch_next(&self) {
46        let next = self.input_queue.borrow_mut().pop_front();
47        let Some((args, handler)) = next else {
48            return;
49        };
50        handler(args);
51    }
52}
53
54impl IRawEventGrouperDispatchQueue for ManualRawEventGrouperDispatchQueue {
55    fn add(&self, args: RawEvent, handler: RawEventHandler) {
56        self.input_queue.borrow_mut().push_back((args, handler));
57    }
58}
59
60/// The manual queue as the input of a dispatcher implementation.
61pub struct ManualRawEventGrouperDispatchQueueDispatcherInputProvider {
62    queue: Rc<ManualRawEventGrouperDispatchQueue>,
63}
64
65impl ManualRawEventGrouperDispatchQueueDispatcherInputProvider {
66    pub fn new(queue: Rc<ManualRawEventGrouperDispatchQueue>) -> Self {
67        Self { queue }
68    }
69}
70
71impl IManagedDispatcherInputProvider for ManualRawEventGrouperDispatchQueueDispatcherInputProvider {
72    fn has_input(&self) -> bool {
73        self.queue.has_jobs()
74    }
75
76    fn dispatch_next_input_event(&self) {
77        self.queue.dispatch_next()
78    }
79}
80
81/// A queue that empties itself through the dispatcher, at input priority.
82pub struct AutomaticRawEventGrouperDispatchQueue {
83    this: Weak<AutomaticRawEventGrouperDispatchQueue>,
84    input_queue: RefCell<VecDeque<(RawEvent, RawEventHandler)>>,
85    dispatcher: Arc<Dispatcher>,
86}
87
88impl AutomaticRawEventGrouperDispatchQueue {
89    pub fn new(dispatcher: Option<Arc<Dispatcher>>) -> Rc<Self> {
90        Rc::new_cyclic(|this| Self {
91            this: this.clone(),
92            input_queue: RefCell::new(VecDeque::new()),
93            dispatcher: dispatcher.unwrap_or_else(Dispatcher::current_dispatcher),
94        })
95    }
96
97    fn post_dispatch(&self) {
98        let this = self.this.clone();
99        self.dispatcher.post_local(
100            move || {
101                if let Some(this) = this.upgrade() {
102                    this.dispatch_from_queue();
103                }
104            },
105            DispatcherPriority::INPUT,
106        );
107    }
108
109    fn dispatch_from_queue(&self) {
110        loop {
111            let next = self.input_queue.borrow_mut().pop_front();
112            let Some((args, handler)) = next else {
113                return;
114            };
115
116            handler(args);
117
118            // The priority above input (`DispatcherPriority.Input + 1`) is the default one.
119            if self.dispatcher.has_jobs_with_priority(DispatcherPriority::DEFAULT) {
120                self.post_dispatch();
121                return;
122            }
123        }
124    }
125}
126
127impl IRawEventGrouperDispatchQueue for AutomaticRawEventGrouperDispatchQueue {
128    fn add(&self, args: RawEvent, handler: RawEventHandler) {
129        let count = {
130            let mut queue = self.input_queue.borrow_mut();
131            queue.push_back((args, handler));
132            queue.len()
133        };
134
135        if count == 1 {
136            self.post_dispatch();
137        }
138    }
139}
140
141type MergedPoints = Rc<RefCell<Vec<RawPointerPoint>>>;
142
143struct GrouperState {
144    event_callback: RawEventHandler,
145    last_touch_points: RefCell<HashMap<i64, RawEvent>>,
146    last_event: RefCell<Option<RawEvent>>,
147    // The points that were merged into a queued event, by the address of
148    // the event (which the queue keeps alive until it is dispatched). The
149    // reference appends to the pooled list behind the lazy value of the
150    // event; the lazy value of the port cannot be appended to, so the
151    // list lives here and the lazy value reads it when it is first asked
152    // for.
153    merged_points: RefCell<HashMap<usize, MergedPoints>>,
154    disposed: Cell<bool>,
155}
156
157/// Queues raw input events and merges consecutive pointer moves.
158pub struct RawEventGrouper {
159    state: Rc<GrouperState>,
160    queue: Rc<dyn IRawEventGrouperDispatchQueue>,
161    dispatch: RawEventHandler,
162}
163
164fn same_event(a: &RawEvent, b: &RawEvent) -> bool {
165    std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b))
166}
167
168fn as_pointer_event(args: &RawEvent) -> Option<&RawPointerEventArgs> {
169    args.downcast_ref::<RawPointerEventArgs>()
170}
171
172fn as_touch_event(args: &RawEvent) -> Option<&RawTouchEventArgs> {
173    args.downcast_ref::<RawTouchEventArgs>()
174}
175
176fn event_key(event: &RawPointerEventArgs) -> usize {
177    event as *const RawPointerEventArgs as usize
178}
179
180impl RawEventGrouper {
181    pub fn new(
182        event_callback: impl Fn(RawEvent) + 'static,
183        queue: Option<Rc<dyn IRawEventGrouperDispatchQueue>>,
184    ) -> Self {
185        let state = Rc::new(GrouperState {
186            event_callback: Rc::new(event_callback),
187            last_touch_points: RefCell::new(HashMap::new()),
188            last_event: RefCell::new(None),
189            merged_points: RefCell::new(HashMap::new()),
190            disposed: Cell::new(false),
191        });
192        let queue = queue.unwrap_or_else(|| AutomaticRawEventGrouperDispatchQueue::new(None));
193        let dispatch: RawEventHandler = {
194            let state = state.clone();
195            Rc::new(move |ev| Self::dispatch(&state, ev))
196        };
197        Self { state, queue, dispatch }
198    }
199
200    fn add_to_queue(&self, args: RawEvent) {
201        *self.state.last_event.borrow_mut() = Some(args.clone());
202        self.queue.add(args, self.dispatch.clone());
203    }
204
205    fn dispatch(state: &Rc<GrouperState>, ev: RawEvent) {
206        if !state.disposed.get() {
207            let is_last = state.last_event.borrow().as_ref().is_some_and(|last| same_event(last, &ev));
208            if is_last {
209                *state.last_event.borrow_mut() = None;
210            }
211
212            if let Some(touch_update) = as_touch_event(&ev) {
213                if touch_update.type_() == RawPointerEventType::TouchUpdate {
214                    state.last_touch_points.borrow_mut().remove(&touch_update.raw_pointer_id());
215                }
216            }
217
218            (state.event_callback)(ev.clone());
219        }
220
221        // The merged points of the event are released with it (the
222        // reference returns its pooled list here).
223        if let Some(pointer) = as_pointer_event(&ev) {
224            state.merged_points.borrow_mut().remove(&event_key(pointer));
225        }
226    }
227
228    pub fn handle_event(&self, args: RawEvent) {
229        /*
230         Try to update already enqueued events if
231         1) they are still not handled (_lastEvent and _lastTouchPoints shouldn't contain said event in that case)
232         2) previous event belongs to the same "event block", events in the same block:
233           - belong from the same device
234           - are pointer move events (Move/TouchUpdate)
235           - have the same type
236           - have same modifiers
237
238         Even if nothing is updated and the event is actually enqueued, we need to update the relevant tracking info
239        */
240        let last_event = self.state.last_event.borrow().clone();
241        let merged = (|| {
242            let pointer_event = as_pointer_event(&args)?;
243            let last_event = last_event.as_ref()?;
244            let last_pointer_event = as_pointer_event(last_event)?;
245            let same_block = Rc::ptr_eq(last_event.device(), args.device())
246                && last_pointer_event.input_modifiers() == pointer_event.input_modifiers()
247                && last_pointer_event.type_() == pointer_event.type_()
248                && matches!(last_pointer_event.type_(), RawPointerEventType::Move | RawPointerEventType::TouchUpdate);
249            if !same_block {
250                return None;
251            }
252
253            if let Some(touch_event) = as_touch_event(&args) {
254                let last_touch_event =
255                    self.state.last_touch_points.borrow().get(&touch_event.raw_pointer_id()).cloned();
256                match last_touch_event {
257                    Some(last_touch_event) => {
258                        let last = as_pointer_event(&last_touch_event).expect("a touch event is a pointer event");
259                        Self::merge_events(&self.state, last, pointer_event);
260                    }
261                    None => {
262                        self.state.last_touch_points.borrow_mut().insert(touch_event.raw_pointer_id(), args.clone());
263                        self.add_to_queue(args.clone());
264                    }
265                }
266            } else {
267                Self::merge_events(&self.state, last_pointer_event, pointer_event);
268            }
269
270            Some(())
271        })();
272        if merged.is_some() {
273            return;
274        }
275
276        self.state.last_touch_points.borrow_mut().clear();
277        if let Some(touch_event) = as_touch_event(&args) {
278            if touch_event.type_() == RawPointerEventType::TouchUpdate {
279                self.state.last_touch_points.borrow_mut().insert(touch_event.raw_pointer_id(), args.clone());
280            }
281        }
282        self.add_to_queue(args);
283    }
284
285    fn merge_events(state: &GrouperState, last: &RawPointerEventArgs, current: &RawPointerEventArgs) {
286        let list = state.merged_points.borrow_mut().entry(event_key(last)).or_default().clone();
287        if last.intermediate_points().is_none() {
288            let list = list.clone();
289            let points: IntermediatePoints = Rc::new(LazyCell::new(Box::new(move || Some(list.borrow().clone()))));
290            last.set_intermediate_points(Some(points));
291        }
292        let point = last.point();
293        let mut merged = RawPointerPoint::new();
294        merged.position = last.position();
295        merged.pressure = point.pressure;
296        merged.set_contact_rect(point.contact_rect());
297        merged.twist = point.twist;
298        merged.x_tilt = point.x_tilt;
299        merged.y_tilt = point.y_tilt;
300        list.borrow_mut().push(merged);
301        last.set_position(current.position());
302        last.set_timestamp(current.timestamp());
303        last.set_input_modifiers(current.input_modifiers());
304    }
305
306    pub fn dispose(&self) {
307        self.state.disposed.set(true);
308        *self.state.last_event.borrow_mut() = None;
309        self.state.last_touch_points.borrow_mut().clear();
310        self.state.merged_points.borrow_mut().clear();
311    }
312}