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, Rect};
17use crate::keyboard::KeyHandler;
18use crate::scroll::PIXELS_PER_NOTCH;
19use crate::tree::{ButtonNode, Change, Node, NodeId, NodeKind, Tree, borrow};
20use crate::window::Window;
21
22const PIXELS_PER_STEP: f32 = 15.0;
24
25const VALUE120_PER_STEP: i32 = 120;
27
28pub(crate) struct Handler<F: ?Sized> {
30 disabled: Cell<bool>,
31 f: F,
32}
33
34impl<F> Handler<F> {
35 pub(crate) fn new(f: F) -> Handler<F> {
36 Handler {
37 disabled: Cell::new(false),
38 f,
39 }
40 }
41}
42
43type ClickHandler = Rc<Handler<dyn Fn(&Cx)>>;
44type ScrollHandler = Rc<Handler<dyn Fn(&Cx, i32)>>;
45
46#[derive(Default)]
48pub(crate) struct Handlers {
49 pub(crate) click: Option<ClickHandler>,
50 pub(crate) press: Option<ClickHandler>,
51 pub(crate) right_click: Option<ClickHandler>,
52 pub(crate) middle_click: Option<ClickHandler>,
53 pub(crate) scroll: Option<ScrollHandler>,
54 pub(crate) key: Option<KeyHandler>,
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum PointerButton {
60 Left,
62 Right,
64 Middle,
66}
67
68#[derive(Clone, Copy, Debug, PartialEq)]
71pub enum ScrollDelta {
72 Wheel(i32),
75 Pixels(f32),
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum CursorShape {
82 Default,
84 Pointer,
86 Text,
88}
89
90#[derive(Default)]
92pub(crate) struct Pointer {
93 position: Option<Point>,
94 hovered: Option<NodeId>,
96 inside: Vec<(NodeId, Signal<bool>)>,
99 pressed: Option<(NodeId, PointerButton)>,
101 wheel: i32,
103 pixels: f32,
104 caused_layout: bool,
106}
107
108impl Pointer {
109 pub(crate) fn forget(&mut self, removed: &[NodeId]) {
111 if self.hovered.is_some_and(|id| removed.contains(&id)) {
112 self.hovered = None;
113 }
114 let (gone, kept): (Vec<_>, Vec<_>) = std::mem::take(&mut self.inside)
115 .into_iter()
116 .partition(|(id, _)| removed.contains(id));
117 for (_, signal) in gone {
118 if !kept.iter().any(|(_, other)| *other == signal) {
120 signal.try_set(false);
121 }
122 }
123 self.inside = kept;
124 if self.pressed.is_some_and(|(id, _)| removed.contains(&id)) {
125 self.pressed = None;
126 }
127 }
128}
129
130impl Window {
131 pub fn pointer_motion(&self, position: Point) -> CursorShape {
134 let mut tree = borrow(&self.tree);
135 tree.pointer.position = Some(position);
136 tree.hover_at(position);
137 tree.cursor()
138 }
139
140 pub fn pointer_leave(&self) {
142 let mut tree = borrow(&self.tree);
143 tree.pointer.position = None;
144 tree.set_pressed(None);
145 tree.set_hovered(None, &[]);
146 }
147
148 pub fn pointer_button(&self, button: PointerButton, pressed: bool) {
152 let call = borrow(&self.tree).button(button, pressed);
153 if let Some(call) = call {
154 call.run();
155 }
156 }
157
158 pub fn pointer_scroll(&self, delta: ScrollDelta) {
162 let scrolled = borrow(&self.tree).scroll(delta);
163 match scrolled {
164 Some(Scrolled::Button(call)) => call.run(),
165 Some(Scrolled::View(signal, offset)) => {
167 signal.try_set(offset);
168 }
169 None => {}
170 }
171 }
172
173 pub fn pointer_scroll_end(&self) {
177 borrow(&self.tree).pointer.pixels = 0.0;
178 }
179
180 pub fn cursor(&self) -> CursorShape {
182 self.tree.borrow().cursor()
183 }
184}
185
186impl Tree {
187 fn hit(&self, id: NodeId, point: Point, path: &mut Vec<NodeId>) -> Option<NodeId> {
192 let node = self.node(id)?;
193 let point = local(node, point)?;
194 let rect = node.rect?;
195 if !node.inner_bounds.contains(point) {
198 return None;
199 }
200 let style = node.effective_style();
201 let border = style.border.map_or(0.0, |border| border.width.max(0.0));
203 let inside = Rect::new(
204 rect.x + border,
205 rect.y + border,
206 rect.width - 2.0 * border,
207 rect.height - 2.0 * border,
208 );
209 let children = if node.clips() && !inside.contains(point) {
210 Vec::new()
211 } else {
212 self.taffy.children(id).unwrap_or_default()
213 };
214 path.push(id);
215 if let Some(hit) = children
216 .into_iter()
217 .rev()
218 .find_map(|child| self.hit(child, point, path))
219 {
220 return Some(hit);
221 }
222 if rect.contains(point) && matches!(node.kind, NodeKind::Button(_) | NodeKind::Input(_)) {
223 return Some(id);
224 }
225 path.pop();
226 None
227 }
228
229 fn hover_at(&mut self, point: Point) {
232 let mut path = Vec::new();
233 let hit = self.hit(self.root, point, &mut path);
234 self.set_hovered(hit, &path);
235 }
236
237 fn cursor(&self) -> CursorShape {
238 match self
239 .pointer
240 .hovered
241 .and_then(|id| self.node(id))
242 .map(|node| &node.kind)
243 {
244 Some(NodeKind::Input(_)) => CursorShape::Text,
245 Some(_) => CursorShape::Pointer,
246 None => CursorShape::Default,
247 }
248 }
249
250 pub(crate) fn pointer_after_layout(&mut self) {
255 if std::mem::take(&mut self.pointer.caused_layout) {
256 return;
257 }
258 if let Some(position) = self.pointer.position {
259 self.hover_at(position);
260 }
261 }
262
263 fn set_hovered(&mut self, hovered: Option<NodeId>, path: &[NodeId]) {
266 self.set_inside(path);
267 let old = self.pointer.hovered;
268 if old == hovered {
269 return;
270 }
271 self.pointer.hovered = hovered;
272 self.pointer.wheel = 0;
273 self.pointer.pixels = 0.0;
274 if let Some(old) = old {
275 self.set_button_state(old, |button| button.hovered = false);
276 }
277 if let Some(new) = hovered {
278 self.set_button_state(new, |button| button.hovered = true);
279 }
280 }
281
282 fn set_inside(&mut self, path: &[NodeId]) {
285 let inside: Vec<(NodeId, Signal<bool>)> = path
286 .iter()
287 .filter_map(|&id| match &self.node(id)?.kind {
288 NodeKind::Button(button) => Some((id, button.hover_signal?)),
289 _ => None,
290 })
291 .collect();
292 let holds = |list: &[(NodeId, Signal<bool>)], signal| list.iter().any(|(_, s)| *s == signal);
295 let mut seen = Vec::new();
296 for &(_, signal) in self.pointer.inside.iter().chain(&inside) {
297 if seen.contains(&signal) {
298 continue;
299 }
300 seen.push(signal);
301 let now = holds(&inside, signal);
302 if holds(&self.pointer.inside, signal) != now {
303 signal.try_set(now);
304 }
305 }
306 self.pointer.inside = inside;
307 }
308
309 fn set_pressed(&mut self, pressed: Option<(NodeId, PointerButton)>) {
310 let old = std::mem::replace(&mut self.pointer.pressed, pressed);
311 if let Some((old, _)) = old {
312 self.set_button_state(old, |button| button.pressed = false);
313 }
314 if let Some((new, _)) = pressed {
315 self.set_button_state(new, |button| button.pressed = true);
316 }
317 }
318
319 fn set_button_state(&mut self, id: NodeId, f: impl FnOnce(&mut ButtonNode)) {
322 if self.update_button_style(id, f) == Change::Layout {
323 self.pointer.caused_layout = true;
324 }
325 }
326
327 fn button(&mut self, button: PointerButton, pressed: bool) -> Option<Call> {
328 if pressed {
329 if self.pointer.pressed.is_none()
330 && let Some(target) = self.pointer.hovered
331 {
332 self.set_pressed(Some((target, button)));
333 if button == PointerButton::Left {
334 self.press_field(target);
335 return self.call(target, Action::Press);
336 }
337 }
338 return None;
339 }
340 let (target, held) = self.pointer.pressed?;
341 if held != button {
342 return None;
343 }
344 self.set_pressed(None);
345 if self.pointer.hovered != Some(target) {
346 return None;
347 }
348 let action = match button {
349 PointerButton::Left => Action::Click,
350 PointerButton::Right => Action::RightClick,
351 PointerButton::Middle => Action::MiddleClick,
352 };
353 self.call(target, action)
354 }
355
356 fn scroll(&mut self, delta: ScrollDelta) -> Option<Scrolled> {
357 let handled = self.pointer.hovered.filter(|&id| self.scrolls(id));
358 let Some(target) = handled else {
359 return self.scroll_view(delta);
360 };
361 let down = match delta {
363 ScrollDelta::Wheel(value120) => {
364 self.pointer.wheel = self.pointer.wheel.saturating_add(value120);
365 let steps = self.pointer.wheel / VALUE120_PER_STEP;
366 self.pointer.wheel -= steps * VALUE120_PER_STEP;
367 steps
368 }
369 ScrollDelta::Pixels(pixels) => {
370 if !pixels.is_finite() {
371 return None;
372 }
373 self.pointer.pixels += pixels;
374 let steps = (self.pointer.pixels / PIXELS_PER_STEP).trunc();
375 self.pointer.pixels -= steps * PIXELS_PER_STEP;
376 steps as i32
377 }
378 };
379 if down == 0 {
380 return None;
381 }
382 self
384 .call(target, Action::Scroll(down.saturating_neg()))
385 .map(Scrolled::Button)
386 }
387
388 fn press_field(&mut self, id: NodeId) {
391 let Some(position) = self
392 .pointer
393 .position
394 .and_then(|at| self.local_point(id, at))
395 else {
396 return;
397 };
398 let Some(NodeKind::Input(input)) = self.node(id).map(|node| &node.kind) else {
399 return;
400 };
401 let at = input.hit(position);
402 self.focus(Some(id));
403 self.update_input(id, |input| {
404 input.edit.cursor = at;
405 input.edit.anchor = None;
406 });
407 }
408
409 fn scrolls(&self, id: NodeId) -> bool {
411 let Some(NodeKind::Button(button)) = self.node(id).map(|node| &node.kind) else {
412 return false;
413 };
414 untrack(|| {
415 button
416 .handlers
417 .try_with(|handlers| handlers.scroll.is_some())
418 })
419 .unwrap_or(false)
420 }
421
422 fn scroll_view(&mut self, delta: ScrollDelta) -> Option<Scrolled> {
425 let position = self.pointer.position?;
426 let id = self.scroll_view_at(self.root, position)?;
427 let pixels = match delta {
428 ScrollDelta::Wheel(value120) => value120 as f32 / VALUE120_PER_STEP as f32 * PIXELS_PER_NOTCH,
429 ScrollDelta::Pixels(pixels) if pixels.is_finite() => pixels,
430 ScrollDelta::Pixels(_) => return None,
431 };
432 let NodeKind::Scroll(scroll) = &self.node(id)?.kind else {
433 return None;
434 };
435 let now = untrack(|| scroll.signal.try_get())?;
438 let now = if now.is_nan() {
439 0.0
440 } else {
441 now.clamp(0.0, scroll.most)
442 };
443 let offset = (now + pixels).clamp(0.0, scroll.most);
444 (offset != now).then_some(Scrolled::View(scroll.signal, offset))
445 }
446
447 fn scroll_view_at(&self, id: NodeId, point: Point) -> Option<NodeId> {
450 let node = self.node(id)?;
451 let point = local(node, point)?;
452 if !node.inner_bounds.contains(point) {
454 return None;
455 }
456 let children = self.taffy.children(id).unwrap_or_default();
457 children
458 .into_iter()
459 .rev()
460 .find_map(|child| self.scroll_view_at(child, point))
461 .or_else(|| {
462 let inside = node.rect.is_some_and(|rect| rect.contains(point));
463 (inside && matches!(node.kind, NodeKind::Scroll(_))).then_some(id)
464 })
465 }
466
467 fn local_point(&self, id: NodeId, point: Point) -> Option<Point> {
472 let mut path = vec![id];
473 while let Some(parent) = path.last().and_then(|&id| self.taffy.parent(id)) {
474 path.push(parent);
475 }
476 path
477 .iter()
478 .rev()
479 .try_fold(point, |point, &id| local(self.node(id)?, point))
480 }
481
482 pub(crate) fn window_rect(&self, id: NodeId, rect: Rect) -> Rect {
486 let mut rect = rect;
487 let mut at = Some(id);
488 while let Some(current) = at {
489 if let Some(transform) = self.node(current).and_then(|node| node.transform) {
490 rect = transform.map_rect(rect);
491 }
492 at = self.taffy.parent(current);
493 }
494 rect
495 }
496
497 pub(crate) fn click(&self, id: NodeId) -> Option<Call> {
499 self.call(id, Action::Click)
500 }
501
502 fn call(&self, id: NodeId, action: Action) -> Option<Call> {
503 let node = self.node(id)?;
504 let NodeKind::Button(button) = &node.kind else {
505 return None;
506 };
507 Some(Call {
508 cx: node.cx.clone(),
509 path: Rc::clone(&node.path),
510 handlers: button.handlers,
511 action,
512 })
513 }
514}
515
516fn local(node: &Node, point: Point) -> Option<Point> {
520 match node.transform {
521 Some(transform) => Some(transform.invert()?.map(point)),
522 None => Some(point),
523 }
524}
525
526enum Scrolled {
528 Button(Call),
530 View(Signal<f32>, f32),
532}
533
534#[derive(Clone, Copy)]
535enum Action {
536 Click,
537 Press,
538 RightClick,
539 MiddleClick,
540 Scroll(i32),
541}
542
543pub(crate) struct Call {
545 cx: Cx,
546 path: Rc<str>,
547 handlers: Signal<Handlers>,
548 action: Action,
549}
550
551impl Call {
552 pub(crate) fn run(self) {
553 let handlers = untrack(|| {
555 self.handlers.try_with(|handlers| match self.action {
556 Action::Click => handlers.click.clone().map(Either::Click),
557 Action::Press => handlers.press.clone().map(Either::Click),
558 Action::RightClick => handlers.right_click.clone().map(Either::Click),
559 Action::MiddleClick => handlers.middle_click.clone().map(Either::Click),
560 Action::Scroll(_) => handlers.scroll.clone().map(Either::Scroll),
561 })
562 });
563 let Some(Some(handler)) = handlers else {
564 return;
565 };
566 match (handler, self.action) {
567 (Either::Click(handler), action) => {
568 let name = match action {
569 Action::RightClick => "on_right_click",
570 Action::Press => "on_press",
571 Action::MiddleClick => "on_middle_click",
572 _ => "on_click",
573 };
574 run_isolated(&self.path, name, handler, |f| f(&self.cx));
575 }
576 (Either::Scroll(handler), Action::Scroll(steps)) => {
577 run_isolated(&self.path, "on_scroll", handler, |f| f(&self.cx, steps));
578 }
579 (Either::Scroll(_), _) => {}
580 }
581 }
582}
583
584enum Either {
585 Click(ClickHandler),
586 Scroll(ScrollHandler),
587}
588
589pub(crate) fn run_isolated<F: ?Sized, R>(
594 path: &str,
595 name: &str,
596 handler: Rc<Handler<F>>,
597 call: impl FnOnce(&F) -> R,
598) -> Option<R> {
599 if handler.disabled.get() {
600 return None;
601 }
602 let result = match panic::catch_unwind(AssertUnwindSafe(|| call(&handler.f))) {
603 Ok(result) => Some(result),
604 Err(payload) => {
605 handler.disabled.set(true);
606 tracing::error!(
607 "handler {path}.{name} panicked and is disabled: {}",
608 panic_message(payload.as_ref())
609 );
610 None
611 }
612 };
613 if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(move || drop(handler))) {
614 tracing::error!(
615 "dropping handler {path}.{name} panicked: {}",
616 panic_message(payload.as_ref())
617 );
618 }
619 result
620}
621
622pub(crate) fn panic_message(payload: &(dyn Any + Send)) -> &str {
623 if let Some(message) = payload.downcast_ref::<&str>() {
624 message
625 } else if let Some(message) = payload.downcast_ref::<String>() {
626 message
627 } else {
628 "(panic payload is not a string)"
629 }
630}