focus_tracker_core/
config.rs1use bon::bon;
2
3use crate::{FocusTrackerError, FocusTrackerResult};
4use std::time::Duration;
5
6fn validate_icon_size(size: u32) -> FocusTrackerResult<u32> {
7 if size == 0 {
8 return Err(FocusTrackerError::InvalidConfig {
9 reason: "icon size cannot be zero".into(),
10 });
11 }
12 if size > 512 {
13 return Err(FocusTrackerError::InvalidConfig {
14 reason: "icon size cannot be greater than 512 pixels".into(),
15 });
16 }
17 Ok(size)
18}
19
20fn validate_poll_interval(interval: Duration) -> FocusTrackerResult<Duration> {
21 if interval.is_zero() {
22 return Err(FocusTrackerError::InvalidConfig {
23 reason: "poll interval cannot be zero".into(),
24 });
25 }
26 if interval > Duration::from_secs(10) {
27 return Err(FocusTrackerError::InvalidConfig {
28 reason: "poll interval cannot be greater than 10 seconds".into(),
29 });
30 }
31 Ok(interval)
32}
33
34fn validate_icon_cache_capacity(capacity: usize) -> FocusTrackerResult<usize> {
35 if capacity == 0 {
36 return Err(FocusTrackerError::InvalidConfig {
37 reason: "icon cache capacity cannot be zero".into(),
38 });
39 }
40 Ok(capacity)
41}
42
43#[derive(Debug, Clone)]
44pub struct IconConfig {
45 pub size: Option<u32>,
46 pub filter_type: image::imageops::FilterType,
47}
48
49impl Default for IconConfig {
50 fn default() -> Self {
51 Self {
52 size: None,
53 filter_type: image::imageops::FilterType::Lanczos3,
54 }
55 }
56}
57
58#[bon]
59impl IconConfig {
60 #[builder]
77 pub fn new(
78 #[builder(with = |size: u32| -> Result<_, FocusTrackerError> {
79 validate_icon_size(size)
80 })]
81 size: Option<u32>,
82
83 #[builder(default = image::imageops::FilterType::Lanczos3)]
84 filter_type: image::imageops::FilterType,
85 ) -> Self {
86 Self { size, filter_type }
87 }
88}
89
90impl IconConfig {
91 #[must_use]
92 pub fn get_size_or_default(&self) -> u32 {
93 self.size.unwrap_or(128)
94 }
95}
96
97#[derive(Debug, Clone)]
98pub struct FocusTrackerConfig {
99 pub poll_interval: Duration,
100 pub icon: IconConfig,
101 pub icon_cache_capacity: usize,
102}
103
104impl Default for FocusTrackerConfig {
105 fn default() -> Self {
106 Self {
107 poll_interval: Duration::from_millis(100),
108 icon: IconConfig::default(),
109 icon_cache_capacity: 64,
110 }
111 }
112}
113
114#[bon]
115impl FocusTrackerConfig {
116 #[builder]
131 pub fn new(
132 #[builder(
133 default = Duration::from_millis(100),
134 with = |interval: Duration| -> Result<_, FocusTrackerError> {
135 validate_poll_interval(interval)
136 },
137 )]
138 poll_interval: Duration,
139 #[builder(default)] icon: IconConfig,
140 #[builder(
141 default = 64,
142 with = |capacity: usize| -> Result<_, FocusTrackerError> {
143 validate_icon_cache_capacity(capacity)
144 },
145 )]
146 icon_cache_capacity: usize,
147 ) -> Self {
148 Self {
149 poll_interval,
150 icon,
151 icon_cache_capacity,
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn default_icon_config() {
162 let config = IconConfig::default();
163 assert_eq!(config.size, None);
164 assert_eq!(config.get_size_or_default(), 128);
165 }
166
167 #[test]
168 fn icon_builder_defaults() {
169 let config = IconConfig::builder().build();
170 assert_eq!(config.size, None);
171 assert_eq!(config.get_size_or_default(), 128);
172 }
173
174 #[test]
175 fn icon_builder_with_size() {
176 let config = IconConfig::builder().size(256).unwrap().build();
177 assert_eq!(config.size, Some(256));
178 assert_eq!(config.get_size_or_default(), 256);
179 }
180
181 #[test]
182 fn icon_builder_max_size() {
183 let config = IconConfig::builder().size(512).unwrap().build();
184 assert_eq!(config.size, Some(512));
185 }
186
187 #[test]
188 fn icon_builder_min_size() {
189 let config = IconConfig::builder().size(1).unwrap().build();
190 assert_eq!(config.size, Some(1));
191 }
192
193 #[test]
194 fn icon_builder_zero_size_errors() {
195 assert!(IconConfig::builder().size(0).is_err());
196 }
197
198 #[test]
199 fn icon_builder_oversized_errors() {
200 assert!(IconConfig::builder().size(513).is_err());
201 assert!(IconConfig::builder().size(1024).is_err());
202 }
203
204 #[test]
205 fn icon_builder_custom_filter() {
206 let config = IconConfig::builder()
207 .filter_type(image::imageops::FilterType::Nearest)
208 .build();
209 assert!(matches!(
210 config.filter_type,
211 image::imageops::FilterType::Nearest
212 ));
213 }
214
215 #[test]
216 fn default_config() {
217 let config = FocusTrackerConfig::default();
218 assert_eq!(config.poll_interval, Duration::from_millis(100));
219 assert_eq!(config.icon.size, None);
220 assert_eq!(config.icon_cache_capacity, 64);
221 }
222
223 #[test]
224 fn config_builder_defaults() {
225 let config = FocusTrackerConfig::builder().build();
226 assert_eq!(config.poll_interval, Duration::from_millis(100));
227 assert_eq!(config.icon.size, None);
228 assert_eq!(config.icon_cache_capacity, 64);
229 }
230
231 #[test]
232 fn config_builder_icon_cache_capacity() {
233 let config = FocusTrackerConfig::builder()
234 .icon_cache_capacity(128)
235 .unwrap()
236 .build();
237 assert_eq!(config.icon_cache_capacity, 128);
238 }
239
240 #[test]
241 fn config_builder_zero_cache_capacity_errors() {
242 assert!(
243 FocusTrackerConfig::builder()
244 .icon_cache_capacity(0)
245 .is_err()
246 );
247 }
248
249 #[test]
250 fn config_builder_poll_interval() {
251 let config = FocusTrackerConfig::builder()
252 .poll_interval(Duration::from_millis(250))
253 .unwrap()
254 .build();
255 assert_eq!(config.poll_interval, Duration::from_millis(250));
256 }
257
258 #[test]
259 fn config_builder_max_interval() {
260 let config = FocusTrackerConfig::builder()
261 .poll_interval(Duration::from_secs(10))
262 .unwrap()
263 .build();
264 assert_eq!(config.poll_interval, Duration::from_secs(10));
265 }
266
267 #[test]
268 fn config_builder_zero_interval_errors() {
269 assert!(
270 FocusTrackerConfig::builder()
271 .poll_interval(Duration::ZERO)
272 .is_err()
273 );
274 }
275
276 #[test]
277 fn config_builder_large_interval_errors() {
278 assert!(
279 FocusTrackerConfig::builder()
280 .poll_interval(Duration::from_secs(11))
281 .is_err()
282 );
283 }
284
285 #[test]
286 fn config_builder_with_icon() {
287 let icon = IconConfig::builder().size(64).unwrap().build();
288 let config = FocusTrackerConfig::builder().icon(icon).build();
289 assert_eq!(config.icon.size, Some(64));
290 }
291
292 #[test]
293 fn config_builder_full() {
294 let config = FocusTrackerConfig::builder()
295 .poll_interval(Duration::from_millis(50))
296 .unwrap()
297 .icon(IconConfig::builder().size(64).unwrap().build())
298 .build();
299
300 assert_eq!(config.poll_interval, Duration::from_millis(50));
301 assert_eq!(config.icon.size, Some(64));
302 assert_eq!(config.icon.get_size_or_default(), 64);
303 }
304}