use super::{FloatingSpace, ScrollingSpace, Workspace, WorkspaceId};
use crate::common::{Rect, WindowId};
pub struct Monitor {
screen_rect: Rect,
work_area: Rect,
workspaces: Vec<Workspace>,
active_workspace: usize,
}
impl Monitor {
#[must_use]
pub fn new(
screen_rect: Rect,
work_area: Rect,
workspaces: Vec<Workspace>,
active_workspace: usize,
) -> Self {
let active_workspace = if workspaces.is_empty() {
0
} else {
active_workspace.min(workspaces.len() - 1)
};
Self {
screen_rect,
work_area,
workspaces,
active_workspace,
}
}
#[must_use]
pub fn screen_rect(&self) -> Rect {
self.screen_rect
}
#[must_use]
pub fn work_area(&self) -> Rect {
self.work_area
}
#[must_use]
pub fn workspaces(&self) -> &[Workspace] {
&self.workspaces
}
pub fn workspaces_mut(&mut self) -> &mut [Workspace] {
&mut self.workspaces
}
#[must_use]
pub fn active_workspace_index(&self) -> usize {
self.active_workspace
}
#[must_use]
pub fn active_workspace(&self) -> &Workspace {
&self.workspaces[self.active_workspace]
}
#[must_use]
pub fn active_workspace_mut(&mut self) -> &mut Workspace {
&mut self.workspaces[self.active_workspace]
}
#[must_use]
pub fn active_scrolling(&self) -> &ScrollingSpace {
&self.active_workspace().scrolling
}
#[must_use]
pub fn active_scrolling_mut(&mut self) -> &mut ScrollingSpace {
&mut self.active_workspace_mut().scrolling
}
#[must_use]
pub fn active_floating(&self) -> &FloatingSpace {
&self.active_workspace().floating
}
#[must_use]
pub fn active_floating_mut(&mut self) -> &mut FloatingSpace {
&mut self.active_workspace_mut().floating
}
#[must_use]
pub fn find_workspace_index(&self, id: WorkspaceId) -> Option<usize> {
self.workspaces.iter().position(|ws| ws.id == id)
}
#[must_use]
pub fn workspace(&self, id: WorkspaceId) -> Option<&Workspace> {
self.find_workspace_index(id).map(|i| &self.workspaces[i])
}
#[must_use]
pub fn workspace_mut(&mut self, id: WorkspaceId) -> Option<&mut Workspace> {
match self.find_workspace_index(id) {
Some(i) => Some(&mut self.workspaces[i]),
None => None,
}
}
#[must_use]
pub fn active_workspace_id(&self) -> WorkspaceId {
self.active_workspace().id
}
#[must_use]
pub fn set_active_workspace(&mut self, id: WorkspaceId) -> Option<usize> {
match self.find_workspace_index(id) {
Some(new_idx) => {
let prev = self.active_workspace;
self.active_workspace = new_idx;
Some(prev)
}
None => None,
}
}
#[must_use]
pub fn find_workspace_containing(&self, window: WindowId) -> Option<WorkspaceId> {
self.workspaces.iter().find_map(|ws| {
let in_tiling = ws.scrolling.virtual_layout().find_window(window).is_some();
let in_floating = ws
.floating
.windows()
.iter()
.any(|entry| entry.window_id == window);
(in_tiling || in_floating).then_some(ws.id)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::Rect;
use crate::layout::types::{MonitorInfo, Padding};
fn make_scrolling() -> ScrollingSpace {
ScrollingSpace::new(
MonitorInfo {
work_area: Rect {
x: 0,
y: 0,
width: 1920,
height: 1080,
},
},
960,
320,
100,
Padding {
window_gap: 4,
up: 0,
down: 0,
},
4,
)
}
fn make_monitor_with_ids(ids: &[u32], active_idx: usize) -> Monitor {
let workspaces: Vec<Workspace> = ids
.iter()
.map(|&id| Workspace::new(WorkspaceId(id), make_scrolling()))
.collect();
let rect = Rect {
x: 0,
y: 0,
width: 1920,
height: 1080,
};
Monitor::new(rect, rect, workspaces, active_idx)
}
#[test]
fn find_workspace_index_returns_position_for_existing_id() {
let monitor = make_monitor_with_ids(&[1, 2, 3], 0);
assert_eq!(monitor.find_workspace_index(WorkspaceId(1)), Some(0));
assert_eq!(monitor.find_workspace_index(WorkspaceId(2)), Some(1));
assert_eq!(monitor.find_workspace_index(WorkspaceId(3)), Some(2));
}
#[test]
fn find_workspace_index_returns_none_for_missing_id() {
let monitor = make_monitor_with_ids(&[1, 2, 3], 0);
assert_eq!(monitor.find_workspace_index(WorkspaceId(99)), None);
}
#[test]
fn find_workspace_index_works_for_unordered_ids() {
let monitor = make_monitor_with_ids(&[5, 1, 9], 0);
assert_eq!(monitor.find_workspace_index(WorkspaceId(9)), Some(2));
assert_eq!(monitor.find_workspace_index(WorkspaceId(5)), Some(0));
}
#[test]
fn workspace_borrows_by_id() {
let monitor = make_monitor_with_ids(&[1, 2, 3], 0);
assert_eq!(
monitor.workspace(WorkspaceId(2)).map(|ws| ws.id),
Some(WorkspaceId(2)),
);
}
#[test]
fn workspace_returns_none_for_missing_id() {
let monitor = make_monitor_with_ids(&[1, 2, 3], 0);
assert!(monitor.workspace(WorkspaceId(99)).is_none());
}
#[test]
fn workspace_mut_borrows_by_id() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 0);
let ws = monitor
.workspace_mut(WorkspaceId(3))
.expect("workspace 3 should exist");
assert_eq!(ws.id, WorkspaceId(3));
}
#[test]
fn workspace_mut_returns_none_for_missing_id() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 0);
assert!(monitor.workspace_mut(WorkspaceId(99)).is_none());
}
#[test]
fn active_workspace_id_reflects_current_active() {
let monitor = make_monitor_with_ids(&[5, 10, 15], 1);
assert_eq!(monitor.active_workspace_id(), WorkspaceId(10));
}
#[test]
fn set_active_workspace_updates_index_and_returns_previous() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 0);
let prev = monitor.set_active_workspace(WorkspaceId(3));
assert_eq!(prev, Some(0));
assert_eq!(monitor.active_workspace_index(), 2);
assert_eq!(monitor.active_workspace_id(), WorkspaceId(3));
}
#[test]
fn set_active_workspace_returns_none_for_missing_id_leaving_state_unchanged() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 0);
let prev = monitor.set_active_workspace(WorkspaceId(99));
assert_eq!(prev, None);
assert_eq!(monitor.active_workspace_index(), 0);
}
#[test]
fn set_active_workspace_to_current_returns_previous_without_changing_state() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 1);
let prev = monitor.set_active_workspace(WorkspaceId(2));
assert_eq!(prev, Some(1));
assert_eq!(monitor.active_workspace_index(), 1);
}
#[test]
fn set_active_workspace_round_trip_restores_original_active() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 0);
let _ = monitor.set_active_workspace(WorkspaceId(3));
let prev = monitor.set_active_workspace(WorkspaceId(1));
assert_eq!(prev, Some(2));
assert_eq!(monitor.active_workspace_index(), 0);
}
#[test]
fn find_workspace_containing_returns_none_for_empty_workspaces() {
let monitor = make_monitor_with_ids(&[1, 2, 3], 0);
assert_eq!(monitor.find_workspace_containing(WindowId(100)), None);
}
#[test]
fn find_workspace_containing_finds_tiling_window() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 0);
monitor
.workspace_mut(WorkspaceId(2))
.expect("workspace 2 exists")
.scrolling
.add_window(WindowId(100));
assert_eq!(
monitor.find_workspace_containing(WindowId(100)),
Some(WorkspaceId(2))
);
}
#[test]
fn find_workspace_containing_returns_none_for_untracked_window() {
let mut monitor = make_monitor_with_ids(&[1, 2, 3], 0);
monitor
.workspace_mut(WorkspaceId(1))
.expect("workspace 1 exists")
.scrolling
.add_window(WindowId(100));
assert_eq!(monitor.find_workspace_containing(WindowId(999)), None);
}
#[test]
fn screen_rect_returns_stored_value() {
let screen = Rect {
x: 0,
y: 0,
width: 1920,
height: 1200,
};
let work = Rect {
x: 0,
y: 0,
width: 1920,
height: 1160,
};
let monitor = Monitor::new(screen, work, Vec::new(), 0);
assert_eq!(monitor.screen_rect(), screen);
}
#[test]
fn screen_rect_and_work_area_are_stored_independently() {
let screen = Rect {
x: 0,
y: 0,
width: 1920,
height: 1200,
};
let work = Rect {
x: 0,
y: 0,
width: 1920,
height: 1160,
};
let monitor = Monitor::new(screen, work, Vec::new(), 0);
assert_ne!(monitor.screen_rect(), monitor.work_area());
assert_eq!(
monitor.screen_rect().height - monitor.work_area().height,
40
);
}
}