| 1 | use crate::gpu::Viewport; |
| 2 | |
| 3 | /// The least and greatest scroll offsets on each axis, in device pixels: how far the |
| 4 | /// view's corner may sit from the page origin. |
| 5 | #[derive(Clone, Copy, Debug, PartialEq)] |
| 6 | pub struct Scroll { |
| 7 | pub min: [f32; 2], |
| 8 | pub max: [f32; 2], |
| 9 | } |
| 10 | |
| 11 | impl Scroll { |
| 12 | /// Each content rectangle comes with the room, in device pixels, OneNote leaves beyond |
| 13 | /// it up and toward the page's start: left, or right on a right-to-left page. |
| 14 | pub fn new( |
| 15 | viewport: Viewport, |
| 16 | bounds: impl Iterator<Item = ([f32; 4], f32)>, |
| 17 | rtl: bool, |
| 18 | ) -> Self { |
| 19 | let mut min = [0.0_f32; 2]; |
| 20 | let mut max = [0.0_f32; 2]; |
| 21 | let mut end = f32::NEG_INFINITY; |
| 22 | for (rect, pad) in bounds { |
| 23 | for axis in 0..2 { |
| 24 | min[axis] = min[axis].min(rect[axis] * viewport.scale - pad); |
| 25 | max[axis] = max[axis].max(rect[axis + 2]); |
| 26 | } |
| 27 | end = end.max(rect[2] * viewport.scale + pad); |
| 28 | } |
| 29 | // OneNote stops at the page origin unless content reaches past it; a right-to-left |
| 30 | // page ends where its content does. |
| 31 | for axis in 0..2 { |
| 32 | max[axis] = if rtl && axis == 0 && end.is_finite() { |
| 33 | end - viewport.size[0] as f32 |
| 34 | } else { |
| 35 | (max[axis] + 36.0) * viewport.scale - viewport.size[axis] as f32 |
| 36 | } |
| 37 | .max(min[axis]); |
| 38 | } |
| 39 | Self { min, max } |
| 40 | } |
| 41 | |
| 42 | pub(crate) fn clamp(&self, viewport: &mut Viewport) { |
| 43 | for axis in 0..2 { |
| 44 | viewport.origin[axis] = -(-viewport.origin[axis]).clamp(self.min[axis], self.max[axis]); |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | #[cfg(test)] |
| 50 | mod tests { |
| 51 | use super::*; |
| 52 | |
| 53 | #[test] |
| 54 | fn bounds_include_negative_objects_and_clamp_both_ends() { |
| 55 | let mut viewport = Viewport { |
| 56 | size: [800, 600], |
| 57 | scale: 2.0, |
| 58 | origin: [9999.0; 2], |
| 59 | }; |
| 60 | let scroll = Scroll::new( |
| 61 | viewport, |
| 62 | [([-80.0, -20.0, 1000.0, 1200.0], 11.0)].into_iter(), |
| 63 | false, |
| 64 | ); |
| 65 | scroll.clamp(&mut viewport); |
| 66 | assert_eq!(viewport.origin, [171.0, 51.0]); |
| 67 | viewport.origin = [-9999.0; 2]; |
| 68 | scroll.clamp(&mut viewport); |
| 69 | assert_eq!(viewport.origin, [-1272.0, -1872.0]); |
| 70 | } |
| 71 | |
| 72 | #[test] |
| 73 | fn pictures_reach_no_further_than_their_corner() { |
| 74 | let viewport = Viewport { |
| 75 | size: [800, 600], |
| 76 | scale: 2.0, |
| 77 | origin: [0.0; 2], |
| 78 | }; |
| 79 | let bounds = [ |
| 80 | ([-0.5, -22.35, 613.0, 141.3], 0.0), |
| 81 | ([36.0, 86.4, 400.0, 180.0], 11.0), |
| 82 | ]; |
| 83 | let scroll = Scroll::new(viewport, bounds.into_iter(), false); |
| 84 | assert_eq!(scroll.min, [-1.0, -44.7]); |
| 85 | } |
| 86 | |
| 87 | #[test] |
| 88 | fn right_to_left_pages_end_at_their_content() { |
| 89 | let viewport = Viewport { |
| 90 | size: [800, 600], |
| 91 | scale: 2.0, |
| 92 | origin: [0.0; 2], |
| 93 | }; |
| 94 | let bounds = [ |
| 95 | ([10835.0, 14.4, 10962.0, 28.0], 11.0), |
| 96 | ([11220.0, 84.0, 11334.0, 145.0], 0.0), |
| 97 | ]; |
| 98 | let scroll = Scroll::new(viewport, bounds.into_iter(), true); |
| 99 | assert_eq!(scroll.min, [0.0, 0.0]); |
| 100 | assert_eq!(scroll.max[0], 11334.0 * 2.0 - 800.0); |
| 101 | } |
| 102 | } |