use super::raw::{IRawInputEventArgs, RawDragEvent, RawPointerEventArgs, RawPointerEventType};
use super::{
IInputDevice, IInputRoot, IPointer, InputElement, KeyModifiers, PointerEventArgs, PointerPointProperties,
PointerType,
};
use crate::reactive::IObserver;
use crate::{PixelPoint, Point, Rect, Ref};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
pub struct PointerOverPreProcessor {
last_active_pointer_device: RefCell<Option<Rc<dyn IInputDevice>>>,
current_pointer: RefCell<Option<(Rc<dyn IPointer>, PixelPoint)>>,
last_known_position: Cell<Option<PixelPoint>>,
input_root: Rc<dyn IInputRoot>,
}
impl PointerOverPreProcessor {
pub fn new(input_root: Rc<dyn IInputRoot>) -> Self {
Self {
last_active_pointer_device: RefCell::new(None),
current_pointer: RefCell::new(None),
last_known_position: Cell::new(None),
input_root,
}
}
pub fn last_position(&self) -> Option<PixelPoint> {
self.last_known_position.get()
}
fn take_current_pointer(&self) -> Option<(Rc<dyn IPointer>, PixelPoint)> {
self.current_pointer.borrow_mut().take()
}
fn current_pointer(&self) -> Option<(Rc<dyn IPointer>, PixelPoint)> {
self.current_pointer.borrow().clone()
}
fn set_current_pointer(&self, value: Option<(Rc<dyn IPointer>, PixelPoint)>) {
let old = self.current_pointer.replace(value);
drop(old);
}
fn set_last_active_pointer_device(&self, value: Option<Rc<dyn IInputDevice>>) {
let old = self.last_active_pointer_device.replace(value);
drop(old);
}
fn is_this_root(&self, root: &Rc<dyn IInputRoot>) -> bool {
std::ptr::addr_eq(Rc::as_ptr(root), Rc::as_ptr(&self.input_root))
}
fn process(&self, value: &dyn IRawInputEventArgs) {
if let Some(drag_args) = value.downcast_ref::<RawDragEvent>() {
self.last_known_position
.set(Some(self.input_root.root_element().point_to_screen(drag_args.location())));
return;
}
let Some(args) = value.downcast_ref::<RawPointerEventArgs>() else { return };
if !self.is_this_root(args.root()) {
return;
}
let device = value.device();
let Some(pointer_device) = device.as_pointer_device() else { return };
let is_last_active = self
.last_active_pointer_device
.borrow()
.as_ref()
.is_some_and(|last| std::ptr::addr_eq(Rc::as_ptr(last), Rc::as_ptr(device)));
if !is_last_active {
self.clear_pointer_over();
self.set_last_active_pointer_device(Some(device.clone()));
}
let type_ = args.type_();
let root = args.root();
if matches!(
type_,
RawPointerEventType::LeaveWindow
| RawPointerEventType::NonClientLeftButtonDown
| RawPointerEventType::TouchCancel
| RawPointerEventType::TouchEnd
) {
if let Some((last_pointer, last_position)) = self.take_current_pointer() {
self.clear_pointer_over_for(
&last_pointer,
root,
0,
Some(Self::point_to_client(root, last_position)),
PointerPointProperties::new(args.input_modifiers(), type_.to_update_kind()),
args.input_modifiers().to_key_modifiers(),
);
}
} else if matches!(type_, RawPointerEventType::TouchBegin | RawPointerEventType::TouchUpdate) {
self.last_known_position.set(Some(root.root_element().point_to_screen(args.position())));
} else if let Some(pointer) = pointer_device.try_get_pointer(args) {
if pointer.type_() != PointerType::Touch && type_ != RawPointerEventType::CancelCapture {
let element =
Self::get_effective_pointer_over_element(args.input_hit_test_result().1, pointer.captured());
self.set_pointer_over(
&pointer,
root,
element,
args.timestamp(),
args.position(),
PointerPointProperties::new(args.input_modifiers(), type_.to_update_kind()),
args.input_modifiers().to_key_modifiers(),
);
}
}
}
pub fn scene_invalidated(&self, dirty_rect: Rect) {
let Some((pointer, position)) = self.current_pointer() else { return };
let root_element = self.input_root.root_element();
let client_point = Self::point_to_client(&self.input_root, position);
if dirty_rect.contains(client_point) {
let element = Self::get_effective_pointer_over_element(
root_element.input_hit_test(client_point),
pointer.captured(),
);
self.set_pointer_over(
&pointer,
&self.input_root,
element,
0,
client_point,
PointerPointProperties::NONE,
KeyModifiers::NONE,
);
} else if !root_element.bounds().contains(client_point) {
self.clear_pointer_over_for(
&pointer,
&self.input_root,
0,
Some(client_point),
PointerPointProperties::NONE,
KeyModifiers::NONE,
);
}
}
fn get_effective_pointer_over_element(
hit_test_element: Option<Ref<InputElement>>,
captured: Option<Ref<InputElement>>,
) -> Option<Ref<InputElement>> {
if captured.is_some() && hit_test_element != captured {
None
} else {
hit_test_element
}
}
fn clear_pointer_over(&self) {
if let Some((pointer, position)) = self.current_pointer() {
let client_point = Self::point_to_client(&self.input_root, position);
self.clear_pointer_over_for(
&pointer,
&self.input_root,
0,
Some(client_point),
PointerPointProperties::NONE,
KeyModifiers::NONE,
);
}
self.set_current_pointer(None);
self.set_last_active_pointer_device(None);
}
fn clear_pointer_over_for(
&self,
pointer: &Rc<dyn IPointer>,
root: &Rc<dyn IInputRoot>,
timestamp: u64,
position: Option<Point>,
properties: PointerPointProperties,
input_modifiers: KeyModifiers,
) {
let Some(element) = root.pointer_over_element() else { return };
let root_element = root.root_element();
let e = PointerEventArgs::new(
Some(InputElement::pointer_exited_event()),
&element,
pointer.clone(),
position.map(|_| &***root_element as &crate::Visual),
position.unwrap_or_default(),
timestamp,
properties,
input_modifiers,
);
if !element.is_attached_to_visual_tree() {
if root_element.is_pointer_over() {
Self::clear_children_pointer_over(&e, &root_element, true);
}
}
let mut current = Some(element);
while let Some(element) = current {
e.set_source(&element);
e.set_handled(false);
element.raise_event(&e);
current = Self::get_visual_parent(&element);
}
root.set_pointer_over_element(None);
root.set_cursor_element(pointer.captured());
self.set_last_active_pointer_device(None);
self.set_current_pointer(None);
}
fn clear_children_pointer_over(e: &PointerEventArgs, element: &Ref<InputElement>, clear_root: bool) {
if let Some(children) = element.visual_children_snapshot() {
for el in children.iter() {
if let Some(child) = el.cast::<InputElement>() {
if child.is_pointer_over() {
Self::clear_children_pointer_over(e, &child, true);
break;
}
}
}
}
if clear_root {
e.set_source(element);
e.set_handled(false);
element.raise_event(e);
}
}
#[allow(clippy::too_many_arguments)]
fn set_pointer_over(
&self,
pointer: &Rc<dyn IPointer>,
root: &Rc<dyn IInputRoot>,
element: Option<Ref<InputElement>>,
timestamp: u64,
position: Point,
properties: PointerPointProperties,
input_modifiers: KeyModifiers,
) {
let pointer_over_element = root.pointer_over_element();
let screen_position = root.root_element().point_to_screen(position);
self.last_known_position.set(Some(screen_position));
if element != pointer_over_element {
match &element {
Some(element) => self.set_pointer_over_to_element(
pointer,
root,
element,
timestamp,
position,
properties,
input_modifiers,
),
None => self.clear_pointer_over_for(
pointer,
root,
timestamp,
Some(position),
properties,
input_modifiers,
),
}
}
self.set_current_pointer(Some((pointer.clone(), screen_position)));
}
#[allow(clippy::too_many_arguments)]
fn set_pointer_over_to_element(
&self,
pointer: &Rc<dyn IPointer>,
root: &Rc<dyn IInputRoot>,
element: &Ref<InputElement>,
timestamp: u64,
position: Point,
properties: PointerPointProperties,
input_modifiers: KeyModifiers,
) {
let mut branch: Option<Ref<InputElement>> = None;
let mut el = Some(element.clone());
while let Some(current) = el {
if current.is_pointer_over() {
branch = Some(current);
break;
}
el = Self::get_visual_parent(¤t);
}
el = root.pointer_over_element();
let root_element = root.root_element();
let e = PointerEventArgs::new(
Some(InputElement::pointer_exited_event()),
el.clone().map(Ref::upcast::<crate::FerroObject>),
pointer.clone(),
Some(&root_element),
position,
timestamp,
properties,
input_modifiers,
);
if let (Some(old), Some(branch)) = (&el, &branch) {
if !old.is_attached_to_visual_tree() {
Self::clear_children_pointer_over(&e, branch, false);
}
}
while let Some(current) = el {
if Some(¤t) == branch.as_ref() {
break;
}
e.set_source(¤t);
e.set_handled(false);
current.raise_event(&e);
el = Self::get_visual_parent(¤t);
}
root.set_pointer_over_element(Some(element.clone()));
el = Some(element.clone());
root.set_cursor_element(pointer.captured().or_else(|| Some(element.clone())));
e.set_routed_event(Some(InputElement::pointer_entered_event()));
while let Some(current) = el {
if Some(¤t) == branch.as_ref() {
break;
}
e.set_source(¤t);
e.set_handled(false);
current.raise_event(&e);
el = Self::get_visual_parent(¤t);
}
}
fn get_visual_parent(e: &InputElement) -> Option<Ref<InputElement>> {
e.visual_parent().and_then(|parent| parent.downcast::<InputElement>().ok())
}
fn point_to_client(root: &Rc<dyn IInputRoot>, p: PixelPoint) -> Point {
root.root_element().point_to_client(p)
}
}
impl IObserver<Rc<dyn IRawInputEventArgs>> for PointerOverPreProcessor {
fn on_next(&self, value: Rc<dyn IRawInputEventArgs>) {
self.process(&*value);
}
fn on_completed(&self) {
self.clear_pointer_over();
}
}