leftwm_core/models/
xyhw.rs

1//! Various window and workspace sizing structs.
2#![allow(clippy::module_name_repetitions)]
3use leftwm_layouts::geometry::Rect;
4use serde::{Deserialize, Serialize};
5use std::cmp;
6use std::ops::Add;
7use std::ops::Sub;
8
9/// Struct containing min/max width and height and window placement. x,y from top left.
10#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy)]
11pub struct Xyhw {
12    x: i32,
13    y: i32,
14    h: i32,
15    w: i32,
16    minw: i32,
17    maxw: i32,
18    minh: i32,
19    maxh: i32,
20}
21
22/// Modifiable struct that can be used to generate an Xyhw struct. Contains min/max width and
23/// height and window placement. x,y from top left.
24#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy)]
25pub struct XyhwBuilder {
26    pub x: i32,
27    pub y: i32,
28    pub h: i32,
29    pub w: i32,
30    pub minw: i32,
31    pub maxw: i32,
32    pub minh: i32,
33    pub maxh: i32,
34}
35
36impl Default for XyhwBuilder {
37    fn default() -> Self {
38        Self {
39            x: 0,
40            y: 0,
41            w: 0,
42            h: 0,
43            minw: -999_999_999,
44            maxw: 999_999_999,
45            minh: -999_999_999,
46            maxh: 999_999_999,
47        }
48    }
49}
50
51impl Default for Xyhw {
52    fn default() -> Self {
53        Self {
54            x: 0,
55            y: 0,
56            w: 0,
57            h: 0,
58            minw: -999_999_999,
59            maxw: 999_999_999,
60            minh: -999_999_999,
61            maxh: 999_999_999,
62        }
63    }
64}
65
66impl Add for Xyhw {
67    type Output = Self;
68    fn add(self, other: Self) -> Self {
69        Self {
70            x: self.x + other.x,
71            y: self.y + other.y,
72            w: self.w + other.w,
73            h: self.h + other.h,
74            minw: cmp::max(self.minw, other.minw),
75            maxw: cmp::min(self.maxw, other.maxw),
76            minh: cmp::max(self.minh, other.minh),
77            maxh: cmp::min(self.maxh, other.maxh),
78        }
79    }
80}
81
82impl Sub for Xyhw {
83    type Output = Self;
84    fn sub(self, other: Self) -> Self {
85        Self {
86            x: self.x - other.x,
87            y: self.y - other.y,
88            w: self.w - other.w,
89            h: self.h - other.h,
90            minw: cmp::max(self.minw, other.minw),
91            maxw: cmp::min(self.maxw, other.maxw),
92            minh: cmp::max(self.minh, other.minh),
93            maxh: cmp::min(self.maxh, other.maxh),
94        }
95    }
96}
97
98impl From<XyhwBuilder> for Xyhw {
99    fn from(xywh: XyhwBuilder) -> Self {
100        let mut b = Self {
101            x: xywh.x,
102            y: xywh.y,
103            w: xywh.w,
104            h: xywh.h,
105            minw: xywh.minw,
106            maxw: xywh.maxw,
107            minh: xywh.minh,
108            maxh: xywh.maxh,
109        };
110        b.update_limits();
111        b
112    }
113}
114
115impl From<Rect> for Xyhw {
116    fn from(rect: Rect) -> Self {
117        Self {
118            x: rect.x,
119            y: rect.y,
120            w: rect.w as i32,
121            h: rect.h as i32,
122            ..Default::default()
123        }
124    }
125}
126
127impl From<Xyhw> for Rect {
128    fn from(xyhw: Xyhw) -> Self {
129        Rect {
130            x: xyhw.x,
131            y: xyhw.y,
132            w: xyhw.w.unsigned_abs(),
133            h: xyhw.h.unsigned_abs(),
134        }
135    }
136}
137
138impl Xyhw {
139    #[must_use]
140    pub const fn x(&self) -> i32 {
141        self.x
142    }
143    #[must_use]
144    pub const fn y(&self) -> i32 {
145        self.y
146    }
147    #[must_use]
148    pub const fn h(&self) -> i32 {
149        self.h
150    }
151    #[must_use]
152    pub const fn w(&self) -> i32 {
153        self.w
154    }
155
156    #[must_use]
157    pub const fn minw(&self) -> i32 {
158        self.minw
159    }
160    #[must_use]
161    pub const fn maxw(&self) -> i32 {
162        self.maxw
163    }
164    #[must_use]
165    pub const fn minh(&self) -> i32 {
166        self.minh
167    }
168    #[must_use]
169    pub const fn maxh(&self) -> i32 {
170        self.maxh
171    }
172
173    pub fn clear_minmax(&mut self) {
174        self.minw = -999_999_999;
175        self.maxw = 999_999_999;
176        self.minh = -999_999_999;
177        self.maxh = 999_999_999;
178        self.update_limits();
179    }
180
181    pub fn set_x(&mut self, value: i32) {
182        self.x = value;
183        self.update_limits();
184    }
185    pub fn set_y(&mut self, value: i32) {
186        self.y = value;
187        self.update_limits();
188    }
189    pub fn set_h(&mut self, value: i32) {
190        self.h = value;
191        self.update_limits();
192    }
193    pub fn set_w(&mut self, value: i32) {
194        self.w = value;
195        self.update_limits();
196    }
197
198    pub fn set_minw(&mut self, value: i32) {
199        self.minw = value;
200        self.update_limits();
201    }
202    pub fn set_maxw(&mut self, value: i32) {
203        self.maxw = value;
204        self.update_limits();
205    }
206    pub fn set_minh(&mut self, value: i32) {
207        self.minh = value;
208        self.update_limits();
209    }
210    pub fn set_maxh(&mut self, value: i32) {
211        self.maxh = value;
212        self.update_limits();
213    }
214
215    fn update_limits(&mut self) {
216        if self.h > self.maxh {
217            self.h = self.maxh;
218        }
219        if self.w > self.maxw {
220            self.w = self.maxw;
221        }
222        if self.h < self.minh {
223            self.h = self.minh;
224        }
225        if self.w < self.minw {
226            self.w = self.minw;
227        }
228    }
229
230    #[must_use]
231    pub const fn contains_point(&self, x: i32, y: i32) -> bool {
232        let max_x = self.x + self.w;
233        let max_y = self.y + self.h;
234        (self.x <= x && x <= max_x) && (self.y <= y && y <= max_y)
235    }
236
237    pub const fn contains_xyhw(&self, other: &Self) -> bool {
238        let other_max_x = other.x + other.w;
239        let other_max_y = other.y + other.h;
240        self.contains_point(other.x, other.y) && self.contains_point(other_max_x, other_max_y)
241    }
242
243    #[must_use]
244    pub const fn volume(&self) -> u64 {
245        self.h as u64 * self.w as u64
246    }
247
248    /// Trim a Xyhw out of another Xyhw so that they don't overlap.
249    #[must_use]
250    pub const fn without(&self, other: &Self) -> Self {
251        let mut without = *self;
252        if other.w > other.h {
253            // horizontal trim
254            if other.y > self.y + (self.h / 2) {
255                // bottom check
256                let bottom_over = (without.y + without.h) - other.y;
257                if bottom_over > 0 {
258                    without.h -= bottom_over;
259                }
260            } else {
261                // top check
262                let top_over = (other.y + other.h) - without.y;
263                if top_over > 0 {
264                    without.y += top_over;
265                    without.h -= top_over;
266                }
267            }
268        } else {
269            // vertical trim
270            let left_over = (other.x + other.w) - without.x;
271            if other.x > self.x + (self.w / 2) {
272                // right check
273                let right_over = (without.x + without.w) - other.x;
274                if right_over > 0 {
275                    without.w -= right_over;
276                }
277            } else {
278                // left check
279                if left_over > 0 {
280                    without.x += left_over;
281                    without.w -= left_over;
282                }
283            }
284        }
285        without
286    }
287
288    #[must_use]
289    pub fn center_halfed(&self) -> Self {
290        XyhwBuilder {
291            x: self.x + (self.w / 2) - (self.w / 4),
292            y: self.y + (self.h / 2) - (self.h / 4),
293            h: (self.h / 2),
294            w: (self.w / 2),
295            ..XyhwBuilder::default()
296        }
297        .into()
298    }
299
300    pub fn center_relative(&mut self, outer: Self, border: i32) {
301        // If this is a Splash screen, for some reason w/h are sometimes not sized properly. We
302        // therefore want to use the minimum or maximum sizing instead in order to center the
303        // windows properly.
304        self.x = outer.x() + outer.w() / 2 - self.w / 2 - border;
305        self.y = outer.y() + outer.h() / 2 - self.h / 2 - border;
306    }
307
308    pub const fn center(&self) -> (i32, i32) {
309        let x = self.x + (self.w / 2);
310        let y = self.y + (self.h / 2);
311        (x, y)
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn center_halfed() {
321        let a = Xyhw {
322            x: 10,
323            y: 10,
324            w: 2000,
325            h: 1000,
326            ..Xyhw::default()
327        };
328        let correct = Xyhw {
329            x: 510,
330            y: 260,
331            w: 1000,
332            h: 500,
333            ..Xyhw::default()
334        };
335        let result = a.center_halfed();
336        assert_eq!(result, correct);
337    }
338
339    #[test]
340    fn without_should_trim_from_the_top() {
341        let a = Xyhw {
342            y: 5,
343            h: 1000,
344            w: 1000,
345            ..Xyhw::default()
346        };
347        let b = Xyhw {
348            h: 10,
349            w: 100,
350            ..Xyhw::default()
351        };
352        let result = a.without(&b);
353        assert_eq!(
354            result,
355            Xyhw {
356                x: 0,
357                y: 10,
358                h: 995,
359                w: 1000,
360                ..Xyhw::default()
361            }
362        );
363    }
364
365    #[test]
366    fn without_should_trim_from_the_left() {
367        let a = Xyhw {
368            x: 0,
369            y: 0,
370            h: 1000,
371            w: 1000,
372            ..Xyhw::default()
373        };
374        let b = Xyhw {
375            h: 100,
376            w: 10,
377            ..Xyhw::default()
378        };
379        let result = a.without(&b);
380        assert_eq!(
381            result,
382            Xyhw {
383                x: 10,
384                y: 0,
385                w: 990,
386                h: 1000,
387                ..Xyhw::default()
388            }
389        );
390    }
391
392    #[test]
393    fn without_should_trim_from_the_bottom() {
394        let a = Xyhw {
395            x: 0,
396            y: 0,
397            h: 1000,
398            w: 1000,
399            ..Xyhw::default()
400        };
401        let b = Xyhw {
402            y: 990,
403            x: 0,
404            h: 10,
405            w: 100,
406            ..Xyhw::default()
407        };
408        let result = a.without(&b);
409        assert_eq!(
410            result,
411            Xyhw {
412                x: 0,
413                y: 0,
414                h: 990,
415                w: 1000,
416                ..Xyhw::default()
417            }
418        );
419    }
420
421    #[test]
422    fn without_should_trim_from_the_right() {
423        let a = Xyhw {
424            x: 0,
425            y: 0,
426            h: 1000,
427            w: 1000,
428            ..Xyhw::default()
429        };
430        let b = Xyhw {
431            x: 990,
432            y: 0,
433            h: 100,
434            w: 10,
435            ..Xyhw::default()
436        };
437        let result = a.without(&b);
438        assert_eq!(
439            result,
440            Xyhw {
441                x: 0,
442                y: 0,
443                w: 990,
444                h: 1000,
445                ..Xyhw::default()
446            }
447        );
448    }
449
450    #[test]
451    fn contains_xyhw_should_detect_a_inner_window() {
452        let a = Xyhw {
453            x: 0,
454            y: 0,
455            h: 1000,
456            w: 1000,
457            ..Xyhw::default()
458        };
459        let b = Xyhw {
460            x: 100,
461            y: 100,
462            h: 800,
463            w: 800,
464            ..Xyhw::default()
465        };
466        assert!(a.contains_xyhw(&b));
467    }
468
469    #[test]
470    fn contains_xyhw_should_detect_a_upper_left_corner_outside() {
471        let a = Xyhw {
472            x: 100,
473            y: 100,
474            h: 800,
475            w: 800,
476            ..Xyhw::default()
477        };
478        let b = Xyhw {
479            x: 0,
480            y: 0,
481            h: 200,
482            w: 200,
483            ..Xyhw::default()
484        };
485        assert!(!a.contains_xyhw(&b));
486    }
487
488    #[test]
489    fn contains_xyhw_should_detect_a_lower_right_corner_outside() {
490        let a = Xyhw {
491            x: 100,
492            y: 100,
493            h: 800,
494            w: 800,
495            ..Xyhw::default()
496        };
497        let b = Xyhw {
498            x: 800,
499            y: 800,
500            h: 200,
501            w: 200,
502            ..Xyhw::default()
503        };
504        assert!(!a.contains_xyhw(&b));
505    }
506}