fui-rs 0.2.6

Retained Rust UI for native desktop and WebAssembly applications
Documentation
use crate::event;
use crate::node::{NodeHandle, NodeRef, WeakNodeRef};

const DEFAULT_DRAG_THRESHOLD: f32 = 4.0;

#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct DragStartedEvent {
    pub(crate) x: f32,
    pub(crate) y: f32,
    pub(crate) modifiers: u32,
}

#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct DragCompletedEvent {
    pub(crate) x: f32,
    pub(crate) y: f32,
    pub(crate) total_delta_x: f32,
    pub(crate) total_delta_y: f32,
    pub(crate) modifiers: u32,
    pub(crate) cancelled: bool,
}

type DragStartedCallback = Box<dyn Fn(DragStartedEvent)>;
type DragCompletedCallback = Box<dyn Fn(DragCompletedEvent)>;

pub(crate) struct DragGesture {
    host: WeakNodeRef,
    threshold_value: f32,
    pointer_down_value: bool,
    drag_started_value: bool,
    awaiting_long_press_value: bool,
    pointer_captured_value: bool,
    start_x: f32,
    start_y: f32,
    last_pointer_x: f32,
    last_pointer_y: f32,
    last_dispatched_x: f32,
    last_dispatched_y: f32,
    last_modifiers: u32,
    started_callback: Option<DragStartedCallback>,
    completed_callback: Option<DragCompletedCallback>,
}

impl DragGesture {
    pub(crate) fn new(host: &NodeRef) -> Self {
        Self {
            host: host.downgrade(),
            threshold_value: DEFAULT_DRAG_THRESHOLD,
            pointer_down_value: false,
            drag_started_value: false,
            awaiting_long_press_value: false,
            pointer_captured_value: false,
            start_x: 0.0,
            start_y: 0.0,
            last_pointer_x: 0.0,
            last_pointer_y: 0.0,
            last_dispatched_x: 0.0,
            last_dispatched_y: 0.0,
            last_modifiers: 0,
            started_callback: None,
            completed_callback: None,
        }
    }

    pub(crate) fn threshold(&mut self, value: f32) -> &mut Self {
        self.threshold_value = if value > 0.0 { value } else { 0.0 };
        self
    }

    pub(crate) fn set_started(&mut self, callback: impl Fn(DragStartedEvent) + 'static) {
        self.started_callback = Some(Box::new(callback));
    }

    pub(crate) fn set_completed(&mut self, callback: impl Fn(DragCompletedEvent) + 'static) {
        self.completed_callback = Some(Box::new(callback));
    }

    pub(crate) fn is_dragging(&self) -> bool {
        self.drag_started_value
    }

    pub(crate) fn handle_pointer_down(
        &mut self,
        x: f32,
        y: f32,
        modifiers: u32,
        wait_for_long_press: bool,
    ) {
        if self.pointer_down_value {
            self.cancel();
        }
        self.pointer_down_value = true;
        self.drag_started_value = false;
        self.awaiting_long_press_value = wait_for_long_press;
        self.start_x = x;
        self.start_y = y;
        self.last_pointer_x = x;
        self.last_pointer_y = y;
        self.last_dispatched_x = x;
        self.last_dispatched_y = y;
        self.last_modifiers = modifiers;
        if !wait_for_long_press {
            self.capture_drag_pointer();
        }
        if !wait_for_long_press && self.threshold_value <= 0.0 {
            self.begin_drag(x, y, modifiers);
        }
    }

    pub(crate) fn handle_pointer_move(&mut self, x: f32, y: f32, modifiers: u32) {
        if !self.pointer_down_value {
            return;
        }
        self.last_pointer_x = x;
        self.last_pointer_y = y;
        self.last_modifiers = modifiers;
        if self.awaiting_long_press_value {
            return;
        }
        if !self.drag_started_value
            && !self.has_exceeded_threshold(x - self.start_x, y - self.start_y)
        {
            return;
        }
        if !self.drag_started_value {
            self.begin_drag(x, y, modifiers);
        }
        self.emit_delta(x, y);
    }

    pub(crate) fn handle_long_press(&mut self, x: f32, y: f32, modifiers: u32) -> bool {
        if !self.pointer_down_value {
            self.pointer_down_value = true;
            self.drag_started_value = false;
            self.start_x = x;
            self.start_y = y;
            self.last_dispatched_x = x;
            self.last_dispatched_y = y;
        }
        self.awaiting_long_press_value = false;
        self.last_pointer_x = x;
        self.last_pointer_y = y;
        self.last_modifiers = modifiers;
        self.capture_drag_pointer();
        self.begin_drag(x, y, modifiers);
        true
    }

    pub(crate) fn handle_pointer_up(&mut self, x: f32, y: f32, modifiers: u32) {
        if !self.pointer_down_value {
            return;
        }
        self.last_pointer_x = x;
        self.last_pointer_y = y;
        self.last_modifiers = modifiers;
        if self.drag_started_value {
            self.emit_delta(x, y);
            if let Some(callback) = self.completed_callback.as_ref() {
                callback(DragCompletedEvent {
                    x,
                    y,
                    total_delta_x: x - self.start_x,
                    total_delta_y: y - self.start_y,
                    modifiers,
                    cancelled: false,
                });
            }
        }
        self.pointer_down_value = false;
        self.drag_started_value = false;
        self.awaiting_long_press_value = false;
        self.release_drag_pointer();
    }

    pub(crate) fn cancel(&mut self) {
        if !self.pointer_down_value {
            return;
        }
        if self.drag_started_value {
            if let Some(callback) = self.completed_callback.as_ref() {
                callback(DragCompletedEvent {
                    x: self.last_pointer_x,
                    y: self.last_pointer_y,
                    total_delta_x: self.last_pointer_x - self.start_x,
                    total_delta_y: self.last_pointer_y - self.start_y,
                    modifiers: self.last_modifiers,
                    cancelled: true,
                });
            }
        }
        self.pointer_down_value = false;
        self.drag_started_value = false;
        self.awaiting_long_press_value = false;
        self.release_drag_pointer();
    }

    fn begin_drag(&mut self, x: f32, y: f32, modifiers: u32) {
        if self.drag_started_value {
            return;
        }
        self.drag_started_value = true;
        self.last_dispatched_x = self.start_x;
        self.last_dispatched_y = self.start_y;
        if let Some(callback) = self.started_callback.as_ref() {
            callback(DragStartedEvent { x, y, modifiers });
        }
    }

    fn emit_delta(&mut self, x: f32, y: f32) {
        if x == self.last_dispatched_x && y == self.last_dispatched_y {
            return;
        }
        self.last_dispatched_x = x;
        self.last_dispatched_y = y;
    }

    fn has_exceeded_threshold(&self, total_delta_x: f32, total_delta_y: f32) -> bool {
        if self.threshold_value <= 0.0 {
            return true;
        }
        ((total_delta_x * total_delta_x) + (total_delta_y * total_delta_y))
            >= (self.threshold_value * self.threshold_value)
    }

    fn capture_drag_pointer(&mut self) {
        if self.pointer_captured_value {
            return;
        }
        let Some(host) = self.host.upgrade() else {
            return;
        };
        let handle = host.handle();
        if handle == NodeHandle::INVALID {
            return;
        }
        self.pointer_captured_value = true;
        event::capture_pointer(handle);
        unsafe { crate::ffi::fui_set_pointer_capture(handle.raw()) };
    }

    fn release_drag_pointer(&mut self) {
        if !self.pointer_captured_value {
            return;
        }
        self.pointer_captured_value = false;
        let Some(host) = self.host.upgrade() else {
            return;
        };
        let handle = host.handle();
        if handle == NodeHandle::INVALID {
            return;
        }
        event::release_pointer(handle);
        unsafe { crate::ffi::fui_release_pointer_capture() };
    }
}