1use super::StateMachine;
5use crate::component::Layout;
6use crate::input::{MouseButton, MouseState, RawEvent, RawEventKind};
7use crate::layout::leaf;
8use crate::{
9 AbsPoint, AbsVector, Dispatchable, InputResult, PxPoint, Slot, SourceID, UnResolve, layout,
10};
11use core::f32;
12use derive_where::derive_where;
13use enum_variant_type::EnumVariantType;
14use feather_macro::Dispatch;
15use smallvec::SmallVec;
16use std::collections::HashMap;
17use std::rc::Rc;
18use std::sync::Arc;
19use winit::event::DeviceId;
20use winit::keyboard::NamedKey;
21
22#[derive(Debug, Dispatch, EnumVariantType, Clone, PartialEq)]
26#[evt(derive(Clone), module = "mouse_area_event")]
27pub enum MouseAreaEvent {
28 OnClick(MouseButton, AbsPoint),
29 OnDblClick(MouseButton, AbsPoint),
30 OnDrag(MouseButton, AbsVector),
31 Default,
32 Hover,
33 Active,
34}
35
36#[derive(Default, Clone, PartialEq)]
37struct MouseAreaState {
38 lastdown: HashMap<(DeviceId, u64), (PxPoint, bool)>,
39 hover: bool,
40 deadzone: f32,
41}
42
43impl MouseAreaState {
44 fn hover_event(buttons: u16, hover: bool) -> MouseAreaEvent {
45 let active = (buttons & MouseButton::Left as u16) != 0;
46 match (active, hover) {
47 (true, true) => MouseAreaEvent::Active,
48 (true, false) => MouseAreaEvent::Hover,
49 (false, true) => MouseAreaEvent::Hover,
50 (false, false) => MouseAreaEvent::Default,
51 }
52 }
53}
54
55impl super::EventRouter for MouseAreaState {
56 type Input = RawEvent;
57 type Output = MouseAreaEvent;
58
59 fn process(
60 mut this: crate::AccessCell<Self>,
61 input: Self::Input,
62 area: crate::PxRect,
63 _: crate::PxRect,
64 dpi: crate::RelDim,
65 _: &std::sync::Weak<crate::Driver>,
66 ) -> InputResult<SmallVec<[Self::Output; 1]>> {
67 match input {
68 RawEvent::Key {
69 down,
70 logical_key: winit::keyboard::Key::Named(code),
71 ..
72 } => {
73 if (code == NamedKey::Enter || code == NamedKey::Accept) && down {
74 return InputResult::Consume(
75 [MouseAreaEvent::OnClick(
76 crate::input::MouseButton::Left,
77 AbsPoint::zero(),
78 )]
79 .into(),
80 );
81 }
82 }
83 RawEvent::MouseOn { all_buttons, .. } | RawEvent::MouseOff { all_buttons, .. } => {
84 this.hover = matches!(input, RawEvent::MouseOff { .. });
85 let hover = Self::hover_event(all_buttons, this.hover);
86 return InputResult::Consume([hover].into());
87 }
88 RawEvent::MouseMove {
89 device_id,
90 pos,
91 all_buttons,
92 ..
93 } => {
94 let hover = Self::hover_event(all_buttons, this.hover);
95 for i in 0..5 {
96 let deadzone = this.deadzone;
97 if let Some((last_pos, drag)) = this.lastdown.get_mut(&(device_id, (1 << i))) {
98 let diff = pos - *last_pos;
99 if !*drag && diff.dot(diff) > deadzone {
100 *drag = true;
101 }
102
103 let b = match i {
104 0 => MouseButton::Left,
105 1 => MouseButton::Middle,
106 2 => MouseButton::Right,
107 3 => MouseButton::Back,
108 4 => MouseButton::Forward,
109 _ => panic!("Impossible number"),
110 };
111 if *drag {
112 *last_pos = pos;
113 return InputResult::Consume(SmallVec::from_iter([
114 hover,
115 MouseAreaEvent::OnDrag(b, diff.unresolve(dpi)),
116 ]));
117 }
118 }
119 }
120
121 return InputResult::Consume([hover].into());
122 }
123 RawEvent::Mouse {
124 device_id,
125 state,
126 pos,
127 button,
128 ..
129 } => {
130 let hover = Self::hover_event(button as u16, this.hover);
131 match state {
132 MouseState::Down => {
133 if area.contains(pos) {
134 this.lastdown
135 .insert((device_id, button as u64), (pos, false));
136 return InputResult::Consume([hover].into());
137 }
138 }
139 MouseState::Up => {
140 if let Some((last_pos, drag)) =
141 this.lastdown.remove(&(device_id, button as u64))
142 && area.contains(pos)
143 {
144 return InputResult::Consume(SmallVec::from_iter([
145 if drag {
146 let diff = pos - last_pos;
147 MouseAreaEvent::OnDrag(button, diff.unresolve(dpi))
148 } else {
149 MouseAreaEvent::OnClick(button, pos.unresolve(dpi))
150 },
151 hover,
152 ]));
153 }
154 }
155 MouseState::DblClick => {
156 if let Some((last_pos, drag)) =
157 this.lastdown.remove(&(device_id, button as u64))
158 && area.contains(pos)
159 {
160 return InputResult::Consume(if drag {
161 SmallVec::from_iter([
162 MouseAreaEvent::OnClick(button, pos.unresolve(dpi)),
163 MouseAreaEvent::OnDblClick(button, pos.unresolve(dpi)),
164 hover,
165 ])
166 } else {
167 SmallVec::from_iter([
168 if drag {
169 let diff = pos - last_pos;
170 MouseAreaEvent::OnDrag(button, diff.unresolve(dpi))
171 } else {
172 MouseAreaEvent::OnClick(button, pos.unresolve(dpi))
173 },
174 hover,
175 ])
176 });
177 }
178 }
179 }
180 }
181 RawEvent::Touch {
182 device_id,
183 index,
184 state,
185 pos,
186 ..
187 } => match state {
188 crate::input::TouchState::Start => {
189 let hover = Self::hover_event(MouseButton::Left as u16, this.hover);
190 if area.contains(pos.xy()) {
191 this.lastdown
192 .insert((device_id, index as u64), (pos.xy(), false));
193 return InputResult::Consume([hover].into());
194 }
195 }
196 crate::input::TouchState::Move => {
197 let deadzone = this.deadzone;
198 let hover = Self::hover_event(MouseButton::Left as u16, this.hover);
199 if let Some((last_pos, drag)) =
200 this.lastdown.get_mut(&(device_id, index as u64))
201 {
202 let diff = pos.xy() - *last_pos;
203 if !*drag && diff.dot(diff) > deadzone {
204 *drag = true;
205 }
206 if *drag {
207 return InputResult::Consume(SmallVec::from_iter([
208 hover,
209 MouseAreaEvent::OnDrag(MouseButton::Left, diff.unresolve(dpi)),
210 ]));
211 }
212 return InputResult::Consume(SmallVec::new());
213 }
214 }
215 crate::input::TouchState::End => {
216 let hover = Self::hover_event(0, this.hover);
217 if let Some((last_pos, drag)) = this.lastdown.remove(&(device_id, index as u64))
218 && area.contains(pos.xy())
219 {
220 let diff = pos.xy() - last_pos;
221 return InputResult::Consume(SmallVec::from_iter([
222 if drag {
223 MouseAreaEvent::OnDrag(MouseButton::Left, diff.unresolve(dpi))
224 } else {
225 MouseAreaEvent::OnClick(MouseButton::Left, pos.xy().unresolve(dpi))
226 },
227 hover,
228 ]));
229 }
230 }
231 },
232 _ => (),
233 }
234 InputResult::Forward(SmallVec::new())
235 }
236}
237
238#[derive_where(Clone)]
239pub struct MouseArea<T> {
240 pub id: Arc<SourceID>,
241 props: Rc<T>,
242 deadzone: f32, slots: [Option<Slot>; MouseAreaEvent::SIZE],
244}
245
246impl<T: leaf::Prop> MouseArea<T> {
247 pub fn new(
248 id: Arc<SourceID>,
249 props: T,
250 deadzone: Option<f32>,
251 slots: [Option<Slot>; MouseAreaEvent::SIZE],
252 ) -> Self {
253 Self {
254 id,
255 props: props.into(),
256 deadzone: deadzone.unwrap_or(f32::INFINITY),
257 slots,
258 }
259 }
260}
261
262impl<T: leaf::Prop> crate::StateMachineChild for MouseArea<T> {
263 fn id(&self) -> Arc<SourceID> {
264 self.id.clone()
265 }
266 fn init(
267 &self,
268 _: &std::sync::Weak<crate::Driver>,
269 ) -> Result<Box<dyn super::StateMachineWrapper>, crate::Error> {
270 Ok(Box::new(StateMachine {
271 state: MouseAreaState {
272 lastdown: HashMap::new(),
273 hover: false,
274 deadzone: f32::INFINITY,
275 },
276 input_mask: RawEventKind::Mouse as u64
277 | RawEventKind::MouseMove as u64
278 | RawEventKind::Touch as u64
279 | RawEventKind::Key as u64,
280 output: self.slots.clone(),
281 changed: true,
282 }))
283 }
284}
285
286impl<T: leaf::Prop + 'static> super::Component for MouseArea<T>
287where
288 for<'a> &'a T: Into<&'a (dyn leaf::Prop + 'static)>,
289{
290 type Props = T;
291
292 fn layout(
293 &self,
294 manager: &mut crate::StateManager,
295 _: &crate::graphics::Driver,
296 _: &Arc<SourceID>,
297 ) -> Box<dyn Layout<T>> {
298 manager
300 .get_mut::<StateMachine<MouseAreaState, { MouseAreaEvent::SIZE }>>(&self.id)
301 .map(|state| {
302 state.state.deadzone = self.deadzone;
303 })
304 .unwrap();
305
306 Box::new(layout::Node::<T, dyn leaf::Prop> {
307 props: self.props.clone(),
308 children: Default::default(),
309 id: Arc::downgrade(&self.id),
310 renderable: None,
311 layer: None,
312 })
313 }
314}