rwpspread 0.5.1

Multi-Monitor Wallpaper Spanning Utility
use crate::helpers::Helpers;
use crate::wayland::Monitor;
use std::collections::HashMap;
use std::i32;

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LayoutMonitor {
    pub x1: i32,
    pub y1: i32,
    pub x2: i32,
    pub y2: i32,
    pub initial_width: u32,
    pub initial_height: u32,
    pub width: u32,
    pub height: u32,
}

impl LayoutMonitor {
    /// Convert from Monitor to self
    pub fn from_monitor(monitor: &Monitor) -> Self {
        let x1 = monitor.x.min(monitor.x + monitor.width as i32);
        let x2 = monitor.x.max(monitor.x + monitor.width as i32);
        let y1 = monitor.y.min(monitor.y + monitor.height as i32);
        let y2 = monitor.y.max(monitor.y + monitor.height as i32);
        Self {
            x1,
            y1,
            x2,
            y2,
            initial_width: monitor.width,
            initial_height: monitor.height,
            width: monitor.width,
            height: monitor.height,
        }
    }
    /// Apply translation
    fn translate(&mut self, dx: i32, dy: i32) {
        self.x1 += dx;
        self.x2 += dx;
        self.y1 += dy;
        self.y2 += dy;
    }
    /// Calculate and return the absolute shift amount based on the difference of the scaled and inital width
    fn abs_shift_diff(&self) -> (i32, i32) {
        let x_diff: i32 = self.initial_width as i32 - self.width as i32;
        let y_diff: i32 = self.initial_height as i32 - self.height as i32;

        ((x_diff / 2).abs(), (y_diff / 2).abs())
    }
    /// Scale and save the new monitor size based on scale factor
    fn scale(&mut self, scale_factor: f32) -> &mut Self {
        self.width = Helpers::round_2((self.width as f32 * scale_factor).round() as u32);
        self.height = Helpers::round_2((self.height as f32 * scale_factor).round() as u32);

        let (x_shift, y_shift) = self.abs_shift_diff();

        if self.initial_width < self.width {
            self.x1 -= x_shift;
            self.x2 += x_shift;
        } else {
            self.x1 += x_shift;
            self.x2 -= x_shift;
        }

        if self.initial_height < self.height {
            self.y1 -= y_shift;
            self.y2 += y_shift;
        } else {
            self.y1 += y_shift;
            self.y2 -= y_shift;
        }

        self
    }
    /// Calculate and return ppi based on monitor diagonal in inches
    pub fn ppi(&self, diagonal_inches: u32) -> u32 {
        let diagonal_pixels = ((self.width).pow(2) + (self.height).pow(2)).isqrt() as u64;

        (diagonal_pixels as f64 / (diagonal_inches as f64)).round() as u32
    }
    /// Calculate and return ppi based on monitor diagonal in inches
    pub fn ppi_scale(&mut self, diagonal_inches: u32, max_ppi: u32) -> &mut Self {
        let factor = max_ppi as f32 / self.ppi(diagonal_inches) as f32;

        self.scale(factor)
    }
}

pub struct Layout {
    pub monitors: Vec<LayoutMonitor>,
    pub ppi_advice: bool,
}
impl Layout {
    /// Create a new layout from input monitors
    pub fn from_monitors(monitors: &[Monitor]) -> Self {
        let mut layout_monitors: Vec<LayoutMonitor> = Vec::with_capacity(monitors.len());

        for monitor in monitors {
            layout_monitors.push(LayoutMonitor::from_monitor(monitor));
        }

        let all_same_resolution = layout_monitors
            .iter()
            .map(|this| {
                let result = layout_monitors.iter().all(|monitor| {
                    (monitor.initial_width == this.initial_width
                        && monitor.initial_height == this.initial_height)
                        || (monitor.initial_height == this.initial_width
                            && monitor.initial_width == this.initial_height)
                });
                result
            })
            .all(|result| result == true);

        Self {
            monitors: layout_monitors,
            ppi_advice: !all_same_resolution,
        }
    }
    /// Normalize a set of monitors so that all coordinates are positive
    fn normalize_to_positive(&mut self) {
        let min_x = self.monitors.iter().map(|r| r.x1).fold(i32::MAX, i32::min);
        let min_y = self.monitors.iter().map(|r| r.y1).fold(i32::MAX, i32::min);

        let dx = if min_x < 0 { -min_x } else { 0 };
        let dy = if min_y < 0 { -min_y } else { 0 };

        for r in self.monitors.iter_mut() {
            r.translate(dx, dy);
        }
    }
    /// Calculate maximum ppi value from layout's monitors
    fn calculate_max_ppi(&self, diagonals: &HashMap<String, u32>) -> u32 {
        if let Some(ppi_max) = &self
            .monitors
            .iter()
            .zip(diagonals)
            .map(|(monitor, (_, &diagonal))| monitor.ppi(diagonal))
            .max()
        {
            return ppi_max.to_owned();
        } else {
            return 0;
        }
    }
    /// Compensate for different ppi values of monitors by scaling them dynamically
    pub fn compensate_ppi(&mut self, diagonals: &HashMap<String, u32>) {
        let max_ppi = self.calculate_max_ppi(&diagonals);

        for (r, (_, &d)) in self.monitors.iter_mut().zip(diagonals) {
            r.ppi_scale(d, max_ppi);
        }
    }
    /// Resolves touching and overlapping monitors
    pub fn resolve_layout(&mut self, padding: u32, max_iterations: usize) {
        for _ in 0..max_iterations {
            let mut changed = false;

            for i in 0..self.monitors.len() {
                for j in (i + 1)..self.monitors.len() {
                    let (mut a, mut b) = (self.monitors[i], self.monitors[j]);

                    let (x_overlap, y_overlap);
                    if padding > 0 {
                        x_overlap = a.x2 >= b.x1 && a.x1 <= b.x2;
                        y_overlap = a.y2 >= b.y1 && a.y1 <= b.y2;
                    } else {
                        x_overlap = a.x2 > b.x1 && a.x1 < b.x2;
                        y_overlap = a.y2 > b.y1 && a.y1 < b.y2;
                    }

                    if x_overlap && y_overlap {
                        // Compute overlap depth
                        let overlap_x = (a.x2.min(b.x2) - a.x1.max(b.x1)).max(0);
                        let overlap_y = (a.y2.min(b.y2) - a.y1.max(b.y1)).max(0);

                        // Determine smallest axis of overlap
                        if overlap_x < overlap_y {
                            let dir = if a.x1 < b.x1 { -1 } else { 1 };
                            let move_dist = (overlap_x + padding as i32) / 2;
                            a.translate(dir * move_dist, 0);
                            b.translate(-dir * move_dist, 0);
                        } else {
                            let dir = if a.y1 < b.y1 { -1 } else { 1 };
                            let move_dist = (overlap_y + padding as i32) / 2;
                            a.translate(0, dir * move_dist);
                            b.translate(0, -dir * move_dist);
                        }

                        self.monitors[i] = a;
                        self.monitors[j] = b;
                        changed = true;
                    }
                }
            }

            if !changed {
                break;
            }
        }

        self.normalize_to_positive();
    }
}