use dioxus::prelude::*;
use dioxus_desktop::tao::event::{ElementState, Event, MouseButton, WindowEvent};
use dioxus_desktop::{use_wry_event_handler, window};
use crate::core::{DndContext, JoinedWindow, Point};
use super::super::bridge::{
bridged, current_bridged_generation, current_generation, subscribed_generation,
BridgeGeneration,
};
use super::GlobalCapability;
fn poller_run_current(
expected: BridgeGeneration,
current: Option<BridgeGeneration>,
bridge_active: bool,
owns_origin: bool,
capability_available: bool,
geometry_live: bool,
) -> bool {
current == Some(expected)
&& bridge_active
&& owns_origin
&& capability_available
&& geometry_live
}
pub(super) fn poller_owns_generation<T: Clone + 'static>(
joined: JoinedWindow<T>,
ctx: &DndContext<T>,
capability: Signal<GlobalCapability>,
expected: BridgeGeneration,
) -> bool {
poller_run_current(
expected,
current_generation(joined),
bridged(joined, ctx),
joined.world.origin_window() == Some(joined.key),
capability.peek().available(),
joined.geometry.live(),
) && joined
.world
.is_drag_generation(expected.world, expected.session)
}
pub(crate) fn use_cursor_poller_leg<T: Clone + PartialEq + 'static>(
joined: JoinedWindow<T>,
ctx: DndContext<T>,
capability: Signal<GlobalCapability>,
) {
let _poller = use_resource(move || {
let capability_available = capability().available();
let bridge_active = bridged(joined, &ctx);
let owns_origin = joined.world.origin_window() == Some(joined.key);
let geometry_live = joined.geometry.live();
let generation = subscribed_generation(joined);
let should_poll = generation.is_some_and(|generation| {
poller_run_current(
generation,
current_generation(joined),
bridge_active,
owns_origin,
capability_available,
geometry_live,
) && joined
.world
.is_drag_generation(generation.world, generation.session)
});
let desktop = window();
async move {
let Some(generation) = generation.filter(|_| should_poll) else {
return;
};
tracing::debug!(leg = "cursor-poller", ?generation, "bridge leg engaged");
loop {
tokio::time::sleep(std::time::Duration::from_millis(30)).await;
if !poller_owns_generation(joined, &ctx, capability, generation) {
break;
}
let Ok(pos) = desktop.cursor_position() else {
continue;
};
let global = Point::new(pos.x, pos.y);
if !joined.geometry.contains_global(global) {
if poller_owns_generation(joined, &ctx, capability, generation) {
joined.world.track_global(global);
}
}
}
}
});
}
fn release_owns_generation<T: Clone + 'static>(
joined: JoinedWindow<T>,
ctx: &DndContext<T>,
capability: Signal<GlobalCapability>,
expected: BridgeGeneration,
foreign_only: bool,
) -> bool {
let origin = joined.world.origin_window();
current_bridged_generation(joined, ctx) == Some(expected)
&& capability.peek().available()
&& joined.geometry.live()
&& joined.world.record(joined.key).is_some()
&& origin.is_some_and(|origin| !foreign_only || origin != joined.key)
}
pub(crate) fn use_release_leg<T: Clone + PartialEq + 'static>(
joined: JoinedWindow<T>,
ctx: DndContext<T>,
capability: Signal<GlobalCapability>,
) {
use_wry_event_handler(move |event, _| {
let Some(generation) = current_bridged_generation(joined, &ctx) else {
return;
};
if !capability.peek().available() || !joined.geometry.live() {
return;
}
let Event::WindowEvent { event, .. } = event else {
return;
};
match event {
WindowEvent::MouseInput {
state: ElementState::Released,
button: MouseButton::Left,
..
} => {
let Ok(pos) = window().cursor_position() else {
return;
};
if release_owns_generation(joined, &ctx, capability, generation, false) {
tracing::debug!(
leg = "release",
event = "mouse-up",
?generation,
"bridge leg engaged"
);
joined.world.drop_at_global(Point::new(pos.x, pos.y));
}
}
WindowEvent::CursorMoved { position, .. } => {
if !release_owns_generation(joined, &ctx, capability, generation, true) {
return;
}
let scale = joined.geometry.scale().max(f64::EPSILON);
let client = Point::new(position.x / scale, position.y / scale);
if let Some(global) = joined.geometry.to_global(client) {
if release_owns_generation(joined, &ctx, capability, generation, true) {
tracing::debug!(
leg = "release",
event = "foreign-move",
?generation,
"bridge leg engaged"
);
joined.world.drop_at_global(global);
}
}
}
WindowEvent::CursorEntered { .. } => {
let Ok(pos) = window().cursor_position() else {
return;
};
if release_owns_generation(joined, &ctx, capability, generation, true) {
tracing::debug!(
leg = "release",
event = "foreign-enter",
?generation,
"bridge leg engaged"
);
joined.world.drop_at_global(Point::new(pos.x, pos.y));
}
}
_ => {}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::DragSessionId;
#[test]
fn poller_run_is_bound_to_its_captured_generation() {
let drag_n = BridgeGeneration {
world: 41,
session: Some(DragSessionId(10)),
};
let drag_n_plus_one = BridgeGeneration {
world: 42,
session: Some(DragSessionId(11)),
};
assert!(poller_run_current(
drag_n,
Some(drag_n),
true,
true,
true,
true
));
assert!(!poller_run_current(
drag_n,
Some(drag_n_plus_one),
true,
true,
true,
true
));
assert!(!poller_run_current(drag_n, None, true, true, true, true));
let untracked_n = BridgeGeneration {
world: 50,
session: None,
};
let untracked_n_plus_one = BridgeGeneration {
world: 51,
session: None,
};
assert!(poller_run_current(
untracked_n,
Some(untracked_n),
true,
true,
true,
true
));
assert!(!poller_run_current(
untracked_n,
Some(untracked_n_plus_one),
true,
true,
true,
true
));
}
#[test]
fn poller_requires_every_ownership_gate() {
let drag = BridgeGeneration {
world: 7,
session: None,
};
for gates in [
(false, true, true, true),
(true, false, true, true),
(true, true, false, true),
(true, true, true, false),
] {
assert!(!poller_run_current(
drag,
Some(drag),
gates.0,
gates.1,
gates.2,
gates.3
));
}
}
}