// client/src/output/layout.rs use super::display::MonitorInfo; use tracing::debug; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rect { pub x: i32, pub y: i32, pub w: i32, pub h: i32, } /// Compute rectangles for the current monitor topology. /// /// Inputs: the ordered monitor list plus the active video streams and their /// nominal sizes. /// Outputs: one rectangle per stream, laid out across the available monitors /// or split across a single display when only one monitor is present. /// Why: the display policy is pure so the monitor selection logic can be /// exercised by unit tests without GTK or Wayland state. pub fn assign_rectangles( monitors: &[MonitorInfo], streams: &[(&str, i32, i32)], // (name, w, h) ) -> Vec { let mut rects = vec![ Rect { x: 0, y: 0, w: 0, h: 0 }; streams.len() ]; match monitors.len() { 0 => return rects, // impossible, but keep compiler happy 1 => { // One monitor: side-by-side layout, full height let m = &monitors[0].geometry; let count = streams.len().max(1) as i32; let base_w = m.width() / count; let mut x = m.x(); for (idx, _stream) in streams.iter().enumerate() { let w = if idx == streams.len() - 1 { m.width() - base_w * (count - 1) } else { base_w }; rects[idx] = Rect { x, y: m.y(), w, h: m.height(), }; x += w; } } _ => { // ≥2 monitors: map 1-to-1 until we run out, full screen per monitor for (idx, _stream) in streams.iter().enumerate() { if idx >= monitors.len() { break; } let m = &monitors[idx]; let geom = m.geometry; rects[idx] = Rect { x: geom.x(), y: geom.y(), w: geom.width(), h: geom.height(), }; } } } debug!("📐 final rectangles = {:?}", rects); rects } #[cfg(test)] mod tests { use super::{Rect, assign_rectangles}; use crate::output::display::MonitorInfo; use gtk::gdk; fn monitor(x: i32, y: i32, w: i32, h: i32, is_internal: bool) -> MonitorInfo { MonitorInfo { geometry: gdk::Rectangle::new(x, y, w, h), scale_factor: 1, is_internal, } } #[test] fn single_monitor_splits_streams_evenly() { let rects = assign_rectangles( &[monitor(10, 20, 1920, 1080, false)], &[("left", 0, 0), ("right", 0, 0)], ); assert_eq!( rects, vec![ Rect { x: 10, y: 20, w: 960, h: 1080, }, Rect { x: 970, y: 20, w: 960, h: 1080, }, ] ); } #[test] fn multiple_monitors_map_streams_one_to_one() { let rects = assign_rectangles( &[ monitor(0, 0, 1280, 720, false), monitor(1280, 0, 1920, 1080, true), ], &[("left", 0, 0), ("right", 0, 0), ("extra", 0, 0)], ); assert_eq!( rects, vec![ Rect { x: 0, y: 0, w: 1280, h: 720, }, Rect { x: 1280, y: 0, w: 1920, h: 1080, }, Rect { x: 0, y: 0, w: 0, h: 0, }, ] ); } }