1use blockworx_geom::{Bounded, Bounds};
7
8pub struct ZoomBounds;
9
10impl Bounds for ZoomBounds {
11 const MIN: f32 = 0.1;
12 const MAX: f32 = 10.0;
13}
14
15#[derive(
18 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
19)]
20#[serde(transparent)]
21pub struct Zoom(Bounded<ZoomBounds>);
22
23impl Zoom {
24 pub const fn new(scale: f32) -> Self {
25 Self(Bounded::new(scale))
26 }
27
28 pub const fn get(self) -> f32 {
29 self.0.get()
30 }
31
32 pub const fn unity() -> Self {
33 Self::new(1.0)
34 }
35
36 pub const fn min_value() -> Self {
37 Self(Bounded::min_value())
38 }
39
40 pub const fn max_value() -> Self {
41 Self(Bounded::max_value())
42 }
43
44 pub const WORKED_MIN: Self = Self::new(0.25);
48 pub const WORKED_MAX: Self = Self::new(4.0);
49
50 pub fn framing(
54 viewport: blockworx_geom::Vec2,
55 content: blockworx_geom::Vec2,
56 max_zoom: Self,
57 ) -> Self {
58 let zoom = (viewport.x / content.x).min(viewport.y / content.y);
59 Self::new(zoom.min(max_zoom.get()))
60 }
61}
62
63impl From<Zoom> for f32 {
64 fn from(zoom: Zoom) -> f32 {
65 zoom.get()
66 }
67}
68
69#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
72pub enum ZoomStep {
73 In,
74 Out,
75}
76
77impl ZoomStep {
78 pub fn factor(self) -> Factor {
81 const STEP: f32 = 1.25;
82 Factor::new(match self {
83 ZoomStep::In => STEP,
84 ZoomStep::Out => 1.0 / STEP,
85 })
86 }
87}
88
89#[derive(Clone, Copy, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
92pub struct Factor(f32);
93
94const SCROLL_ZOOM_RATE: f32 = 0.002;
97
98#[derive(Clone, Copy, PartialEq, Debug)]
102pub struct ScrollPx(f32);
103
104impl ScrollPx {
105 pub const fn up(pixels: f32) -> Self {
106 Self(pixels)
107 }
108}
109
110impl Factor {
111 pub const IDENTITY: Self = Self(1.0);
113
114 pub const fn new(factor: f32) -> Self {
115 Self(factor)
116 }
117
118 #[must_use]
120 pub fn of_scroll(scroll: ScrollPx) -> Self {
121 Self((scroll.0 * SCROLL_ZOOM_RATE).exp())
122 }
123
124 pub const fn get(self) -> f32 {
125 self.0
126 }
127}
128
129impl From<Factor> for f32 {
130 fn from(factor: Factor) -> Self {
131 factor.0
132 }
133}
134
135#[cfg(test)]
136mod scroll_tests {
137 use super::*;
138
139 #[test]
140 fn scrolling_up_magnifies_and_scrolling_down_shrinks() {
141 assert_eq!(Factor::of_scroll(ScrollPx::up(0.0)), Factor::IDENTITY);
142 assert!(Factor::of_scroll(ScrollPx::up(50.0)).get() > 1.0);
143 assert!(Factor::of_scroll(ScrollPx::up(-50.0)).get() < 1.0);
144 }
145
146 #[test]
149 fn opposite_notches_cancel() {
150 let there = Factor::of_scroll(ScrollPx::up(37.0)).get();
151 let back = Factor::of_scroll(ScrollPx::up(-37.0)).get();
152 assert!((there * back - 1.0).abs() < 1e-5, "{there} {back}");
153 }
154}