#![allow(unsafe_code)]
use core::ptr::NonNull;
use core::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use objc2::MainThreadMarker;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2_app_kit::{NSEvent, NSEventMask, NSEventPhase};
pub struct ScrollMomentumMonitor {
monitor: Retained<AnyObject>,
_handler: block2::RcBlock<dyn Fn(NonNull<NSEvent>) -> *mut NSEvent>,
latest_is_momentum: Arc<AtomicBool>,
}
impl ScrollMomentumMonitor {
pub fn install() -> Option<Self> {
if MainThreadMarker::new().is_none() {
log::debug!("Not on the main thread; cannot detect momentum scrolling");
return None;
}
let latest_is_momentum = Arc::new(AtomicBool::new(false));
let handler = {
let latest_is_momentum = Arc::clone(&latest_is_momentum);
block2::RcBlock::new(move |event: NonNull<NSEvent>| -> *mut NSEvent {
let is_momentum = unsafe { event.as_ref() }.momentumPhase() != NSEventPhase::None;
latest_is_momentum.store(is_momentum, Ordering::Relaxed);
event.as_ptr() })
};
let monitor = unsafe {
NSEvent::addLocalMonitorForEventsMatchingMask_handler(
NSEventMask::ScrollWheel,
&handler,
)
}?;
Some(Self {
monitor,
_handler: handler,
latest_is_momentum,
})
}
pub fn latest_scroll_event_is_momentum(&self) -> bool {
self.latest_is_momentum.load(Ordering::Relaxed)
}
}
impl Drop for ScrollMomentumMonitor {
fn drop(&mut self) {
unsafe { NSEvent::removeMonitor(&self.monitor) };
}
}