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