window management library
git clone https://git.lucas.co/cce-window-manager.git
src/pan.rs (2.7K)
1 // Cell-aligned viewport panning.
2 //
3 // The desktop grid repeats every `period = cell_size + gap_width` virtual
4 // units, with cell k's rect starting at k*period. A pan offset is "aligned"
5 // when it is a multiple of the period: the viewport origin then sits exactly
6 // on a cell boundary, so the visible grid is in phase with the screen edge.
7 //
8 // `aligned_step` is the policy behind the PanLeft/Right/Up/Down actions: a
9 // press moves the viewport to the ADJACENT aligned offset in that direction —
10 // one full period from an aligned start, or just the remaining fraction from
11 // an unaligned one (a free-form pan re-aligns on the first keyed step rather
12 // than staying forever out of phase). The mechanism animates toward the
13 // returned value; repeated presses mid-flight should pass the current
14 // animation target as `current` so each press queues one more cell.
15
16 /// Offsets within this distance of an aligned value count as aligned — the
17 /// same tolerance the pan animation uses to snap onto its target, so a
18 /// finished animation's landing point steps a full period, never a crumb.
19 const ALIGN_EPSILON: f64 = 0.5;
20
21 /// The next cell-aligned pan offset from `current`, one step in `dir`
22 /// (negative = left/up, positive = right/down). Returns `current` unchanged
23 /// for a degenerate period or a zero direction.
24 pub fn aligned_step(current: f64, period: f64, dir: f64) -> f64 {
25 if period <= 0.0 || dir == 0.0 {
26 return current;
27 }
28 if dir > 0.0 {
29 (((current + ALIGN_EPSILON) / period).floor() + 1.0) * period
30 } else {
31 (((current - ALIGN_EPSILON) / period).ceil() - 1.0) * period
32 }
33 }
34
35 #[cfg(test)]
36 mod tests {
37 use super::*;
38
39 const P: f64 = 512.0;
40
41 #[test]
42 fn aligned_start_steps_a_full_period() {
43 assert_eq!(aligned_step(0.0, P, 1.0), 512.0);
44 assert_eq!(aligned_step(0.0, P, -1.0), -512.0);
45 assert_eq!(aligned_step(-1024.0, P, 1.0), -512.0);
46 }
47
48 #[test]
49 fn unaligned_start_realigns_first() {
50 // Mid-cell pans land on the adjacent boundary, not a full period out.
51 assert_eq!(aligned_step(100.0, P, 1.0), 512.0);
52 assert_eq!(aligned_step(100.0, P, -1.0), 0.0);
53 assert_eq!(aligned_step(-100.0, P, -1.0), -512.0);
54 }
55
56 #[test]
57 fn near_aligned_counts_as_aligned() {
58 // The animation stops within 0.5 of its target; a step from there
59 // must cross a whole period, not crawl onto the boundary it's on.
60 assert_eq!(aligned_step(511.9, P, 1.0), 1024.0);
61 assert_eq!(aligned_step(512.1, P, -1.0), 0.0);
62 }
63
64 #[test]
65 fn degenerate_inputs_are_inert() {
66 assert_eq!(aligned_step(100.0, 0.0, 1.0), 100.0);
67 assert_eq!(aligned_step(100.0, -5.0, 1.0), 100.0);
68 assert_eq!(aligned_step(100.0, P, 0.0), 100.0);
69 }
70 }