use dioxus::prelude::*;
use super::monitor::{CancelReason, DndEvent, DndMonitor, DragSnapshot, DropReceipt};
use super::session::SourceCompletion;
use super::types::{DragId, DragMode, DragSessionId, DropEffect, Point, PointerKind, Rect, ZoneId};
#[derive(Store, Debug, Clone, PartialEq)]
pub struct DragState<T: 'static> {
pub payload: Option<T>,
pub source: Option<ZoneId>,
pub over: Option<ZoneId>,
pub pointer: Point,
pub grab: Point,
pub effect: DropEffect,
pub mode: DragMode,
pub pointer_kind: PointerKind,
pub source_rect: Option<Rect>,
pub refocus: Option<T>,
pub settle: Option<Rect>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct DragStart<T> {
pub payload: T,
pub source: Option<ZoneId>,
pub pointer: Point,
pub grab: Point,
pub effect: DropEffect,
pub mode: DragMode,
pub pointer_kind: PointerKind,
pub source_rect: Option<Rect>,
}
impl<T> DragStart<T> {
pub fn new(payload: T, pointer: Point) -> Self {
Self {
payload,
source: None,
pointer,
grab: Point::default(),
effect: DropEffect::default(),
mode: DragMode::default(),
pointer_kind: PointerKind::default(),
source_rect: None,
}
}
pub fn with_source(mut self, source: Option<ZoneId>) -> Self {
self.source = source;
self
}
pub fn with_grab(mut self, grab: Point) -> Self {
self.grab = grab;
self
}
pub fn with_effect(mut self, effect: DropEffect) -> Self {
self.effect = effect;
self
}
pub fn with_mode(mut self, mode: DragMode) -> Self {
self.mode = mode;
self
}
pub fn with_pointer_kind(mut self, pointer_kind: PointerKind) -> Self {
self.pointer_kind = pointer_kind;
self
}
pub fn with_source_rect(mut self, source_rect: Option<Rect>) -> Self {
self.source_rect = source_rect;
self
}
}
pub(super) enum DragIdentity {
Generated,
Explicit(DragId),
}
impl<T> Default for DragState<T> {
fn default() -> Self {
Self {
payload: None,
source: None,
over: None,
pointer: Point::default(),
grab: Point::default(),
effect: DropEffect::default(),
mode: DragMode::default(),
pointer_kind: PointerKind::default(),
source_rect: None,
refocus: None,
settle: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum DragPhase {
#[default]
Idle,
Dragging,
Settling,
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct DragRuntimeState {
id: Option<DragId>,
identity_explicit: bool,
session: Option<DragSessionId>,
proposed_effect: DropEffect,
phase: DragPhase,
}
impl Default for DragRuntimeState {
fn default() -> Self {
Self {
id: None,
identity_explicit: false,
session: None,
proposed_effect: DropEffect::default(),
phase: DragPhase::Idle,
}
}
}
pub(super) struct DragRuntime<T: 'static> {
state: Signal<DragRuntimeState>,
pub(super) completion: Signal<Option<SourceCompletion>>,
monitor: DndMonitor<T>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum PhaseAuthority {
State,
Runtime,
}
impl<T> Copy for DragRuntime<T> {}
impl<T> Clone for DragRuntime<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> PartialEq for DragRuntime<T> {
fn eq(&self, other: &Self) -> bool {
self.state == other.state
&& self.completion == other.completion
&& self.monitor == other.monitor
}
}
impl<T: Clone + 'static> DragRuntime<T> {
fn new() -> Self {
Self {
state: Signal::new(DragRuntimeState::default()),
completion: Signal::new(None),
monitor: DndMonitor::new(),
}
}
}
pub struct DndContext<T: Clone + 'static> {
state: Store<DragState<T>>,
announcement: Signal<String>,
pub(super) runtime: DragRuntime<T>,
phase_authority: PhaseAuthority,
}
impl<T: Clone + 'static> Copy for DndContext<T> {}
impl<T: Clone + 'static> Clone for DndContext<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Clone + 'static> PartialEq for DndContext<T> {
fn eq(&self, other: &Self) -> bool {
self.announcement == other.announcement
}
}
impl<T: Clone + 'static> DndContext<T> {
pub fn from_parts(state: Store<DragState<T>>, announcement: Signal<String>) -> Self {
Self::from_parts_with_authority(state, announcement, PhaseAuthority::State)
}
pub(crate) fn managed(state: Store<DragState<T>>, announcement: Signal<String>) -> Self {
Self::from_parts_with_authority(state, announcement, PhaseAuthority::Runtime)
}
fn from_parts_with_authority(
state: Store<DragState<T>>,
announcement: Signal<String>,
phase_authority: PhaseAuthority,
) -> Self {
let phase = if state.settle().peek().is_some() {
DragPhase::Settling
} else if state.payload().peek().is_some() {
DragPhase::Dragging
} else {
DragPhase::Idle
};
let mut runtime = DragRuntime::new();
{
let mut runtime_state = runtime.state.write();
runtime_state.phase = phase;
runtime_state.proposed_effect = *state.effect().peek();
}
Self {
state,
announcement,
runtime,
phase_authority,
}
}
pub fn start(
&mut self,
payload: T,
source: Option<ZoneId>,
pointer: Point,
grab: Point,
effect: DropEffect,
mode: DragMode,
) {
if !self.prepare_start() {
return;
}
self.start_with_metadata(
DragIdentity::Generated,
None,
DragStart::new(payload, pointer)
.with_source(source)
.with_grab(grab)
.with_effect(effect)
.with_mode(mode),
);
}
pub fn start_with_id(&mut self, id: DragId, start: DragStart<T>) {
if !self.prepare_start() {
return;
}
self.start_with_metadata(DragIdentity::Explicit(id), None, start);
}
pub(super) fn start_with_metadata(
&mut self,
identity: DragIdentity,
session: Option<DragSessionId>,
start: DragStart<T>,
) {
let (id, identity_explicit) = match identity {
DragIdentity::Generated => (DragId::auto(), false),
DragIdentity::Explicit(id) => (id, true),
};
self.runtime.state.set(DragRuntimeState {
id: Some(id),
identity_explicit,
session,
proposed_effect: start.effect,
phase: DragPhase::Dragging,
});
self.state.set(DragState {
payload: Some(start.payload),
source: start.source,
over: None,
pointer: start.pointer,
grab: start.grab,
effect: start.effect,
mode: start.mode,
pointer_kind: start.pointer_kind,
source_rect: start.source_rect,
refocus: None,
settle: None,
});
self.runtime
.monitor
.emit_lazy(|| self.snapshot().map(DndEvent::Started));
}
pub fn set_pointer_kind(&mut self, kind: PointerKind) {
self.state.pointer_kind().set(kind);
}
pub fn request_refocus(&mut self, payload: T) {
self.state.refocus().set(Some(payload));
}
pub fn claim_refocus(&mut self, payload: &T) -> bool
where
T: PartialEq,
{
let mut refocus = self.state.refocus();
let hit = refocus.peek().as_ref() == Some(payload);
if hit {
refocus.set(None);
}
hit
}
pub fn set_source_rect(&mut self, rect: Option<Rect>) {
self.state.source_rect().set(rect);
}
pub fn update_pointer(&mut self, pointer: Point) {
if pointer.x == 0.0 && pointer.y == 0.0 {
return;
}
self.state.pointer().set(pointer);
self.runtime
.monitor
.emit_lazy(|| self.snapshot().map(DndEvent::Moved));
}
pub(crate) fn set_proposed_effect(&mut self, effect: DropEffect) {
let mut runtime = self.runtime.state;
if runtime.peek().proposed_effect != effect {
runtime.write().proposed_effect = effect;
}
}
pub fn enter(&mut self, zone: ZoneId) {
let previous = self.over();
if previous == Some(zone) {
return;
}
self.state.over().set(Some(zone));
self.runtime.monitor.emit_lazy(|| {
self.snapshot().map(|drag| DndEvent::TargetChanged {
drag,
previous,
current: Some(zone),
})
});
}
pub fn leave(&mut self, zone: ZoneId) {
let previous = self.over();
if previous == Some(zone) {
self.state.over().set(None);
self.runtime.monitor.emit_lazy(|| {
self.snapshot().map(|drag| DndEvent::TargetChanged {
drag,
previous,
current: None,
})
});
}
}
pub fn take(&mut self) -> Option<(T, Option<ZoneId>)> {
let (payload, source) = {
let mut s = self.state.write();
(s.payload.take(), s.source)
};
let payload = payload?;
self.state.set(DragState::default());
self.runtime.state.set(DragRuntimeState::default());
Some((payload, source))
}
pub fn take_settling(&mut self, to: Rect) -> Option<(T, Option<ZoneId>)> {
let mut s = self.state.write();
let payload = s.payload.clone()?;
let source = s.source;
s.over = None;
s.settle = Some(to);
drop(s);
self.runtime.state.write().phase = DragPhase::Settling;
Some((payload, source))
}
pub fn retarget_settle(&mut self, to: Rect) {
let mut settle = self.state.settle();
if settle.peek().is_some() && *settle.peek() != Some(to) {
settle.set(Some(to));
}
}
pub fn finish_settle(&mut self) {
if self.phase_peek() == DragPhase::Settling {
self.state.set(DragState::default());
self.runtime.state.set(DragRuntimeState::default());
}
}
pub(crate) fn phase_peek(&self) -> DragPhase {
match self.phase_authority {
PhaseAuthority::State => {
if self.state.settle().peek().is_some() {
DragPhase::Settling
} else if self.state.payload().peek().is_some() {
DragPhase::Dragging
} else {
DragPhase::Idle
}
}
PhaseAuthority::Runtime => self
.runtime
.state
.try_peek()
.map(|runtime| runtime.phase)
.unwrap_or(DragPhase::Idle),
}
}
pub(crate) fn alive(&self) -> bool {
self.announcement.try_peek().is_ok()
}
pub fn cancel(&mut self) {
self.cancel_with_reason(CancelReason::User);
}
pub fn cancel_with_reason(&mut self, reason: CancelReason) {
if self.phase_peek() == DragPhase::Settling {
self.finish_settle();
return;
}
if let Some(session) = self.drag_session_id() {
if self.cancel_session(session, reason) {
return;
}
}
self.cancel_state(reason);
}
pub(super) fn cancel_state(&mut self, reason: CancelReason) {
let snapshot = (self.phase_peek() == DragPhase::Dragging
&& self.runtime.monitor.has_listeners())
.then(|| self.snapshot())
.flatten();
self.state.set(DragState::default());
self.runtime.state.set(DragRuntimeState::default());
if let Some(drag) = snapshot {
self.runtime
.monitor
.emit(DndEvent::Cancelled { drag, reason });
}
}
pub fn dragging(&self) -> bool {
match self.phase_authority {
PhaseAuthority::State => {
self.state.payload().is_some() && self.state.settle().is_none()
}
PhaseAuthority::Runtime => self.runtime.state.read().phase == DragPhase::Dragging,
}
}
pub fn settling(&self) -> Option<Rect> {
match self.phase_authority {
PhaseAuthority::State => self.state.settle().cloned(),
PhaseAuthority::Runtime => (self.runtime.state.read().phase == DragPhase::Settling)
.then(|| self.state.settle().cloned())
.flatten(),
}
}
pub(crate) fn settling_peek(&self) -> bool {
self.phase_peek() == DragPhase::Settling
}
pub fn payload(&self) -> Option<T> {
self.state.payload().cloned()
}
pub fn drag_id(&self) -> Option<DragId> {
self.runtime.state.read().id
}
pub fn has_explicit_drag_id(&self) -> bool {
self.runtime.state.read().identity_explicit
}
pub fn drag_session_id(&self) -> Option<DragSessionId> {
self.runtime.state.read().session
}
pub fn over(&self) -> Option<ZoneId> {
self.state.over().cloned()
}
pub fn source(&self) -> Option<ZoneId> {
self.state.source().cloned()
}
pub fn pointer(&self) -> Point {
self.state.pointer().cloned()
}
pub fn grab(&self) -> Point {
self.state.grab().cloned()
}
pub fn source_rect(&self) -> Option<Rect> {
self.state.source_rect().cloned()
}
pub fn effect(&self) -> DropEffect {
self.state.effect().cloned()
}
pub(crate) fn proposed_effect(&self) -> DropEffect {
self.runtime.state.read().proposed_effect
}
pub fn mode(&self) -> DragMode {
self.state.mode().cloned()
}
pub fn pointer_kind(&self) -> PointerKind {
self.state.pointer_kind().cloned()
}
pub fn snapshot(&self) -> Option<DragSnapshot<T>> {
Some(DragSnapshot {
id: self.drag_id()?,
session: self.drag_session_id(),
payload: self.payload()?,
source: self.source(),
over: self.over(),
pointer: self.pointer(),
grab: self.grab(),
effect: self.effect(),
mode: self.mode(),
pointer_kind: self.pointer_kind(),
source_rect: self.source_rect(),
})
}
pub(crate) fn monitor_mut(&mut self) -> DndMonitor<T> {
self.runtime.monitor
}
pub(crate) fn emit_dropped(&self, receipt: DropReceipt<T>) {
self.runtime.monitor.emit(DndEvent::Dropped(receipt));
}
pub(crate) fn monitor_has_listeners(&self) -> bool {
self.runtime.monitor.has_listeners()
}
pub fn announce(&mut self, msg: impl Into<String>) {
self.announcement.set(msg.into());
}
pub fn announcement(&self) -> String {
self.announcement.read().clone()
}
}