Fix compositor output routing and pointer hints; format imports
This commit is contained in:
1 parent
88e4f3c135
commit
46509f880a
108 files changed
+3682
-8371
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user