Skip to main content

libghostty_vt/selection/
gesture.rs

1//! [Selection gestures](Gesture) provide a reusable state machine for turning
2//! UI pointer interactions into selection snapshots.
3//!
4//! A caller creates one [`Gesture`] per active gesture stream, reuses typed
5//! gesture event objects for synthetic [press](PressEvent), [drag](DragEvent),
6//! [release](ReleaseEvent), [autoscroll tick](AutoscrollTickEvent),
7//! and [deep-press](DeepPressEvent) events, and applies each event with
8//! their respective `apply` method (e.g. [PressEvent::apply]). The returned
9//! [`Selection`] is a snapshot; the embedder decides whether to render it,
10//! format/copy it, or install it as the terminal's active selection.
11
12use std::{mem::MaybeUninit, time::Duration};
13
14use crate::{
15    alloc::{Allocator, Object},
16    error::{Error, Result, from_optional_result, from_result},
17    ffi,
18    screen::GridRef,
19    selection::Selection,
20    terminal::{PointCoordinate, Terminal},
21};
22
23#[doc(inline)]
24pub use ffi::SelectionGestureGeometry as Geometry;
25
26/// Opaque handle to state for interpreting terminal selection gestures.
27///
28/// The gesture owns only the state required to interpret pointer events.
29/// Calls that use a gesture are not concurrency-safe and must be serialized
30/// with terminal mutations.
31///
32/// # Memory management
33///
34/// When dropped, this type will temporarily **leak** a small amount of memory
35/// belonging to the internal [`TrackedGridRef`](crate::screen::TrackedGridRef)s.
36/// They will instead be reclaimed when the terminal they belong to is dropped.
37/// This memory can be preemptively reclaimed by calling the [`Gesture::reset`]
38/// method if needed.
39#[derive(Debug)]
40pub struct Gesture<'alloc> {
41    inner: Object<'alloc, ffi::SelectionGestureImpl>,
42}
43impl<'alloc> Gesture<'alloc> {
44    /// Create a new selection gesture instance.
45    pub fn new() -> Result<Self> {
46        // SAFETY: A NULL allocator is always valid
47        unsafe { Self::new_inner(std::ptr::null()) }
48    }
49
50    /// Create a new selection gesture instance with a custom allocator.
51    ///
52    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
53    /// regarding custom memory management and lifetimes.
54    pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
55        // SAFETY: Borrow checking should forbid invalid allocators
56        unsafe { Self::new_inner(alloc.to_raw()) }
57    }
58
59    unsafe fn new_inner(alloc: *const ffi::Allocator) -> Result<Self> {
60        let mut raw: ffi::SelectionGesture = std::ptr::null_mut();
61        let result = unsafe { ffi::ghostty_selection_gesture_new(alloc, &raw mut raw) };
62        from_result(result)?;
63        Ok(Self {
64            inner: Object::new(raw)?,
65        })
66    }
67
68    fn get<T>(
69        &self,
70        terminal: &Terminal<'_, '_>,
71        tag: ffi::SelectionGestureData::Type,
72    ) -> Result<T> {
73        let mut value = MaybeUninit::<T>::zeroed();
74        let result = unsafe {
75            ffi::ghostty_selection_gesture_get(
76                self.inner.as_raw(),
77                terminal.inner.as_raw(),
78                tag,
79                value.as_mut_ptr().cast(),
80            )
81        };
82        from_result(result)?;
83        // SAFETY: Value should be initialized after successful call.
84        Ok(unsafe { value.assume_init() })
85    }
86    /// Reset any active selection gesture state.
87    ///
88    /// This cancels the active click sequence and releases any tracked terminal
89    /// references owned by the gesture without dropping the gesture object.
90    pub fn reset(&mut self, terminal: &Terminal<'_, '_>) {
91        unsafe {
92            ffi::ghostty_selection_gesture_reset(self.inner.as_raw(), terminal.inner.as_raw());
93        }
94    }
95
96    /// Get the current click count. 0 means inactive.
97    pub fn click_count(&self, terminal: &Terminal<'_, '_>) -> Result<u8> {
98        self.get(terminal, ffi::SelectionGestureData::CLICK_COUNT)
99    }
100    /// Whether the current/last left-click gesture has dragged.
101    pub fn dragged(&self, terminal: &Terminal<'_, '_>) -> Result<bool> {
102        self.get(terminal, ffi::SelectionGestureData::DRAGGED)
103    }
104    /// Get the current autoscroll request.
105    pub fn autoscroll(&self, terminal: &Terminal<'_, '_>) -> Result<Autoscroll> {
106        let v = self.get::<ffi::SelectionGestureAutoscroll::Type>(
107            terminal,
108            ffi::SelectionGestureData::AUTOSCROLL,
109        )?;
110        Autoscroll::try_from(v).map_err(|_| Error::InvalidValue)
111    }
112    /// Get the current gesture behavior.
113    pub fn behavior(&self, terminal: &Terminal<'_, '_>) -> Result<Behavior> {
114        let v = self.get::<ffi::SelectionGestureBehavior::Type>(
115            terminal,
116            ffi::SelectionGestureData::BEHAVIOR,
117        )?;
118        Behavior::try_from(v).map_err(|_| Error::InvalidValue)
119    }
120    /// Get the current left-click anchor.
121    ///
122    /// Returns `None` if there is no valid active anchor.
123    pub fn anchor<'t>(&self, terminal: &'t Terminal<'_, '_>) -> Result<Option<GridRef<'t>>> {
124        let mut grid_ref = ffi::sized!(ffi::GridRef);
125        let result = unsafe {
126            ffi::ghostty_selection_gesture_get(
127                self.inner.as_raw(),
128                terminal.inner.as_raw(),
129                ffi::SelectionGestureData::ANCHOR,
130                (&raw mut grid_ref).cast(),
131            )
132        };
133        let grid_ref = from_optional_result(result, grid_ref)?;
134        // SAFETY: We trust libghostty to return a GridRef
135        // with the correct lifetime requirements.
136        Ok(grid_ref.map(|v| unsafe { GridRef::from_raw(v) }))
137    }
138}
139impl Drop for Gesture<'_> {
140    fn drop(&mut self) {
141        // NOTE: We can't pass the terminal in here to eagerly reclaim memory
142        // taken up by the tracked grid refs, since that would require taking
143        // a reference to the terminal and hold it within the selection gesture
144        // struct and thereby prevent any mutation.
145        //
146        // However, leaking a bit of memory here is mostly fine since the
147        // memory will eventually be reclaimed by the terminal anyway,
148        // (which in the typical use case will be shortly after freeing
149        // the selection gesture), and memory-conscious embedders can simply
150        // call `reset` to manually reclaim memory if needed.
151        unsafe {
152            ffi::ghostty_selection_gesture_free(self.inner.as_raw(), std::ptr::null_mut());
153        }
154    }
155}
156
157#[derive(Debug)]
158struct Event<'alloc> {
159    inner: Object<'alloc, ffi::SelectionGestureEventImpl>,
160}
161impl<'alloc> Event<'alloc> {
162    unsafe fn new_inner(
163        alloc: *const ffi::Allocator,
164        ty: ffi::SelectionGestureEventType::Type,
165    ) -> Result<Self> {
166        let mut raw: ffi::SelectionGestureEvent = std::ptr::null_mut();
167        let result = unsafe { ffi::ghostty_selection_gesture_event_new(alloc, &raw mut raw, ty) };
168        from_result(result)?;
169        Ok(Self {
170            inner: Object::new(raw)?,
171        })
172    }
173
174    fn set<T>(&mut self, field: ffi::SelectionGestureEventOption::Type, v: &T) -> Result<()> {
175        let result = unsafe {
176            ffi::ghostty_selection_gesture_event_set(
177                self.inner.as_raw(),
178                field,
179                std::ptr::from_ref(v).cast(),
180            )
181        };
182        from_result(result)?;
183        Ok(())
184    }
185
186    fn unset(&mut self, field: ffi::SelectionGestureEventOption::Type) -> Result<()> {
187        let result = unsafe {
188            ffi::ghostty_selection_gesture_event_set(self.inner.as_raw(), field, std::ptr::null())
189        };
190        from_result(result)?;
191        Ok(())
192    }
193
194    fn apply<'t>(
195        &mut self,
196        gesture: &mut Gesture<'_>,
197        terminal: &'t Terminal<'_, '_>,
198    ) -> Result<Option<Selection<'t>>> {
199        let mut selection = ffi::sized!(ffi::Selection);
200
201        let result = unsafe {
202            ffi::ghostty_selection_gesture_event(
203                gesture.inner.as_raw(),
204                terminal.inner.as_raw(),
205                self.inner.as_raw(),
206                &raw mut selection,
207            )
208        };
209        let selection = from_optional_result(result, selection)?;
210
211        // SAFETY: We trust that libghostty will give us a
212        // selection object with correct lifetimes.
213        Ok(selection.map(|v| unsafe { Selection::from_raw(v) }))
214    }
215}
216impl Drop for Event<'_> {
217    fn drop(&mut self) {
218        unsafe {
219            ffi::ghostty_selection_gesture_event_free(self.inner.as_raw());
220        }
221    }
222}
223
224/// Opaque handle to reusable input data for selection gesture press operations.
225#[derive(Debug)]
226pub struct PressEvent<'alloc> {
227    base: Event<'alloc>,
228}
229impl<'alloc> PressEvent<'alloc> {
230    /// Create a new selection gesture press event instance.
231    pub fn new() -> Result<Self> {
232        // SAFETY: A NULL allocator is always valid
233        unsafe { Self::new_inner(std::ptr::null()) }
234    }
235
236    /// Create a new selection gesture press event instance with a custom allocator.
237    ///
238    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
239    /// regarding custom memory management and lifetimes.
240    pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
241        // SAFETY: Borrow checking should forbid invalid allocators
242        unsafe { Self::new_inner(alloc.to_raw()) }
243    }
244
245    unsafe fn new_inner(alloc: *const ffi::Allocator) -> Result<Self> {
246        Ok(Self {
247            base: unsafe { Event::new_inner(alloc, ffi::SelectionGestureEventType::PRESS)? },
248        })
249    }
250
251    /// Set the surface-space pointer position.
252    #[inline]
253    pub fn set_position(&mut self, x: f64, y: f64) -> Result<&mut Self> {
254        let value = ffi::SurfacePosition { x, y };
255        self.base
256            .set(ffi::SelectionGestureEventOption::POSITION, &value)?;
257        Ok(self)
258    }
259
260    /// Set the maximum repeat-click distance in pixels.
261    #[inline]
262    pub fn set_repeat_distance(&mut self, value: f64) -> Result<&mut Self> {
263        self.base
264            .set(ffi::SelectionGestureEventOption::REPEAT_DISTANCE, &value)?;
265        Ok(self)
266    }
267
268    /// Set the monotonic event time.
269    ///
270    /// If unset, press treats the event as untimed and only single-click behavior
271    /// is available.
272    ///
273    /// Intervals above [`u64::MAX`] nanoseconds in length will be
274    /// silently truncated.
275    #[inline]
276    pub fn set_time(&mut self, value: Duration) -> Result<&mut Self> {
277        let nanos = value.as_nanos() as u64;
278        self.base
279            .set(ffi::SelectionGestureEventOption::TIME_NS, &nanos)?;
280        Ok(self)
281    }
282
283    /// Set the maximum interval between repeat clicks.
284    ///
285    /// Intervals above [`u64::MAX`] nanoseconds in length will be
286    /// silently truncated.
287    #[inline]
288    pub fn set_repeat_interval(&mut self, value: Duration) -> Result<&mut Self> {
289        let nanos = value.as_nanos() as u64;
290        self.base
291            .set(ffi::SelectionGestureEventOption::REPEAT_INTERVAL_NS, &nanos)?;
292        Ok(self)
293    }
294
295    /// Set the word-boundary codepoints.
296    ///
297    /// The codepoints are copied into event-owned storage when set.
298    /// If unset, operations that need word boundaries use Ghostty's defaults.
299    #[inline]
300    pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
301        let cp = ffi::Codepoints {
302            // It is safe to cast char -> u32 in a readonly fashion.
303            ptr: value.as_ptr().cast(),
304            len: value.len(),
305        };
306
307        self.base.set(
308            ffi::SelectionGestureEventOption::WORD_BOUNDARY_CODEPOINTS,
309            &cp,
310        )?;
311        Ok(self)
312    }
313
314    /// Set the selection behavior table.
315    ///
316    /// If unset, press uses the default behavior table: cell, word, line.
317    #[inline]
318    pub fn set_behaviors(&mut self, value: &Behaviors) -> Result<&mut Self> {
319        self.base
320            .set(ffi::SelectionGestureEventOption::BEHAVIORS, &value.inner)?;
321        Ok(self)
322    }
323
324    /// Apply a selection gesture press event and return the resulting selection snapshot.
325    #[inline]
326    pub fn apply<'t>(
327        &mut self,
328        gesture: &mut Gesture<'_>,
329        terminal: &'t Terminal<'_, '_>,
330        grid_ref: GridRef<'t>,
331    ) -> Result<Option<Selection<'t>>> {
332        self.base
333            .set(ffi::SelectionGestureEventOption::REF, &grid_ref.inner)?;
334        self.base.apply(gesture, terminal)
335    }
336}
337
338/// Opaque handle to reusable input data for selection gesture release operations.
339#[derive(Debug)]
340pub struct ReleaseEvent<'alloc> {
341    base: Event<'alloc>,
342}
343impl<'alloc> ReleaseEvent<'alloc> {
344    /// Create a new selection gesture release event instance.
345    pub fn new() -> Result<Self> {
346        // SAFETY: A NULL allocator is always valid
347        unsafe { Self::new_inner(std::ptr::null()) }
348    }
349
350    /// Create a new selection gesture release event instance with a custom allocator.
351    ///
352    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
353    /// regarding custom memory management and lifetimes.
354    pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
355        // SAFETY: Borrow checking should forbid invalid allocators
356        unsafe { Self::new_inner(alloc.to_raw()) }
357    }
358
359    unsafe fn new_inner(alloc: *const ffi::Allocator) -> Result<Self> {
360        Ok(Self {
361            base: unsafe { Event::new_inner(alloc, ffi::SelectionGestureEventType::RELEASE)? },
362        })
363    }
364
365    /// Apply a selection gesture release event and return the resulting selection snapshot.
366    #[inline]
367    pub fn apply<'t>(
368        &mut self,
369        gesture: &mut Gesture<'_>,
370        terminal: &'t Terminal<'_, '_>,
371        grid_ref: Option<GridRef<'t>>,
372    ) -> Result<()> {
373        match grid_ref {
374            Some(g) => self
375                .base
376                .set(ffi::SelectionGestureEventOption::REF, &g.inner)?,
377            None => self.base.unset(ffi::SelectionGestureEventOption::REF)?,
378        }
379
380        // A release event always returns None.
381        _ = self.base.apply(gesture, terminal)?;
382        Ok(())
383    }
384}
385
386/// Opaque handle to reusable input data for selection gesture drag operations.
387#[derive(Debug)]
388pub struct DragEvent<'alloc> {
389    base: Event<'alloc>,
390}
391impl<'alloc> DragEvent<'alloc> {
392    /// Create a new selection gesture drag event instance.
393    pub fn new() -> Result<Self> {
394        // SAFETY: A NULL allocator is always valid
395        unsafe { Self::new_inner(std::ptr::null()) }
396    }
397
398    /// Create a new selection gesture drag event instance with a custom allocator.
399    ///
400    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
401    /// regarding custom memory management and lifetimes.
402    pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
403        // SAFETY: Borrow checking should forbid invalid allocators
404        unsafe { Self::new_inner(alloc.to_raw()) }
405    }
406
407    unsafe fn new_inner(alloc: *const ffi::Allocator) -> Result<Self> {
408        Ok(Self {
409            base: unsafe { Event::new_inner(alloc, ffi::SelectionGestureEventType::DRAG)? },
410        })
411    }
412
413    /// Set the surface-space pointer position.
414    #[inline]
415    pub fn set_position(&mut self, x: f64, y: f64) -> Result<&mut Self> {
416        let value = ffi::SurfacePosition { x, y };
417        self.base
418            .set(ffi::SelectionGestureEventOption::POSITION, &value)?;
419        Ok(self)
420    }
421
422    /// Set the whether this drag should produce a rectangular selection.
423    #[inline]
424    pub fn set_rectangle(&mut self, value: bool) -> Result<&mut Self> {
425        self.base
426            .set(ffi::SelectionGestureEventOption::RECTANGLE, &value)?;
427        Ok(self)
428    }
429
430    /// Set the word-boundary codepoints.
431    ///
432    /// The codepoints are copied into event-owned storage when set.
433    /// If unset, operations that need word boundaries use Ghostty's defaults.
434    #[inline]
435    pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
436        let cp = ffi::Codepoints {
437            // It is safe to cast char -> u32 in a readonly fashion.
438            ptr: value.as_ptr().cast(),
439            len: value.len(),
440        };
441
442        self.base.set(
443            ffi::SelectionGestureEventOption::WORD_BOUNDARY_CODEPOINTS,
444            &cp,
445        )?;
446        Ok(self)
447    }
448
449    /// Apply a selection gesture drag event and return the resulting selection snapshot.
450    #[inline]
451    pub fn apply<'t>(
452        &mut self,
453        gesture: &mut Gesture<'_>,
454        terminal: &'t Terminal<'_, '_>,
455        grid_ref: GridRef<'t>,
456        geometry: Geometry,
457    ) -> Result<Option<Selection<'t>>> {
458        self.base
459            .set(ffi::SelectionGestureEventOption::REF, &grid_ref.inner)?;
460        self.base
461            .set(ffi::SelectionGestureEventOption::GEOMETRY, &geometry)?;
462        self.base.apply(gesture, terminal)
463    }
464}
465
466/// Opaque handle to reusable input data for selection gesture autoscroll tick operations.
467#[derive(Debug)]
468pub struct AutoscrollTickEvent<'alloc> {
469    base: Event<'alloc>,
470}
471impl<'alloc> AutoscrollTickEvent<'alloc> {
472    /// Create a new selection gesture autoscroll tick event instance.
473    pub fn new() -> Result<Self> {
474        // SAFETY: A NULL allocator is always valid
475        unsafe { Self::new_inner(std::ptr::null()) }
476    }
477
478    /// Create a new selection gesture autoscroll tick event instance with a custom allocator.
479    ///
480    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
481    /// regarding custom memory management and lifetimes.
482    pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
483        // SAFETY: Borrow checking should forbid invalid allocators
484        unsafe { Self::new_inner(alloc.to_raw()) }
485    }
486
487    unsafe fn new_inner(alloc: *const ffi::Allocator) -> Result<Self> {
488        Ok(Self {
489            base: unsafe {
490                Event::new_inner(alloc, ffi::SelectionGestureEventType::AUTOSCROLL_TICK)?
491            },
492        })
493    }
494
495    /// Set the surface-space pointer position.
496    #[inline]
497    pub fn set_position(&mut self, x: f64, y: f64) -> Result<&mut Self> {
498        let value = ffi::SurfacePosition { x, y };
499        self.base
500            .set(ffi::SelectionGestureEventOption::POSITION, &value)?;
501        Ok(self)
502    }
503
504    /// Set the whether this drag should produce a rectangular selection.
505    #[inline]
506    pub fn set_rectangle(&mut self, value: bool) -> Result<&mut Self> {
507        self.base
508            .set(ffi::SelectionGestureEventOption::RECTANGLE, &value)?;
509        Ok(self)
510    }
511
512    /// Set the word-boundary codepoints.
513    ///
514    /// The codepoints are copied into event-owned storage when set.
515    /// If unset, operations that need word boundaries use Ghostty's defaults.
516    #[inline]
517    pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
518        let cp = ffi::Codepoints {
519            // It is safe to cast char -> u32 in a readonly fashion.
520            ptr: value.as_ptr().cast(),
521            len: value.len(),
522        };
523
524        self.base.set(
525            ffi::SelectionGestureEventOption::WORD_BOUNDARY_CODEPOINTS,
526            &cp,
527        )?;
528        Ok(self)
529    }
530
531    /// Apply a selection gesture autoscroll tick event and return the resulting selection snapshot.
532    #[inline]
533    pub fn apply<'t>(
534        &mut self,
535        gesture: &mut Gesture<'_>,
536        terminal: &'t Terminal<'_, '_>,
537        viewport: PointCoordinate,
538        geometry: Geometry,
539    ) -> Result<Option<Selection<'t>>> {
540        let viewport = ffi::PointCoordinate::from(viewport);
541        self.base
542            .set(ffi::SelectionGestureEventOption::VIEWPORT, &viewport)?;
543        self.base
544            .set(ffi::SelectionGestureEventOption::GEOMETRY, &geometry)?;
545        self.base.apply(gesture, terminal)
546    }
547}
548
549/// Opaque handle to reusable input data for selection gesture deep press operations.
550#[derive(Debug)]
551pub struct DeepPressEvent<'alloc> {
552    base: Event<'alloc>,
553}
554impl<'alloc> DeepPressEvent<'alloc> {
555    /// Create a new selection gesture deep press event instance.
556    pub fn new() -> Result<Self> {
557        // SAFETY: A NULL allocator is always valid
558        unsafe { Self::new_inner(std::ptr::null()) }
559    }
560
561    /// Create a new selection gesture deep press event instance with a custom allocator.
562    ///
563    /// See the [crate-level documentation](crate#memory-management-and-lifetimes)
564    /// regarding custom memory management and lifetimes.
565    pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
566        // SAFETY: Borrow checking should forbid invalid allocators
567        unsafe { Self::new_inner(alloc.to_raw()) }
568    }
569
570    unsafe fn new_inner(alloc: *const ffi::Allocator) -> Result<Self> {
571        Ok(Self {
572            base: unsafe { Event::new_inner(alloc, ffi::SelectionGestureEventType::DEEP_PRESS)? },
573        })
574    }
575
576    /// Set the word-boundary codepoints.
577    ///
578    /// The codepoints are copied into event-owned storage when set.
579    /// If unset, operations that need word boundaries use Ghostty's defaults.
580    #[inline]
581    pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
582        let cp = ffi::Codepoints {
583            // It is safe to cast char -> u32 in a readonly fashion.
584            ptr: value.as_ptr().cast(),
585            len: value.len(),
586        };
587
588        self.base.set(
589            ffi::SelectionGestureEventOption::WORD_BOUNDARY_CODEPOINTS,
590            &cp,
591        )?;
592        Ok(self)
593    }
594
595    /// Apply a selection gesture deep press event and return the resulting selection snapshot.
596    #[inline]
597    pub fn apply<'t>(
598        &mut self,
599        gesture: &mut Gesture<'_>,
600        terminal: &'t Terminal<'_, '_>,
601    ) -> Result<Option<Selection<'t>>> {
602        self.base.apply(gesture, terminal)
603    }
604}
605
606/// Current autoscroll direction for an active selection drag gesture.
607#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
608#[repr(u32)]
609#[non_exhaustive]
610pub enum Autoscroll {
611    /// No selection autoscroll is requested.
612    None,
613    /// Selection dragging should autoscroll the viewport upward.
614    Up,
615    /// Selection dragging should autoscroll the viewport downward.
616    Down,
617}
618
619/// Selection behavior chosen for a gesture's click sequence.
620#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
621#[repr(u32)]
622#[non_exhaustive]
623pub enum Behavior {
624    /// Cell-granular drag selection.
625    Cell = ffi::SelectionGestureBehavior::CELL,
626    /// Word selection on press and word-granular drag selection.
627    Word = ffi::SelectionGestureBehavior::WORD,
628    /// Line selection on press and line-granular drag selection.
629    Line = ffi::SelectionGestureBehavior::LINE,
630    /// Semantic command output selection on press and drag.
631    Output = ffi::SelectionGestureBehavior::OUTPUT,
632}
633
634/// Selection behaviors for single-, double-, and triple-click gestures.
635#[derive(Clone, Copy, Debug, Default)]
636pub struct Behaviors {
637    inner: ffi::SelectionGestureBehaviors,
638}
639impl Behaviors {
640    /// Create the default selection behaviors.
641    pub fn new() -> Self {
642        Self::default()
643    }
644
645    /// Set the single click behavior.
646    pub fn with_single_click_behavior(mut self, behavior: Behavior) -> Self {
647        self.inner.single_click = behavior.into();
648        self
649    }
650    /// Set the double click behavior.
651    pub fn with_double_click_behavior(mut self, behavior: Behavior) -> Self {
652        self.inner.double_click = behavior.into();
653        self
654    }
655    /// Set the triple click behavior.
656    pub fn with_triple_click_behavior(mut self, behavior: Behavior) -> Self {
657        self.inner.triple_click = behavior.into();
658        self
659    }
660}