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, 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> {
191 let node = self.node(id)?;
192 let rect = node.rect.filter(|rect| rect.contains(point))?;
193 let style = node.effective_style();
194 let border = style.border.map_or(0.0, |border| border.width.max(0.0));
196 let inside = Rect::new(
197 rect.x + border,
198 rect.y + border,
199 rect.width - 2.0 * border,
200 rect.height - 2.0 * border,
201 );
202 let children = if node.clips() && !inside.contains(point) {
203 Vec::new()
204 } else {
205 self.taffy.children(id).unwrap_or_default()
206 };
207 path.push(id);
208 if let Some(hit) = children
209 .into_iter()
210 .rev()
211 .find_map(|child| self.hit(child, point, path))
212 {
213 return Some(hit);
214 }
215 if matches!(node.kind, NodeKind::Button(_) | NodeKind::Input(_)) {
216 return Some(id);
217 }
218 path.pop();
219 None
220 }
221
222 fn hover_at(&mut self, point: Point) {
225 let mut path = Vec::new();
226 let hit = self.hit(self.root, point, &mut path);
227 self.set_hovered(hit, &path);
228 }
229
230 fn cursor(&self) -> CursorShape {
231 match self
232 .pointer
233 .hovered
234 .and_then(|id| self.node(id))
235 .map(|node| &node.kind)
236 {
237 Some(NodeKind::Input(_)) => CursorShape::Text,
238 Some(_) => CursorShape::Pointer,
239 None => CursorShape::Default,
240 }
241 }
242
243 pub(crate) fn pointer_after_layout(&mut self) {
248 if std::mem::take(&mut self.pointer.caused_layout) {
249 return;
250 }
251 if let Some(position) = self.pointer.position {
252 self.hover_at(position);
253 }
254 }
255
256 fn set_hovered(&mut self, hovered: Option<NodeId>, path: &[NodeId]) {
259 self.set_inside(path);
260 let old = self.pointer.hovered;
261 if old == hovered {
262 return;
263 }
264 self.pointer.hovered = hovered;
265 self.pointer.wheel = 0;
266 self.pointer.pixels = 0.0;
267 if let Some(old) = old {
268 self.set_button_state(old, |button| button.hovered = false);
269 }
270 if let Some(new) = hovered {
271 self.set_button_state(new, |button| button.hovered = true);
272 }
273 }
274
275 fn set_inside(&mut self, path: &[NodeId]) {
278 let inside: Vec<(NodeId, Signal<bool>)> = path
279 .iter()
280 .filter_map(|&id| match &self.node(id)?.kind {
281 NodeKind::Button(button) => Some((id, button.hover_signal?)),
282 _ => None,
283 })
284 .collect();
285 let holds = |list: &[(NodeId, Signal<bool>)], signal| list.iter().any(|(_, s)| *s == signal);
288 let mut seen = Vec::new();
289 for &(_, signal) in self.pointer.inside.iter().chain(&inside) {
290 if seen.contains(&signal) {
291 continue;
292 }
293 seen.push(signal);
294 let now = holds(&inside, signal);
295 if holds(&self.pointer.inside, signal) != now {
296 signal.try_set(now);
297 }
298 }
299 self.pointer.inside = inside;
300 }
301
302 fn set_pressed(&mut self, pressed: Option<(NodeId, PointerButton)>) {
303 let old = std::mem::replace(&mut self.pointer.pressed, pressed);
304 if let Some((old, _)) = old {
305 self.set_button_state(old, |button| button.pressed = false);
306 }
307 if let Some((new, _)) = pressed {
308 self.set_button_state(new, |button| button.pressed = true);
309 }
310 }
311
312 fn set_button_state(&mut self, id: NodeId, f: impl FnOnce(&mut ButtonNode)) {
315 if self.update_button_style(id, f) == Change::Layout {
316 self.pointer.caused_layout = true;
317 }
318 }
319
320 fn button(&mut self, button: PointerButton, pressed: bool) -> Option<Call> {
321 if pressed {
322 if self.pointer.pressed.is_none()
323 && let Some(target) = self.pointer.hovered
324 {
325 self.set_pressed(Some((target, button)));
326 if button == PointerButton::Left {
327 self.press_field(target);
328 return self.call(target, Action::Press);
329 }
330 }
331 return None;
332 }
333 let (target, held) = self.pointer.pressed?;
334 if held != button {
335 return None;
336 }
337 self.set_pressed(None);
338 if self.pointer.hovered != Some(target) {
339 return None;
340 }
341 let action = match button {
342 PointerButton::Left => Action::Click,
343 PointerButton::Right => Action::RightClick,
344 PointerButton::Middle => Action::MiddleClick,
345 };
346 self.call(target, action)
347 }
348
349 fn scroll(&mut self, delta: ScrollDelta) -> Option<Scrolled> {
350 let handled = self.pointer.hovered.filter(|&id| self.scrolls(id));
351 let Some(target) = handled else {
352 return self.scroll_view(delta);
353 };
354 let down = match delta {
356 ScrollDelta::Wheel(value120) => {
357 self.pointer.wheel = self.pointer.wheel.saturating_add(value120);
358 let steps = self.pointer.wheel / VALUE120_PER_STEP;
359 self.pointer.wheel -= steps * VALUE120_PER_STEP;
360 steps
361 }
362 ScrollDelta::Pixels(pixels) => {
363 if !pixels.is_finite() {
364 return None;
365 }
366 self.pointer.pixels += pixels;
367 let steps = (self.pointer.pixels / PIXELS_PER_STEP).trunc();
368 self.pointer.pixels -= steps * PIXELS_PER_STEP;
369 steps as i32
370 }
371 };
372 if down == 0 {
373 return None;
374 }
375 self
377 .call(target, Action::Scroll(down.saturating_neg()))
378 .map(Scrolled::Button)
379 }
380
381 fn press_field(&mut self, id: NodeId) {
384 let Some(position) = self.pointer.position else {
385 return;
386 };
387 let Some(NodeKind::Input(input)) = self.node(id).map(|node| &node.kind) else {
388 return;
389 };
390 let at = input.hit(position);
391 self.focus(Some(id));
392 self.update_input(id, |input| {
393 input.edit.cursor = at;
394 input.edit.anchor = None;
395 });
396 }
397
398 fn scrolls(&self, id: NodeId) -> bool {
400 let Some(NodeKind::Button(button)) = self.node(id).map(|node| &node.kind) else {
401 return false;
402 };
403 untrack(|| {
404 button
405 .handlers
406 .try_with(|handlers| handlers.scroll.is_some())
407 })
408 .unwrap_or(false)
409 }
410
411 fn scroll_view(&mut self, delta: ScrollDelta) -> Option<Scrolled> {
414 let position = self.pointer.position?;
415 let id = self.scroll_view_at(self.root, position)?;
416 let pixels = match delta {
417 ScrollDelta::Wheel(value120) => value120 as f32 / VALUE120_PER_STEP as f32 * PIXELS_PER_NOTCH,
418 ScrollDelta::Pixels(pixels) if pixels.is_finite() => pixels,
419 ScrollDelta::Pixels(_) => return None,
420 };
421 let NodeKind::Scroll(scroll) = &self.node(id)?.kind else {
422 return None;
423 };
424 let now = untrack(|| scroll.signal.try_get())?;
427 let now = if now.is_nan() {
428 0.0
429 } else {
430 now.clamp(0.0, scroll.most)
431 };
432 let offset = (now + pixels).clamp(0.0, scroll.most);
433 (offset != now).then_some(Scrolled::View(scroll.signal, offset))
434 }
435
436 fn scroll_view_at(&self, id: NodeId, point: Point) -> Option<NodeId> {
439 let node = self.node(id)?;
440 if !node.rect.is_some_and(|rect| rect.contains(point)) {
441 return None;
442 }
443 let children = self.taffy.children(id).unwrap_or_default();
444 children
445 .into_iter()
446 .rev()
447 .find_map(|child| self.scroll_view_at(child, point))
448 .or_else(|| matches!(node.kind, NodeKind::Scroll(_)).then_some(id))
449 }
450
451 pub(crate) fn click(&self, id: NodeId) -> Option<Call> {
453 self.call(id, Action::Click)
454 }
455
456 fn call(&self, id: NodeId, action: Action) -> Option<Call> {
457 let node = self.node(id)?;
458 let NodeKind::Button(button) = &node.kind else {
459 return None;
460 };
461 Some(Call {
462 cx: node.cx.clone(),
463 path: Rc::clone(&node.path),
464 handlers: button.handlers,
465 action,
466 })
467 }
468}
469
470enum Scrolled {
472 Button(Call),
474 View(Signal<f32>, f32),
476}
477
478#[derive(Clone, Copy)]
479enum Action {
480 Click,
481 Press,
482 RightClick,
483 MiddleClick,
484 Scroll(i32),
485}
486
487pub(crate) struct Call {
489 cx: Cx,
490 path: Rc<str>,
491 handlers: Signal<Handlers>,
492 action: Action,
493}
494
495impl Call {
496 pub(crate) fn run(self) {
497 let handlers = untrack(|| {
499 self.handlers.try_with(|handlers| match self.action {
500 Action::Click => handlers.click.clone().map(Either::Click),
501 Action::Press => handlers.press.clone().map(Either::Click),
502 Action::RightClick => handlers.right_click.clone().map(Either::Click),
503 Action::MiddleClick => handlers.middle_click.clone().map(Either::Click),
504 Action::Scroll(_) => handlers.scroll.clone().map(Either::Scroll),
505 })
506 });
507 let Some(Some(handler)) = handlers else {
508 return;
509 };
510 match (handler, self.action) {
511 (Either::Click(handler), action) => {
512 let name = match action {
513 Action::RightClick => "on_right_click",
514 Action::Press => "on_press",
515 Action::MiddleClick => "on_middle_click",
516 _ => "on_click",
517 };
518 run_isolated(&self.path, name, handler, |f| f(&self.cx));
519 }
520 (Either::Scroll(handler), Action::Scroll(steps)) => {
521 run_isolated(&self.path, "on_scroll", handler, |f| f(&self.cx, steps));
522 }
523 (Either::Scroll(_), _) => {}
524 }
525 }
526}
527
528enum Either {
529 Click(ClickHandler),
530 Scroll(ScrollHandler),
531}
532
533pub(crate) fn run_isolated<F: ?Sized, R>(
538 path: &str,
539 name: &str,
540 handler: Rc<Handler<F>>,
541 call: impl FnOnce(&F) -> R,
542) -> Option<R> {
543 if handler.disabled.get() {
544 return None;
545 }
546 let result = match panic::catch_unwind(AssertUnwindSafe(|| call(&handler.f))) {
547 Ok(result) => Some(result),
548 Err(payload) => {
549 handler.disabled.set(true);
550 tracing::error!(
551 "handler {path}.{name} panicked and is disabled: {}",
552 panic_message(payload.as_ref())
553 );
554 None
555 }
556 };
557 if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(move || drop(handler))) {
558 tracing::error!(
559 "dropping handler {path}.{name} panicked: {}",
560 panic_message(payload.as_ref())
561 );
562 }
563 result
564}
565
566pub(crate) fn panic_message(payload: &(dyn Any + Send)) -> &str {
567 if let Some(message) = payload.downcast_ref::<&str>() {
568 message
569 } else if let Some(message) = payload.downcast_ref::<String>() {
570 message
571 } else {
572 "(panic payload is not a string)"
573 }
574}