Fix compositor output routing and pointer hints; format imports

This commit is contained in:
Samuel Onoja committed 2026-09-30 06:39:58 +01:00
1 parent 88e4f3c135
commit 46509f880a
108 files changed
+3682 -8371

No files matched your search

+91 -335
View File
@@ -3,8 +3,8 @@ use std::error::Error;
use std::fmt;
use ferese_layout::{
Axis, ColumnWidth, Direction, GapConfig, LayoutError, LayoutResult, LayoutTree, Rect,
ScrollingLayout, SizeConstraints, ViewportFocusStrategy, WindowId,
Axis, ColumnWidth, Direction, GapConfig, LayoutError, LayoutResult, LayoutTree, Rect, ScrollingLayout,
SizeConstraints, ViewportFocusStrategy, WindowId,
};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -44,11 +44,7 @@ impl Default for WorkspaceLayout {
}
impl WorkspaceLayout {
pub fn new(
mode: LayoutMode,
default_column_width: ColumnWidth,
focus_strategy: ViewportFocusStrategy,
) -> Self {
pub fn new(mode: LayoutMode, default_column_width: ColumnWidth, focus_strategy: ViewportFocusStrategy) -> Self {
match mode {
LayoutMode::Scrolling => {
let mut layout = ScrollingLayout::with_default_width(default_column_width);
@@ -161,17 +157,11 @@ impl WorkspaceLayout {
constraints,
focused.filter(|window| layout.contains(*window)),
),
Self::Tree(layout) => {
layout.geometry_with_constraints(bounds, gaps, constraints, focused)
}
Self::Tree(layout) => layout.geometry_with_constraints(bounds, gaps, constraints, focused),
}
}
pub fn automatic_axis(
&self,
focused: Option<WindowId>,
bounds: Rect,
) -> Result<Axis, LayoutError> {
pub fn automatic_axis(&self, focused: Option<WindowId>, bounds: Rect) -> Result<Axis, LayoutError> {
match self {
Self::Scrolling(_) => Ok(Axis::Horizontal),
Self::Tree(layout) => layout.automatic_axis(focused, bounds),
@@ -190,24 +180,14 @@ impl WorkspaceLayout {
}
}
pub fn move_window(
&mut self,
window: WindowId,
direction: Direction,
bounds: Rect,
) -> Result<bool, LayoutError> {
pub fn move_window(&mut self, window: WindowId, direction: Direction, bounds: Rect) -> Result<bool, LayoutError> {
match self {
Self::Scrolling(layout) => layout.move_window(window, direction),
Self::Tree(layout) => layout.move_window(window, direction, bounds),
}
}
pub fn resize_window(
&mut self,
window: WindowId,
direction: Direction,
amount: f64,
) -> Result<bool, LayoutError> {
pub fn resize_window(&mut self, window: WindowId, direction: Direction, amount: f64) -> Result<bool, LayoutError> {
match self {
Self::Scrolling(layout) => layout.resize_window(window, direction, amount),
Self::Tree(layout) => layout.resize_window(window, direction, amount),
@@ -229,11 +209,7 @@ impl WorkspaceLayout {
}
}
pub fn cycle_column_width(
&mut self,
window: WindowId,
presets: &[ColumnWidth],
) -> Result<bool, LayoutError> {
pub fn cycle_column_width(&mut self, window: WindowId, presets: &[ColumnWidth]) -> Result<bool, LayoutError> {
match self {
Self::Scrolling(layout) => layout.cycle_column_width(window, presets),
Self::Tree(_) => Ok(false),
@@ -280,11 +256,7 @@ impl WorkspaceLayout {
for column in columns {
for (row, window) in column.windows.into_iter().enumerate() {
let axis = if row == 0 {
Axis::Horizontal
} else {
Axis::Vertical
};
let axis = if row == 0 { Axis::Horizontal } else { Axis::Vertical };
tree.insert(window, previous, axis, 0.5)?;
previous = Some(window);
}
@@ -444,12 +416,7 @@ impl WorkspaceSet {
}
/// Update policy without moving windows or replacing existing column widths.
pub fn reconfigure_defaults(
&mut self,
mode: LayoutMode,
width: ColumnWidth,
strategy: ViewportFocusStrategy,
) {
pub fn reconfigure_defaults(&mut self, mode: LayoutMode, width: ColumnWidth, strategy: ViewportFocusStrategy) {
self.default_layout_mode = mode;
self.default_column_width = width;
self.scrolling_focus_strategy = strategy;
@@ -470,11 +437,7 @@ impl WorkspaceSet {
let workspace = Workspace {
id: active,
name: "1".to_owned(),
layout: WorkspaceLayout::new(
default_layout_mode,
default_column_width,
scrolling_focus_strategy,
),
layout: WorkspaceLayout::new(default_layout_mode, default_column_width, scrolling_focus_strategy),
floating: Vec::new(),
last_focused: None,
fullscreen: None,
@@ -520,12 +483,8 @@ impl WorkspaceSet {
debug_assert!(self.validate().is_ok());
let focused = self.active().last_focused;
let visible_floating = focused.filter(|window| {
matches!(
self.placement(*window),
Some(WindowPlacement::Floating { .. })
)
});
let visible_floating =
focused.filter(|window| matches!(self.placement(*window), Some(WindowPlacement::Floating { .. })));
Ok(visible_floating.or(self.active().fullscreen).or(focused))
}
@@ -543,8 +502,7 @@ impl WorkspaceSet {
pub fn ordered(&self) -> Vec<&Workspace> {
let mut workspaces = self.iter().collect::<Vec<_>>();
workspaces
.sort_unstable_by_key(|workspace| workspace_order_key(&workspace.name, workspace.id));
workspaces.sort_unstable_by_key(|workspace| workspace_order_key(&workspace.name, workspace.id));
workspaces
}
@@ -552,20 +510,12 @@ impl WorkspaceSet {
let mut empty = self
.workspaces
.values()
.filter(|workspace| {
workspace.layout.window_ids().next().is_none() && workspace.floating.is_empty()
})
.filter(|workspace| workspace.layout.window_ids().next().is_none() && workspace.floating.is_empty())
.map(|workspace| workspace.id)
.collect::<Vec<_>>();
empty.sort_by_key(|id| id.0);
let visible_empty = empty
.iter()
.any(|id| *id == self.active || protected.contains(id));
let spare = if visible_empty {
None
} else {
empty.first().copied()
};
let visible_empty = empty.iter().any(|id| *id == self.active || protected.contains(id));
let spare = if visible_empty { None } else { empty.first().copied() };
let mut removed = Vec::new();
for id in empty {
if id == self.active || protected.contains(&id) || Some(id) == spare {
@@ -588,32 +538,20 @@ impl WorkspaceSet {
self.placements.get(&window).copied()
}
pub fn set_active_layout_mode(
&mut self,
mode: LayoutMode,
bounds: Rect,
) -> Result<bool, WorkspaceError> {
pub fn set_active_layout_mode(&mut self, mode: LayoutMode, bounds: Rect) -> Result<bool, WorkspaceError> {
let default_column_width = self.default_column_width;
let focus_strategy = self.scrolling_focus_strategy;
let workspace = self.active_mut();
let focused = tiled_focus(workspace);
let changed = workspace.layout.set_mode(
mode,
bounds,
focused,
default_column_width,
focus_strategy,
)?;
let changed = workspace
.layout
.set_mode(mode, bounds, focused, default_column_width, focus_strategy)?;
debug_assert!(self.validate().is_ok());
Ok(changed)
}
pub fn set_floating_rect(
&mut self,
window: WindowId,
rect: Rect,
) -> Result<(), WorkspaceError> {
pub fn set_floating_rect(&mut self, window: WindowId, rect: Rect) -> Result<(), WorkspaceError> {
let placement = self
.placements
.get_mut(&window)
@@ -639,35 +577,23 @@ impl WorkspaceSet {
self.focus_window_with_reveal(window, false)
}
fn focus_window_with_reveal(
&mut self,
window: WindowId,
reveal: bool,
) -> Result<(), WorkspaceError> {
fn focus_window_with_reveal(&mut self, window: WindowId, reveal: bool) -> Result<(), WorkspaceError> {
if self.workspace_for_window(window) != Some(self.active) {
return Err(WorkspaceError::InvalidState(
"focused window is not on the active workspace",
));
}
if self.active().fullscreen.is_some_and(|fullscreen| {
fullscreen != window
&& !matches!(
self.placement(window),
Some(WindowPlacement::Floating { .. })
)
fullscreen != window && !matches!(self.placement(window), Some(WindowPlacement::Floating { .. }))
}) {
return Err(WorkspaceError::InvalidState(
"focused window is hidden by fullscreen",
));
return Err(WorkspaceError::InvalidState("focused window is hidden by fullscreen"));
}
if self.placement(window) == Some(WindowPlacement::Tiled) {
if reveal {
self.active_mut().layout.activate_window(window)?;
} else {
self.active_mut()
.layout
.activate_window_without_reveal(window)?;
self.active_mut().layout.activate_window_without_reveal(window)?;
}
}
self.active_mut().last_focused = Some(window);
@@ -676,11 +602,7 @@ impl WorkspaceSet {
Ok(())
}
pub fn stack_window(
&mut self,
window: WindowId,
target: WindowId,
) -> Result<(), WorkspaceError> {
pub fn stack_window(&mut self, window: WindowId, target: WindowId) -> Result<(), WorkspaceError> {
let workspace = self
.workspace_for_window(window)
.ok_or(LayoutError::UnknownWindow(window))?;
@@ -721,11 +643,7 @@ impl WorkspaceSet {
Ok(changed)
}
pub fn cycle_column_width(
&mut self,
window: WindowId,
presets: &[ColumnWidth],
) -> Result<bool, WorkspaceError> {
pub fn cycle_column_width(&mut self, window: WindowId, presets: &[ColumnWidth]) -> Result<bool, WorkspaceError> {
if self.workspace_for_window(window) != Some(self.active)
|| self.placement(window) != Some(WindowPlacement::Tiled)
{
@@ -734,10 +652,7 @@ impl WorkspaceSet {
));
}
let changed = self
.active_mut()
.layout
.cycle_column_width(window, presets)?;
let changed = self.active_mut().layout.cycle_column_width(window, presets)?;
debug_assert!(self.validate().is_ok());
Ok(changed)
}
@@ -804,12 +719,7 @@ impl WorkspaceSet {
self.activate(workspace)
}
pub fn insert_window(
&mut self,
window: WindowId,
axis: Axis,
ratio: f64,
) -> Result<(), WorkspaceError> {
pub fn insert_window(&mut self, window: WindowId, axis: Axis, ratio: f64) -> Result<(), WorkspaceError> {
if self.window_workspaces.contains_key(&window) {
return Err(WorkspaceError::DuplicateWindow(window));
}
@@ -1004,11 +914,7 @@ impl WorkspaceSet {
Ok(enabled)
}
pub fn set_fullscreen(
&mut self,
window: WindowId,
enabled: bool,
) -> Result<bool, WorkspaceError> {
pub fn set_fullscreen(&mut self, window: WindowId, enabled: bool) -> Result<bool, WorkspaceError> {
let workspace_id = self
.workspace_for_window(window)
.ok_or(LayoutError::UnknownWindow(window))?;
@@ -1038,18 +944,14 @@ impl WorkspaceSet {
return Err(WorkspaceError::InvalidState("active workspace is missing"));
}
if self.names.len() != self.workspaces.len() {
return Err(WorkspaceError::InvalidState(
"workspace name index is inconsistent",
));
return Err(WorkspaceError::InvalidState("workspace name index is inconsistent"));
}
let mut seen_windows = HashSet::new();
for (id, workspace) in &self.workspaces {
if workspace.id != *id || self.names.get(&workspace.name) != Some(id) {
return Err(WorkspaceError::InvalidState(
"workspace index is inconsistent",
));
return Err(WorkspaceError::InvalidState("workspace index is inconsistent"));
}
workspace.layout.validate()?;
@@ -1059,9 +961,7 @@ impl WorkspaceSet {
|| self.window_workspaces.get(&window) != Some(id)
|| self.placements.get(&window) != Some(&WindowPlacement::Tiled)
{
return Err(WorkspaceError::InvalidState(
"window ownership is inconsistent",
));
return Err(WorkspaceError::InvalidState("window ownership is inconsistent"));
}
}
@@ -1071,10 +971,7 @@ impl WorkspaceSet {
if !seen_floating.insert(*window)
|| !seen_windows.insert(*window)
|| self.window_workspaces.get(window) != Some(id)
|| !matches!(
self.placements.get(window),
Some(WindowPlacement::Floating { .. })
)
|| !matches!(self.placements.get(window), Some(WindowPlacement::Floating { .. }))
{
return Err(WorkspaceError::InvalidState(
"floating window ownership is inconsistent",
@@ -1086,9 +983,7 @@ impl WorkspaceSet {
.last_focused
.is_some_and(|window| self.window_workspaces.get(&window) != Some(id))
{
return Err(WorkspaceError::InvalidState(
"workspace focus is inconsistent",
));
return Err(WorkspaceError::InvalidState("workspace focus is inconsistent"));
}
if workspace
@@ -1101,12 +996,8 @@ impl WorkspaceSet {
}
}
if seen_windows.len() != self.window_workspaces.len()
|| self.placements.len() != self.window_workspaces.len()
{
return Err(WorkspaceError::InvalidState(
"window index retains stale entries",
));
if seen_windows.len() != self.window_workspaces.len() || self.placements.len() != self.window_workspaces.len() {
return Err(WorkspaceError::InvalidState("window index retains stale entries"));
}
Ok(())
@@ -1114,13 +1005,10 @@ impl WorkspaceSet {
}
fn first_window(workspace: &Workspace) -> Option<WindowId> {
workspace.layout.preferred_window().or_else(|| {
workspace
.floating
.iter()
.copied()
.min_by_key(|window| window.0)
})
workspace
.layout
.preferred_window()
.or_else(|| workspace.floating.iter().copied().min_by_key(|window| window.0))
}
fn tiled_focus(workspace: &Workspace) -> Option<WindowId> {
@@ -1151,10 +1039,7 @@ mod tests {
let original_first = set.active_id();
let second = set.ensure_numeric(2).unwrap();
set.activate(second).unwrap();
assert_eq!(
set.prune_empty(&std::collections::HashSet::new()),
vec![original_first]
);
assert_eq!(set.prune_empty(&std::collections::HashSet::new()), vec![original_first]);
let tenth = set.ensure_numeric(10).unwrap();
let first = set.ensure_numeric(1).unwrap();
@@ -1188,19 +1073,13 @@ mod tests {
.unwrap();
let spare = set.ensure_numeric(3).unwrap();
let excess = set.ensure_numeric(4).unwrap();
assert_eq!(
set.prune_empty(&std::collections::HashSet::new()),
vec![excess]
);
assert_eq!(set.prune_empty(&std::collections::HashSet::new()), vec![excess]);
assert!(set.workspace(first).is_some());
assert!(set.workspace(floating).is_some());
assert!(set.workspace(spare).is_some());
set.activate(spare).unwrap();
set.remove_window(super::WindowId(1)).unwrap();
assert_eq!(
set.prune_empty(&std::collections::HashSet::new()),
vec![first]
);
assert_eq!(set.prune_empty(&std::collections::HashSet::new()), vec![first]);
assert!(set.validate().is_ok());
assert_ne!(set.ensure_numeric(1).unwrap(), first);
}
@@ -1222,9 +1101,7 @@ mod tests {
fn live_policy_updates_existing_layouts_and_default_widths_not_custom_widths() {
let mut workspaces = WorkspaceSet::default();
for id in 1..=3 {
workspaces
.insert_window(WindowId(id), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(id), Axis::Horizontal, 0.5).unwrap();
}
let WorkspaceLayout::Scrolling(layout) = &mut workspaces.active_mut().layout else {
panic!()
@@ -1263,30 +1140,18 @@ mod tests {
#[test]
fn reconfiguration_preserves_windows_and_current_widths_but_updates_defaults() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
let workspace = workspaces.active_id();
workspaces.reconfigure_defaults(
LayoutMode::Tree,
ColumnWidth::Full,
ViewportFocusStrategy::Minimal,
);
workspaces.reconfigure_defaults(LayoutMode::Tree, ColumnWidth::Full, ViewportFocusStrategy::Minimal);
let WorkspaceLayout::Scrolling(layout) = &workspaces.active().layout else {
panic!("existing layout must survive");
};
assert_eq!(layout.columns()[0].width, ColumnWidth::Proportion(0.5));
assert_eq!(layout.default_width(), ColumnWidth::Full);
assert_eq!(
workspaces.workspace_for_window(WindowId(1)),
Some(workspace)
);
assert_eq!(workspaces.workspace_for_window(WindowId(1)), Some(workspace));
workspaces.switch_to_numeric(2).unwrap();
assert_eq!(workspaces.active().layout.mode(), LayoutMode::Tree);
assert_eq!(
workspaces.workspace_for_window(WindowId(1)),
Some(workspace)
);
assert_eq!(workspaces.workspace_for_window(WindowId(1)), Some(workspace));
}
use super::*;
@@ -1304,14 +1169,9 @@ mod tests {
#[test]
fn configured_layout_defaults_apply_to_new_workspaces() {
let mut workspaces = WorkspaceSet::new(
LayoutMode::Scrolling,
ColumnWidth::Full,
ViewportFocusStrategy::Minimal,
);
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
let mut workspaces =
WorkspaceSet::new(LayoutMode::Scrolling, ColumnWidth::Full, ViewportFocusStrategy::Minimal);
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
let first = workspaces
.active_mut()
.layout
@@ -1323,10 +1183,7 @@ mod tests {
)
.unwrap();
assert_eq!(
first.geometry[&WindowId(1)],
Rect::new(4.0, 4.0, 992.0, 792.0)
);
assert_eq!(first.geometry[&WindowId(1)], Rect::new(4.0, 4.0, 992.0, 792.0));
workspaces.switch_to_numeric(2).unwrap();
assert_eq!(workspaces.active().layout.mode(), LayoutMode::Scrolling);
}
@@ -1334,14 +1191,10 @@ mod tests {
#[test]
fn switching_restores_workspace_focus() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
assert_eq!(workspaces.switch_to_numeric(2).unwrap(), None);
workspaces
.insert_window(WindowId(2), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(2), Axis::Horizontal, 0.5).unwrap();
assert_eq!(workspaces.switch_to_numeric(1).unwrap(), Some(WindowId(1)));
assert_eq!(workspaces.switch_to_numeric(2).unwrap(), Some(WindowId(2)));
@@ -1351,12 +1204,8 @@ mod tests {
#[test]
fn moving_a_window_updates_membership_without_switching() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces
.insert_window(WindowId(2), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
workspaces.insert_window(WindowId(2), Axis::Horizontal, 0.5).unwrap();
let first = workspaces.active_id();
let second = workspaces
.move_window_to_numeric(WindowId(2), 2, Axis::Horizontal, 0.5)
@@ -1364,32 +1213,16 @@ mod tests {
assert_eq!(workspaces.active_id(), first);
assert_eq!(workspaces.workspace_for_window(WindowId(2)), Some(second));
assert!(
!workspaces
.workspace(first)
.unwrap()
.layout
.contains(WindowId(2))
);
assert!(
workspaces
.workspace(second)
.unwrap()
.layout
.contains(WindowId(2))
);
assert!(!workspaces.workspace(first).unwrap().layout.contains(WindowId(2)));
assert!(workspaces.workspace(second).unwrap().layout.contains(WindowId(2)));
assert!(workspaces.validate().is_ok());
}
#[test]
fn removing_focus_chooses_a_deterministic_remaining_window() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(2), Axis::Horizontal, 0.5)
.unwrap();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(2), Axis::Horizontal, 0.5).unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
workspaces.remove_window(WindowId(1)).unwrap();
assert_eq!(workspaces.active().last_focused, Some(WindowId(2)));
@@ -1400,9 +1233,7 @@ mod tests {
fn closing_a_scrolling_column_focuses_its_adjacent_column() {
let mut workspaces = WorkspaceSet::default();
for id in 1..=4 {
workspaces
.insert_window(WindowId(id), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(id), Axis::Horizontal, 0.5).unwrap();
}
workspaces.focus_window(WindowId(3)).unwrap();
@@ -1414,9 +1245,7 @@ mod tests {
#[test]
fn focus_rejects_a_window_on_an_inactive_workspace() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
workspaces
.move_window_to_numeric(WindowId(1), 2, Axis::Horizontal, 0.5)
.unwrap();
@@ -1433,9 +1262,7 @@ mod tests {
#[test]
fn floating_toggle_removes_and_restores_tiled_membership() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
let rect = Rect::new(10.0, 20.0, 640.0, 480.0);
assert_eq!(
@@ -1461,21 +1288,14 @@ mod tests {
#[test]
fn floating_geometry_updates_are_authoritative_and_validated() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
assert_eq!(
workspaces.set_floating_rect(WindowId(1), Rect::default()),
Err(WorkspaceError::InvalidState("window is not floating"))
);
workspaces
.toggle_floating(
WindowId(1),
Rect::new(10.0, 20.0, 640.0, 480.0),
Axis::Horizontal,
0.5,
)
.toggle_floating(WindowId(1), Rect::new(10.0, 20.0, 640.0, 480.0), Axis::Horizontal, 0.5)
.unwrap();
workspaces
.set_floating_rect(WindowId(1), Rect::new(30.0, 40.0, f64::NAN, -2.0))
@@ -1497,15 +1317,9 @@ mod tests {
ViewportFocusStrategy::Center,
ViewportFocusStrategy::Paged,
] {
let mut workspaces = WorkspaceSet::new(
LayoutMode::Scrolling,
ColumnWidth::Proportion(0.5),
strategy,
);
let mut workspaces = WorkspaceSet::new(LayoutMode::Scrolling, ColumnWidth::Proportion(0.5), strategy);
for id in 1..=3 {
workspaces
.insert_window(WindowId(id), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(id), Axis::Horizontal, 0.5).unwrap();
}
let bounds = Rect::new(0.0, 0.0, 1000.0, 800.0);
let gaps = GapConfig {
@@ -1555,20 +1369,13 @@ mod tests {
ViewportFocusStrategy::Paged,
);
for id in 1..=4 {
workspaces
.insert_window(WindowId(id), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(id), Axis::Horizontal, 0.5).unwrap();
}
let bounds = Rect::new(0.0, 0.0, 1000.0, 800.0);
let before = workspaces
.active_mut()
.layout
.geometry_with_constraints(
bounds,
GapConfig::default(),
&HashMap::new(),
Some(WindowId(4)),
)
.geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(WindowId(4)))
.unwrap();
let scroll = workspaces.active().layout.viewport_x();
workspaces
@@ -1582,12 +1389,7 @@ mod tests {
let after = workspaces
.active_mut()
.layout
.geometry_with_constraints(
bounds,
GapConfig::default(),
&HashMap::new(),
Some(WindowId(5)),
)
.geometry_with_constraints(bounds, GapConfig::default(), &HashMap::new(), Some(WindowId(5)))
.unwrap();
assert_eq!(before.geometry, after.geometry);
assert_eq!(scroll, workspaces.active().layout.viewport_x());
@@ -1598,9 +1400,7 @@ mod tests {
#[test]
fn floating_window_can_follow_its_parent_workspace_without_focus() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
let parent_workspace = workspaces.active_id();
workspaces.switch_to_numeric(2).unwrap();
workspaces
@@ -1612,10 +1412,7 @@ mod tests {
)
.unwrap();
assert_eq!(
workspaces.workspace_for_window(WindowId(2)),
Some(parent_workspace)
);
assert_eq!(workspaces.workspace_for_window(WindowId(2)), Some(parent_workspace));
assert_eq!(
workspaces.workspace(parent_workspace).unwrap().last_focused,
Some(WindowId(1))
@@ -1626,9 +1423,7 @@ mod tests {
#[test]
fn fullscreen_preserves_underlying_placement() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
let before = workspaces.placement(WindowId(1));
assert!(workspaces.toggle_fullscreen(WindowId(1)).unwrap());
@@ -1646,13 +1441,9 @@ mod tests {
#[test]
fn mapping_under_fullscreen_does_not_change_focus() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
workspaces.toggle_fullscreen(WindowId(1)).unwrap();
workspaces
.insert_window(WindowId(2), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(2), Axis::Horizontal, 0.5).unwrap();
assert_eq!(workspaces.active().last_focused, Some(WindowId(1)));
assert_eq!(workspaces.active().fullscreen, Some(WindowId(1)));
@@ -1664,17 +1455,10 @@ mod tests {
fn floating_dialog_can_focus_above_fullscreen() {
let mut workspaces = WorkspaceSet::default();
let workspace = workspaces.active_id();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
workspaces.toggle_fullscreen(WindowId(1)).unwrap();
workspaces
.insert_floating_window(
WindowId(2),
workspace,
Rect::new(100.0, 100.0, 500.0, 400.0),
true,
)
.insert_floating_window(WindowId(2), workspace, Rect::new(100.0, 100.0, 500.0, 400.0), true)
.unwrap();
assert_eq!(workspaces.active().last_focused, Some(WindowId(2)));
@@ -1688,14 +1472,10 @@ mod tests {
#[test]
fn switching_to_fullscreen_restores_the_visible_window() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
workspaces.toggle_fullscreen(WindowId(1)).unwrap();
workspaces.switch_to_numeric(2).unwrap();
workspaces
.insert_window(WindowId(2), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(2), Axis::Horizontal, 0.5).unwrap();
workspaces
.move_window_to_numeric(WindowId(2), 1, Axis::Horizontal, 0.5)
.unwrap();
@@ -1704,9 +1484,7 @@ mod tests {
assert_eq!(workspaces.active().last_focused, Some(WindowId(1)));
assert_eq!(
workspaces.focus_window(WindowId(2)),
Err(WorkspaceError::InvalidState(
"focused window is hidden by fullscreen"
))
Err(WorkspaceError::InvalidState("focused window is hidden by fullscreen"))
);
assert!(workspaces.validate().is_ok());
}
@@ -1714,12 +1492,8 @@ mod tests {
#[test]
fn grouping_tiled_windows_preserves_workspace_membership() {
let mut workspaces = WorkspaceSet::default();
workspaces
.insert_window(WindowId(1), Axis::Horizontal, 0.5)
.unwrap();
workspaces
.insert_window(WindowId(2), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(1), Axis::Horizontal, 0.5).unwrap();
workspaces.insert_window(WindowId(2), Axis::Horizontal, 0.5).unwrap();
workspaces.stack_window(WindowId(2), WindowId(1)).unwrap();
assert_eq!(workspaces.active().last_focused, Some(WindowId(2)));
@@ -1740,26 +1514,16 @@ mod tests {
fn layout_mode_conversion_preserves_membership_and_focus() {
let mut workspaces = WorkspaceSet::default();
for id in 1..=4 {
workspaces
.insert_window(WindowId(id), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(id), Axis::Horizontal, 0.5).unwrap();
}
workspaces.focus_window(WindowId(2)).unwrap();
let bounds = Rect::new(0.0, 0.0, 1_000.0, 800.0);
assert!(
workspaces
.set_active_layout_mode(LayoutMode::Tree, bounds)
.unwrap()
);
assert!(workspaces.set_active_layout_mode(LayoutMode::Tree, bounds).unwrap());
assert_eq!(workspaces.active().layout.mode(), LayoutMode::Tree);
assert_eq!(workspaces.active().last_focused, Some(WindowId(2)));
assert_eq!(
workspaces
.active()
.layout
.window_ids()
.collect::<HashSet<_>>(),
workspaces.active().layout.window_ids().collect::<HashSet<_>>(),
HashSet::from([WindowId(1), WindowId(2), WindowId(3), WindowId(4)])
);
@@ -1777,19 +1541,14 @@ mod tests {
fn scrolling_column_commands_preserve_workspace_state() {
let mut workspaces = WorkspaceSet::default();
for id in 1..=3 {
workspaces
.insert_window(WindowId(id), Axis::Horizontal, 0.5)
.unwrap();
workspaces.insert_window(WindowId(id), Axis::Horizontal, 0.5).unwrap();
}
workspaces.stack_window(WindowId(3), WindowId(2)).unwrap();
assert!(workspaces.extract_window(WindowId(3)).unwrap());
assert!(
workspaces
.cycle_column_width(
WindowId(3),
&[ColumnWidth::Proportion(0.5), ColumnWidth::Full],
)
.cycle_column_width(WindowId(3), &[ColumnWidth::Proportion(0.5), ColumnWidth::Full],)
.unwrap()
);
let bounds = Rect::new(0.0, 0.0, 1_000.0, 800.0);
@@ -1817,9 +1576,7 @@ mod tests {
0 if windows.len() < 48 => {
let window = WindowId(next_window);
next_window += 1;
workspaces
.insert_window(window, random_axis(random), 0.5)
.unwrap();
workspaces.insert_window(window, random_axis(random), 0.5).unwrap();
windows.push(window);
}
1 if !windows.is_empty() => {
@@ -1866,8 +1623,7 @@ mod tests {
})
.collect::<Vec<_>>();
if let Some(window) = candidates.get(random as usize % candidates.len().max(1))
{
if let Some(window) = candidates.get(random as usize % candidates.len().max(1)) {
workspaces.focus_window(*window).unwrap();
}
}