156 lines
4.1 KiB
Rust
156 lines
4.1 KiB
Rust
// 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<Rect> {
|
|
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,
|
|
},
|
|
]
|
|
);
|
|
}
|
|
}
|