1use crate::{
2 defaults::zoom::*,
3 error::{ConfigError, Result},
4};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(transparent)]
10pub struct ZoomSteps(Vec<f64>);
11
12impl ZoomSteps {
13 pub fn new(mut steps: Vec<f64>) -> Result<Self> {
14 if steps.is_empty() {
15 return Err(ConfigError::ValidationError(
16 "Zoom steps cannot be empty".into(),
17 ));
18 }
19 steps.sort_by(|a, b| {
20 a.partial_cmp(b)
21 .unwrap_or(std::cmp::Ordering::Equal)
22 });
23 steps.dedup_by(|a, b| (*a - *b).abs() < f64::EPSILON);
24 Ok(Self(steps))
25 }
26
27 pub fn as_slice(&self) -> &[f64] {
28 &self.0
29 }
30}
31
32impl Default for ZoomSteps {
33 fn default() -> Self {
34 Self::new(DEFAULT_ZOOM_STEPS.to_vec())
35 .expect("Default zoom steps must be valid")
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
40pub enum FitMode {
41 OneToOne,
43 FitLonger,
45 FitShorter,
47 Custom,
49}
50
51impl Default for FitMode {
52 fn default() -> Self {
53 FitMode::FitLonger
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ZoomConfig {
59 pub min_zoom: f64,
60 pub max_zoom: f64,
61 pub default_zoom: f64,
62 pub zoom_step: f64,
63 pub use_predefined_steps: bool,
64 pub zoom_steps: ZoomSteps,
65 pub focal_point_enabled: bool,
66 pub transition_enabled: bool,
67 pub transition_duration: f64,
68 pub fit_to_window: bool,
69 pub maintain_aspect_ratio: bool,
70 pub default_fit_mode: FitMode,
71}
72
73impl Default for ZoomConfig {
74 fn default() -> Self {
75 Self {
76 min_zoom: MIN_ZOOM,
77 max_zoom: MAX_ZOOM,
78 default_zoom: DEFAULT_ZOOM,
79 zoom_step: ZOOM_STEP,
80 use_predefined_steps: USE_PREDEFINED_STEPS,
81 zoom_steps: ZoomSteps::default(),
82 focal_point_enabled: FOCAL_POINT_ENABLED,
83 transition_enabled: TRANSITION_ENABLED,
84 transition_duration: TRANSITION_DURATION,
85 fit_to_window: FIT_TO_WINDOW,
86 maintain_aspect_ratio: MAINTAIN_ASPECT_RATIO,
87 default_fit_mode: FitMode::default(),
88 }
89 }
90}
91
92impl ZoomConfig {
93 pub fn validate(&self) -> Result<()> {
94 if self.min_zoom <= 0.0 {
95 return Err(ConfigError::ValidationError(
96 "min_zoom must be positive".into(),
97 ));
98 }
99
100 if self.max_zoom <= self.min_zoom {
101 return Err(ConfigError::ValidationError(format!(
102 "max_zoom ({}) must be greater than min_zoom ({})",
103 self.max_zoom, self.min_zoom
104 )));
105 }
106
107 if self.default_zoom < self.min_zoom
108 || self.default_zoom > self.max_zoom
109 {
110 return Err(ConfigError::ValidationError(format!(
111 "default_zoom must be between {} and {}",
112 self.min_zoom, self.max_zoom
113 )));
114 }
115
116 if self.zoom_step <= 0.0 {
117 return Err(ConfigError::ValidationError(
118 "zoom_step must be positive".into(),
119 ));
120 }
121
122 if self.transition_duration < 0.0 {
123 return Err(ConfigError::ValidationError(
124 "transition_duration cannot be negative".into(),
125 ));
126 }
127
128 if self.use_predefined_steps {
129 for &step in self.zoom_steps.as_slice() {
130 if step < self.min_zoom || step > self.max_zoom {
131 return Err(ConfigError::ValidationError(format!(
132 "zoom step {} is outside allowed range [{}, {}]",
133 step, self.min_zoom, self.max_zoom
134 )));
135 }
136 }
137 }
138
139 Ok(())
140 }
141}