use super::Report;
use block2::RcBlock;
use objc2::{MainThreadMarker, Message, rc::Retained, runtime::AnyObject};
use objc2_app_kit::{NSEvent, NSEventMask, NSView};
use std::ptr::NonNull;
pub(super) struct Monitor(Retained<AnyObject>);
impl Monitor {
pub(super) fn new(page: &NSView, reports: async_channel::Sender<Report>) -> Option<Self> {
let mtm = MainThreadMarker::new().expect("the page is built on the main thread");
let page = page.retain();
let block = RcBlock::new(move |event: NonNull<NSEvent>| -> *mut NSEvent {
match landed(&page, unsafe { event.as_ref() }, mtm) {
Some(Landed::Page) => {
let _ = reports.try_send(Report::Pressed);
}
Some(Landed::Host) if super::holds(&page) => super::release(&page),
_ => {}
}
event.as_ptr()
});
let mask =
NSEventMask::LeftMouseDown | NSEventMask::RightMouseDown | NSEventMask::OtherMouseDown;
let monitor =
unsafe { NSEvent::addLocalMonitorForEventsMatchingMask_handler(mask, &block) }?;
Some(Self(monitor))
}
}
impl Drop for Monitor {
fn drop(&mut self) {
unsafe { NSEvent::removeMonitor(&self.0) };
}
}
enum Landed {
Page,
Host,
}
fn landed(page: &NSView, event: &NSEvent, mtm: MainThreadMarker) -> Option<Landed> {
let (window, pressed) = (page.window()?, event.window(mtm)?);
if !std::ptr::eq(&*window, &*pressed) {
return None;
}
let host = unsafe { page.superview() }?;
let frame = unsafe { host.superview() }?;
let point = frame.convertPoint_fromView(event.locationInWindow(), None);
let hit = host.hitTest(point)?;
if hit.isDescendantOf(page) {
Some(Landed::Page)
} else if std::ptr::eq(&*hit, &*host) {
Some(Landed::Host)
} else {
None
}
}