1use a7n_proto::seq::{delta, is_newer};
6use a7n_proto::v1::{Edge, Motion};
7
8use crate::layout::Corner;
9
10mod geometry;
11mod scroll;
12pub use geometry::{Bounds, landing_point};
13pub use scroll::ScrollScale;
14
15pub const CORNER_BOX: i64 = 8;
17
18#[derive(Debug)]
20pub struct MotionScale {
21 speed: f64,
22 remainder: (f64, f64),
23}
24
25impl MotionScale {
26 #[must_use]
28 pub fn new(speed: f64) -> Self {
29 Self {
30 speed,
31 remainder: (0.0, 0.0),
32 }
33 }
34
35 pub fn apply(&mut self, dx: i32, dy: i32) -> (i32, i32) {
36 (
37 Self::axis(f64::from(dx), self.speed, &mut self.remainder.0),
38 Self::axis(f64::from(dy), self.speed, &mut self.remainder.1),
39 )
40 }
41
42 #[allow(clippy::cast_possible_truncation)] fn axis(delta: f64, speed: f64, remainder: &mut f64) -> i32 {
44 if !delta.is_finite() {
45 return 0;
46 }
47 let scaled = delta * speed + *remainder;
48 *remainder = scaled.fract();
49 scaled as i32
50 }
51}
52
53#[derive(Debug, Default)]
54pub struct MotionCounter {
55 seq: u32,
56 x: i32,
57 y: i32,
58}
59
60impl MotionCounter {
61 pub fn push(&mut self, dx: i32, dy: i32) -> Motion {
62 self.seq = self.seq.wrapping_add(1);
63 self.x = self.x.wrapping_add(dx);
64 self.y = self.y.wrapping_add(dy);
65 self.current()
66 }
67
68 #[must_use]
70 pub fn current(&self) -> Motion {
71 Motion {
72 route: 0,
73 motion_seq: self.seq,
74 x: self.x,
75 y: self.y,
76 }
77 }
78
79 pub fn reset(&mut self) {
80 *self = Self::default();
81 }
82}
83
84#[derive(Debug, Default)]
85pub struct MotionTracker {
86 seq: u32,
87 x: i32,
88 y: i32,
89}
90
91impl MotionTracker {
92 pub fn apply(&mut self, seq: u32, x: i32, y: i32) -> Option<(i32, i32)> {
93 if !is_newer(seq, self.seq) {
94 return None;
95 }
96 Some(self.update(seq, x, y))
97 }
98
99 pub fn resync(&mut self, seq: u32, x: i32, y: i32) -> Option<(i32, i32)> {
100 if is_newer(seq, self.seq) || (seq == self.seq && (x, y) != (self.x, self.y)) {
101 return Some(self.update(seq, x, y));
102 }
103 None
104 }
105
106 pub fn reset(&mut self) {
107 *self = Self::default();
108 }
109
110 fn update(&mut self, seq: u32, x: i32, y: i32) -> (i32, i32) {
111 let movement = (delta(x, self.x), delta(y, self.y));
112 (self.seq, self.x, self.y) = (seq, x, y);
113 movement
114 }
115}
116
117#[must_use]
119pub fn clamp_position(
120 (left, top, width, height): (i32, i32, u32, u32),
121 x: i32,
122 y: i32,
123) -> (i32, i32) {
124 (
125 x.clamp(left, left.saturating_add_unsigned(width.saturating_sub(1))),
126 y.clamp(top, top.saturating_add_unsigned(height.saturating_sub(1))),
127 )
128}
129
130#[must_use]
132pub fn within_notch(notch: Bounds, x: i32) -> bool {
133 i64::from(x) >= i64::from(notch.x) && i64::from(x) < i64::from(notch.x) + i64::from(notch.width)
134}
135
136#[must_use]
138#[allow(clippy::cast_possible_truncation)] pub fn edge_push(
140 bounds @ (left, top, width, height): (i32, i32, u32, u32),
141 monitors: &[Bounds],
142 x: i32,
143 y: i32,
144 dx: i32,
145 dy: i32,
146) -> Option<(Edge, i32, f32, Bounds)> {
147 if width == 0 || height == 0 {
148 return None;
149 }
150 let monitor = geometry::nearest_monitor(bounds, monitors, x, y);
151 let (x, y) = clamp_position(monitor.rect(), x, y);
152 let (ml, mt, mw, mh) = monitor.rect();
153 let right = i64::from(ml) + i64::from(mw) - 1;
154 let bottom = i64::from(mt) + i64::from(mh) - 1;
155 let outside = |px, py| !monitors.iter().any(|m| m.contains(px, py));
156 let (edge, overshoot, position, start, length) =
157 if x == ml && dx < 0 && outside(i64::from(x) - 1, i64::from(y)) {
158 (Edge::Left, dx.saturating_neg(), y, top, height)
159 } else if i64::from(x) == right && dx > 0 && outside(right + 1, i64::from(y)) {
160 (Edge::Right, dx, y, top, height)
161 } else if y == mt && dy < 0 && outside(i64::from(x), i64::from(y) - 1) {
162 (Edge::Top, dy.saturating_neg(), x, left, width)
163 } else if i64::from(y) == bottom && dy > 0 && outside(i64::from(x), bottom + 1) {
164 (Edge::Bottom, dy, x, left, width)
165 } else {
166 return None;
167 };
168 let along =
169 (f64::from(position) - f64::from(start)) / f64::from(length.saturating_sub(1).max(1));
170 Some((edge, overshoot, along as f32, monitor))
171}
172
173#[must_use]
175pub fn corner_push(
176 bounds @ (left, top, width, height): (i32, i32, u32, u32),
177 monitors: &[Bounds],
178 x: i32,
179 y: i32,
180 dx: i32,
181 dy: i32,
182) -> Option<(Edge, Corner)> {
183 let (edge, _, _, monitor) = edge_push(bounds, monitors, x, y, dx, dy)?;
184 let (x, y) = clamp_position(monitor.rect(), x, y);
185 let on_bbox = match edge {
186 Edge::Left => monitor.x == left,
187 Edge::Right => {
188 i64::from(monitor.x) + i64::from(monitor.width) == i64::from(left) + i64::from(width)
189 }
190 Edge::Top => monitor.y == top,
191 Edge::Bottom => {
192 i64::from(monitor.y) + i64::from(monitor.height) == i64::from(top) + i64::from(height)
193 }
194 Edge::Unspecified => false,
195 };
196 if !on_bbox {
197 return None;
198 }
199 let right = i64::from(left) + i64::from(width) - 1;
200 let bottom = i64::from(top) + i64::from(height) - 1;
201 let (position, start, end, delta) = match edge {
202 Edge::Left | Edge::Right => (y, top, bottom, dy),
203 Edge::Top | Edge::Bottom => (x, left, right, dx),
204 Edge::Unspecified => return None,
205 };
206 let at_end = if delta < 0 && i64::from(position) - i64::from(start) <= CORNER_BOX {
207 false
208 } else if delta > 0 && end - i64::from(position) <= CORNER_BOX {
209 true
210 } else {
211 return None;
212 };
213 let corner = match (edge, at_end) {
214 (Edge::Left | Edge::Top, false) => Corner::TopLeft,
215 (Edge::Right, false) | (Edge::Top, true) => Corner::TopRight,
216 (Edge::Left, true) | (Edge::Bottom, false) => Corner::BottomLeft,
217 (Edge::Right | Edge::Bottom, true) => Corner::BottomRight,
218 (Edge::Unspecified, _) => return None,
219 };
220 Some((edge, corner))
221}
222
223#[cfg(test)]
224mod tests;