From 49ddf66c2f77d6dab8bdb84de7345b9c3a28f9df Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Fri, 31 Jan 2025 17:55:15 +0300 Subject: layout: Move tests to separate file This way changing just the tests won't rebuild the main library. --- src/layout/mod.rs | 3172 +-------------------------------------------------- src/layout/tests.rs | 3163 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3166 insertions(+), 3169 deletions(-) create mode 100644 src/layout/tests.rs (limited to 'src/layout') diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 683592b2..1dd93eac 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -81,6 +81,9 @@ pub mod shadow; pub mod tile; pub mod workspace; +#[cfg(test)] +mod tests; + /// Size changes up to this many pixels don't animate. pub const RESIZE_ANIMATION_THRESHOLD: f64 = 10.; @@ -4311,3172 +4314,3 @@ impl Default for MonitorSet { Self::NoOutputs { workspaces: vec![] } } } - -#[cfg(test)] -mod tests { - use std::cell::Cell; - - use niri_config::{FloatOrInt, OutputName, WorkspaceName, WorkspaceReference}; - use proptest::prelude::*; - use proptest_derive::Arbitrary; - use smithay::output::{Mode, PhysicalProperties, Subpixel}; - use smithay::utils::Rectangle; - - use super::*; - - impl Default for Layout { - fn default() -> Self { - Self::with_options(Clock::with_time(Duration::ZERO), Default::default()) - } - } - - #[derive(Debug)] - struct TestWindowInner { - id: usize, - parent_id: Cell>, - bbox: Cell>, - initial_bbox: Rectangle, - requested_size: Cell>>, - min_size: Size, - max_size: Size, - pending_fullscreen: Cell, - pending_activated: Cell, - } - - #[derive(Debug, Clone)] - struct TestWindow(Rc); - - #[derive(Debug, Clone, Copy, Arbitrary)] - struct TestWindowParams { - #[proptest(strategy = "1..=5usize")] - id: usize, - #[proptest(strategy = "arbitrary_parent_id()")] - parent_id: Option, - is_floating: bool, - #[proptest(strategy = "arbitrary_bbox()")] - bbox: Rectangle, - #[proptest(strategy = "arbitrary_min_max_size()")] - min_max_size: (Size, Size), - } - - impl TestWindowParams { - pub fn new(id: usize) -> Self { - Self { - id, - parent_id: None, - is_floating: false, - bbox: Rectangle::from_size(Size::from((100, 200))), - min_max_size: Default::default(), - } - } - } - - impl TestWindow { - fn new(params: TestWindowParams) -> Self { - Self(Rc::new(TestWindowInner { - id: params.id, - parent_id: Cell::new(params.parent_id), - bbox: Cell::new(params.bbox), - initial_bbox: params.bbox, - requested_size: Cell::new(None), - min_size: params.min_max_size.0, - max_size: params.min_max_size.1, - pending_fullscreen: Cell::new(false), - pending_activated: Cell::new(false), - })) - } - - fn communicate(&self) -> bool { - if let Some(size) = self.0.requested_size.get() { - assert!(size.w >= 0); - assert!(size.h >= 0); - - let mut new_bbox = self.0.initial_bbox; - if size.w != 0 { - new_bbox.size.w = size.w; - } - if size.h != 0 { - new_bbox.size.h = size.h; - } - - if self.0.bbox.get() != new_bbox { - self.0.bbox.set(new_bbox); - return true; - } - } - - false - } - } - - impl LayoutElement for TestWindow { - type Id = usize; - - fn id(&self) -> &Self::Id { - &self.0.id - } - - fn size(&self) -> Size { - self.0.bbox.get().size - } - - fn buf_loc(&self) -> Point { - (0, 0).into() - } - - fn is_in_input_region(&self, _point: Point) -> bool { - false - } - - fn render( - &self, - _renderer: &mut R, - _location: Point, - _scale: Scale, - _alpha: f32, - _target: RenderTarget, - ) -> SplitElements> { - SplitElements::default() - } - - fn request_size( - &mut self, - size: Size, - _animate: bool, - _transaction: Option, - ) { - self.0.requested_size.set(Some(size)); - self.0.pending_fullscreen.set(false); - } - - fn request_fullscreen(&mut self, _size: Size) { - self.0.pending_fullscreen.set(true); - } - - fn min_size(&self) -> Size { - self.0.min_size - } - - fn max_size(&self) -> Size { - self.0.max_size - } - - fn is_wl_surface(&self, _wl_surface: &WlSurface) -> bool { - false - } - - fn set_preferred_scale_transform(&self, _scale: output::Scale, _transform: Transform) {} - - fn has_ssd(&self) -> bool { - false - } - - fn output_enter(&self, _output: &Output) {} - - fn output_leave(&self, _output: &Output) {} - - fn set_offscreen_element_id(&self, _id: Option) {} - - fn set_activated(&mut self, active: bool) { - self.0.pending_activated.set(active); - } - - fn set_bounds(&self, _bounds: Size) {} - - fn is_ignoring_opacity_window_rule(&self) -> bool { - false - } - - fn configure_intent(&self) -> ConfigureIntent { - ConfigureIntent::CanSend - } - - fn send_pending_configure(&mut self) {} - - fn set_active_in_column(&mut self, _active: bool) {} - - fn set_floating(&mut self, _floating: bool) {} - - fn is_fullscreen(&self) -> bool { - false - } - - fn is_pending_fullscreen(&self) -> bool { - self.0.pending_fullscreen.get() - } - - fn requested_size(&self) -> Option> { - self.0.requested_size.get() - } - - fn is_child_of(&self, parent: &Self) -> bool { - self.0.parent_id.get() == Some(parent.0.id) - } - - fn refresh(&self) {} - - fn rules(&self) -> &ResolvedWindowRules { - static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty(); - &EMPTY - } - - fn animation_snapshot(&self) -> Option<&LayoutElementRenderSnapshot> { - None - } - - fn take_animation_snapshot(&mut self) -> Option { - None - } - - fn set_interactive_resize(&mut self, _data: Option) {} - - fn cancel_interactive_resize(&mut self) {} - - fn on_commit(&mut self, _serial: Serial) {} - - fn interactive_resize_data(&self) -> Option { - None - } - } - - fn arbitrary_bbox() -> impl Strategy> { - any::<(i16, i16, u16, u16)>().prop_map(|(x, y, w, h)| { - let loc: Point = Point::from((x.into(), y.into())); - let size: Size = Size::from((w.max(1).into(), h.max(1).into())); - Rectangle::new(loc, size) - }) - } - - fn arbitrary_size_change() -> impl Strategy { - prop_oneof![ - (0..).prop_map(SizeChange::SetFixed), - (0f64..).prop_map(SizeChange::SetProportion), - any::().prop_map(SizeChange::AdjustFixed), - any::().prop_map(SizeChange::AdjustProportion), - // Interactive resize can have negative values here. - Just(SizeChange::SetFixed(-100)), - ] - } - - fn arbitrary_position_change() -> impl Strategy { - prop_oneof![ - (-1000f64..1000f64).prop_map(PositionChange::SetFixed), - (-1000f64..1000f64).prop_map(PositionChange::AdjustFixed), - any::().prop_map(PositionChange::SetFixed), - any::().prop_map(PositionChange::AdjustFixed), - ] - } - - fn arbitrary_min_max() -> impl Strategy { - prop_oneof![ - Just((0, 0)), - (1..65536).prop_map(|n| (n, n)), - (1..65536).prop_map(|min| (min, 0)), - (1..).prop_map(|max| (0, max)), - (1..65536, 1..).prop_map(|(min, max): (i32, i32)| (min, max.max(min))), - ] - } - - fn arbitrary_min_max_size() -> impl Strategy, Size)> { - prop_oneof![ - 5 => (arbitrary_min_max(), arbitrary_min_max()).prop_map( - |((min_w, max_w), (min_h, max_h))| { - let min_size = Size::from((min_w, min_h)); - let max_size = Size::from((max_w, max_h)); - (min_size, max_size) - }, - ), - 1 => arbitrary_min_max().prop_map(|(w, h)| { - let size = Size::from((w, h)); - (size, size) - }), - ] - } - - fn arbitrary_view_offset_gesture_delta() -> impl Strategy { - prop_oneof![(-10f64..10f64), (-50000f64..50000f64),] - } - - fn arbitrary_resize_edge() -> impl Strategy { - prop_oneof![ - Just(ResizeEdge::RIGHT), - Just(ResizeEdge::BOTTOM), - Just(ResizeEdge::LEFT), - Just(ResizeEdge::TOP), - Just(ResizeEdge::BOTTOM_RIGHT), - Just(ResizeEdge::BOTTOM_LEFT), - Just(ResizeEdge::TOP_RIGHT), - Just(ResizeEdge::TOP_LEFT), - Just(ResizeEdge::empty()), - ] - } - - fn arbitrary_scale() -> impl Strategy { - prop_oneof![Just(1.), Just(1.5), Just(2.),] - } - - fn arbitrary_msec_delta() -> impl Strategy { - prop_oneof![ - 1 => Just(-1000), - 2 => Just(-10), - 1 => Just(0), - 2 => Just(10), - 6 => Just(1000), - ] - } - - fn arbitrary_parent_id() -> impl Strategy> { - prop_oneof![ - 5 => Just(None), - 1 => prop::option::of(1..=5usize), - ] - } - - fn arbitrary_scroll_direction() -> impl Strategy { - prop_oneof![Just(ScrollDirection::Left), Just(ScrollDirection::Right)] - } - - #[derive(Debug, Clone, Copy, Arbitrary)] - enum Op { - AddOutput(#[proptest(strategy = "1..=5usize")] usize), - AddScaledOutput { - #[proptest(strategy = "1..=5usize")] - id: usize, - #[proptest(strategy = "arbitrary_scale()")] - scale: f64, - }, - RemoveOutput(#[proptest(strategy = "1..=5usize")] usize), - FocusOutput(#[proptest(strategy = "1..=5usize")] usize), - AddNamedWorkspace { - #[proptest(strategy = "1..=5usize")] - ws_name: usize, - #[proptest(strategy = "prop::option::of(1..=5usize)")] - output_name: Option, - }, - UnnameWorkspace { - #[proptest(strategy = "1..=5usize")] - ws_name: usize, - }, - AddWindow { - params: TestWindowParams, - }, - AddWindowNextTo { - params: TestWindowParams, - #[proptest(strategy = "1..=5usize")] - next_to_id: usize, - }, - AddWindowToNamedWorkspace { - params: TestWindowParams, - #[proptest(strategy = "1..=5usize")] - ws_name: usize, - }, - CloseWindow(#[proptest(strategy = "1..=5usize")] usize), - FullscreenWindow(#[proptest(strategy = "1..=5usize")] usize), - SetFullscreenWindow { - #[proptest(strategy = "1..=5usize")] - window: usize, - is_fullscreen: bool, - }, - FocusColumnLeft, - FocusColumnRight, - FocusColumnFirst, - FocusColumnLast, - FocusColumnRightOrFirst, - FocusColumnLeftOrLast, - FocusWindowOrMonitorUp(#[proptest(strategy = "1..=2u8")] u8), - FocusWindowOrMonitorDown(#[proptest(strategy = "1..=2u8")] u8), - FocusColumnOrMonitorLeft(#[proptest(strategy = "1..=2u8")] u8), - FocusColumnOrMonitorRight(#[proptest(strategy = "1..=2u8")] u8), - FocusWindowDown, - FocusWindowUp, - FocusWindowDownOrColumnLeft, - FocusWindowDownOrColumnRight, - FocusWindowUpOrColumnLeft, - FocusWindowUpOrColumnRight, - FocusWindowOrWorkspaceDown, - FocusWindowOrWorkspaceUp, - FocusWindow(#[proptest(strategy = "1..=5usize")] usize), - MoveColumnLeft, - MoveColumnRight, - MoveColumnToFirst, - MoveColumnToLast, - MoveColumnLeftOrToMonitorLeft(#[proptest(strategy = "1..=2u8")] u8), - MoveColumnRightOrToMonitorRight(#[proptest(strategy = "1..=2u8")] u8), - MoveWindowDown, - MoveWindowUp, - MoveWindowDownOrToWorkspaceDown, - MoveWindowUpOrToWorkspaceUp, - ConsumeOrExpelWindowLeft { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - }, - ConsumeOrExpelWindowRight { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - }, - ConsumeWindowIntoColumn, - ExpelWindowFromColumn, - SwapWindowInDirection( - #[proptest(strategy = "arbitrary_scroll_direction()")] ScrollDirection, - ), - CenterColumn, - CenterWindow { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - }, - FocusWorkspaceDown, - FocusWorkspaceUp, - FocusWorkspace(#[proptest(strategy = "0..=4usize")] usize), - FocusWorkspaceAutoBackAndForth(#[proptest(strategy = "0..=4usize")] usize), - FocusWorkspacePrevious, - MoveWindowToWorkspaceDown, - MoveWindowToWorkspaceUp, - MoveWindowToWorkspace { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - window_id: Option, - #[proptest(strategy = "0..=4usize")] - workspace_idx: usize, - }, - MoveColumnToWorkspaceDown, - MoveColumnToWorkspaceUp, - MoveColumnToWorkspace(#[proptest(strategy = "0..=4usize")] usize), - MoveWorkspaceDown, - MoveWorkspaceUp, - MoveWorkspaceToIndex { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - ws_name: Option, - #[proptest(strategy = "0..=4usize")] - target_idx: usize, - }, - MoveWorkspaceToMonitor { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - ws_name: Option, - #[proptest(strategy = "0..=5usize")] - output_id: usize, - }, - SetWorkspaceName { - #[proptest(strategy = "1..=5usize")] - new_ws_name: usize, - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - ws_name: Option, - }, - UnsetWorkspaceName { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - ws_name: Option, - }, - MoveWindowToOutput { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - window_id: Option, - #[proptest(strategy = "1..=5usize")] - output_id: usize, - #[proptest(strategy = "proptest::option::of(0..=4usize)")] - target_ws_idx: Option, - }, - MoveColumnToOutput(#[proptest(strategy = "1..=5usize")] usize), - SwitchPresetColumnWidth, - SwitchPresetWindowWidth { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - }, - SwitchPresetWindowHeight { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - }, - MaximizeColumn, - SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), - SetWindowWidth { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - #[proptest(strategy = "arbitrary_size_change()")] - change: SizeChange, - }, - SetWindowHeight { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - #[proptest(strategy = "arbitrary_size_change()")] - change: SizeChange, - }, - ResetWindowHeight { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - }, - ToggleWindowFloating { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - }, - SetWindowFloating { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - floating: bool, - }, - FocusFloating, - FocusTiling, - SwitchFocusFloatingTiling, - MoveFloatingWindow { - #[proptest(strategy = "proptest::option::of(1..=5usize)")] - id: Option, - #[proptest(strategy = "arbitrary_position_change()")] - x: PositionChange, - #[proptest(strategy = "arbitrary_position_change()")] - y: PositionChange, - animate: bool, - }, - SetParent { - #[proptest(strategy = "1..=5usize")] - id: usize, - #[proptest(strategy = "prop::option::of(1..=5usize)")] - new_parent_id: Option, - }, - Communicate(#[proptest(strategy = "1..=5usize")] usize), - Refresh { - is_active: bool, - }, - AdvanceAnimations { - #[proptest(strategy = "arbitrary_msec_delta()")] - msec_delta: i32, - }, - MoveWorkspaceToOutput(#[proptest(strategy = "1..=5usize")] usize), - ViewOffsetGestureBegin { - #[proptest(strategy = "1..=5usize")] - output_idx: usize, - is_touchpad: bool, - }, - ViewOffsetGestureUpdate { - #[proptest(strategy = "arbitrary_view_offset_gesture_delta()")] - delta: f64, - timestamp: Duration, - is_touchpad: bool, - }, - ViewOffsetGestureEnd { - is_touchpad: Option, - }, - WorkspaceSwitchGestureBegin { - #[proptest(strategy = "1..=5usize")] - output_idx: usize, - is_touchpad: bool, - }, - WorkspaceSwitchGestureUpdate { - #[proptest(strategy = "-400f64..400f64")] - delta: f64, - timestamp: Duration, - is_touchpad: bool, - }, - WorkspaceSwitchGestureEnd { - cancelled: bool, - is_touchpad: Option, - }, - InteractiveMoveBegin { - #[proptest(strategy = "1..=5usize")] - window: usize, - #[proptest(strategy = "1..=5usize")] - output_idx: usize, - #[proptest(strategy = "-20000f64..20000f64")] - px: f64, - #[proptest(strategy = "-20000f64..20000f64")] - py: f64, - }, - InteractiveMoveUpdate { - #[proptest(strategy = "1..=5usize")] - window: usize, - #[proptest(strategy = "-20000f64..20000f64")] - dx: f64, - #[proptest(strategy = "-20000f64..20000f64")] - dy: f64, - #[proptest(strategy = "1..=5usize")] - output_idx: usize, - #[proptest(strategy = "-20000f64..20000f64")] - px: f64, - #[proptest(strategy = "-20000f64..20000f64")] - py: f64, - }, - InteractiveMoveEnd { - #[proptest(strategy = "1..=5usize")] - window: usize, - }, - InteractiveResizeBegin { - #[proptest(strategy = "1..=5usize")] - window: usize, - #[proptest(strategy = "arbitrary_resize_edge()")] - edges: ResizeEdge, - }, - InteractiveResizeUpdate { - #[proptest(strategy = "1..=5usize")] - window: usize, - #[proptest(strategy = "-20000f64..20000f64")] - dx: f64, - #[proptest(strategy = "-20000f64..20000f64")] - dy: f64, - }, - InteractiveResizeEnd { - #[proptest(strategy = "1..=5usize")] - window: usize, - }, - } - - impl Op { - fn apply(self, layout: &mut Layout) { - match self { - Op::AddOutput(id) => { - let name = format!("output{id}"); - if layout.outputs().any(|o| o.name() == name) { - return; - } - - let output = Output::new( - name.clone(), - PhysicalProperties { - size: Size::from((1280, 720)), - subpixel: Subpixel::Unknown, - make: String::new(), - model: String::new(), - }, - ); - output.change_current_state( - Some(Mode { - size: Size::from((1280, 720)), - refresh: 60000, - }), - None, - None, - None, - ); - output.user_data().insert_if_missing(|| OutputName { - connector: name, - make: None, - model: None, - serial: None, - }); - layout.add_output(output.clone()); - } - Op::AddScaledOutput { id, scale } => { - let name = format!("output{id}"); - if layout.outputs().any(|o| o.name() == name) { - return; - } - - let output = Output::new( - name.clone(), - PhysicalProperties { - size: Size::from((1280, 720)), - subpixel: Subpixel::Unknown, - make: String::new(), - model: String::new(), - }, - ); - output.change_current_state( - Some(Mode { - size: Size::from((1280, 720)), - refresh: 60000, - }), - None, - Some(smithay::output::Scale::Fractional(scale)), - None, - ); - output.user_data().insert_if_missing(|| OutputName { - connector: name, - make: None, - model: None, - serial: None, - }); - layout.add_output(output.clone()); - } - Op::RemoveOutput(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.remove_output(&output); - } - Op::FocusOutput(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.focus_output(&output); - } - Op::AddNamedWorkspace { - ws_name, - output_name, - } => { - layout.ensure_named_workspace(&WorkspaceConfig { - name: WorkspaceName(format!("ws{ws_name}")), - open_on_output: output_name.map(|name| format!("output{name}")), - }); - } - Op::UnnameWorkspace { ws_name } => { - layout.unname_workspace(&format!("ws{ws_name}")); - } - Op::SetWorkspaceName { - new_ws_name, - ws_name, - } => { - let ws_ref = - ws_name.map(|ws_name| WorkspaceReference::Name(format!("ws{ws_name}"))); - layout.set_workspace_name(format!("ws{new_ws_name}"), ws_ref); - } - Op::UnsetWorkspaceName { ws_name } => { - let ws_ref = - ws_name.map(|ws_name| WorkspaceReference::Name(format!("ws{ws_name}"))); - layout.unset_workspace_name(ws_ref); - } - Op::AddWindow { mut params } => { - if layout.has_window(¶ms.id) { - return; - } - if let Some(parent_id) = params.parent_id { - if parent_id_causes_loop(layout, params.id, parent_id) { - params.parent_id = None; - } - } - - let win = TestWindow::new(params); - layout.add_window( - win, - AddWindowTarget::Auto, - None, - None, - false, - params.is_floating, - ActivateWindow::default(), - ); - } - Op::AddWindowNextTo { - mut params, - next_to_id, - } => { - let mut found_next_to = false; - - if let Some(InteractiveMoveState::Moving(move_)) = &layout.interactive_move { - let win_id = move_.tile.window().0.id; - if win_id == params.id { - return; - } - if win_id == next_to_id { - found_next_to = true; - } - } - - match &mut layout.monitor_set { - MonitorSet::Normal { monitors, .. } => { - for mon in monitors { - for ws in &mut mon.workspaces { - for win in ws.windows() { - if win.0.id == params.id { - return; - } - - if win.0.id == next_to_id { - found_next_to = true; - } - } - } - } - } - MonitorSet::NoOutputs { workspaces, .. } => { - for ws in workspaces { - for win in ws.windows() { - if win.0.id == params.id { - return; - } - - if win.0.id == next_to_id { - found_next_to = true; - } - } - } - } - } - - if !found_next_to { - return; - } - - if let Some(parent_id) = params.parent_id { - if parent_id_causes_loop(layout, params.id, parent_id) { - params.parent_id = None; - } - } - - let win = TestWindow::new(params); - layout.add_window( - win, - AddWindowTarget::NextTo(&next_to_id), - None, - None, - false, - params.is_floating, - ActivateWindow::default(), - ); - } - Op::AddWindowToNamedWorkspace { - mut params, - ws_name, - } => { - let ws_name = format!("ws{ws_name}"); - let mut ws_id = None; - - if let Some(InteractiveMoveState::Moving(move_)) = &layout.interactive_move { - if move_.tile.window().0.id == params.id { - return; - } - } - - match &mut layout.monitor_set { - MonitorSet::Normal { monitors, .. } => { - for mon in monitors { - for ws in &mut mon.workspaces { - for win in ws.windows() { - if win.0.id == params.id { - return; - } - } - - if ws - .name - .as_ref() - .is_some_and(|name| name.eq_ignore_ascii_case(&ws_name)) - { - ws_id = Some(ws.id()); - } - } - } - } - MonitorSet::NoOutputs { workspaces, .. } => { - for ws in workspaces { - for win in ws.windows() { - if win.0.id == params.id { - return; - } - } - - if ws - .name - .as_ref() - .is_some_and(|name| name.eq_ignore_ascii_case(&ws_name)) - { - ws_id = Some(ws.id()); - } - } - } - } - - let Some(ws_id) = ws_id else { - return; - }; - - if let Some(parent_id) = params.parent_id { - if parent_id_causes_loop(layout, params.id, parent_id) { - params.parent_id = None; - } - } - - let win = TestWindow::new(params); - layout.add_window( - win, - AddWindowTarget::Workspace(ws_id), - None, - None, - false, - params.is_floating, - ActivateWindow::default(), - ); - } - Op::CloseWindow(id) => { - layout.remove_window(&id, Transaction::new()); - } - Op::FullscreenWindow(id) => { - layout.toggle_fullscreen(&id); - } - Op::SetFullscreenWindow { - window, - is_fullscreen, - } => { - layout.set_fullscreen(&window, is_fullscreen); - } - Op::FocusColumnLeft => layout.focus_left(), - Op::FocusColumnRight => layout.focus_right(), - Op::FocusColumnFirst => layout.focus_column_first(), - Op::FocusColumnLast => layout.focus_column_last(), - Op::FocusColumnRightOrFirst => layout.focus_column_right_or_first(), - Op::FocusColumnLeftOrLast => layout.focus_column_left_or_last(), - Op::FocusWindowOrMonitorUp(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.focus_window_up_or_output(&output); - } - Op::FocusWindowOrMonitorDown(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.focus_window_down_or_output(&output); - } - Op::FocusColumnOrMonitorLeft(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.focus_column_left_or_output(&output); - } - Op::FocusColumnOrMonitorRight(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.focus_column_right_or_output(&output); - } - Op::FocusWindowDown => layout.focus_down(), - Op::FocusWindowUp => layout.focus_up(), - Op::FocusWindowDownOrColumnLeft => layout.focus_down_or_left(), - Op::FocusWindowDownOrColumnRight => layout.focus_down_or_right(), - Op::FocusWindowUpOrColumnLeft => layout.focus_up_or_left(), - Op::FocusWindowUpOrColumnRight => layout.focus_up_or_right(), - Op::FocusWindowOrWorkspaceDown => layout.focus_window_or_workspace_down(), - Op::FocusWindowOrWorkspaceUp => layout.focus_window_or_workspace_up(), - Op::FocusWindow(id) => layout.activate_window(&id), - Op::MoveColumnLeft => layout.move_left(), - Op::MoveColumnRight => layout.move_right(), - Op::MoveColumnToFirst => layout.move_column_to_first(), - Op::MoveColumnToLast => layout.move_column_to_last(), - Op::MoveColumnLeftOrToMonitorLeft(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.move_column_left_or_to_output(&output); - } - Op::MoveColumnRightOrToMonitorRight(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.move_column_right_or_to_output(&output); - } - Op::MoveWindowDown => layout.move_down(), - Op::MoveWindowUp => layout.move_up(), - Op::MoveWindowDownOrToWorkspaceDown => layout.move_down_or_to_workspace_down(), - Op::MoveWindowUpOrToWorkspaceUp => layout.move_up_or_to_workspace_up(), - Op::ConsumeOrExpelWindowLeft { id } => { - let id = id.filter(|id| layout.has_window(id)); - layout.consume_or_expel_window_left(id.as_ref()); - } - Op::ConsumeOrExpelWindowRight { id } => { - let id = id.filter(|id| layout.has_window(id)); - layout.consume_or_expel_window_right(id.as_ref()); - } - Op::ConsumeWindowIntoColumn => layout.consume_into_column(), - Op::ExpelWindowFromColumn => layout.expel_from_column(), - Op::SwapWindowInDirection(direction) => layout.swap_window_in_direction(direction), - Op::CenterColumn => layout.center_column(), - Op::CenterWindow { id } => { - let id = id.filter(|id| layout.has_window(id)); - layout.center_window(id.as_ref()); - } - Op::FocusWorkspaceDown => layout.switch_workspace_down(), - Op::FocusWorkspaceUp => layout.switch_workspace_up(), - Op::FocusWorkspace(idx) => layout.switch_workspace(idx), - Op::FocusWorkspaceAutoBackAndForth(idx) => { - layout.switch_workspace_auto_back_and_forth(idx) - } - Op::FocusWorkspacePrevious => layout.switch_workspace_previous(), - Op::MoveWindowToWorkspaceDown => layout.move_to_workspace_down(), - Op::MoveWindowToWorkspaceUp => layout.move_to_workspace_up(), - Op::MoveWindowToWorkspace { - window_id, - workspace_idx, - } => { - let window_id = window_id.filter(|id| layout.has_window(id)); - layout.move_to_workspace(window_id.as_ref(), workspace_idx); - } - Op::MoveColumnToWorkspaceDown => layout.move_column_to_workspace_down(), - Op::MoveColumnToWorkspaceUp => layout.move_column_to_workspace_up(), - Op::MoveColumnToWorkspace(idx) => layout.move_column_to_workspace(idx), - Op::MoveWindowToOutput { - window_id, - output_id: id, - target_ws_idx, - } => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - let mon = layout.monitor_for_output(&output).unwrap(); - - let window_id = window_id.filter(|id| layout.has_window(id)); - let target_ws_idx = target_ws_idx.filter(|idx| mon.workspaces.len() > *idx); - layout.move_to_output(window_id.as_ref(), &output, target_ws_idx); - } - Op::MoveColumnToOutput(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.move_column_to_output(&output); - } - Op::MoveWorkspaceDown => layout.move_workspace_down(), - Op::MoveWorkspaceUp => layout.move_workspace_up(), - Op::MoveWorkspaceToIndex { - ws_name: Some(ws_name), - target_idx, - } => { - let MonitorSet::Normal { monitors, .. } = &mut layout.monitor_set else { - return; - }; - - let Some((old_idx, old_output)) = monitors.iter().find_map(|monitor| { - monitor - .workspaces - .iter() - .enumerate() - .find_map(|(i, ws)| { - if ws.name == Some(format!("ws{ws_name}")) { - Some(i) - } else { - None - } - }) - .map(|i| (i, monitor.output.clone())) - }) else { - return; - }; - - layout.move_workspace_to_idx(Some((Some(old_output), old_idx)), target_idx) - } - Op::MoveWorkspaceToIndex { - ws_name: None, - target_idx, - } => layout.move_workspace_to_idx(None, target_idx), - Op::MoveWorkspaceToMonitor { - ws_name: None, - output_id: id, - } => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - layout.move_workspace_to_output(&output); - } - Op::MoveWorkspaceToMonitor { - ws_name: Some(ws_name), - output_id: id, - } => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - let MonitorSet::Normal { monitors, .. } = &mut layout.monitor_set else { - return; - }; - - let Some((old_idx, old_output)) = monitors.iter().find_map(|monitor| { - monitor - .workspaces - .iter() - .enumerate() - .find_map(|(i, ws)| { - if ws.name == Some(format!("ws{ws_name}")) { - Some(i) - } else { - None - } - }) - .map(|i| (i, monitor.output.clone())) - }) else { - return; - }; - - layout.move_workspace_to_output_by_id(old_idx, Some(old_output), output); - } - Op::SwitchPresetColumnWidth => layout.toggle_width(), - Op::SwitchPresetWindowWidth { id } => { - let id = id.filter(|id| layout.has_window(id)); - layout.toggle_window_width(id.as_ref()); - } - Op::SwitchPresetWindowHeight { id } => { - let id = id.filter(|id| layout.has_window(id)); - layout.toggle_window_height(id.as_ref()); - } - Op::MaximizeColumn => layout.toggle_full_width(), - Op::SetColumnWidth(change) => layout.set_column_width(change), - Op::SetWindowWidth { id, change } => { - let id = id.filter(|id| layout.has_window(id)); - layout.set_window_width(id.as_ref(), change); - } - Op::SetWindowHeight { id, change } => { - let id = id.filter(|id| layout.has_window(id)); - layout.set_window_height(id.as_ref(), change); - } - Op::ResetWindowHeight { id } => { - let id = id.filter(|id| layout.has_window(id)); - layout.reset_window_height(id.as_ref()); - } - Op::ToggleWindowFloating { id } => { - let id = id.filter(|id| layout.has_window(id)); - layout.toggle_window_floating(id.as_ref()); - } - Op::SetWindowFloating { id, floating } => { - let id = id.filter(|id| layout.has_window(id)); - layout.set_window_floating(id.as_ref(), floating); - } - Op::FocusFloating => { - layout.focus_floating(); - } - Op::FocusTiling => { - layout.focus_tiling(); - } - Op::SwitchFocusFloatingTiling => { - layout.switch_focus_floating_tiling(); - } - Op::MoveFloatingWindow { id, x, y, animate } => { - let id = id.filter(|id| layout.has_window(id)); - layout.move_floating_window(id.as_ref(), x, y, animate); - } - Op::SetParent { - id, - mut new_parent_id, - } => { - if !layout.has_window(&id) { - return; - } - - if let Some(parent_id) = new_parent_id { - if parent_id_causes_loop(layout, id, parent_id) { - new_parent_id = None; - } - } - - let mut update = false; - - if let Some(InteractiveMoveState::Moving(move_)) = &layout.interactive_move { - if move_.tile.window().0.id == id { - move_.tile.window().0.parent_id.set(new_parent_id); - update = true; - } - } - - match &mut layout.monitor_set { - MonitorSet::Normal { monitors, .. } => { - 'outer: for mon in monitors { - for ws in &mut mon.workspaces { - for win in ws.windows() { - if win.0.id == id { - win.0.parent_id.set(new_parent_id); - update = true; - break 'outer; - } - } - } - } - } - MonitorSet::NoOutputs { workspaces, .. } => { - 'outer: for ws in workspaces { - for win in ws.windows() { - if win.0.id == id { - win.0.parent_id.set(new_parent_id); - update = true; - break 'outer; - } - } - } - } - } - - if update { - if let Some(new_parent_id) = new_parent_id { - layout.descendants_added(&new_parent_id); - } - } - } - Op::Communicate(id) => { - let mut update = false; - - if let Some(InteractiveMoveState::Moving(move_)) = &layout.interactive_move { - if move_.tile.window().0.id == id { - if move_.tile.window().communicate() { - update = true; - } - - if update { - // FIXME: serial. - layout.update_window(&id, None); - } - return; - } - } - - match &mut layout.monitor_set { - MonitorSet::Normal { monitors, .. } => { - 'outer: for mon in monitors { - for ws in &mut mon.workspaces { - for win in ws.windows() { - if win.0.id == id { - if win.communicate() { - update = true; - } - break 'outer; - } - } - } - } - } - MonitorSet::NoOutputs { workspaces, .. } => { - 'outer: for ws in workspaces { - for win in ws.windows() { - if win.0.id == id { - if win.communicate() { - update = true; - } - break 'outer; - } - } - } - } - } - - if update { - // FIXME: serial. - layout.update_window(&id, None); - } - } - Op::Refresh { is_active } => { - layout.refresh(is_active); - } - Op::AdvanceAnimations { msec_delta } => { - let mut now = layout.clock.now_unadjusted(); - if msec_delta >= 0 { - now = now.saturating_add(Duration::from_millis(msec_delta as u64)); - } else { - now = now.saturating_sub(Duration::from_millis(-msec_delta as u64)); - } - layout.clock.set_unadjusted(now); - layout.advance_animations(); - } - Op::MoveWorkspaceToOutput(id) => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.move_workspace_to_output(&output); - } - Op::ViewOffsetGestureBegin { - output_idx: id, - is_touchpad: normalize, - } => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.view_offset_gesture_begin(&output, normalize); - } - Op::ViewOffsetGestureUpdate { - delta, - timestamp, - is_touchpad, - } => { - layout.view_offset_gesture_update(delta, timestamp, is_touchpad); - } - Op::ViewOffsetGestureEnd { is_touchpad } => { - // We don't handle cancels in this gesture. - layout.view_offset_gesture_end(false, is_touchpad); - } - Op::WorkspaceSwitchGestureBegin { - output_idx: id, - is_touchpad, - } => { - let name = format!("output{id}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - - layout.workspace_switch_gesture_begin(&output, is_touchpad); - } - Op::WorkspaceSwitchGestureUpdate { - delta, - timestamp, - is_touchpad, - } => { - layout.workspace_switch_gesture_update(delta, timestamp, is_touchpad); - } - Op::WorkspaceSwitchGestureEnd { - cancelled, - is_touchpad, - } => { - layout.workspace_switch_gesture_end(cancelled, is_touchpad); - } - Op::InteractiveMoveBegin { - window, - output_idx, - px, - py, - } => { - let name = format!("output{output_idx}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - layout.interactive_move_begin(window, &output, Point::from((px, py))); - } - Op::InteractiveMoveUpdate { - window, - dx, - dy, - output_idx, - px, - py, - } => { - let name = format!("output{output_idx}"); - let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { - return; - }; - layout.interactive_move_update( - &window, - Point::from((dx, dy)), - output, - Point::from((px, py)), - ); - } - Op::InteractiveMoveEnd { window } => { - layout.interactive_move_end(&window); - } - Op::InteractiveResizeBegin { window, edges } => { - layout.interactive_resize_begin(window, edges); - } - Op::InteractiveResizeUpdate { window, dx, dy } => { - layout.interactive_resize_update(&window, Point::from((dx, dy))); - } - Op::InteractiveResizeEnd { window } => { - layout.interactive_resize_end(&window); - } - } - } - } - - #[track_caller] - fn check_ops(ops: &[Op]) -> Layout { - let mut layout = Layout::default(); - for op in ops { - op.apply(&mut layout); - layout.verify_invariants(); - } - layout - } - - #[track_caller] - fn check_ops_with_options(options: Options, ops: &[Op]) -> Layout { - let mut layout = Layout::with_options(Clock::with_time(Duration::ZERO), options); - - for op in ops { - op.apply(&mut layout); - layout.verify_invariants(); - } - - layout - } - - #[test] - fn operations_dont_panic() { - let every_op = [ - Op::AddOutput(0), - Op::AddOutput(1), - Op::AddOutput(2), - Op::RemoveOutput(0), - Op::RemoveOutput(1), - Op::RemoveOutput(2), - Op::FocusOutput(0), - Op::FocusOutput(1), - Op::FocusOutput(2), - Op::AddNamedWorkspace { - ws_name: 1, - output_name: Some(1), - }, - Op::UnnameWorkspace { ws_name: 1 }, - Op::AddWindow { - params: TestWindowParams::new(0), - }, - Op::AddWindow { - params: TestWindowParams::new(1), - }, - Op::AddWindowNextTo { - params: TestWindowParams::new(2), - next_to_id: 1, - }, - Op::AddWindowToNamedWorkspace { - params: TestWindowParams::new(3), - ws_name: 1, - }, - Op::CloseWindow(0), - Op::CloseWindow(1), - Op::CloseWindow(2), - Op::FullscreenWindow(1), - Op::FullscreenWindow(2), - Op::FullscreenWindow(3), - Op::FocusColumnLeft, - Op::FocusColumnRight, - Op::FocusColumnRightOrFirst, - Op::FocusColumnLeftOrLast, - Op::FocusWindowOrMonitorUp(0), - Op::FocusWindowOrMonitorDown(1), - Op::FocusColumnOrMonitorLeft(0), - Op::FocusColumnOrMonitorRight(1), - Op::FocusWindowUp, - Op::FocusWindowUpOrColumnLeft, - Op::FocusWindowUpOrColumnRight, - Op::FocusWindowOrWorkspaceUp, - Op::FocusWindowDown, - Op::FocusWindowDownOrColumnLeft, - Op::FocusWindowDownOrColumnRight, - Op::FocusWindowOrWorkspaceDown, - Op::MoveColumnLeft, - Op::MoveColumnRight, - Op::MoveColumnLeftOrToMonitorLeft(0), - Op::MoveColumnRightOrToMonitorRight(1), - Op::ConsumeWindowIntoColumn, - Op::ExpelWindowFromColumn, - Op::CenterColumn, - Op::FocusWorkspaceDown, - Op::FocusWorkspaceUp, - Op::FocusWorkspace(1), - Op::FocusWorkspace(2), - Op::MoveWindowToWorkspaceDown, - Op::MoveWindowToWorkspaceUp, - Op::MoveWindowToWorkspace { - window_id: None, - workspace_idx: 1, - }, - Op::MoveWindowToWorkspace { - window_id: None, - workspace_idx: 2, - }, - Op::MoveColumnToWorkspaceDown, - Op::MoveColumnToWorkspaceUp, - Op::MoveColumnToWorkspace(1), - Op::MoveColumnToWorkspace(2), - Op::MoveWindowDown, - Op::MoveWindowDownOrToWorkspaceDown, - Op::MoveWindowUp, - Op::MoveWindowUpOrToWorkspaceUp, - Op::ConsumeOrExpelWindowLeft { id: None }, - Op::ConsumeOrExpelWindowRight { id: None }, - Op::MoveWorkspaceToOutput(1), - ]; - - for third in every_op { - for second in every_op { - for first in every_op { - // eprintln!("{first:?}, {second:?}, {third:?}"); - - let mut layout = Layout::default(); - first.apply(&mut layout); - layout.verify_invariants(); - second.apply(&mut layout); - layout.verify_invariants(); - third.apply(&mut layout); - layout.verify_invariants(); - } - } - } - } - - #[test] - fn operations_from_starting_state_dont_panic() { - if std::env::var_os("RUN_SLOW_TESTS").is_none() { - eprintln!("ignoring slow test"); - return; - } - - // Running every op from an empty state doesn't get us to all the interesting states. So, - // also run it from a manually-created starting state with more things going on to exercise - // more code paths. - let setup_ops = [ - Op::AddOutput(1), - Op::AddWindow { - params: TestWindowParams::new(1), - }, - Op::MoveWindowToWorkspaceDown, - Op::AddWindow { - params: TestWindowParams::new(2), - }, - Op::AddWindow { - params: TestWindowParams::new(3), - }, - Op::FocusColumnLeft, - Op::ConsumeWindowIntoColumn, - Op::AddWindow { - params: TestWindowParams::new(4), - }, - Op::AddOutput(2), - Op::AddWindow { - params: TestWindowParams::new(5), - }, - Op::MoveWindowToOutput { - window_id: None, - output_id: 2, - target_ws_idx: None, - }, - Op::FocusOutput(1), - Op::Communicate(1), - Op::Communicate(2), - Op::Communicate(3), - Op::Communicate(4), - Op::Communicate(5), - ]; - - let every_op = [ - Op::AddOutput(0), - Op::AddOutput(1), - Op::AddOutput(2), - Op::RemoveOutput(0), - Op::RemoveOutput(1), - Op::RemoveOutput(2), - Op::FocusOutput(0), - Op::FocusOutput(1), - Op::FocusOutput(2), - Op::AddNamedWorkspace { - ws_name: 1, - output_name: Some(1), - }, - Op::UnnameWorkspace { ws_name: 1 }, - Op::AddWindow { - params: TestWindowParams::new(0), - }, - Op::AddWindow { - params: TestWindowParams::new(1), - }, - Op::AddWindow { - params: TestWindowParams::new(2), - }, - Op::AddWindowNextTo { - params: TestWindowParams::new(6), - next_to_id: 0, - }, - Op::AddWindowNextTo { - params: TestWindowParams::new(7), - next_to_id: 1, - }, - Op::AddWindowToNamedWorkspace { - params: TestWindowParams::new(5), - ws_name: 1, - }, - Op::CloseWindow(0), - Op::CloseWindow(1), - Op::CloseWindow(2), - Op::FullscreenWindow(1), - Op::FullscreenWindow(2), - Op::FullscreenWindow(3), - Op::SetFullscreenWindow { - window: 1, - is_fullscreen: false, - }, - Op::SetFullscreenWindow { - window: 1, - is_fullscreen: true, - }, - Op::SetFullscreenWindow { - window: 2, - is_fullscreen: false, - }, - Op::SetFullscreenWindow { - window: 2, - is_fullscreen: true, - }, - Op::FocusColumnLeft, - Op::FocusColumnRight, - Op::FocusColumnRightOrFirst, - Op::FocusColumnLeftOrLast, - Op::FocusWindowOrMonitorUp(0), - Op::FocusWindowOrMonitorDown(1), - Op::FocusColumnOrMonitorLeft(0), - Op::FocusColumnOrMonitorRight(1), - Op::FocusWindowUp, - Op::FocusWindowUpOrColumnLeft, - Op::FocusWindowUpOrColumnRight, - Op::FocusWindowOrWorkspaceUp, - Op::FocusWindowDown, - Op::FocusWindowDownOrColumnLeft, - Op::FocusWindowDownOrColumnRight, - Op::FocusWindowOrWorkspaceDown, - Op::MoveColumnLeft, - Op::MoveColumnRight, - Op::MoveColumnLeftOrToMonitorLeft(0), - Op::MoveColumnRightOrToMonitorRight(1), - Op::ConsumeWindowIntoColumn, - Op::ExpelWindowFromColumn, - Op::CenterColumn, - Op::FocusWorkspaceDown, - Op::FocusWorkspaceUp, - Op::FocusWorkspace(1), - Op::FocusWorkspace(2), - Op::FocusWorkspace(3), - Op::MoveWindowToWorkspaceDown, - Op::MoveWindowToWorkspaceUp, - Op::MoveWindowToWorkspace { - window_id: None, - workspace_idx: 1, - }, - Op::MoveWindowToWorkspace { - window_id: None, - workspace_idx: 2, - }, - Op::MoveWindowToWorkspace { - window_id: None, - workspace_idx: 3, - }, - Op::MoveColumnToWorkspaceDown, - Op::MoveColumnToWorkspaceUp, - Op::MoveColumnToWorkspace(1), - Op::MoveColumnToWorkspace(2), - Op::MoveColumnToWorkspace(3), - Op::MoveWindowDown, - Op::MoveWindowDownOrToWorkspaceDown, - Op::MoveWindowUp, - Op::MoveWindowUpOrToWorkspaceUp, - Op::ConsumeOrExpelWindowLeft { id: None }, - Op::ConsumeOrExpelWindowRight { id: None }, - ]; - - for thi