layout: Refactor window opening targets

This commit is contained in:
Ivan Molodetskikh
2024-12-26 09:37:38 +03:00
parent 0d71cb93af
commit daaee43be3
8 changed files with 492 additions and 501 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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.))

View File

@@ -428,10 +428,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
self.bring_up_descendants_of(idx);
}
pub fn add_tile_above(&mut self, above: &W::Id, mut tile: Tile<W>) {
// 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<W>, activate: bool) {
let idx = self.idx_of(above).unwrap();
let above_pos = self.data[idx].logical_pos;

View File

@@ -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<W: LayoutElement> InteractiveMoveState<W> {
fn moving(&self) -> Option<&InteractiveMoveData<W>> {
match self {
@@ -797,71 +812,6 @@ impl<W: LayoutElement> Layout<W> {
}
}
/// 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<ColumnWidth>,
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<W: LayoutElement> Layout<W> {
pub fn add_window(
&mut self,
window: W,
target: AddWindowTarget<W>,
width: Option<ColumnWidth>,
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,144 +855,138 @@ impl<W: LayoutElement> Layout<W> {
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<ColumnWidth>,
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);
let (ws_idx, target) = match target {
AddWindowTarget::Auto => {
if workspaces.is_empty() {
workspaces.push(Workspace::new_no_outputs(
self.clock.clone(),
self.options.clone(),
));
}
(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 tile = ws.make_tile(window);
ws.add_tile(
tile,
target,
activate,
resolved_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<ColumnWidth>,
is_full_width: bool,
is_floating: bool,
activate: ActivateWindow,
) {
let width = self.resolve_default_width(&window, width);
let MonitorSet::Normal {
monitors,
active_monitor_idx,
..
} = &mut self.monitor_set
else {
panic!()
};
let (mon_idx, mon) = monitors
.iter_mut()
.enumerate()
.find(|(_, mon)| mon.output == *output)
.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[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(
&mut self,
window: &W::Id,
@@ -2801,12 +2746,18 @@ impl<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
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<W: LayoutElement> Layout<W> {
self.windows().any(|(_, win)| win.id() == window)
}
fn resolve_default_width(&self, window: &W, width: Option<ColumnWidth>) -> ColumnWidth {
fn resolve_default_width(
&self,
window: &W,
width: Option<ColumnWidth>,
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<W: LayoutElement> Layout<W> {
*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,
},
];

View File

@@ -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<usize>,
},
/// Next to this existing window.
NextTo(&'a W::Id),
}
pub type MonitorRenderElement<R> =
RelocateRenderElement<CropRenderElement<WorkspaceRenderElement<R>>>;
@@ -220,56 +239,18 @@ impl<W: LayoutElement> Monitor<W> {
pub fn add_window(
&mut self,
mut workspace_idx: usize,
window: W,
activate: bool,
target: MonitorAddWindowTarget<W>,
activate: ActivateWindow,
width: ColumnWidth,
is_full_width: bool,
is_floating: bool,
) {
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(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,
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];
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<W>, activate: bool) {
@@ -295,16 +276,39 @@ impl<W: LayoutElement> Monitor<W> {
pub fn add_tile(
&mut self,
mut workspace_idx: usize,
column_idx: Option<usize>,
tile: Tile<W>,
activate: bool,
target: MonitorAddWindowTarget<W>,
activate: ActivateWindow,
width: ColumnWidth,
is_full_width: bool,
is_floating: bool,
) {
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))
}
};
let workspace = &mut self.workspaces[workspace_idx];
workspace.add_tile(column_idx, tile, activate, width, is_full_width);
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);
@@ -319,30 +323,7 @@ impl<W: LayoutElement> Monitor<W> {
workspace_idx += 1;
}
if activate {
self.activate_workspace(workspace_idx);
}
}
pub fn add_floating_tile(&mut self, mut workspace_idx: usize, tile: Tile<W>, activate: bool) {
let workspace = &mut self.workspaces[workspace_idx];
workspace.add_floating_tile(tile, activate);
// 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 {
if activate.map_smart(|| false) {
self.activate_workspace(workspace_idx);
}
}
@@ -518,24 +499,24 @@ impl<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
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();

View File

@@ -783,6 +783,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
&mut self,
right_of: &W::Id,
tile: Tile<W>,
activate: bool,
width: ColumnWidth,
is_full_width: bool,
) {
@@ -793,9 +794,6 @@ impl<W: LayoutElement> ScrollingSpace<W> {
.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);
}

View File

@@ -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::<OutputName>().unwrap();
@@ -488,55 +500,138 @@ impl<W: LayoutElement> Workspace<W> {
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<W> {
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<usize>,
tile: Tile<W>,
activate: bool,
mut tile: Tile<W>,
target: WorkspaceAddWindowTarget<W>,
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<W>, 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<W: LayoutElement> Workspace<W> {
}
}
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<W>,
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<W>, activate: bool) {
for (tile, _) in column.tiles() {
self.enter_output_for_window(tile.window());
@@ -702,10 +733,12 @@ impl<W: LayoutElement> Workspace<W> {
pub fn resolve_default_width(
&self,
default_width: Option<Option<ColumnWidth>>,
is_floating: bool,
) -> Option<ColumnWidth> {
match default_width {
Some(Some(width)) => Some(width),
Some(None) => None,
None if is_floating => None,
None => self.options.default_column_width,
}
}