From daaee43be387d75c62c7ba63d33b0b8f9ae192c8 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Thu, 26 Dec 2024 09:37:38 +0300 Subject: layout: Refactor window opening targets --- niri-visual-tests/src/cases/layout.rs | 13 +- src/handlers/compositor.rs | 64 ++--- src/handlers/xdg_shell.rs | 2 +- src/layout/floating.rs | 5 +- src/layout/mod.rs | 468 +++++++++++++++++----------------- src/layout/monitor.rs | 204 +++++++-------- src/layout/scrolling.rs | 4 +- src/layout/workspace.rs | 229 ++++++++++------- 8 files changed, 490 insertions(+), 499 deletions(-) diff --git a/niri-visual-tests/src/cases/layout.rs b/niri-visual-tests/src/cases/layout.rs index 40ca1f87..4fa2f24d 100644 --- a/niri-visual-tests/src/cases/layout.rs +++ b/niri-visual-tests/src/cases/layout.rs @@ -3,7 +3,7 @@ use std::time::Duration; use niri::animation::Clock; use niri::layout::scrolling::ColumnWidth; -use niri::layout::{ActivateWindow, LayoutElement as _, Options}; +use niri::layout::{ActivateWindow, AddWindowTarget, LayoutElement as _, Options}; use niri::render_helpers::RenderTarget; use niri_config::{Color, FloatOrInt, OutputName}; use smithay::backend::renderer::element::RenderElement; @@ -170,6 +170,7 @@ impl Layout { self.layout.add_window( window.clone(), + AddWindowTarget::Auto, width, false, false, @@ -194,8 +195,14 @@ impl Layout { ); window.communicate(); - self.layout - .add_window_right_of(right_of.id(), window.clone(), width, false, false); + self.layout.add_window( + window.clone(), + AddWindowTarget::NextTo(right_of.id()), + width, + false, + false, + ActivateWindow::default(), + ); self.windows.push(window); } diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs index 0e9397ee..3856849f 100644 --- a/src/handlers/compositor.rs +++ b/src/handlers/compositor.rs @@ -19,7 +19,7 @@ use smithay::{delegate_compositor, delegate_shm}; use super::xdg_shell::add_mapped_toplevel_pre_commit_hook; use crate::handlers::XDG_ACTIVATION_TOKEN_TIMEOUT; -use crate::layout::ActivateWindow; +use crate::layout::{ActivateWindow, AddWindowTarget}; use crate::niri::{ClientState, State}; use crate::utils::send_scale_transform; use crate::utils::transaction::Transaction; @@ -96,7 +96,7 @@ impl CompositorHandler for State { let toplevel = window.toplevel().expect("no X11 support"); - let (rules, width, is_full_width, output, workspace_name) = + let (rules, width, is_full_width, output, workspace_id) = if let InitialConfigureState::Configured { rules, width, @@ -110,10 +110,12 @@ impl CompositorHandler for State { output.filter(|o| self.niri.layout.monitor_for_output(o).is_some()); // Check that the workspace still exists. - let workspace_name = workspace_name - .filter(|n| self.niri.layout.find_workspace_by_name(n).is_some()); + let workspace_id = workspace_name + .as_deref() + .and_then(|n| self.niri.layout.find_workspace_by_name(n)) + .map(|(_, ws)| ws.id()); - (rules, width, is_full_width, output, workspace_name) + (rules, width, is_full_width, output, workspace_id) } else { error!("window map must happen after initial configure"); (ResolvedWindowRules::empty(), None, false, None, None) @@ -160,46 +162,24 @@ impl CompositorHandler for State { } }; - let output = if let Some(p) = parent { - // Open dialogs immediately to the right of their parent window. - // - // FIXME: do we want to use activate here? How do we want things to behave - // exactly? - self.niri.layout.add_window_right_of( - &p, - mapped, - width, - is_full_width, - is_floating, - ) - } else if let Some(workspace_name) = &workspace_name { - self.niri.layout.add_window_to_named_workspace( - workspace_name, - mapped, - width, - is_full_width, - is_floating, - activate, - ) + let target = if let Some(p) = &parent { + // Open dialogs next to their parent window. + AddWindowTarget::NextTo(p) + } else if let Some(id) = workspace_id { + AddWindowTarget::Workspace(id) } else if let Some(output) = &output { - self.niri.layout.add_window_on_output( - output, - mapped, - width, - is_full_width, - is_floating, - activate, - ); - Some(output) + AddWindowTarget::Output(output) } else { - self.niri.layout.add_window( - mapped, - width, - is_full_width, - is_floating, - activate, - ) + AddWindowTarget::Auto }; + let output = self.niri.layout.add_window( + mapped, + target, + width, + is_full_width, + is_floating, + activate, + ); if let Some(output) = output.cloned() { self.niri.layout.start_open_animation_for_window(&window); diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 5cb515ce..a3559542 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -859,7 +859,7 @@ impl State { }); } - width = ws.resolve_default_width(rules.default_width); + width = ws.resolve_default_width(rules.default_width, is_floating); let configure_width = if is_full_width { Some(ColumnWidth::Proportion(1.)) diff --git a/src/layout/floating.rs b/src/layout/floating.rs index dac4ed12..3633ad18 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -428,10 +428,7 @@ impl FloatingSpace { self.bring_up_descendants_of(idx); } - pub fn add_tile_above(&mut self, above: &W::Id, mut tile: Tile) { - // Activate the new window if above was active. - let activate = Some(above) == self.active_window_id.as_ref(); - + pub fn add_tile_above(&mut self, above: &W::Id, mut tile: Tile, activate: bool) { let idx = self.idx_of(above).unwrap(); let above_pos = self.data[idx].logical_pos; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 691140a2..8f512c3b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -35,6 +35,7 @@ use std::mem; use std::rc::Rc; use std::time::Duration; +use monitor::MonitorAddWindowTarget; use niri_config::{ CenterFocusedColumn, Config, CornerRadius, FloatOrInt, PresetSize, Struts, Workspace as WorkspaceConfig, @@ -48,7 +49,7 @@ use smithay::output::{self, Output}; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform}; use tile::{Tile, TileRenderElement}; -use workspace::WorkspaceId; +use workspace::{WorkspaceAddWindowTarget, WorkspaceId}; pub use self::monitor::MonitorRenderElement; use self::monitor::{Monitor, WorkspaceSwitch}; @@ -424,6 +425,20 @@ pub enum ActivateWindow { No, } +/// Where to put a newly added window. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum AddWindowTarget<'a, W: LayoutElement> { + /// No particular preference. + #[default] + Auto, + /// On this output. + Output(&'a Output), + /// On this workspace. + Workspace(WorkspaceId), + /// Next to this existing window. + NextTo(&'a W::Id), +} + impl InteractiveMoveState { fn moving(&self) -> Option<&InteractiveMoveData> { match self { @@ -797,71 +812,6 @@ impl Layout { } } - /// Adds a new window to the layout on a specific workspace. - pub fn add_window_to_named_workspace( - &mut self, - workspace_name: &str, - window: W, - width: Option, - is_full_width: bool, - is_floating: bool, - activate: ActivateWindow, - ) -> Option<&Output> { - let width = self.resolve_default_width(&window, width); - - match &mut self.monitor_set { - MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } => { - let (mon_idx, mon, ws_idx) = monitors - .iter_mut() - .enumerate() - .find_map(|(mon_idx, mon)| { - mon.find_named_workspace_index(workspace_name) - .map(move |ws_idx| (mon_idx, mon, ws_idx)) - }) - .unwrap(); - - if activate == ActivateWindow::Yes { - *active_monitor_idx = mon_idx; - } - - let activate = activate.map_smart(|| { - // Don't steal focus from an active fullscreen window. - let ws = &mon.workspaces[ws_idx]; - if mon_idx == *active_monitor_idx && ws.is_active_fullscreen() { - return false; - } - - // Don't activate if on a different workspace. - if mon.active_workspace_idx != ws_idx { - return false; - } - - true - }); - - mon.add_window(ws_idx, window, activate, width, is_full_width, is_floating); - Some(&mon.output) - } - MonitorSet::NoOutputs { workspaces } => { - let ws = workspaces - .iter_mut() - .find(|ws| { - ws.name - .as_ref() - .map_or(false, |name| name.eq_ignore_ascii_case(workspace_name)) - }) - .unwrap(); - let activate = activate.map_smart(|| true); - ws.add_window(window, activate, width, is_full_width, is_floating); - None - } - } - } - pub fn add_column_by_idx( &mut self, monitor_idx: usize, @@ -891,12 +841,13 @@ impl Layout { pub fn add_window( &mut self, window: W, + target: AddWindowTarget, width: Option, is_full_width: bool, is_floating: bool, activate: ActivateWindow, ) -> Option<&Output> { - let width = self.resolve_default_width(&window, width); + let resolved_width = self.resolve_default_width(&window, width, is_floating); match &mut self.monitor_set { MonitorSet::Normal { @@ -904,142 +855,136 @@ impl Layout { active_monitor_idx, .. } => { - let mon = &mut monitors[*active_monitor_idx]; + let (mon_idx, target) = match target { + AddWindowTarget::Auto => (*active_monitor_idx, MonitorAddWindowTarget::Auto), + AddWindowTarget::Output(output) => { + let mon_idx = monitors + .iter() + .position(|mon| mon.output == *output) + .unwrap(); - let activate = activate.map_smart(|| { - // Don't steal focus from an active fullscreen window. - let ws = &mon.workspaces[mon.active_workspace_idx]; - !ws.is_active_fullscreen() - }); + (mon_idx, MonitorAddWindowTarget::Auto) + } + AddWindowTarget::Workspace(ws_id) => { + let mon_idx = monitors + .iter() + .position(|mon| mon.workspaces.iter().any(|ws| ws.id() == ws_id)) + .unwrap(); + + ( + mon_idx, + MonitorAddWindowTarget::Workspace { + id: ws_id, + column_idx: None, + }, + ) + } + AddWindowTarget::NextTo(next_to) => { + if let Some(output) = self + .interactive_move + .as_ref() + .and_then(|move_| { + if let InteractiveMoveState::Moving(move_) = move_ { + Some(move_) + } else { + None + } + }) + .filter(|move_| next_to == move_.tile.window().id()) + .map(|move_| move_.output.clone()) + { + // The next_to window is being interactively moved. + let mon_idx = monitors + .iter() + .position(|mon| mon.output == output) + .unwrap_or(*active_monitor_idx); + + (mon_idx, MonitorAddWindowTarget::Auto) + } else { + let mon_idx = monitors + .iter() + .position(|mon| { + mon.workspaces.iter().any(|ws| ws.has_window(next_to)) + }) + .unwrap(); + (mon_idx, MonitorAddWindowTarget::NextTo(next_to)) + } + } + }; + let mon = &mut monitors[mon_idx]; mon.add_window( - mon.active_workspace_idx, window, + target, activate, - width, + resolved_width, is_full_width, is_floating, ); - Some(&mon.output) - } - MonitorSet::NoOutputs { workspaces } => { - let ws = if let Some(ws) = workspaces.get_mut(0) { - ws - } else { - workspaces.push(Workspace::new_no_outputs( - self.clock.clone(), - self.options.clone(), - )); - &mut workspaces[0] - }; - let activate = activate.map_smart(|| true); - ws.add_window(window, activate, width, is_full_width, is_floating); - None - } - } - } - /// Adds a new window to the layout immediately to the right of another window. - /// - /// If that another window was active, activates the new window. - /// - /// Returns an output that the window was added to, if there were any outputs. - pub fn add_window_right_of( - &mut self, - right_of: &W::Id, - window: W, - width: Option, - is_full_width: bool, - is_floating: bool, - ) -> Option<&Output> { - if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { - if right_of == move_.tile.window().id() { - let output = move_.output.clone(); - let activate = ActivateWindow::default(); - if self.monitor_for_output(&output).is_some() { - self.add_window_on_output( - &output, - window, - width, - is_full_width, - is_floating, - activate, - ); - return Some(&self.monitor_for_output(&output).unwrap().output); - } else { - return self.add_window(window, width, is_full_width, is_floating, activate); + if activate.map_smart(|| false) { + *active_monitor_idx = mon_idx; } - } - } - - let width = self.resolve_default_width(&window, width); - - match &mut self.monitor_set { - MonitorSet::Normal { monitors, .. } => { - let mon = monitors - .iter_mut() - .find(|mon| mon.workspaces.iter().any(|ws| ws.has_window(right_of))) - .unwrap(); - mon.add_window_right_of(right_of, window, width, is_full_width, is_floating); Some(&mon.output) } MonitorSet::NoOutputs { workspaces } => { - let ws = workspaces - .iter_mut() - .find(|ws| ws.has_window(right_of)) - .unwrap(); - ws.add_window_right_of(right_of, window, width, is_full_width, is_floating); - None - } - } - } - - /// Adds a new window to the layout on a specific output. - pub fn add_window_on_output( - &mut self, - output: &Output, - window: W, - width: Option, - is_full_width: bool, - is_floating: bool, - activate: ActivateWindow, - ) { - let width = self.resolve_default_width(&window, width); + let (ws_idx, target) = match target { + AddWindowTarget::Auto => { + if workspaces.is_empty() { + workspaces.push(Workspace::new_no_outputs( + self.clock.clone(), + self.options.clone(), + )); + } - let MonitorSet::Normal { - monitors, - active_monitor_idx, - .. - } = &mut self.monitor_set - else { - panic!() - }; + (0, WorkspaceAddWindowTarget::Auto) + } + AddWindowTarget::Output(_) => panic!(), + AddWindowTarget::Workspace(ws_id) => { + let ws_idx = workspaces.iter().position(|ws| ws.id() == ws_id).unwrap(); + (ws_idx, WorkspaceAddWindowTarget::Auto) + } + AddWindowTarget::NextTo(next_to) => { + if self + .interactive_move + .as_ref() + .and_then(|move_| { + if let InteractiveMoveState::Moving(move_) = move_ { + Some(move_) + } else { + None + } + }) + .filter(|move_| next_to == move_.tile.window().id()) + .is_some() + { + // The next_to window is being interactively moved. + (0, WorkspaceAddWindowTarget::Auto) + } else { + let ws_idx = workspaces + .iter() + .position(|ws| ws.has_window(next_to)) + .unwrap(); + (ws_idx, WorkspaceAddWindowTarget::NextTo(next_to)) + } + } + }; + let ws = &mut workspaces[ws_idx]; - let (mon_idx, mon) = monitors - .iter_mut() - .enumerate() - .find(|(_, mon)| mon.output == *output) - .unwrap(); + let tile = ws.make_tile(window); + ws.add_tile( + tile, + target, + activate, + resolved_width, + is_full_width, + is_floating, + ); - if activate == ActivateWindow::Yes { - *active_monitor_idx = mon_idx; + None + } } - - let activate = activate.map_smart(|| { - // Don't steal focus from an active fullscreen window. - let ws = &mon.workspaces[mon.active_workspace_idx]; - mon_idx != *active_monitor_idx || !ws.is_active_fullscreen() - }); - - mon.add_window( - mon.active_workspace_idx, - window, - activate, - width, - is_full_width, - is_floating, - ); } pub fn remove_window( @@ -2801,12 +2746,18 @@ impl Layout { if mon_idx == new_idx && ws_idx == workspace_idx { return; } + let ws_id = monitors[new_idx].workspaces[workspace_idx].id(); let mon = &mut monitors[mon_idx]; let activate = window.map_or(true, |win| { mon_idx == *active_monitor_idx && mon.active_window().map(|win| win.id()) == Some(win) }); + let activate = if activate { + ActivateWindow::Yes + } else { + ActivateWindow::No + }; let ws = &mut mon.workspaces[ws_idx]; let transaction = Transaction::new(); @@ -2821,19 +2772,18 @@ impl Layout { removed.tile.stop_move_animations(); let mon = &mut monitors[new_idx]; - if removed.is_floating { - mon.add_floating_tile(workspace_idx, removed.tile, activate); - } else { - mon.add_tile( - workspace_idx, - None, - removed.tile, - activate, - removed.width, - removed.is_full_width, - ); - } - if activate { + mon.add_tile( + removed.tile, + MonitorAddWindowTarget::Workspace { + id: ws_id, + column_idx: None, + }, + activate, + removed.width, + removed.is_full_width, + removed.is_floating, + ); + if activate.map_smart(|| false) { *active_monitor_idx = new_idx; } @@ -3443,13 +3393,17 @@ impl Layout { match position { InsertPosition::NewColumn(column_idx) => { + let ws_id = mon.workspaces[ws_idx].id(); mon.add_tile( - ws_idx, - Some(column_idx), move_.tile, - true, + MonitorAddWindowTarget::Workspace { + id: ws_id, + column_idx: Some(column_idx), + }, + ActivateWindow::Yes, move_.width, move_.is_full_width, + false, ); } InsertPosition::InColumn(column_idx, tile_idx) => { @@ -3474,7 +3428,18 @@ impl Layout { tile.floating_window_size = Some(size); } - mon.add_floating_tile(ws_idx, tile, true); + let ws_id = mon.workspaces[ws_idx].id(); + mon.add_tile( + tile, + MonitorAddWindowTarget::Workspace { + id: ws_id, + column_idx: None, + }, + ActivateWindow::Yes, + move_.width, + move_.is_full_width, + true, + ); } } @@ -3490,18 +3455,23 @@ impl Layout { tile.animate_move_from(window_render_loc - new_window_render_loc); } MonitorSet::NoOutputs { workspaces, .. } => { - let ws = if let Some(ws) = workspaces.get_mut(0) { - ws - } else { + if workspaces.is_empty() { workspaces.push(Workspace::new_no_outputs( self.clock.clone(), self.options.clone(), )); - &mut workspaces[0] - }; + } + let ws = &mut workspaces[0]; // No point in trying to use the pointer position without outputs. - ws.add_tile(None, move_.tile, true, move_.width, move_.is_full_width); + ws.add_tile( + move_.tile, + WorkspaceAddWindowTarget::Auto, + ActivateWindow::Yes, + move_.width, + move_.is_full_width, + move_.is_floating, + ); } } } @@ -3894,8 +3864,19 @@ impl Layout { self.windows().any(|(_, win)| win.id() == window) } - fn resolve_default_width(&self, window: &W, width: Option) -> ColumnWidth { + fn resolve_default_width( + &self, + window: &W, + width: Option, + is_floating: bool, + ) -> ColumnWidth { let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w))); + if is_floating { + return width; + } + + // Add border width to account for the issue that the scrolling layout currently doesn't + // take borders into account for fixed sizes. if let ColumnWidth::Fixed(w) = &mut width { let rules = window.rules(); let border_config = rules.border.resolve_against(self.options.border); @@ -3903,6 +3884,7 @@ impl Layout { *w += border_config.width.0 * 2.; } } + width } } @@ -4242,10 +4224,10 @@ mod tests { AddWindow { params: TestWindowParams, }, - AddWindowRightOf { + AddWindowNextTo { params: TestWindowParams, #[proptest(strategy = "1..=5usize")] - right_of_id: usize, + next_to_id: usize, }, AddWindowToNamedWorkspace { params: TestWindowParams, @@ -4548,25 +4530,26 @@ mod tests { let win = TestWindow::new(params); layout.add_window( win, + AddWindowTarget::Auto, None, false, params.is_floating, ActivateWindow::default(), ); } - Op::AddWindowRightOf { + Op::AddWindowNextTo { mut params, - right_of_id, + next_to_id, } => { - let mut found_right_of = false; + 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 == right_of_id { - found_right_of = true; + if win_id == next_to_id { + found_next_to = true; } } @@ -4579,8 +4562,8 @@ mod tests { return; } - if win.0.id == right_of_id { - found_right_of = true; + if win.0.id == next_to_id { + found_next_to = true; } } } @@ -4593,15 +4576,15 @@ mod tests { return; } - if win.0.id == right_of_id { - found_right_of = true; + if win.0.id == next_to_id { + found_next_to = true; } } } } } - if !found_right_of { + if !found_next_to { return; } @@ -4612,14 +4595,21 @@ mod tests { } let win = TestWindow::new(params); - layout.add_window_right_of(&right_of_id, win, None, false, params.is_floating); + layout.add_window( + win, + AddWindowTarget::NextTo(&next_to_id), + None, + false, + params.is_floating, + ActivateWindow::default(), + ); } Op::AddWindowToNamedWorkspace { mut params, ws_name, } => { let ws_name = format!("ws{ws_name}"); - let mut found_workspace = false; + let mut ws_id = None; if let Some(InteractiveMoveState::Moving(move_)) = &layout.interactive_move { if move_.tile.window().0.id == params.id { @@ -4642,7 +4632,7 @@ mod tests { .as_ref() .map_or(false, |name| name.eq_ignore_ascii_case(&ws_name)) { - found_workspace = true; + ws_id = Some(ws.id()); } } } @@ -4660,15 +4650,15 @@ mod tests { .as_ref() .map_or(false, |name| name.eq_ignore_ascii_case(&ws_name)) { - found_workspace = true; + ws_id = Some(ws.id()); } } } } - if !found_workspace { + 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) { @@ -4677,9 +4667,9 @@ mod tests { } let win = TestWindow::new(params); - layout.add_window_to_named_workspace( - &ws_name, + layout.add_window( win, + AddWindowTarget::Workspace(ws_id), None, false, params.is_floating, @@ -5116,9 +5106,9 @@ mod tests { Op::AddWindow { params: TestWindowParams::new(1), }, - Op::AddWindowRightOf { + Op::AddWindowNextTo { params: TestWindowParams::new(2), - right_of_id: 1, + next_to_id: 1, }, Op::AddWindowToNamedWorkspace { params: TestWindowParams::new(3), @@ -5265,13 +5255,13 @@ mod tests { Op::AddWindow { params: TestWindowParams::new(2), }, - Op::AddWindowRightOf { + Op::AddWindowNextTo { params: TestWindowParams::new(6), - right_of_id: 0, + next_to_id: 0, }, - Op::AddWindowRightOf { + Op::AddWindowNextTo { params: TestWindowParams::new(7), - right_of_id: 1, + next_to_id: 1, }, Op::AddWindowToNamedWorkspace { params: TestWindowParams::new(5), @@ -5663,9 +5653,9 @@ mod tests { Op::AddWindow { params: TestWindowParams::new(2), }, - Op::AddWindowRightOf { + Op::AddWindowNextTo { params: TestWindowParams::new(3), - right_of_id: 1, + next_to_id: 1, }, ]; @@ -5699,9 +5689,9 @@ mod tests { Op::AddWindow { params: TestWindowParams::new(2), }, - Op::AddWindowRightOf { + Op::AddWindowNextTo { params: TestWindowParams::new(3), - right_of_id: 1, + next_to_id: 1, }, ]; diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index 3ef1b605..d61d8a45 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -11,8 +11,10 @@ use smithay::utils::{Logical, Point, Rectangle}; use super::scrolling::{Column, ColumnWidth}; use super::tile::Tile; -use super::workspace::{OutputId, Workspace, WorkspaceId, WorkspaceRenderElement}; -use super::{LayoutElement, Options}; +use super::workspace::{ + OutputId, Workspace, WorkspaceAddWindowTarget, WorkspaceId, WorkspaceRenderElement, +}; +use super::{ActivateWindow, LayoutElement, Options}; use crate::animation::{Animation, Clock}; use crate::input::swipe_tracker::SwipeTracker; use crate::render_helpers::renderer::NiriRenderer; @@ -66,6 +68,23 @@ pub struct WorkspaceSwitchGesture { is_touchpad: bool, } +/// Where to put a newly added window. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum MonitorAddWindowTarget<'a, W: LayoutElement> { + /// No particular preference. + #[default] + Auto, + /// On this workspace. + Workspace { + /// Id of the target workspace. + id: WorkspaceId, + /// Override where the window will open as a new column. + column_idx: Option, + }, + /// Next to this existing window. + NextTo(&'a W::Id), +} + pub type MonitorRenderElement = RelocateRenderElement>>; @@ -220,56 +239,18 @@ impl Monitor { pub fn add_window( &mut self, - mut workspace_idx: usize, - window: W, - activate: bool, - width: ColumnWidth, - is_full_width: bool, - is_floating: bool, - ) { - let workspace = &mut self.workspaces[workspace_idx]; - - workspace.add_window(window, activate, width, is_full_width, is_floating); - - // After adding a new window, workspace becomes this output's own. - workspace.original_output = OutputId::new(&self.output); - - if workspace_idx == self.workspaces.len() - 1 { - self.add_workspace_bottom(); - } - - if self.options.empty_workspace_above_first && workspace_idx == 0 { - self.add_workspace_top(); - workspace_idx += 1; - } - - if activate { - self.activate_workspace(workspace_idx); - } - } - - pub fn add_window_right_of( - &mut self, - right_of: &W::Id, window: W, + target: MonitorAddWindowTarget, + activate: ActivateWindow, width: ColumnWidth, is_full_width: bool, is_floating: bool, ) { - let workspace_idx = self - .workspaces - .iter_mut() - .position(|ws| ws.has_window(right_of)) - .unwrap(); - let workspace = &mut self.workspaces[workspace_idx]; + // Currently, everything a workspace sets on a Tile is the same across all workspaces of a + // monitor. So we can use any workspace, not necessarily the exact target workspace. + let tile = self.workspaces[0].make_tile(window); - workspace.add_window_right_of(right_of, window, width, is_full_width, is_floating); - - // After adding a new window, workspace becomes this output's own. - workspace.original_output = OutputId::new(&self.output); - - // Since we're adding window right of something, the workspace isn't empty, and therefore - // cannot be the last one, so we never need to insert a new empty workspace. + self.add_tile(tile, target, activate, width, is_full_width, is_floating); } pub fn add_column(&mut self, mut workspace_idx: usize, column: Column, activate: bool) { @@ -295,39 +276,39 @@ impl Monitor { pub fn add_tile( &mut self, - mut workspace_idx: usize, - column_idx: Option, tile: Tile, - activate: bool, + target: MonitorAddWindowTarget, + activate: ActivateWindow, width: ColumnWidth, is_full_width: bool, + is_floating: bool, ) { - let workspace = &mut self.workspaces[workspace_idx]; - - workspace.add_tile(column_idx, tile, activate, width, is_full_width); - - // After adding a new window, workspace becomes this output's own. - workspace.original_output = OutputId::new(&self.output); - - if workspace_idx == self.workspaces.len() - 1 { - // Insert a new empty workspace. - self.add_workspace_bottom(); - } - - if self.options.empty_workspace_above_first && workspace_idx == 0 { - self.add_workspace_top(); - workspace_idx += 1; - } - - if activate { - self.activate_workspace(workspace_idx); - } - } + let (mut workspace_idx, target) = match target { + MonitorAddWindowTarget::Auto => { + (self.active_workspace_idx, WorkspaceAddWindowTarget::Auto) + } + MonitorAddWindowTarget::Workspace { id, column_idx } => { + let idx = self.workspaces.iter().position(|ws| ws.id() == id).unwrap(); + let target = if let Some(column_idx) = column_idx { + WorkspaceAddWindowTarget::NewColumnAt(column_idx) + } else { + WorkspaceAddWindowTarget::Auto + }; + (idx, target) + } + MonitorAddWindowTarget::NextTo(win_id) => { + let idx = self + .workspaces + .iter_mut() + .position(|ws| ws.has_window(win_id)) + .unwrap(); + (idx, WorkspaceAddWindowTarget::NextTo(win_id)) + } + }; - pub fn add_floating_tile(&mut self, mut workspace_idx: usize, tile: Tile, activate: bool) { let workspace = &mut self.workspaces[workspace_idx]; - workspace.add_floating_tile(tile, activate); + workspace.add_tile(tile, target, activate, width, is_full_width, is_floating); // After adding a new window, workspace becomes this output's own. workspace.original_output = OutputId::new(&self.output); @@ -342,7 +323,7 @@ impl Monitor { workspace_idx += 1; } - if activate { + if activate.map_smart(|| false) { self.activate_workspace(workspace_idx); } } @@ -518,24 +499,24 @@ impl Monitor { if new_idx == source_workspace_idx { return; } + let new_id = self.workspaces[new_idx].id(); let workspace = &mut self.workspaces[source_workspace_idx]; let Some(removed) = workspace.remove_active_tile(Transaction::new()) else { return; }; - if removed.is_floating { - self.add_floating_tile(new_idx, removed.tile, true); - } else { - self.add_tile( - new_idx, - None, - removed.tile, - true, - removed.width, - removed.is_full_width, - ); - } + self.add_tile( + removed.tile, + MonitorAddWindowTarget::Workspace { + id: new_id, + column_idx: None, + }, + ActivateWindow::Yes, + removed.width, + removed.is_full_width, + removed.is_floating, + ); } pub fn move_to_workspace_down(&mut self) { @@ -545,24 +526,24 @@ impl Monitor { if new_idx == source_workspace_idx { return; } + let new_id = self.workspaces[new_idx].id(); let workspace = &mut self.workspaces[source_workspace_idx]; let Some(removed) = workspace.remove_active_tile(Transaction::new()) else { return; }; - if removed.is_floating { - self.add_floating_tile(new_idx, removed.tile, true); - } else { - self.add_tile( - new_idx, - None, - removed.tile, - true, - removed.width, - removed.is_full_width, - ); - } + self.add_tile( + removed.tile, + MonitorAddWindowTarget::Workspace { + id: new_id, + column_idx: None, + }, + ActivateWindow::Yes, + removed.width, + removed.is_full_width, + removed.is_floating, + ); } pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) { @@ -579,10 +560,16 @@ impl Monitor { if new_idx == source_workspace_idx { return; } + let new_id = self.workspaces[new_idx].id(); let activate = window.map_or(true, |win| { self.active_window().map(|win| win.id()) == Some(win) }); + let activate = if activate { + ActivateWindow::Yes + } else { + ActivateWindow::No + }; let workspace = &mut self.workspaces[source_workspace_idx]; let transaction = Transaction::new(); @@ -594,18 +581,17 @@ impl Monitor { return; }; - if removed.is_floating { - self.add_floating_tile(new_idx, removed.tile, activate); - } else { - self.add_tile( - new_idx, - None, - removed.tile, - activate, - removed.width, - removed.is_full_width, - ); - } + self.add_tile( + removed.tile, + MonitorAddWindowTarget::Workspace { + id: new_id, + column_idx: None, + }, + activate, + removed.width, + removed.is_full_width, + removed.is_floating, + ); if self.workspace_switch.is_none() { self.clean_up_workspaces(); diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index b7b7b84a..5b13d0c6 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -783,6 +783,7 @@ impl ScrollingSpace { &mut self, right_of: &W::Id, tile: Tile, + activate: bool, width: ColumnWidth, is_full_width: bool, ) { @@ -793,9 +794,6 @@ impl ScrollingSpace { .unwrap(); let col_idx = right_of_idx + 1; - // Activate the new window if right_of was active. - let activate = self.active_column_idx == right_of_idx; - self.add_tile(Some(col_idx), tile, activate, width, is_full_width, None); } diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 017efbf6..5e397c93 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -18,7 +18,7 @@ use super::scrolling::{ Column, ColumnWidth, InsertHint, InsertPosition, ScrollingSpace, ScrollingSpaceRenderElement, }; use super::tile::{Tile, TileRenderSnapshot}; -use super::{InteractiveResizeData, LayoutElement, Options, RemovedTile, SizeFrac}; +use super::{ActivateWindow, InteractiveResizeData, LayoutElement, Options, RemovedTile, SizeFrac}; use crate::animation::Clock; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; @@ -157,6 +157,18 @@ enum FloatingActive { Yes, } +/// Where to put a newly added window. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum WorkspaceAddWindowTarget<'a, W: LayoutElement> { + /// No particular preference. + #[default] + Auto, + /// As a new column at this index. + NewColumnAt(usize), + /// Next to this existing window. + NextTo(&'a W::Id), +} + impl OutputId { pub fn new(output: &Output) -> Self { let output_name = output.user_data().get::().unwrap(); @@ -488,55 +500,138 @@ impl Workspace { self.view_size } - pub fn add_window( - &mut self, - window: W, - activate: bool, - width: ColumnWidth, - is_full_width: bool, - is_floating: bool, - ) { - let mut tile = Tile::new( + pub fn make_tile(&self, window: W) -> Tile { + Tile::new( window, self.view_size, self.scale.fractional_scale(), self.clock.clone(), self.options.clone(), - ); - tile.unfullscreen_to_floating = is_floating; - - // If the tile is pending fullscreen, open it in the scrolling layout where it can go - // fullscreen. - if is_floating && !tile.window().is_pending_fullscreen() { - self.add_floating_tile(tile, activate); - } else { - self.add_tile(None, tile, activate, width, is_full_width); - } + ) } pub fn add_tile( &mut self, - col_idx: Option, - tile: Tile, - activate: bool, + mut tile: Tile, + target: WorkspaceAddWindowTarget, + activate: ActivateWindow, width: ColumnWidth, is_full_width: bool, + is_floating: bool, ) { self.enter_output_for_window(tile.window()); - self.scrolling - .add_tile(col_idx, tile, activate, width, is_full_width, None); + tile.unfullscreen_to_floating = is_floating; - if activate { - self.floating_is_active = FloatingActive::No; - } - } + match target { + WorkspaceAddWindowTarget::Auto => { + // Don't steal focus from an active fullscreen window. + let activate = activate.map_smart(|| !self.is_active_fullscreen()); - pub fn add_floating_tile(&mut self, tile: Tile, activate: bool) { - self.enter_output_for_window(tile.window()); - self.floating.add_tile(tile, activate); + // If the tile is pending fullscreen, open it in the scrolling layout where it can + // go fullscreen. + if is_floating && !tile.window().is_pending_fullscreen() { + self.floating.add_tile(tile, activate); - if activate || self.scrolling.is_empty() { - self.floating_is_active = FloatingActive::Yes; + if activate || self.scrolling.is_empty() { + self.floating_is_active = FloatingActive::Yes; + } + } else { + self.scrolling + .add_tile(None, tile, activate, width, is_full_width, None); + + if activate { + self.floating_is_active = FloatingActive::No; + } + } + } + WorkspaceAddWindowTarget::NewColumnAt(col_idx) => { + let activate = activate.map_smart(|| false); + self.scrolling + .add_tile(Some(col_idx), tile, activate, width, is_full_width, None); + + if activate { + self.floating_is_active = FloatingActive::No; + } + } + WorkspaceAddWindowTarget::NextTo(next_to) => { + let activate = activate.map_smart(|| self.active_window().unwrap().id() == next_to); + + let floating_has_window = self.floating.has_window(next_to); + if is_floating || floating_has_window { + if floating_has_window { + self.floating.add_tile_above(next_to, tile, activate); + } else { + // FIXME: use static pos + let (next_to_tile, render_pos) = self + .scrolling + .tiles_with_render_positions() + .find(|(tile, _)| tile.window().id() == next_to) + .unwrap(); + + // Position the new tile in the center above the next_to tile. Think a + // dialog opening on top of a window. + let tile_size = tile.tile_size(); + let pos = render_pos + + (next_to_tile.tile_size().to_point() - tile_size.to_point()) + .downscale(2.); + let pos = self.floating.clamp_within_working_area(pos, tile_size); + let pos = self.floating.logical_to_size_frac(pos); + tile.floating_pos = Some(pos); + + self.floating.add_tile(tile, activate); + + if activate { + self.floating_is_active = FloatingActive::Yes; + } + } + } else { + self.scrolling + .add_tile_right_of(next_to, tile, activate, width, is_full_width); + } + + // if is_floating && !tile.window().is_pending_fullscreen() { + // if floating_has_window { + // self.floating.add_tile_above(next_to, tile, activate); + // } else { + // // FIXME: use static pos + // let (next_to_tile, render_pos) = self + // .scrolling + // .tiles_with_render_positions() + // .find(|(tile, _)| tile.window().id() == next_to) + // .unwrap(); + // + // // Position the new tile in the center above the next_to tile. Think a + // // dialog opening on top of a window. + // let tile_size = tile.tile_size(); + // let pos = render_pos + // + (next_to_tile.tile_size().to_point() - tile_size.to_point()) + // .downscale(2.); + // let pos = self.floating.clamp_within_working_area(pos, tile_size); + // let pos = self.floating.logical_to_size_frac(pos); + // tile.floating_pos = Some(pos); + // + // self.floating.add_tile(tile, activate); + // } + // + // if activate || self.scrolling.is_empty() { + // self.floating_is_active = FloatingActive::Yes; + // } + // } else if floating_has_window { + // self.scrolling + // .add_tile(None, tile, activate, width, is_full_width, None); + // + // if activate { + // self.floating_is_active = FloatingActive::No; + // } + // } else { + // self.scrolling + // .add_tile_right_of(next_to, tile, activate, width, is_full_width); + // + // if activate { + // self.floating_is_active = FloatingActive::No; + // } + // } + } } } @@ -556,70 +651,6 @@ impl Workspace { } } - pub fn add_window_right_of( - &mut self, - right_of: &W::Id, - window: W, - width: ColumnWidth, - is_full_width: bool, - // TODO: smarter enum, so you can override is_floating = false for floating right_of. - is_floating: bool, - ) { - let mut tile = Tile::new( - window, - self.view_size, - self.scale.fractional_scale(), - self.clock.clone(), - self.options.clone(), - ); - tile.unfullscreen_to_floating = is_floating; - self.add_tile_right_of(right_of, tile, width, is_full_width, is_floating); - } - - pub fn add_tile_right_of( - &mut self, - right_of: &W::Id, - mut tile: Tile, - width: ColumnWidth, - is_full_width: bool, - is_floating: bool, - ) { - self.enter_output_for_window(tile.window()); - - // TODO: open-fullscreen into scrolling. - let floating_has_window = self.floating.has_window(right_of); - if is_floating || floating_has_window { - if floating_has_window { - self.floating.add_tile_above(right_of, tile); - } else { - let activate = self.scrolling.active_window().unwrap().id() == right_of; - // FIXME: use static pos - let (right_of_tile, render_pos) = self - .scrolling - .tiles_with_render_positions() - .find(|(tile, _)| tile.window().id() == right_of) - .unwrap(); - - // Position the new tile in the center above the right_of tile. Think a dialog - // opening on top of a window. - let tile_size = tile.tile_size(); - let pos = render_pos - + (right_of_tile.tile_size().to_point() - tile_size.to_point()).downscale(2.); - let pos = self.floating.clamp_within_working_area(pos, tile_size); - let pos = self.floating.logical_to_size_frac(pos); - tile.floating_pos = Some(pos); - - self.floating.add_tile(tile, activate); - if activate { - self.floating_is_active = FloatingActive::Yes; - } - } - } else { - self.scrolling - .add_tile_right_of(right_of, tile, width, is_full_width); - } - } - pub fn add_column(&mut self, column: Column, activate: bool) { for (tile, _) in column.tiles() { self.enter_output_for_window(tile.window()); @@ -702,10 +733,12 @@ impl Workspace { pub fn resolve_default_width( &self, default_width: Option>, + is_floating: bool, ) -> Option { match default_width { Some(Some(width)) => Some(width), Some(None) => None, + None if is_floating => None, None => self.options.default_column_width, } } -- cgit