1use crate::MotionState;
3use crate::core::{Drag, GestureTargets, InView, Interaction, Motion, Target};
4
5use gpui_kit::{
6 AnyElement, App, Bounds, DispatchPhase, Div, Element, ElementId, EntityId, FocusHandle,
7 GlobalElementId, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, KeyDownEvent,
8 KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
9 MouseUpEvent, Pixels, Stateful, Window, prelude::*, px,
10};
11
12use std::{
13 cell::RefCell,
14 rc::Rc,
15 time::{Duration, Instant},
16};
17
18#[derive(Default)]
19struct Data {
20 interaction: Interaction,
21 motion: MotionState,
22 drag: Option<Drag>,
23 focus: Option<FocusHandle>,
24 visibility: Option<InView>,
25 epoch: Option<Instant>,
26 owner: Option<EntityId>,
27 pointer: bool,
28 key: Option<String>,
29 presented: Target,
30}
31
32impl Data {
33 fn now(&self, cx: &App) -> Duration {
34 self.epoch.map_or(Duration::ZERO, |epoch| {
35 cx.background_executor()
36 .now()
37 .saturating_duration_since(epoch)
38 })
39 }
40
41 fn cancel(&mut self, cx: &App) {
42 let now = self.now(cx);
43
44 if let Some(drag) = &mut self.drag {
45 drag.cancel(now);
46 }
47
48 self.pointer = false;
49 self.key = None;
50 self.interaction.pressed = false;
51 self.interaction.dragging = false;
52 }
53
54 fn notify(&self, cx: &mut App) {
55 if let Some(owner) = self.owner {
56 cx.notify(owner);
57 }
58 }
59}
60
61#[derive(Clone, Default)]
65pub struct GestureState(Rc<RefCell<Data>>);
66
67impl GestureState {
68 pub fn with_drag(self, drag: Drag) -> Self {
70 self.0.borrow_mut().drag = Some(drag);
71
72 self
73 }
74
75 pub fn in_view(self, amount: f64, once: bool) -> Self {
77 self.0.borrow_mut().visibility = Some(InView::new(amount, once));
78
79 self
80 }
81
82 pub fn interaction(&self) -> Interaction {
84 self.0.borrow().interaction
85 }
86
87 pub fn presented(&self) -> Target {
89 self.0.borrow().presented
90 }
91
92 pub fn focus_handle(&self) -> Option<FocusHandle> {
94 self.0.borrow().focus.clone()
95 }
96
97 pub fn cancel(&self, cx: &mut App) {
99 let mut data = self.0.borrow_mut();
100
101 data.cancel(cx);
102 data.notify(cx);
103 }
104
105 pub fn bind<E: IntoElement + StatefulInteractiveElement + Styled + 'static>(
119 &self,
120 id: impl Into<ElementId>,
121 child: E,
122 motion: Motion,
123 targets: GestureTargets,
124 ) -> GestureElement<E> {
125 GestureElement {
126 id: id.into(),
127 child: Some(child),
128 state: self.clone(),
129 motion,
130 targets,
131 enabled: true,
132 }
133 }
134}
135
136pub struct GestureElement<E = Stateful<Div>> {
138 id: ElementId,
139 child: Option<E>,
140 state: GestureState,
141 motion: Motion,
142 targets: GestureTargets,
143 enabled: bool,
144}
145
146impl<E> GestureElement<E> {
147 pub fn enabled(mut self, enabled: bool) -> Self {
152 self.enabled = enabled;
153
154 self
155 }
156}
157
158impl<E: IntoElement + StatefulInteractiveElement + Styled + 'static> IntoElement
159 for GestureElement<E>
160{
161 type Element = Self;
162
163 fn into_element(self) -> Self {
164 self
165 }
166}
167
168impl<E: IntoElement + StatefulInteractiveElement + Styled + 'static> Element for GestureElement<E> {
169 type RequestLayoutState = AnyElement;
170 type PrepaintState = Hitbox;
171
172 fn id(&self) -> Option<ElementId> {
173 Some(self.id.clone())
174 }
175
176 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
177 None
178 }
179
180 fn request_layout(
181 &mut self,
182 _: Option<&GlobalElementId>,
183 _: Option<&InspectorElementId>,
184 w: &mut Window,
185 cx: &mut App,
186 ) -> (LayoutId, AnyElement) {
187 let mut data = self.state.0.borrow_mut();
188
189 data.owner = Some(w.current_view());
190 data.epoch
191 .get_or_insert_with(|| cx.background_executor().now());
192 let focus = data
193 .focus
194 .get_or_insert_with(|| cx.focus_handle().tab_stop(true))
195 .clone()
196 .tab_stop(self.enabled);
197
198 let focused = self.enabled && focus.is_focused(w);
199
200 if !self.enabled
201 || !w.is_window_active()
202 || (!focused && (data.key.is_some() || data.pointer))
203 {
204 data.cancel(cx);
205 }
206
207 if !self.enabled || !w.is_window_active() {
208 data.interaction.hovered = false;
209 }
210
211 data.interaction.focused = focused;
212 let now = data.now(cx);
213 let target = self.targets.resolve(self.motion.target(), data.interaction);
214
215 let frame = data
216 .motion
217 .sample(self.motion.with_target(target), now, cx.reduce_motion());
218
219 let mut target = frame.target();
220 let mut active = frame.is_animating();
221
222 if let Some(drag) = &mut data.drag {
223 let offset = drag.sample(now, cx.reduce_motion());
224
225 active |= offset.is_animating();
226 let finite = |a: f32, b: f32| {
227 (f64::from(a) + f64::from(b)).clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32
228 };
229
230 target = target
231 .x(finite(target.x_px(), offset.target().x_px()))
232 .y(finite(target.y_px(), offset.target().y_px()));
233 }
234
235 data.presented = target;
236
237 if active {
238 w.request_animation_frame();
239 }
240
241 let child = frame
242 .apply(
243 self.child
244 .take()
245 .expect("one layout")
246 .track_focus(&focus)
247 .tab_index(0),
248 )
249 .left(px(target.x_px()))
250 .top(px(target.y_px()));
251
252 drop(data);
253 let mut child = child.into_any_element();
254 let layout = child.request_layout(w, cx);
255
256 (layout, child)
257 }
258
259 fn prepaint(
260 &mut self,
261 _: Option<&GlobalElementId>,
262 _: Option<&InspectorElementId>,
263 bounds: Bounds<Pixels>,
264 child: &mut AnyElement,
265 w: &mut Window,
266 cx: &mut App,
267 ) -> Hitbox {
268 let hitbox = w.insert_hitbox(bounds, HitboxBehavior::Normal);
269 let mut data = self.state.0.borrow_mut();
270
271 if let Some(visibility) = &mut data.visibility {
272 let viewport = w.content_mask().bounds;
273
274 let rect = |r: Bounds<Pixels>| {
275 [
276 f64::from(r.origin.x),
277 f64::from(r.origin.y),
278 f64::from(r.size.width),
279 f64::from(r.size.height),
280 ]
281 };
282
283 if visibility.update(rect(viewport), rect(bounds)) {
284 data.interaction.in_view = visibility.is_visible();
285 let owner = data.owner.expect("owner");
286
287 w.defer(cx, move |_, cx| cx.notify(owner));
288 }
289 }
290
291 drop(data);
292 child.prepaint(w, cx);
293
294 hitbox
295 }
296
297 fn paint(
298 &mut self,
299 _: Option<&GlobalElementId>,
300 _: Option<&InspectorElementId>,
301 _: Bounds<Pixels>,
302 child: &mut AnyElement,
303 hitbox: &mut Hitbox,
304 w: &mut Window,
305 cx: &mut App,
306 ) {
307 if !self.enabled {
308 let region = hitbox.clone();
309
310 w.on_mouse_event(move |_: &MouseDownEvent, phase, w, cx| {
311 if phase == DispatchPhase::Capture && region.is_hovered(w) {
312 cx.stop_propagation();
313 }
314 });
315 let region = hitbox.clone();
316
317 w.on_mouse_event(move |_: &MouseUpEvent, phase, w, cx| {
318 if phase == DispatchPhase::Capture && region.is_hovered(w) {
319 cx.stop_propagation();
320 }
321 });
322 let region = hitbox.clone();
323
324 w.on_mouse_event(move |_: &MouseMoveEvent, phase, w, cx| {
325 if phase == DispatchPhase::Capture && region.is_hovered(w) {
326 cx.stop_propagation();
327 }
328 });
329 let focus = self.state.focus_handle();
330
331 w.on_key_event(move |_: &KeyDownEvent, phase, w, cx| {
332 if phase == DispatchPhase::Capture
333 && focus.as_ref().is_some_and(|f| f.is_focused(w))
334 {
335 cx.stop_propagation();
336 }
337 });
338 let focus = self.state.focus_handle();
339
340 w.on_key_event(move |_: &KeyUpEvent, phase, w, cx| {
341 if phase == DispatchPhase::Capture
342 && focus.as_ref().is_some_and(|f| f.is_focused(w))
343 {
344 cx.stop_propagation();
345 }
346 });
347 child.paint(w, cx);
348
349 return;
350 }
351
352 let shared = self.state.0.clone();
353 let region = hitbox.clone();
354
355 w.on_mouse_event(move |event: &MouseDownEvent, phase, w, cx| {
356 if phase == DispatchPhase::Bubble
357 && event.button == MouseButton::Left
358 && region.is_hovered(w)
359 {
360 let mut data = shared.borrow_mut();
361
362 if data.pointer {
363 return;
364 }
365
366 data.pointer = true;
367 data.key = None;
368 data.interaction.pressed = true;
369 let now = data.now(cx);
370
371 if let Some(drag) = &mut data.drag {
372 drag.begin(
373 [f64::from(event.position.x), f64::from(event.position.y)],
374 now,
375 );
376 data.interaction.dragging = true;
377 cx.stop_propagation();
378 }
379
380 if let Some(focus) = &data.focus {
381 focus.focus(w, cx);
382 }
383
384 data.notify(cx);
385 }
386 });
387 let shared = self.state.0.clone();
388 let region = hitbox.clone();
389
390 w.on_mouse_event(move |event: &MouseMoveEvent, phase, w, cx| {
391 if phase != DispatchPhase::Capture {
392 return;
393 }
394
395 let mut data = shared.borrow_mut();
396 let hover = region.is_hovered(w);
397 let changed = data.interaction.hovered != hover;
398
399 data.interaction.hovered = hover;
400
401 if data.pointer {
402 let now = data.now(cx);
403
404 if event.pressed_button != Some(MouseButton::Left) {
405 data.cancel(cx);
406 } else if let Some(drag) = &mut data.drag {
407 drag.move_to(
408 [f64::from(event.position.x), f64::from(event.position.y)],
409 now,
410 );
411 }
412
413 data.notify(cx);
414 } else if changed {
415 data.notify(cx);
416 }
417 });
418 let shared = self.state.0.clone();
419
420 w.on_mouse_event(move |event: &MouseUpEvent, phase, _, cx| {
421 if phase == DispatchPhase::Capture && event.button == MouseButton::Left {
422 let mut data = shared.borrow_mut();
423
424 if !data.pointer {
425 return;
426 }
427
428 let now = data.now(cx);
429
430 if let Some(drag) = &mut data.drag {
431 drag.end(now);
432 }
433
434 data.pointer = false;
435 data.interaction.pressed = false;
436 data.interaction.dragging = false;
437 data.notify(cx);
438 }
439 });
440 let shared = self.state.0.clone();
441
442 w.on_mouse_event(move |_: &MouseExitEvent, phase, _, cx| {
443 if phase == DispatchPhase::Capture {
444 let mut data = shared.borrow_mut();
445
446 if data.interaction.hovered {
447 data.interaction.hovered = false;
448 data.notify(cx);
449 }
450 }
451 });
452 let shared = self.state.0.clone();
453
454 w.on_key_event(move |event: &KeyDownEvent, phase, w, cx| {
455 if phase != DispatchPhase::Capture {
456 return;
457 }
458
459 let mut data = shared.borrow_mut();
460
461 if !data.focus.as_ref().is_some_and(|f| f.is_focused(w)) {
462 return;
463 }
464
465 let key = event.keystroke.key.as_str();
466 let now = data.now(cx);
467
468 if key == "escape" {
469 data.cancel(cx);
470 data.notify(cx);
471 } else if ["enter", "space"].contains(&key) && !event.is_held && !data.pointer {
472 data.key = Some(key.to_owned());
473 data.interaction.pressed = true;
474 data.notify(cx);
475 } else if let Some(delta) = match key {
476 "left" => Some([-10., 0.]),
477 "right" => Some([10., 0.]),
478 "up" => Some([0., -10.]),
479 "down" => Some([0., 10.]),
480 _ => None,
481 } && let Some(drag) = &mut data.drag
482 {
483 drag.move_by(delta, now);
484 data.notify(cx);
485 cx.stop_propagation();
486 }
487 });
488 let shared = self.state.0.clone();
489
490 w.on_key_event(move |event: &KeyUpEvent, phase, _, cx| {
491 if phase == DispatchPhase::Capture {
492 let mut data = shared.borrow_mut();
493
494 if data.key.as_deref() == Some(event.keystroke.key.as_str()) {
495 data.key = None;
496 data.interaction.pressed = data.pointer;
497 data.notify(cx);
498 }
499 }
500 });
501 child.paint(w, cx);
503 }
504}