dioxus_dnd/core/world/
settle.rs1use std::sync::atomic::{AtomicU64, Ordering};
5
6use dioxus::prelude::*;
7
8use super::geometry::WindowKey;
9use super::state::DndWorld;
10
11static NEXT_SETTLE_GENERATION: AtomicU64 = AtomicU64::new(1);
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub(super) struct SettleClaim {
15 presenter: WindowKey,
16 generation: u64,
17}
18
19impl<T: Clone + 'static> DndWorld<T> {
20 pub fn claim_settle(&self, key: WindowKey) {
30 let mut claim = self.settle_claim;
31 claim.set(Some(SettleClaim {
32 presenter: key,
33 generation: NEXT_SETTLE_GENERATION.fetch_add(1, Ordering::Relaxed),
34 }));
35 }
36
37 pub(crate) fn settle_token(&self, key: WindowKey) -> Option<u64> {
43 self.ctx.settling()?;
44 (*self.settle_claim.read())
45 .filter(|claim| claim.presenter == key)
46 .map(|claim| claim.generation)
47 }
48
49 pub(crate) fn peek_settle_token(&self, key: WindowKey) -> Option<u64> {
50 if !self.ctx.settling_peek() {
51 return None;
52 }
53 (*self.settle_claim.peek())
54 .filter(|claim| claim.presenter == key)
55 .map(|claim| claim.generation)
56 }
57
58 pub(super) fn settle_presenter_is(&self, key: WindowKey) -> bool {
59 self.peek_settle_token(key).is_some()
60 }
61
62 pub(super) fn settle_presenter(&self) -> Option<WindowKey> {
63 self.settle_claim
64 .peek()
65 .as_ref()
66 .map(|claim| claim.presenter)
67 }
68
69 pub fn finish_settle_from(&self, key: WindowKey) -> bool {
73 let Some(generation) = self.peek_settle_token(key) else {
74 return false;
75 };
76 self.finish_settle_generation(key, generation)
77 }
78
79 pub(crate) fn finish_settle_generation(&self, key: WindowKey, generation: u64) -> bool {
80 if *self.settle_claim.peek()
81 != Some(SettleClaim {
82 presenter: key,
83 generation,
84 })
85 || self.ctx.settling().is_none()
86 {
87 return false;
88 }
89 let mut ctx = self.ctx;
90 ctx.finish_settle();
91 self.clear_world_state();
92 true
93 }
94
95 pub fn settling_in(&self) -> Option<WindowKey> {
97 self.ctx
98 .settling()
99 .and_then(|_| (*self.settle_claim.read()).map(|claim| claim.presenter))
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use std::cell::RefCell;
106
107 use super::*;
108 use crate::core::{DragMode, DropEffect, Point, Rect};
109
110 thread_local! {
111 static WORLD: RefCell<Option<DndWorld<String>>> = const { RefCell::new(None) };
112 }
113
114 fn test_app() -> Element {
115 let world = use_hook(DndWorld::<String>::new);
116 WORLD.with_borrow_mut(|slot| *slot = Some(world));
117 rsx! {}
118 }
119
120 #[test]
121 fn stale_generation_cannot_finish_its_successor() {
122 let mut dom = VirtualDom::new(test_app);
123 dom.rebuild_in_place();
124 let world = WORLD.with_borrow(|slot| slot.expect("test world"));
125 dom.in_runtime(|| {
126 let origin = WindowKey::auto();
127 let presenter = WindowKey::auto();
128 let mut ctx = world.context();
129 ctx.start(
130 "payload".to_string(),
131 None,
132 Point::new(10.0, 10.0),
133 Point::default(),
134 DropEffect::Move,
135 DragMode::Pointer,
136 );
137 world.begin_from(origin);
138 world.claim_settle(presenter);
139 assert!(ctx.take_settling(Rect::new(0.0, 0.0, 10.0, 10.0)).is_some());
140 let stale = world.settle_token(presenter).unwrap();
141
142 world.claim_settle(presenter);
145 let successor = world.settle_token(presenter).unwrap();
146 assert_ne!(stale, successor);
147 assert!(!world.finish_settle_generation(presenter, stale));
148 assert!(ctx.settling().is_some());
149 assert!(world.finish_settle_generation(presenter, successor));
150 assert!(ctx.payload().is_none());
151 });
152 }
153
154 #[test]
155 fn cancelled_context_settle_retires_the_claim_tokens() {
156 let mut dom = VirtualDom::new(test_app);
157 dom.rebuild_in_place();
158 let world = WORLD.with_borrow(|slot| slot.expect("test world"));
159 dom.in_runtime(|| {
160 let origin = WindowKey::auto();
161 let presenter = WindowKey::auto();
162 let mut ctx = world.context();
163 ctx.start(
164 "payload".to_string(),
165 None,
166 Point::new(10.0, 10.0),
167 Point::default(),
168 DropEffect::Move,
169 DragMode::Pointer,
170 );
171 world.begin_from(origin);
172 world.claim_settle(presenter);
173 assert!(ctx.take_settling(Rect::new(0.0, 0.0, 10.0, 10.0)).is_some());
174 assert!(world.settle_token(presenter).is_some());
175
176 ctx.cancel();
181 assert_eq!(world.settle_token(presenter), None);
182 assert_eq!(world.peek_settle_token(presenter), None);
183 assert_eq!(world.settling_in(), None);
184 assert!(!world.finish_settle_from(presenter));
185 });
186 }
187}