git.lucas.co / cce-window-manager
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 }