use alloc::{collections::btree_map::BTreeMap, vec::Vec};
#[cfg(feature = "std")]
use std::sync::atomic::{AtomicU64, Ordering};
use azul_core::{
dom::{DomId, NodeId, OptionDomNodeId},
drag::{
ActiveDragType, AutoScrollDirection, DragContext, DragData, DragEffect, DropEffect,
FileDropDrag, NodeDrag, ScrollbarAxis, ScrollbarThumbDrag, TextSelectionDrag,
WindowMoveDrag, WindowResizeDrag, WindowResizeEdge,
},
geom::{LogicalPosition, PhysicalPositionI32},
hit_test::HitTest,
selection::TextCursor,
task::{Duration as CoreDuration, Instant as CoreInstant},
window::WindowPosition,
};
use azul_css::AzString;
use azul_css::{impl_option, impl_option_inner, StringVec};
pub use azul_core::drag::{
ActiveDragType as DragType, AutoScrollDirection as AutoScroll, DragContext as UnifiedDragContext,
DragData as UnifiedDragData, DragEffect as UnifiedDragEffect, DropEffect as UnifiedDropEffect,
ScrollbarAxis as ScrollAxis, ScrollbarThumbDrag as ScrollbarDrag,
};
#[cfg(feature = "std")]
static NEXT_EVENT_ID: AtomicU64 = AtomicU64::new(1);
#[cfg(feature = "std")]
pub fn allocate_event_id() -> u64 {
NEXT_EVENT_ID.fetch_add(1, Ordering::Relaxed)
}
#[cfg(not(feature = "std"))]
pub fn allocate_event_id() -> u64 {
0
}
fn duration_to_millis(duration: CoreDuration) -> u64 {
match duration {
#[cfg(feature = "std")]
CoreDuration::System(system_diff) => {
let std_duration: std::time::Duration = system_diff.into();
std_duration.as_millis() as u64
}
#[cfg(not(feature = "std"))]
CoreDuration::System(system_diff) => {
system_diff.secs * 1000 + (system_diff.nanos / 1_000_000) as u64
}
CoreDuration::Tick(tick_diff) => {
tick_diff.tick_diff
}
}
}
pub const MAX_SAMPLES_PER_SESSION: usize = 1000;
pub const DEFAULT_SAMPLE_TIMEOUT_MS: u64 = 2000;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GestureDetectionConfig {
pub drag_distance_threshold: f32,
pub double_click_time_threshold_ms: u64,
pub double_click_distance_threshold: f32,
pub long_press_time_threshold_ms: u64,
pub long_press_distance_threshold: f32,
pub min_samples_for_gesture: usize,
pub swipe_velocity_threshold: f32,
pub pinch_scale_threshold: f32,
pub rotation_angle_threshold: f32,
pub sample_cleanup_interval_ms: u64,
}
impl Default for GestureDetectionConfig {
fn default() -> Self {
Self {
drag_distance_threshold: 5.0,
double_click_time_threshold_ms: 500,
double_click_distance_threshold: 5.0,
long_press_time_threshold_ms: 500,
long_press_distance_threshold: 10.0,
min_samples_for_gesture: 2,
swipe_velocity_threshold: 500.0, pinch_scale_threshold: 0.1, rotation_angle_threshold: 0.1, sample_cleanup_interval_ms: DEFAULT_SAMPLE_TIMEOUT_MS,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct InputSample {
pub position: LogicalPosition,
pub screen_position: LogicalPosition,
pub timestamp: CoreInstant,
pub button_state: u8,
pub event_id: u64,
pub pressure: f32,
pub tilt: (f32, f32),
pub touch_radius: (f32, f32),
}
impl_option!(
InputSample,
OptionInputSample,
copy = false,
[Debug, Clone, PartialEq]
);
#[derive(Debug, Clone, PartialEq)]
pub struct InputSession {
pub samples: Vec<InputSample>,
pub ended: bool,
pub session_id: u64,
pub window_position_at_start: azul_core::window::WindowPosition,
}
impl InputSession {
fn new(session_id: u64, first_sample: InputSample, window_position: azul_core::window::WindowPosition) -> Self {
Self {
samples: vec![first_sample],
ended: false,
session_id,
window_position_at_start: window_position,
}
}
pub fn first_sample(&self) -> Option<&InputSample> {
self.samples.first()
}
pub fn last_sample(&self) -> Option<&InputSample> {
self.samples.last()
}
pub fn duration_ms(&self) -> Option<u64> {
let first = self.first_sample()?;
let last = self.last_sample()?;
let duration = last.timestamp.duration_since(&first.timestamp);
Some(duration_to_millis(duration))
}
pub fn total_distance(&self) -> f32 {
if self.samples.len() < 2 {
return 0.0;
}
let mut total = 0.0;
for i in 1..self.samples.len() {
let prev = &self.samples[i - 1];
let curr = &self.samples[i];
let dx = curr.position.x - prev.position.x;
let dy = curr.position.y - prev.position.y;
total += (dx * dx + dy * dy).sqrt();
}
total
}
pub fn direct_distance(&self) -> Option<f32> {
let first = self.first_sample()?;
let last = self.last_sample()?;
let dx = last.position.x - first.position.x;
let dy = last.position.y - first.position.y;
Some((dx * dx + dy * dy).sqrt())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DetectedDrag {
pub start_position: LogicalPosition,
pub current_position: LogicalPosition,
pub direct_distance: f32,
pub total_distance: f32,
pub duration_ms: u64,
pub sample_count: usize,
pub session_id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DetectedLongPress {
pub position: LogicalPosition,
pub duration_ms: u64,
pub callback_invoked: bool,
pub session_id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GestureDirection {
Up,
Down,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DetectedPinch {
pub scale: f32,
pub center: LogicalPosition,
pub initial_distance: f32,
pub current_distance: f32,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DetectedRotation {
pub angle_radians: f32,
pub center: LogicalPosition,
pub duration_ms: u64,
}
pub type NodeDragState = NodeDrag;
pub type WindowDragState = WindowMoveDrag;
pub type FileDropState = FileDropDrag;
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct PenState {
pub position: LogicalPosition,
pub pressure: f32,
pub tilt: crate::callbacks::PenTilt,
pub in_contact: bool,
pub is_eraser: bool,
pub barrel_button_pressed: bool,
pub device_id: u64,
}
impl_option!(PenState, OptionPenState, [Debug, Clone, Copy, PartialEq]);
impl Default for PenState {
fn default() -> Self {
Self {
position: LogicalPosition::zero(),
pressure: 0.0,
tilt: crate::callbacks::PenTilt {
x_tilt: 0.0,
y_tilt: 0.0,
},
in_contact: false,
is_eraser: false,
barrel_button_pressed: false,
device_id: 0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GestureAndDragManager {
pub config: GestureDetectionConfig,
pub input_sessions: Vec<InputSession>,
pub active_drag: Option<DragContext>,
pub pen_state: Option<PenState>,
long_press_callbacks_invoked: Vec<u64>,
next_session_id: u64,
}
pub type GestureManager = GestureAndDragManager;
impl Default for GestureAndDragManager {
fn default() -> Self {
Self::new()
}
}
impl GestureAndDragManager {
pub fn new() -> Self {
Self {
config: GestureDetectionConfig::default(),
input_sessions: Vec::new(),
next_session_id: 1,
active_drag: None,
pen_state: None,
long_press_callbacks_invoked: Vec::new(),
}
}
pub fn with_config(config: GestureDetectionConfig) -> Self {
Self {
config,
..Self::new()
}
}
pub fn start_input_session(
&mut self,
position: LogicalPosition,
timestamp: CoreInstant,
button_state: u8,
window_position: azul_core::window::WindowPosition,
screen_position: LogicalPosition,
) -> u64 {
self.start_input_session_with_pen(
position,
timestamp,
button_state,
allocate_event_id(),
0.5, (0.0, 0.0), (0.0, 0.0), window_position,
screen_position,
)
}
pub fn start_input_session_with_pen(
&mut self,
position: LogicalPosition,
timestamp: CoreInstant,
button_state: u8,
event_id: u64,
pressure: f32,
tilt: (f32, f32),
touch_radius: (f32, f32),
window_position: azul_core::window::WindowPosition,
screen_position: LogicalPosition,
) -> u64 {
let last_ended_idx = self.input_sessions.iter().rposition(|s| s.ended);
let mut idx = 0usize;
self.input_sessions.retain(|session| {
let keep = !session.ended || Some(idx) == last_ended_idx;
idx += 1;
keep
});
let session_id = self.next_session_id;
self.next_session_id += 1;
let sample = InputSample {
position,
screen_position,
timestamp,
button_state,
event_id,
pressure,
tilt,
touch_radius,
};
let session = InputSession::new(session_id, sample, window_position);
self.input_sessions.push(session);
session_id
}
pub fn record_input_sample(
&mut self,
position: LogicalPosition,
timestamp: CoreInstant,
button_state: u8,
screen_position: LogicalPosition,
) -> bool {
self.record_input_sample_with_pen(
position,
timestamp,
button_state,
allocate_event_id(),
0.5, (0.0, 0.0), (0.0, 0.0), screen_position,
)
}
pub fn record_input_sample_with_pen(
&mut self,
position: LogicalPosition,
timestamp: CoreInstant,
button_state: u8,
event_id: u64,
pressure: f32,
tilt: (f32, f32),
touch_radius: (f32, f32),
screen_position: LogicalPosition,
) -> bool {
let session = match self.input_sessions.last_mut() {
Some(s) => s,
None => return false,
};
if session.ended {
return false;
}
if session.samples.len() >= MAX_SAMPLES_PER_SESSION {
let remove_count = session.samples.len() - MAX_SAMPLES_PER_SESSION + 100;
session.samples.drain(0..remove_count);
}
session.samples.push(InputSample {
position,
screen_position,
timestamp,
button_state,
event_id,
pressure,
tilt,
touch_radius,
});
true
}
pub fn end_current_session(&mut self) {
if let Some(session) = self.input_sessions.last_mut() {
session.ended = true;
}
}
pub fn clear_old_sessions(&mut self, current_time: CoreInstant) {
self.input_sessions.retain(|session| {
if let Some(last_sample) = session.last_sample() {
let duration = current_time.duration_since(&last_sample.timestamp);
let age_ms = duration_to_millis(duration);
age_ms < self.config.sample_cleanup_interval_ms
} else {
false
}
});
let valid_session_ids: Vec<u64> =
self.input_sessions.iter().map(|s| s.session_id).collect();
self.long_press_callbacks_invoked
.retain(|id| valid_session_ids.contains(id));
}
pub fn clear_all_sessions(&mut self) {
self.input_sessions.clear();
self.long_press_callbacks_invoked.clear();
}
pub fn update_pen_state(
&mut self,
position: LogicalPosition,
pressure: f32,
tilt: (f32, f32),
in_contact: bool,
is_eraser: bool,
barrel_button_pressed: bool,
device_id: u64,
) {
self.pen_state = Some(PenState {
position,
pressure,
tilt: crate::callbacks::PenTilt {
x_tilt: tilt.0,
y_tilt: tilt.1,
},
in_contact,
is_eraser,
barrel_button_pressed,
device_id,
});
}
pub fn clear_pen_state(&mut self) {
self.pen_state = None;
}
pub fn get_pen_state(&self) -> Option<&PenState> {
self.pen_state.as_ref()
}
pub fn detect_drag(&self) -> Option<DetectedDrag> {
let session = self.get_current_session()?;
if session.samples.len() < self.config.min_samples_for_gesture {
return None;
}
let direct_distance = session.direct_distance()?;
if direct_distance >= self.config.drag_distance_threshold {
let first = session.first_sample()?;
let last = session.last_sample()?;
Some(DetectedDrag {
start_position: first.position,
current_position: last.position,
direct_distance,
total_distance: session.total_distance(),
duration_ms: session.duration_ms()?,
sample_count: session.samples.len(),
session_id: session.session_id,
})
} else {
None
}
}
pub fn detect_long_press(&self) -> Option<DetectedLongPress> {
let session = self.get_current_session()?;
if session.ended {
return None; }
let duration_ms = session.duration_ms()?;
if duration_ms < self.config.long_press_time_threshold_ms {
return None;
}
let distance = session.direct_distance()?;
if distance <= self.config.long_press_distance_threshold {
let first = session.first_sample()?;
let callback_invoked = self
.long_press_callbacks_invoked
.contains(&session.session_id);
Some(DetectedLongPress {
position: first.position,
duration_ms,
callback_invoked,
session_id: session.session_id,
})
} else {
None
}
}
pub fn mark_long_press_callback_invoked(&mut self, session_id: u64) {
if !self.long_press_callbacks_invoked.contains(&session_id) {
self.long_press_callbacks_invoked.push(session_id);
}
}
pub fn detect_double_click(&self) -> bool {
let sessions = &self.input_sessions;
if sessions.len() < 2 {
return false;
}
let prev_session = &sessions[sessions.len() - 2];
let last_session = &sessions[sessions.len() - 1];
if !prev_session.ended || !last_session.ended {
return false;
}
let prev_first = prev_session.first_sample();
let last_first = last_session.first_sample();
let (prev_first, last_first) = match (prev_first, last_first) {
(Some(p), Some(l)) => (p, l),
_ => return false,
};
let duration = last_first.timestamp.duration_since(&prev_first.timestamp);
let time_delta_ms = duration_to_millis(duration);
if time_delta_ms > self.config.double_click_time_threshold_ms {
return false;
}
let dx = last_first.position.x - prev_first.position.x;
let dy = last_first.position.y - prev_first.position.y;
let distance = (dx * dx + dy * dy).sqrt();
distance < self.config.double_click_distance_threshold
}
pub fn get_drag_direction(&self) -> Option<GestureDirection> {
let session = self.get_current_session()?;
let first = session.first_sample()?;
let last = session.last_sample()?;
let dx = last.position.x - first.position.x;
let dy = last.position.y - first.position.y;
let direction = if dx.abs() > dy.abs() {
if dx > 0.0 {
GestureDirection::Right
} else {
GestureDirection::Left
}
} else {
if dy > 0.0 {
GestureDirection::Down
} else {
GestureDirection::Up
}
};
Some(direction)
}
pub fn get_gesture_velocity(&self) -> Option<f32> {
let session = self.get_current_session()?;
if session.samples.len() < 2 {
return None;
}
let total_distance = session.total_distance();
let duration_ms = session.duration_ms()?;
if duration_ms == 0 {
return None;
}
let duration_secs = duration_ms as f32 / 1000.0;
Some(total_distance / duration_secs)
}
pub fn is_swipe(&self) -> bool {
self.get_gesture_velocity()
.map(|v| v >= self.config.swipe_velocity_threshold)
.unwrap_or(false)
}
pub fn detect_swipe_direction(&self) -> Option<GestureDirection> {
if !self.is_swipe() {
return None;
}
self.get_drag_direction()
}
pub fn detect_pinch(&self) -> Option<DetectedPinch> {
if self.input_sessions.len() < 2 {
return None;
}
let session1 = &self.input_sessions[self.input_sessions.len() - 2];
let session2 = &self.input_sessions[self.input_sessions.len() - 1];
let first1 = session1.first_sample()?;
let first2 = session2.first_sample()?;
let last1 = session1.last_sample()?;
let last2 = session2.last_sample()?;
let dx_initial = first2.position.x - first1.position.x;
let dy_initial = first2.position.y - first1.position.y;
let initial_distance = (dx_initial * dx_initial + dy_initial * dy_initial).sqrt();
let dx_current = last2.position.x - last1.position.x;
let dy_current = last2.position.y - last1.position.y;
let current_distance = (dx_current * dx_current + dy_current * dy_current).sqrt();
if initial_distance < 1.0 {
return None;
}
let scale = current_distance / initial_distance;
let scale_threshold = 1.0 + self.config.pinch_scale_threshold;
if scale > 1.0 / scale_threshold && scale < scale_threshold {
return None; }
let center = LogicalPosition {
x: (last1.position.x + last2.position.x) / 2.0,
y: (last1.position.y + last2.position.y) / 2.0,
};
let duration = last1.timestamp.duration_since(&first1.timestamp);
let duration_ms = duration_to_millis(duration);
Some(DetectedPinch {
scale,
center,
initial_distance,
current_distance,
duration_ms,
})
}
pub fn detect_rotation(&self) -> Option<DetectedRotation> {
if self.input_sessions.len() < 2 {
return None;
}
let session1 = &self.input_sessions[self.input_sessions.len() - 2];
let session2 = &self.input_sessions[self.input_sessions.len() - 1];
let first1 = session1.first_sample()?;
let first2 = session2.first_sample()?;
let last1 = session1.last_sample()?;
let last2 = session2.last_sample()?;
let center = LogicalPosition {
x: (last1.position.x + last2.position.x) / 2.0,
y: (last1.position.y + last2.position.y) / 2.0,
};
let dx_initial = first2.position.x - first1.position.x;
let dy_initial = first2.position.y - first1.position.y;
let initial_angle = dy_initial.atan2(dx_initial);
let dx_current = last2.position.x - last1.position.x;
let dy_current = last2.position.y - last1.position.y;
let current_angle = dy_current.atan2(dx_current);
let mut angle_diff = current_angle - initial_angle;
const PI: f32 = core::f32::consts::PI;
while angle_diff > PI {
angle_diff -= 2.0 * PI;
}
while angle_diff < -PI {
angle_diff += 2.0 * PI;
}
if angle_diff.abs() < self.config.rotation_angle_threshold {
return None;
}
let duration = last1.timestamp.duration_since(&first1.timestamp);
let duration_ms = duration_to_millis(duration);
Some(DetectedRotation {
angle_radians: angle_diff,
center,
duration_ms,
})
}
pub fn get_current_session(&self) -> Option<&InputSession> {
self.input_sessions.last()
}
pub fn get_current_mouse_position(&self) -> Option<LogicalPosition> {
self.get_current_session()
.and_then(|s| s.last_sample())
.map(|sample| sample.position)
}
pub fn get_drag_delta(&self) -> Option<(f32, f32)> {
let session = self.get_current_session()?;
let first = session.first_sample()?;
let last = session.last_sample()?;
Some((
last.position.x - first.position.x,
last.position.y - first.position.y,
))
}
pub fn get_drag_delta_screen(&self) -> Option<(f32, f32)> {
let session = self.get_current_session()?;
let first = session.first_sample()?;
let last = session.last_sample()?;
Some((
last.screen_position.x - first.screen_position.x,
last.screen_position.y - first.screen_position.y,
))
}
pub fn get_drag_delta_screen_incremental(&self) -> Option<(f32, f32)> {
let session = self.get_current_session()?;
let len = session.samples.len();
if len < 2 {
return None;
}
let prev = &session.samples[len - 2];
let last = &session.samples[len - 1];
Some((
last.screen_position.x - prev.screen_position.x,
last.screen_position.y - prev.screen_position.y,
))
}
pub fn get_window_position_at_session_start(&self) -> Option<azul_core::window::WindowPosition> {
let session = self.get_current_session()?;
Some(session.window_position_at_start)
}
pub fn get_drag_context(&self) -> Option<&DragContext> {
self.active_drag.as_ref()
}
pub fn get_drag_context_mut(&mut self) -> Option<&mut DragContext> {
self.active_drag.as_mut()
}
pub fn activate_text_selection_drag(
&mut self,
dom_id: DomId,
anchor_ifc_node: NodeId,
start_mouse_position: LogicalPosition,
) {
let session_id = self.current_session_id().unwrap_or(0);
self.active_drag = Some(DragContext::text_selection(
dom_id,
anchor_ifc_node,
start_mouse_position,
session_id,
));
}
pub fn activate_scrollbar_drag(
&mut self,
scroll_container_node: NodeId,
axis: ScrollbarAxis,
start_mouse_position: LogicalPosition,
start_scroll_offset: f32,
track_length_px: f32,
content_length_px: f32,
viewport_length_px: f32,
) {
let session_id = self.current_session_id().unwrap_or(0);
self.active_drag = Some(DragContext::scrollbar_thumb(
scroll_container_node,
axis,
start_mouse_position,
start_scroll_offset,
track_length_px,
content_length_px,
viewport_length_px,
session_id,
));
}
pub fn activate_node_drag(
&mut self,
dom_id: DomId,
node_id: NodeId,
drag_data: DragData,
_start_hit_test: Option<HitTest>,
) {
if let Some(detected) = self.detect_drag() {
self.active_drag = Some(DragContext::node_drag(
dom_id,
node_id,
detected.start_position,
drag_data,
detected.session_id,
));
}
}
pub fn activate_window_drag(
&mut self,
initial_window_position: WindowPosition,
_start_hit_test: Option<HitTest>,
) {
if let Some(detected) = self.detect_drag() {
self.active_drag = Some(DragContext::window_move(
detected.start_position,
initial_window_position,
detected.session_id,
));
}
}
pub fn start_file_drop(&mut self, files: Vec<AzString>, position: LogicalPosition) {
let session_id = self.current_session_id().unwrap_or(0);
self.active_drag = Some(DragContext::file_drop(files, position, session_id));
}
pub fn update_active_drag_positions(&mut self, position: LogicalPosition) {
if let Some(ref mut drag) = self.active_drag {
drag.update_position(position);
}
}
pub fn update_drop_target(&mut self, target: Option<azul_core::dom::DomNodeId>) {
if let Some(ref mut drag) = self.active_drag {
match &mut drag.drag_type {
ActiveDragType::Node(ref mut node_drag) => {
node_drag.current_drop_target = target.into();
}
ActiveDragType::FileDrop(ref mut file_drop) => {
file_drop.drop_target = target.into();
}
_ => {}
}
}
}
pub fn update_auto_scroll_direction(&mut self, direction: AutoScrollDirection) {
if let Some(ref mut drag) = self.active_drag {
if let Some(text_drag) = drag.as_text_selection_mut() {
text_drag.auto_scroll_direction = direction;
}
}
}
pub fn end_drag(&mut self) -> Option<DragContext> {
self.active_drag.take()
}
pub fn cancel_drag(&mut self) {
if let Some(ref mut drag) = self.active_drag {
drag.cancelled = true;
}
self.active_drag = None;
}
pub fn is_dragging(&self) -> bool {
self.active_drag.is_some()
}
pub fn is_text_selection_dragging(&self) -> bool {
self.active_drag.as_ref().is_some_and(|d| d.is_text_selection())
}
pub fn is_scrollbar_dragging(&self) -> bool {
self.active_drag.as_ref().is_some_and(|d| d.is_scrollbar_thumb())
}
pub fn is_node_dragging_any(&self) -> bool {
self.active_drag.as_ref().is_some_and(|d| d.is_node_drag())
}
pub fn is_node_drag_active(&self) -> bool {
self.is_node_dragging_any()
}
pub fn is_node_dragging(&self, dom_id: DomId, node_id: NodeId) -> bool {
self.active_drag.as_ref().is_some_and(|d| {
if let Some(node_drag) = d.as_node_drag() {
node_drag.dom_id == dom_id && node_drag.node_id == node_id
} else {
false
}
})
}
pub fn is_window_dragging(&self) -> bool {
self.active_drag.as_ref().is_some_and(|d| d.is_window_move())
}
pub fn is_file_dropping(&self) -> bool {
self.active_drag.as_ref().is_some_and(|d| d.is_file_drop())
}
pub fn session_count(&self) -> usize {
self.input_sessions.len()
}
pub fn current_session_id(&self) -> Option<u64> {
self.get_current_session().map(|s| s.session_id)
}
pub fn get_node_drag(&self) -> Option<&NodeDrag> {
self.active_drag.as_ref().and_then(|d| d.as_node_drag())
}
pub fn get_window_drag(&self) -> Option<&WindowMoveDrag> {
self.active_drag.as_ref().and_then(|d| d.as_window_move())
}
pub fn get_file_drop(&self) -> Option<&FileDropDrag> {
self.active_drag.as_ref().and_then(|d| d.as_file_drop())
}
pub fn end_node_drag(&mut self) -> Option<DragContext> {
if self.active_drag.as_ref().is_some_and(|d| d.is_node_drag()) {
self.end_drag()
} else {
None
}
}
pub fn end_window_drag(&mut self) -> Option<DragContext> {
if self.active_drag.as_ref().is_some_and(|d| d.is_window_move()) {
self.end_drag()
} else {
None
}
}
pub fn end_file_drop(&mut self) -> Option<DragContext> {
if self.active_drag.as_ref().is_some_and(|d| d.is_file_drop()) {
self.end_drag()
} else {
None
}
}
pub fn cancel_file_drop(&mut self) {
if self.active_drag.as_ref().is_some_and(|d| d.is_file_drop()) {
self.cancel_drag();
}
}
pub fn get_window_drag_delta(&self) -> Option<(i32, i32)> {
let drag = self.active_drag.as_ref()?.as_window_move()?;
let delta_x = drag.current_position.x - drag.start_position.x;
let delta_y = drag.current_position.y - drag.start_position.y;
match drag.initial_window_position {
WindowPosition::Initialized(_initial_pos) => Some((delta_x as i32, delta_y as i32)),
_ => None,
}
}
pub fn get_window_position_from_drag(&self) -> Option<WindowPosition> {
let drag = self.active_drag.as_ref()?.as_window_move()?;
let delta_x = drag.current_position.x - drag.start_position.x;
let delta_y = drag.current_position.y - drag.start_position.y;
match drag.initial_window_position {
WindowPosition::Initialized(initial_pos) => {
Some(WindowPosition::Initialized(PhysicalPositionI32::new(
initial_pos.x + delta_x as i32,
initial_pos.y + delta_y as i32,
)))
}
_ => None,
}
}
pub fn get_scrollbar_scroll_offset(&self) -> Option<f32> {
self.active_drag.as_ref()?.calculate_scrollbar_scroll_offset()
}
pub fn remap_node_ids(
&mut self,
dom_id: azul_core::dom::DomId,
node_id_map: &std::collections::BTreeMap<azul_core::id::NodeId, azul_core::id::NodeId>,
) {
if let Some(ref mut drag) = self.active_drag {
if !drag.remap_node_ids(dom_id, node_id_map) {
drag.cancelled = true;
self.active_drag = None;
}
}
}
}