use super::StateMachine;
use crate::component::Layout;
use crate::input::{MouseButton, MouseState, RawEvent, RawEventKind};
use crate::layout::leaf;
use crate::{
AbsPoint, AbsVector, Dispatchable, InputResult, PxPoint, Slot, SourceID, UnResolve, layout,
};
use core::f32;
use derive_where::derive_where;
use enum_variant_type::EnumVariantType;
use feather_macro::Dispatch;
use smallvec::SmallVec;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use winit::event::DeviceId;
use winit::keyboard::NamedKey;
#[derive(Debug, Dispatch, EnumVariantType, Clone, PartialEq)]
#[evt(derive(Clone), module = "mouse_area_event")]
pub enum MouseAreaEvent {
OnClick(MouseButton, AbsPoint),
OnDblClick(MouseButton, AbsPoint),
OnDrag(MouseButton, AbsVector),
Default,
Hover,
Active,
}
#[derive(Default, Clone, PartialEq)]
struct MouseAreaState {
lastdown: HashMap<(DeviceId, u64), (PxPoint, bool)>,
hover: bool,
deadzone: f32,
}
impl MouseAreaState {
fn hover_event(buttons: u16, hover: bool) -> MouseAreaEvent {
let active = (buttons & MouseButton::Left as u16) != 0;
match (active, hover) {
(true, true) => MouseAreaEvent::Active,
(true, false) => MouseAreaEvent::Hover,
(false, true) => MouseAreaEvent::Hover,
(false, false) => MouseAreaEvent::Default,
}
}
}
impl super::EventRouter for MouseAreaState {
type Input = RawEvent;
type Output = MouseAreaEvent;
fn process(
mut this: crate::AccessCell<Self>,
input: Self::Input,
area: crate::PxRect,
_: crate::PxRect,
dpi: crate::RelDim,
_: &std::sync::Weak<crate::Driver>,
) -> InputResult<SmallVec<[Self::Output; 1]>> {
match input {
RawEvent::Key {
down,
logical_key: winit::keyboard::Key::Named(code),
..
} => {
if (code == NamedKey::Enter || code == NamedKey::Accept) && down {
return InputResult::Consume(
[MouseAreaEvent::OnClick(
crate::input::MouseButton::Left,
AbsPoint::zero(),
)]
.into(),
);
}
}
RawEvent::MouseOn { all_buttons, .. } | RawEvent::MouseOff { all_buttons, .. } => {
this.hover = matches!(input, RawEvent::MouseOff { .. });
let hover = Self::hover_event(all_buttons, this.hover);
return InputResult::Consume([hover].into());
}
RawEvent::MouseMove {
device_id,
pos,
all_buttons,
..
} => {
let hover = Self::hover_event(all_buttons, this.hover);
for i in 0..5 {
let deadzone = this.deadzone;
if let Some((last_pos, drag)) = this.lastdown.get_mut(&(device_id, (1 << i))) {
let diff = pos - *last_pos;
if !*drag && diff.dot(diff) > deadzone {
*drag = true;
}
let b = match i {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
3 => MouseButton::Back,
4 => MouseButton::Forward,
_ => panic!("Impossible number"),
};
if *drag {
*last_pos = pos;
return InputResult::Consume(SmallVec::from_iter([
hover,
MouseAreaEvent::OnDrag(b, diff.unresolve(dpi)),
]));
}
}
}
return InputResult::Consume([hover].into());
}
RawEvent::Mouse {
device_id,
state,
pos,
button,
..
} => {
let hover = Self::hover_event(button as u16, this.hover);
match state {
MouseState::Down => {
if area.contains(pos) {
this.lastdown
.insert((device_id, button as u64), (pos, false));
return InputResult::Consume([hover].into());
}
}
MouseState::Up => {
if let Some((last_pos, drag)) =
this.lastdown.remove(&(device_id, button as u64))
&& area.contains(pos)
{
return InputResult::Consume(SmallVec::from_iter([
if drag {
let diff = pos - last_pos;
MouseAreaEvent::OnDrag(button, diff.unresolve(dpi))
} else {
MouseAreaEvent::OnClick(button, pos.unresolve(dpi))
},
hover,
]));
}
}
MouseState::DblClick => {
if let Some((last_pos, drag)) =
this.lastdown.remove(&(device_id, button as u64))
&& area.contains(pos)
{
return InputResult::Consume(if drag {
SmallVec::from_iter([
MouseAreaEvent::OnClick(button, pos.unresolve(dpi)),
MouseAreaEvent::OnDblClick(button, pos.unresolve(dpi)),
hover,
])
} else {
SmallVec::from_iter([
if drag {
let diff = pos - last_pos;
MouseAreaEvent::OnDrag(button, diff.unresolve(dpi))
} else {
MouseAreaEvent::OnClick(button, pos.unresolve(dpi))
},
hover,
])
});
}
}
}
}
RawEvent::Touch {
device_id,
index,
state,
pos,
..
} => match state {
crate::input::TouchState::Start => {
let hover = Self::hover_event(MouseButton::Left as u16, this.hover);
if area.contains(pos.xy()) {
this.lastdown
.insert((device_id, index as u64), (pos.xy(), false));
return InputResult::Consume([hover].into());
}
}
crate::input::TouchState::Move => {
let deadzone = this.deadzone;
let hover = Self::hover_event(MouseButton::Left as u16, this.hover);
if let Some((last_pos, drag)) =
this.lastdown.get_mut(&(device_id, index as u64))
{
let diff = pos.xy() - *last_pos;
if !*drag && diff.dot(diff) > deadzone {
*drag = true;
}
if *drag {
return InputResult::Consume(SmallVec::from_iter([
hover,
MouseAreaEvent::OnDrag(MouseButton::Left, diff.unresolve(dpi)),
]));
}
return InputResult::Consume(SmallVec::new());
}
}
crate::input::TouchState::End => {
let hover = Self::hover_event(0, this.hover);
if let Some((last_pos, drag)) = this.lastdown.remove(&(device_id, index as u64))
&& area.contains(pos.xy())
{
let diff = pos.xy() - last_pos;
return InputResult::Consume(SmallVec::from_iter([
if drag {
MouseAreaEvent::OnDrag(MouseButton::Left, diff.unresolve(dpi))
} else {
MouseAreaEvent::OnClick(MouseButton::Left, pos.xy().unresolve(dpi))
},
hover,
]));
}
}
},
_ => (),
}
InputResult::Forward(SmallVec::new())
}
}
#[derive_where(Clone)]
pub struct MouseArea<T> {
pub id: Arc<SourceID>,
props: Rc<T>,
deadzone: f32, slots: [Option<Slot>; MouseAreaEvent::SIZE],
}
impl<T: leaf::Prop> MouseArea<T> {
pub fn new(
id: Arc<SourceID>,
props: T,
deadzone: Option<f32>,
slots: [Option<Slot>; MouseAreaEvent::SIZE],
) -> Self {
Self {
id,
props: props.into(),
deadzone: deadzone.unwrap_or(f32::INFINITY),
slots,
}
}
}
impl<T: leaf::Prop> crate::StateMachineChild for MouseArea<T> {
fn id(&self) -> Arc<SourceID> {
self.id.clone()
}
fn init(
&self,
_: &std::sync::Weak<crate::Driver>,
) -> Result<Box<dyn super::StateMachineWrapper>, crate::Error> {
Ok(Box::new(StateMachine {
state: MouseAreaState {
lastdown: HashMap::new(),
hover: false,
deadzone: f32::INFINITY,
},
input_mask: RawEventKind::Mouse as u64
| RawEventKind::MouseMove as u64
| RawEventKind::Touch as u64
| RawEventKind::Key as u64,
output: self.slots.clone(),
changed: true,
}))
}
}
impl<T: leaf::Prop + 'static> super::Component for MouseArea<T>
where
for<'a> &'a T: Into<&'a (dyn leaf::Prop + 'static)>,
{
type Props = T;
fn layout(
&self,
manager: &mut crate::StateManager,
_: &crate::graphics::Driver,
_: &Arc<SourceID>,
) -> Box<dyn Layout<T>> {
manager
.get_mut::<StateMachine<MouseAreaState, { MouseAreaEvent::SIZE }>>(&self.id)
.map(|state| {
state.state.deadzone = self.deadzone;
})
.unwrap();
Box::new(layout::Node::<T, dyn leaf::Prop> {
props: self.props.clone(),
children: Default::default(),
id: Arc::downgrade(&self.id),
renderable: None,
layer: None,
})
}
}