use a7n_core::input::{clamp_position, corner_push, edge_push, within_notch};
use a7n_proto::v1::Landing;
use super::{Bounds, Daemon, Edge, MachineId, now};
impl Daemon {
pub(super) async fn cross_edge(
&mut self,
bounds: Bounds,
monitors: &[Bounds],
(x, y): (i32, i32),
(dx, dy): (i32, i32),
driven: bool,
) {
let bounds = (bounds.x, bounds.y, bounds.width, bounds.height);
let Some((edge, overshoot, along, display)) =
self.crossing_push(bounds, monitors, (x, y), (dx, dy), driven)
else {
self.input.poll_edge_pressure();
return;
};
let Some((target, landing)) =
self.crossing_exit(bounds, monitors, (x, y), (dx, dy), (edge, along))
else {
self.input.resistance.push(Edge::Unspecified, 0, now());
self.input.sink.clear();
if !driven && self.input.edge_no_target.replace(edge) != Some(edge) {
crate::log::event!("kind=edge_no_target");
}
return;
};
self.input.edge_no_target = None;
if !self.input.resistance.push(edge, overshoot, now()) {
let progress = self.input.resistance.progress();
self.input.sink.push(display, progress);
return;
}
self.input.sink.clear();
self.dispatch_crossing(target, landing, driven).await;
}
fn crossing_push(
&mut self,
bounds: (i32, i32, u32, u32),
monitors: &[Bounds],
(x, y): (i32, i32),
(dx, dy): (i32, i32),
driven: bool,
) -> Option<(Edge, i32, f32, Bounds)> {
self.input.resistance.poll(now());
if self.input.buttons.any_held() {
self.input.fullscreen_hold.reset();
self.input.resistance.push(Edge::Unspecified, 0, now());
return None;
}
if self.input.paused || (!driven && self.input.focus.is_locked()) {
self.input.fullscreen_hold.reset();
return None;
}
let Some(mut push) = edge_push(bounds, monitors, x, y, dx, dy) else {
self.input.fullscreen_hold.reset();
if let Some((edge, _)) = self.input.resistance.progress() {
let inward = match edge {
Edge::Left => dx.saturating_neg(),
Edge::Right => dx,
Edge::Top => dy.saturating_neg(),
Edge::Bottom => dy,
Edge::Unspecified => 0,
}
.min(0);
self.input.resistance.push(edge, inward, now());
}
return None;
};
let notch = if driven {
self.input.injector.as_ref().and_then(|input| input.notch)
} else {
self.input.capture.as_ref().and_then(|input| input.notch)
};
let notch = notch.filter(|notch| {
self.config.input.notch_crossing
&& push.0 == Edge::Top
&& push.3.contains(i64::from(notch.x), i64::from(notch.y))
});
if notch.is_some_and(|notch| !within_notch(notch, x)) {
self.input.fullscreen_hold.reset();
self.input.resistance.push(Edge::Unspecified, 0, now());
self.input.sink.clear();
return None;
}
if self
.input
.fullscreen_hold
.holds(true, || focused_fullscreen_at(push.3.rect(), x, y))
{
self.input.resistance.push(Edge::Unspecified, 0, now());
return None;
}
if let Some(notch) = notch {
push.3 = Bounds {
x: notch.x,
y: notch.y.saturating_add_unsigned(notch.height),
width: notch.width,
height: push.3.height.saturating_sub(notch.height),
};
}
Some(push)
}
fn crossing_exit(
&self,
bounds: (i32, i32, u32, u32),
monitors: &[Bounds],
(x, y): (i32, i32),
(dx, dy): (i32, i32),
(edge, along): (Edge, f32),
) -> Option<(MachineId, Landing)> {
let own = self.identity.machine_id;
self.layout
.state
.exit(own, edge, along, |id| self.is_layout_target(id))
.or_else(|| {
let (edge, corner) = corner_push(bounds, monitors, x, y, dx, dy)?;
self.layout
.state
.exit_corner(own, edge, corner, |id| self.is_layout_target(id))
})
}
async fn dispatch_crossing(&mut self, target: MachineId, landing: Landing, driven: bool) {
let actions = if driven {
self.wol.pending = None;
self.input.focus.handover(target, Some(landing))
} else {
if !self.is_linked(target) {
if self
.auto_wake(target, crate::daemon::wol::WakeAction::Edge(landing), now())
.is_err()
{
crate::log::event!("kind=wake_failed");
}
return;
}
self.wol.pending = None;
self.take_peer(target, Some(landing), now())
};
self.input_actions(actions).await;
}
}
fn focused_fullscreen_at(bounds: (i32, i32, u32, u32), x: i32, y: i32) -> bool {
let (x, y) = clamp_position(bounds, x, y);
let held = a7n_platform::fullscreen::focused_fullscreen_at(x, y);
if held {
crate::log::event!("kind=edge_held_fullscreen");
}
held
}
#[cfg(test)]
mod tests;