1use 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#[derive(Debug)]
40pub struct Gesture<'alloc> {
41 inner: Object<'alloc, ffi::SelectionGestureImpl>,
42}
43impl<'alloc> Gesture<'alloc> {
44 pub fn new() -> Result<Self> {
46 unsafe { Self::new_inner(std::ptr::null()) }
48 }
49
50 pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
55 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 Ok(unsafe { value.assume_init() })
85 }
86 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 pub fn click_count(&self, terminal: &Terminal<'_, '_>) -> Result<u8> {
98 self.get(terminal, ffi::SelectionGestureData::CLICK_COUNT)
99 }
100 pub fn dragged(&self, terminal: &Terminal<'_, '_>) -> Result<bool> {
102 self.get(terminal, ffi::SelectionGestureData::DRAGGED)
103 }
104 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 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 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 Ok(grid_ref.map(|v| unsafe { GridRef::from_raw(v) }))
137 }
138}
139impl Drop for Gesture<'_> {
140 fn drop(&mut self) {
141 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 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#[derive(Debug)]
226pub struct PressEvent<'alloc> {
227 base: Event<'alloc>,
228}
229impl<'alloc> PressEvent<'alloc> {
230 pub fn new() -> Result<Self> {
232 unsafe { Self::new_inner(std::ptr::null()) }
234 }
235
236 pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
241 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 #[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 #[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 #[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 #[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 #[inline]
300 pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
301 let cp = ffi::Codepoints {
302 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 #[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 #[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#[derive(Debug)]
340pub struct ReleaseEvent<'alloc> {
341 base: Event<'alloc>,
342}
343impl<'alloc> ReleaseEvent<'alloc> {
344 pub fn new() -> Result<Self> {
346 unsafe { Self::new_inner(std::ptr::null()) }
348 }
349
350 pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
355 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 #[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 _ = self.base.apply(gesture, terminal)?;
382 Ok(())
383 }
384}
385
386#[derive(Debug)]
388pub struct DragEvent<'alloc> {
389 base: Event<'alloc>,
390}
391impl<'alloc> DragEvent<'alloc> {
392 pub fn new() -> Result<Self> {
394 unsafe { Self::new_inner(std::ptr::null()) }
396 }
397
398 pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
403 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 #[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 #[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 #[inline]
435 pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
436 let cp = ffi::Codepoints {
437 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 #[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#[derive(Debug)]
468pub struct AutoscrollTickEvent<'alloc> {
469 base: Event<'alloc>,
470}
471impl<'alloc> AutoscrollTickEvent<'alloc> {
472 pub fn new() -> Result<Self> {
474 unsafe { Self::new_inner(std::ptr::null()) }
476 }
477
478 pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
483 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 #[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 #[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 #[inline]
517 pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
518 let cp = ffi::Codepoints {
519 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 #[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#[derive(Debug)]
551pub struct DeepPressEvent<'alloc> {
552 base: Event<'alloc>,
553}
554impl<'alloc> DeepPressEvent<'alloc> {
555 pub fn new() -> Result<Self> {
557 unsafe { Self::new_inner(std::ptr::null()) }
559 }
560
561 pub fn new_with_alloc<'ctx: 'alloc>(alloc: &'alloc Allocator<'ctx>) -> Result<Self> {
566 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 #[inline]
581 pub fn set_word_boundary_codepoints(&mut self, value: &[char]) -> Result<&mut Self> {
582 let cp = ffi::Codepoints {
583 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 #[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#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
608#[repr(u32)]
609#[non_exhaustive]
610pub enum Autoscroll {
611 None,
613 Up,
615 Down,
617}
618
619#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
621#[repr(u32)]
622#[non_exhaustive]
623pub enum Behavior {
624 Cell = ffi::SelectionGestureBehavior::CELL,
626 Word = ffi::SelectionGestureBehavior::WORD,
628 Line = ffi::SelectionGestureBehavior::LINE,
630 Output = ffi::SelectionGestureBehavior::OUTPUT,
632}
633
634#[derive(Clone, Copy, Debug, Default)]
636pub struct Behaviors {
637 inner: ffi::SelectionGestureBehaviors,
638}
639impl Behaviors {
640 pub fn new() -> Self {
642 Self::default()
643 }
644
645 pub fn with_single_click_behavior(mut self, behavior: Behavior) -> Self {
647 self.inner.single_click = behavior.into();
648 self
649 }
650 pub fn with_double_click_behavior(mut self, behavior: Behavior) -> Self {
652 self.inner.double_click = behavior.into();
653 self
654 }
655 pub fn with_triple_click_behavior(mut self, behavior: Behavior) -> Self {
657 self.inner.triple_click = behavior.into();
658 self
659 }
660}