1use std::any::Any;
9use std::cell::Cell;
10use std::panic::{self, AssertUnwindSafe};
11use std::rc::Rc;
12
13use crabshell_reactive::{Signal, untrack};
14
15use crate::cx::Cx;
16use crate::geometry::Point;
17use crate::tree::{ButtonNode, Change, NodeId, NodeKind, Tree, borrow};
18use crate::window::Window;
19
20const PIXELS_PER_STEP: f32 = 15.0;
22
23const VALUE120_PER_STEP: i32 = 120;
25
26pub(crate) struct Handler<F: ?Sized> {
28 disabled: Cell<bool>,
29 f: F,
30}
31
32impl<F> Handler<F> {
33 pub(crate) fn new(f: F) -> Handler<F> {
34 Handler {
35 disabled: Cell::new(false),
36 f,
37 }
38 }
39}
40
41type ClickHandler = Rc<Handler<dyn Fn(&Cx)>>;
42type ScrollHandler = Rc<Handler<dyn Fn(&Cx, i32)>>;
43
44#[derive(Default)]
46pub(crate) struct Handlers {
47 pub(crate) click: Option<ClickHandler>,
48 pub(crate) right_click: Option<ClickHandler>,
49 pub(crate) middle_click: Option<ClickHandler>,
50 pub(crate) scroll: Option<ScrollHandler>,
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum PointerButton {
56 Left,
58 Right,
60 Middle,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq)]
67pub enum ScrollDelta {
68 Wheel(i32),
71 Pixels(f32),
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub enum CursorShape {
78 Default,
80 Pointer,
82}
83
84#[derive(Default)]
86pub(crate) struct Pointer {
87 position: Option<Point>,
88 hovered: Option<NodeId>,
90 pressed: Option<(NodeId, PointerButton)>,
92 wheel: i32,
94 pixels: f32,
95 caused_layout: bool,
97}
98
99impl Pointer {
100 pub(crate) fn forget(&mut self, removed: &[NodeId]) {
102 if self.hovered.is_some_and(|id| removed.contains(&id)) {
103 self.hovered = None;
104 }
105 if self.pressed.is_some_and(|(id, _)| removed.contains(&id)) {
106 self.pressed = None;
107 }
108 }
109}
110
111impl Window {
112 pub fn pointer_motion(&self, position: Point) -> CursorShape {
115 let mut tree = borrow(&self.tree);
116 tree.pointer.position = Some(position);
117 let hit = tree.hit(tree.root, position);
118 tree.set_hovered(hit);
119 tree.cursor()
120 }
121
122 pub fn pointer_leave(&self) {
124 let mut tree = borrow(&self.tree);
125 tree.pointer.position = None;
126 tree.set_pressed(None);
127 tree.set_hovered(None);
128 }
129
130 pub fn pointer_button(&self, button: PointerButton, pressed: bool) {
134 let call = borrow(&self.tree).button(button, pressed);
135 if let Some(call) = call {
136 call.run();
137 }
138 }
139
140 pub fn pointer_scroll(&self, delta: ScrollDelta) {
143 let call = borrow(&self.tree).scroll(delta);
144 if let Some(call) = call {
145 call.run();
146 }
147 }
148
149 pub fn pointer_scroll_end(&self) {
153 borrow(&self.tree).pointer.pixels = 0.0;
154 }
155
156 pub fn cursor(&self) -> CursorShape {
158 self.tree.borrow().cursor()
159 }
160}
161
162impl Tree {
163 fn hit(&self, id: NodeId, point: Point) -> Option<NodeId> {
166 let node = self.node(id)?;
167 if !node.rect.is_some_and(|rect| rect.contains(point)) {
168 return None;
169 }
170 let children = self.taffy.children(id).unwrap_or_default();
171 if let Some(hit) = children
172 .into_iter()
173 .rev()
174 .find_map(|child| self.hit(child, point))
175 {
176 return Some(hit);
177 }
178 matches!(node.kind, NodeKind::Button(_)).then_some(id)
179 }
180
181 fn cursor(&self) -> CursorShape {
182 match self.pointer.hovered {
183 Some(_) => CursorShape::Pointer,
184 None => CursorShape::Default,
185 }
186 }
187
188 pub(crate) fn pointer_after_layout(&mut self) {
193 if std::mem::take(&mut self.pointer.caused_layout) {
194 return;
195 }
196 if let Some(position) = self.pointer.position {
197 let hit = self.hit(self.root, position);
198 self.set_hovered(hit);
199 }
200 }
201
202 fn set_hovered(&mut self, hovered: Option<NodeId>) {
203 let old = self.pointer.hovered;
204 if old == hovered {
205 return;
206 }
207 self.pointer.hovered = hovered;
208 self.pointer.wheel = 0;
209 self.pointer.pixels = 0.0;
210 if let Some(old) = old {
211 self.set_button_state(old, |button| button.hovered = false);
212 }
213 if let Some(new) = hovered {
214 self.set_button_state(new, |button| button.hovered = true);
215 }
216 }
217
218 fn set_pressed(&mut self, pressed: Option<(NodeId, PointerButton)>) {
219 let old = std::mem::replace(&mut self.pointer.pressed, pressed);
220 if let Some((old, _)) = old {
221 self.set_button_state(old, |button| button.pressed = false);
222 }
223 if let Some((new, _)) = pressed {
224 self.set_button_state(new, |button| button.pressed = true);
225 }
226 }
227
228 fn set_button_state(&mut self, id: NodeId, f: impl FnOnce(&mut ButtonNode)) {
231 if self.update_button_style(id, f) == Change::Layout {
232 self.pointer.caused_layout = true;
233 }
234 }
235
236 fn button(&mut self, button: PointerButton, pressed: bool) -> Option<Call> {
237 if pressed {
238 if self.pointer.pressed.is_none()
239 && let Some(target) = self.pointer.hovered
240 {
241 self.set_pressed(Some((target, button)));
242 }
243 return None;
244 }
245 let (target, held) = self.pointer.pressed?;
246 if held != button {
247 return None;
248 }
249 self.set_pressed(None);
250 if self.pointer.hovered != Some(target) {
251 return None;
252 }
253 let action = match button {
254 PointerButton::Left => Action::Click,
255 PointerButton::Right => Action::RightClick,
256 PointerButton::Middle => Action::MiddleClick,
257 };
258 self.call(target, action)
259 }
260
261 fn scroll(&mut self, delta: ScrollDelta) -> Option<Call> {
262 let target = self.pointer.hovered?;
263 let down = match delta {
265 ScrollDelta::Wheel(value120) => {
266 self.pointer.wheel = self.pointer.wheel.saturating_add(value120);
267 let steps = self.pointer.wheel / VALUE120_PER_STEP;
268 self.pointer.wheel -= steps * VALUE120_PER_STEP;
269 steps
270 }
271 ScrollDelta::Pixels(pixels) => {
272 if !pixels.is_finite() {
273 return None;
274 }
275 self.pointer.pixels += pixels;
276 let steps = (self.pointer.pixels / PIXELS_PER_STEP).trunc();
277 self.pointer.pixels -= steps * PIXELS_PER_STEP;
278 steps as i32
279 }
280 };
281 if down == 0 {
282 return None;
283 }
284 self.call(target, Action::Scroll(down.saturating_neg()))
286 }
287
288 fn call(&self, id: NodeId, action: Action) -> Option<Call> {
289 let node = self.node(id)?;
290 let NodeKind::Button(button) = &node.kind else {
291 return None;
292 };
293 Some(Call {
294 cx: node.cx.clone(),
295 path: Rc::clone(&node.path),
296 handlers: button.handlers,
297 action,
298 })
299 }
300}
301
302#[derive(Clone, Copy)]
303enum Action {
304 Click,
305 RightClick,
306 MiddleClick,
307 Scroll(i32),
308}
309
310struct Call {
312 cx: Cx,
313 path: Rc<str>,
314 handlers: Signal<Handlers>,
315 action: Action,
316}
317
318impl Call {
319 fn run(self) {
320 let handlers = untrack(|| {
322 self.handlers.try_with(|handlers| match self.action {
323 Action::Click => handlers.click.clone().map(Either::Click),
324 Action::RightClick => handlers.right_click.clone().map(Either::Click),
325 Action::MiddleClick => handlers.middle_click.clone().map(Either::Click),
326 Action::Scroll(_) => handlers.scroll.clone().map(Either::Scroll),
327 })
328 });
329 let Some(Some(handler)) = handlers else {
330 return;
331 };
332 match (handler, self.action) {
333 (Either::Click(handler), action) => {
334 let name = match action {
335 Action::RightClick => "on_right_click",
336 Action::MiddleClick => "on_middle_click",
337 _ => "on_click",
338 };
339 run_isolated(&self.path, name, handler, |f| f(&self.cx));
340 }
341 (Either::Scroll(handler), Action::Scroll(steps)) => {
342 run_isolated(&self.path, "on_scroll", handler, |f| f(&self.cx, steps));
343 }
344 (Either::Scroll(_), _) => {}
345 }
346 }
347}
348
349enum Either {
350 Click(ClickHandler),
351 Scroll(ScrollHandler),
352}
353
354fn run_isolated<F: ?Sized>(path: &str, name: &str, handler: Rc<Handler<F>>, call: impl FnOnce(&F)) {
358 if handler.disabled.get() {
359 return;
360 }
361 if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(|| call(&handler.f))) {
362 handler.disabled.set(true);
363 tracing::error!(
364 "handler {path}.{name} panicked and is disabled: {}",
365 panic_message(payload.as_ref())
366 );
367 }
368 if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(move || drop(handler))) {
369 tracing::error!(
370 "dropping handler {path}.{name} panicked: {}",
371 panic_message(payload.as_ref())
372 );
373 }
374}
375
376pub(crate) fn panic_message(payload: &(dyn Any + Send)) -> &str {
377 if let Some(message) = payload.downcast_ref::<&str>() {
378 message
379 } else if let Some(message) = payload.downcast_ref::<String>() {
380 message
381 } else {
382 "(panic payload is not a string)"
383 }
384}