#![cfg(feature = "drag_drop")]
use std::mem;
use parking_lot::Mutex;
use zng_app::{
event::{event, event_args},
hn, static_id,
view_process::raw_events::{
RAW_APP_DRAG_ENDED_EVENT, RAW_DRAG_CANCELLED_EVENT, RAW_DRAG_DROPPED_EVENT, RAW_DRAG_HOVERED_EVENT, RAW_DRAG_MOVED_EVENT,
},
widget::{
WidgetId,
info::{HitTestInfo, InteractionPath, WidgetInfo, WidgetInfoBuilder},
},
window::WindowId,
};
use zng_app_context::app_local;
use zng_ext_window::{NestedWindowWidgetInfoExt as _, WINDOWS, WINDOWS_DRAG_DROP};
use zng_handle::{Handle, HandleOwner, WeakHandle};
use zng_layout::unit::{DipPoint, DipToPx as _, PxToDip as _};
use zng_state_map::StateId;
use zng_task::channel::IpcBytes;
use zng_txt::{Txt, formatx};
use zng_var::{ArcEq, Var, var};
use zng_view_api::{DragDropId, mouse::ButtonState, touch::TouchPhase};
use crate::{mouse::MOUSE_INPUT_EVENT, touch::TOUCH_INPUT_EVENT};
pub use zng_view_api::drag_drop::{DragDropData, DragDropEffect};
#[allow(non_camel_case_types)]
pub struct DRAG_DROP;
impl DRAG_DROP {
pub fn dragging_data(&self) -> Var<Vec<DragDropData>> {
DRAG_DROP_SV.read().data.read_only()
}
pub fn drag(&self, data: DragDropData, allowed_effects: DragDropEffect) -> DragHandle {
let mut sv = DRAG_DROP_SV.write();
if let Some(d) = &mut sv.app_drag {
if allowed_effects.is_empty() {
tracing::error!("cannot drag, no `allowed_effects`");
return DragHandle::dummy();
}
if d.allowed.is_empty() {
d.allowed = allowed_effects;
} else {
if !d.allowed.contains(allowed_effects) {
tracing::error!("cannot drag, other data already set with incompatible `allowed_effects`");
return DragHandle::dummy();
}
d.allowed |= allowed_effects
}
d.data.push(data);
let (owner, handle) = DragHandle::new();
d.handles.push(owner);
return handle;
}
tracing::error!("cannot drag, not in `DRAG_START_EVENT` interval");
DragHandle::dummy()
}
}
app_local! {
static DRAG_DROP_SV: DragDropService = {
hooks();
DragDropService {
data: var(vec![]),
system_dragging: vec![],
app_drag: None,
app_dragging: vec![],
pos: Default::default(),
pos_window: None,
hits: None,
hovered: Default::default(),
}
};
}
struct DragDropService {
data: Var<Vec<DragDropData>>,
system_dragging: Vec<DragDropData>,
app_drag: Option<AppDragging>,
app_dragging: Vec<AppDragging>,
pos: DipPoint,
pos_window: Option<WindowId>,
hits: Option<HitTestInfo>,
hovered: Option<InteractionPath>,
}
struct AppDragging {
target: InteractionPath,
data: Vec<DragDropData>,
handles: Vec<HandleOwner<()>>,
allowed: DragDropEffect,
view_id: DragDropId,
}
fn hooks() {
RAW_DRAG_DROPPED_EVENT
.hook(|args| {
let mut s = DRAG_DROP_SV.write();
let s = &mut *s;
let len = s.system_dragging.len();
for data in &args.data {
s.system_dragging.retain(|d| d != data);
}
if s.system_dragging.len() != len {
s.data.set(s.system_dragging.clone());
}
if s.pos_window == Some(args.window_id)
&& let Some(hovered) = &s.hovered
{
let hits = s.hits.take().unwrap_or_else(|| HitTestInfo::no_hits(args.window_id));
DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(Some(hovered.clone()), None, s.pos, hits.clone()));
DROP_EVENT.notify(DropArgs::now(
hovered.clone(),
args.data.clone(),
args.allowed,
s.pos,
hits,
args.drop_id,
ArcEq::new(Mutex::new(DragDropEffect::empty())),
));
}
true
})
.perm();
RAW_DRAG_HOVERED_EVENT
.hook(|args| {
let mut s = DRAG_DROP_SV.write();
s.system_dragging.extend(args.data.iter().cloned());
s.data.set(s.system_dragging.clone());
true
})
.perm();
RAW_DRAG_MOVED_EVENT
.hook(|args| {
let mut s = DRAG_DROP_SV.write();
let moved = s.pos != args.position || s.pos_window != Some(args.window_id);
if moved {
s.pos = args.position;
s.pos_window = Some(args.window_id);
let mut position = args.position;
let mut frame_info = match WINDOWS.widget_tree(args.window_id) {
Some(f) => f,
None => {
if let Some(hovered) = s.hovered.take() {
DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(
Some(hovered),
None,
position,
HitTestInfo::no_hits(args.window_id),
));
s.pos_window = None;
}
return true;
}
};
let mut pos_hits = frame_info.root().hit_test(position.to_px(frame_info.scale_factor()));
let target = if let Some(t) = pos_hits.target() {
if let Some(w) = frame_info.get(t.widget_id) {
if let Some(f) = w.nested_window_tree() {
frame_info = f;
let factor = frame_info.scale_factor();
let pos = position.to_px(factor);
let pos = w.inner_transform().inverse().and_then(|t| t.transform_point(pos)).unwrap_or(pos);
pos_hits = frame_info.root().hit_test(pos);
position = pos.to_dip(factor);
pos_hits
.target()
.and_then(|h| frame_info.get(h.widget_id))
.map(|w| w.interaction_path())
.unwrap_or_else(|| frame_info.root().interaction_path())
} else {
w.interaction_path()
}
} else {
tracing::error!("hits target `{}` not found", t.widget_id);
frame_info.root().interaction_path()
}
} else {
frame_info.root().interaction_path()
}
.unblocked();
s.hits = Some(pos_hits.clone());
let hovered_args = if s.hovered != target {
let prev_target = mem::replace(&mut s.hovered, target.clone());
let args = DragHoveredArgs::now(prev_target, target.clone(), position, pos_hits.clone());
Some(args)
} else {
None
};
if let Some(target) = target {
let args = DragMoveArgs::now(frame_info.window_id(), args.coalesced_pos.clone(), position, pos_hits, target);
DRAG_MOVE_EVENT.notify(args);
}
if let Some(args) = hovered_args {
DRAG_HOVERED_EVENT.notify(args);
}
}
true
})
.perm();
RAW_DRAG_CANCELLED_EVENT
.hook(|args| {
let mut s = DRAG_DROP_SV.write();
let changed = !s.system_dragging.is_empty();
s.system_dragging.clear();
if changed {
s.data.set(s.system_dragging.clone());
}
if let Some(prev) = s.hovered.take() {
s.pos_window = None;
DRAG_HOVERED_EVENT.notify(DragHoveredArgs::now(
Some(prev),
None,
s.pos,
s.hits.take().unwrap_or_else(|| HitTestInfo::no_hits(args.window_id)),
));
}
true
})
.perm();
RAW_APP_DRAG_ENDED_EVENT
.hook(|args| {
let mut s = DRAG_DROP_SV.write();
s.app_dragging.retain(|d| {
if d.view_id != args.id {
return true;
}
if !args.applied.is_empty() && !d.allowed.contains(args.applied) {
tracing::error!(
"drop target applied disallowed effect, allowed={:?}, applied={:?}",
d.allowed,
args.applied
);
}
DRAG_END_EVENT.notify(DragEndArgs::now(d.target.clone(), args.applied));
false
});
true
})
.perm();
MOUSE_INPUT_EVENT
.on_event(
false,
hn!(|args| {
if matches!(args.state, ButtonState::Pressed)
&& let Some(wgt) = WINDOWS.widget_info(args.target.widget_id())
&& let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable())
{
args.propagation.stop();
let target = wgt.interaction_path();
let args = DragStartArgs::now(target.clone());
DRAG_START_EVENT.notify(args);
DRAG_DROP_SV.write().app_drag = Some(AppDragging {
target,
data: vec![],
handles: vec![],
allowed: DragDropEffect::empty(),
view_id: DragDropId(0),
}); }
}),
)
.perm();
TOUCH_INPUT_EVENT
.on_event(
false,
hn!(|args| {
if matches!(args.phase, TouchPhase::Start)
&& let Some(wgt) = WINDOWS.widget_info(args.target.widget_id())
&& let Some(wgt) = wgt.self_and_ancestors().find(|w| w.is_draggable())
{
args.propagation.stop();
let target = wgt.interaction_path();
let args = DragStartArgs::now(target.clone());
DRAG_START_EVENT.notify(args);
DRAG_DROP_SV.write().app_drag = Some(AppDragging {
target,
data: vec![],
handles: vec![],
allowed: DragDropEffect::empty(),
view_id: DragDropId(0),
}); }
}),
)
.perm();
DRAG_START_EVENT
.hook(|args| {
let mut s = DRAG_DROP_SV.write();
let mut data = s.app_drag.take();
let mut cancel = args.propagation.is_stopped();
if !cancel {
if let Some(d) = &mut data {
if d.data.is_empty() {
d.data.push(encode_widget_id(args.target.widget_id()));
d.allowed = DragDropEffect::all();
}
match WINDOWS_DRAG_DROP.start_drag_drop(d.target.window_id(), mem::take(&mut d.data), d.allowed) {
Ok(id) => {
d.view_id = id;
s.app_dragging.push(data.take().unwrap());
}
Err(e) => {
tracing::error!("cannot start drag&drop, {e}");
cancel = true;
}
}
} else {
tracing::warn!("external notification of DRAG_START_EVENT ignored")
}
}
if cancel && let Some(d) = data {
DRAG_END_EVENT.notify(DragEndArgs::now(d.target, DragDropEffect::empty()));
}
true
})
.perm();
DROP_EVENT
.hook(|args| {
WINDOWS_DRAG_DROP.drag_dropped(args.target.window_id(), args.drop_id, *args.applied.lock());
true
})
.perm();
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
#[must_use = "dropping the handle cancels the drag operation"]
pub struct DragHandle(Handle<()>);
impl DragHandle {
fn new() -> (HandleOwner<()>, Self) {
let (owner, handle) = Handle::new(());
(owner, Self(handle))
}
pub fn dummy() -> Self {
Self(Handle::dummy(()))
}
pub fn perm(self) {
self.0.perm();
}
pub fn is_permanent(&self) -> bool {
self.0.is_permanent()
}
pub fn cancel(self) {
self.0.force_drop()
}
pub fn is_canceled(&self) -> bool {
self.0.is_dropped()
}
pub fn downgrade(&self) -> WeakDragHandle {
WeakDragHandle(self.0.downgrade())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
pub struct WeakDragHandle(WeakHandle<()>);
impl WeakDragHandle {
pub fn new() -> Self {
Self(WeakHandle::new())
}
pub fn upgrade(&self) -> Option<DragHandle> {
self.0.upgrade().map(DragHandle)
}
}
pub trait WidgetInfoDragDropExt {
fn is_draggable(&self) -> bool;
}
impl WidgetInfoDragDropExt for WidgetInfo {
fn is_draggable(&self) -> bool {
self.meta().flagged(*IS_DRAGGABLE_ID)
}
}
pub trait WidgetInfoBuilderDragDropExt {
fn draggable(&mut self);
}
impl WidgetInfoBuilderDragDropExt for WidgetInfoBuilder {
fn draggable(&mut self) {
self.flag_meta(*IS_DRAGGABLE_ID);
}
}
static_id! {
static ref IS_DRAGGABLE_ID: StateId<()>;
}
event_args! {
pub struct DropArgs {
pub target: InteractionPath,
pub data: Vec<DragDropData>,
pub allowed: DragDropEffect,
pub position: DipPoint,
pub hits: HitTestInfo,
drop_id: DragDropId,
applied: ArcEq<Mutex<DragDropEffect>>,
..
fn is_in_target(&self, id: WidgetId) -> bool {
self.target.contains(id)
}
}
pub struct DragHoveredArgs {
pub prev_target: Option<InteractionPath>,
pub target: Option<InteractionPath>,
pub position: DipPoint,
pub hits: HitTestInfo,
..
fn is_in_target(&self, id: WidgetId) -> bool {
if let Some(p) = &self.prev_target
&& p.contains(id)
{
return true;
}
if let Some(p) = &self.target
&& p.contains(id)
{
return true;
}
false
}
}
pub struct DragMoveArgs {
pub window_id: WindowId,
pub coalesced_pos: Vec<DipPoint>,
pub position: DipPoint,
pub hits: HitTestInfo,
pub target: InteractionPath,
..
fn is_in_target(&self, id: WidgetId) -> bool {
self.target.contains(id)
}
}
pub struct DragStartArgs {
pub target: InteractionPath,
..
fn is_in_target(&self, id: WidgetId) -> bool {
self.target.contains(id)
}
}
pub struct DragEndArgs {
pub target: InteractionPath,
pub applied: DragDropEffect,
..
fn is_in_target(&self, id: WidgetId) -> bool {
self.target.contains(id)
}
fn validate(&self) -> Result<(), Txt> {
if self.applied.is_empty() && self.applied.len() > 1 {
return Err("only one or none `DragDropEffect` can be applied".into());
}
Ok(())
}
}
}
event! {
pub static DROP_EVENT: DropArgs {
let _ = DRAG_DROP_SV.read();
};
pub static DRAG_HOVERED_EVENT: DragHoveredArgs {
let _ = DRAG_DROP_SV.read();
};
pub static DRAG_MOVE_EVENT: DragMoveArgs {
let _ = DRAG_DROP_SV.read();
};
pub static DRAG_START_EVENT: DragStartArgs {
let _ = DRAG_DROP_SV.read();
};
pub static DRAG_END_EVENT: DragEndArgs {
let _ = DRAG_DROP_SV.read();
};
}
impl DropArgs {
pub fn applied(&self, effect: DragDropEffect) {
assert!(effect.len() > 1, "can only apply one effect");
assert!(self.allowed.contains(effect), "source does not allow this effect");
let mut e = self.applied.lock();
if !self.propagation.is_stopped() {
self.propagation.stop();
*e = effect;
} else {
tracing::error!("drop already handled");
}
}
}
impl DragHoveredArgs {
pub fn data(&self) -> Var<Vec<DragDropData>> {
DRAG_DROP.dragging_data()
}
pub fn is_drag_enter(&self, wgt: WidgetId) -> bool {
!self.was_over(wgt) && self.is_over(wgt)
}
pub fn is_drag_leave(&self, wgt: WidgetId) -> bool {
self.was_over(wgt) && !self.is_over(wgt)
}
pub fn was_over(&self, wgt: WidgetId) -> bool {
if let Some(t) = &self.prev_target {
return t.contains(wgt);
}
false
}
pub fn is_over(&self, wgt: WidgetId) -> bool {
if let Some(t) = &self.target {
return t.contains(wgt);
}
false
}
pub fn was_enabled(&self, widget_id: WidgetId) -> bool {
match &self.prev_target {
Some(t) => t.contains_enabled(widget_id),
None => false,
}
}
pub fn was_disabled(&self, widget_id: WidgetId) -> bool {
match &self.prev_target {
Some(t) => t.contains_disabled(widget_id),
None => false,
}
}
pub fn is_enabled(&self, widget_id: WidgetId) -> bool {
match &self.target {
Some(t) => t.contains_enabled(widget_id),
None => false,
}
}
pub fn is_disabled(&self, widget_id: WidgetId) -> bool {
match &self.target {
Some(t) => t.contains_disabled(widget_id),
None => false,
}
}
pub fn is_drag_enter_enabled(&self, wgt: WidgetId) -> bool {
(!self.was_over(wgt) || self.was_disabled(wgt)) && self.is_over(wgt) && self.is_enabled(wgt)
}
pub fn is_drag_leave_enabled(&self, wgt: WidgetId) -> bool {
self.was_over(wgt) && self.was_enabled(wgt) && (!self.is_over(wgt) || self.is_disabled(wgt))
}
pub fn is_drag_enter_disabled(&self, wgt: WidgetId) -> bool {
(!self.was_over(wgt) || self.was_enabled(wgt)) && self.is_over(wgt) && self.is_disabled(wgt)
}
pub fn is_drag_leave_disabled(&self, wgt: WidgetId) -> bool {
self.was_over(wgt) && self.was_disabled(wgt) && (!self.is_over(wgt) || self.is_enabled(wgt))
}
}
impl DragEndArgs {
pub fn was_dropped(&self) -> bool {
!self.applied.is_empty()
}
pub fn was_canceled(&self) -> bool {
self.applied.is_empty()
}
}
pub fn encode_widget_id(id: WidgetId) -> DragDropData {
DragDropData::Extension {
data_type: formatx!("zng/{}", APP_INSTANCE_GUID.read().simple()),
data: IpcBytes::from_slice_blocking(&id.get().to_le_bytes()).unwrap(),
}
}
pub fn decode_widget_id(data: &DragDropData) -> Option<WidgetId> {
if let DragDropData::Extension { data_type, data } = data
&& data.len() == 8
&& let Some(guid) = data_type.strip_prefix("zng/")
&& guid == APP_INSTANCE_GUID.read().simple().to_string()
{
let mut id = [0u8; 8];
id.copy_from_slice(data);
return Some(WidgetId::from_raw(u64::from_le_bytes(id)));
}
None
}
app_local! {
static APP_INSTANCE_GUID: uuid::Uuid = uuid::Uuid::new_v4();
}