diff --git a/cosmic-comp-config/src/workspace.rs b/cosmic-comp-config/src/workspace.rs index 2d4bf2dc..764265b4 100644 --- a/cosmic-comp-config/src/workspace.rs +++ b/cosmic-comp-config/src/workspace.rs @@ -11,6 +11,19 @@ pub struct WorkspaceConfig { pub workspace_mode: WorkspaceMode, #[serde(default = "default_workspace_layout")] pub workspace_layout: WorkspaceLayout, + #[serde(default)] + pub remove_empty: RemoveEmpty, +} + +/// Setting of which empty workspaces the compositor should automatically remove. +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] +pub enum RemoveEmpty { + /// Remove all empty workspaces, except the last one. + #[default] + All, + + /// Remove only trailing empty workspaces, after the last one. + Trailing, } impl Default for WorkspaceConfig { @@ -18,6 +31,7 @@ impl Default for WorkspaceConfig { Self { workspace_mode: WorkspaceMode::OutputBound, workspace_layout: WorkspaceLayout::Vertical, + remove_empty: RemoveEmpty::default(), } } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index e9022c67..aec25ea2 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -12,7 +12,7 @@ use wayland_backend::server::ClientId; use crate::wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities}; use cosmic_comp_config::{ - workspace::{WorkspaceLayout, WorkspaceMode}, + workspace::{RemoveEmpty, WorkspaceConfig, WorkspaceLayout, WorkspaceMode}, TileBehavior, }; use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::{ @@ -551,8 +551,8 @@ impl WorkspaceSet { self.workspaces.push(workspace); } - fn ensure_last_empty(&mut self, state: &mut WorkspaceUpdateGuard) { - // add empty at the end, if necessary + /// Ensures that the last workspace is empty by creating a new one if it's not. + fn ensure_last_empty<'a>(&mut self, state: &mut WorkspaceUpdateGuard) { if self .workspaces .last() @@ -561,30 +561,40 @@ impl WorkspaceSet { { self.add_empty_workspace(state); } + } - // remove empty workspaces in between, if they are not active + /// Removes empty non-active workspaces according to the provided [`RemoveEmpty`] setting. + /// + /// Leaves the last empty workspace intact. + fn remove_empty(&mut self, which: RemoveEmpty, state: &mut WorkspaceUpdateGuard) { let len = self.workspaces.len(); - let mut keep = vec![true; len]; - for (i, workspace) in self.workspaces.iter().enumerate() { - if workspace.is_empty() && i != self.active && i != len - 1 { - state.remove_workspace(workspace.handle); - keep[i] = false; + + for i in (0..len).rev() { + let is_empty = self.workspaces[i].is_empty() && i != self.active; + let is_last = i == len - 1; + + match which { + _ if is_empty && is_last => continue, + RemoveEmpty::All if !is_empty => continue, + RemoveEmpty::Trailing if !is_empty => break, + _ => {} } - } - let mut iter = keep.iter(); - self.workspaces.retain(|_| *iter.next().unwrap()); - self.active -= keep - .iter() - .take(self.active + 1) - .filter(|keep| !**keep) - .count(); + let workspace = self.workspaces.remove(i); + state.remove_workspace(workspace.handle); - if keep.iter().any(|val| *val == false) { - for (i, workspace) in self.workspaces.iter().enumerate() { - workspace_set_idx(state, i as u8 + 1, self.idx, &workspace.handle); + if i < self.active { + self.active -= 1; } } + + if self.workspaces.len() == len { + return; + } + + for (i, workspace) in self.workspaces.iter().enumerate() { + workspace_set_idx(state, i as u8 + 1, self.idx, &workspace.handle); + } } fn update_idx(&mut self, state: &mut WorkspaceUpdateGuard<'_, State>, idx: usize) { @@ -604,6 +614,8 @@ pub struct Workspaces { autotile: bool, autotile_behavior: TileBehavior, theme: cosmic::Theme, + + config: WorkspaceConfig, } impl Workspaces { @@ -616,6 +628,7 @@ impl Workspaces { autotile: config.cosmic_conf.autotile, autotile_behavior: config.cosmic_conf.autotile_behavior, theme, + config: config.cosmic_conf.workspaces.clone(), } } @@ -809,6 +822,7 @@ impl Workspaces { let old_mode = self.mode; self.mode = config.cosmic_conf.workspaces.workspace_mode; self.layout = config.cosmic_conf.workspaces.workspace_layout; + self.config = config.cosmic_conf.workspaces.clone(); if self.sets.len() <= 1 { return; @@ -899,8 +913,22 @@ impl Workspaces { set.add_empty_workspace(workspace_state) } } + } + WorkspaceMode::OutputBound => {} + } + + self.ensure_last_empty(workspace_state); + self.remove_empty(workspace_state); + + for set in self.sets.values_mut() { + set.refresh(xdg_activation_state) + } + } - // add empty at the end, if necessary + /// Ensures that the last workspace is empty by creating a new one if it's not. + fn ensure_last_empty(&mut self, workspace_state: &mut WorkspaceUpdateGuard<'_, State>) { + match self.mode { + WorkspaceMode::Global => { if self .sets .values() @@ -911,54 +939,67 @@ impl Workspaces { set.add_empty_workspace(workspace_state); } } + } + WorkspaceMode::OutputBound => { + for set in self.sets.values_mut() { + set.ensure_last_empty(workspace_state); + } + } + } + } + + /// Removes empty non-active workspaces according to the [`RemoveEmpty`] config setting. + /// + /// Leaves the last empty workspace intact. + fn remove_empty(&mut self, workspace_state: &mut WorkspaceUpdateGuard<'_, State>) { + match self.mode { + WorkspaceMode::Global => { + if self.sets.is_empty() { + return; + } - // remove empty workspaces in between, if they are not active let len = self.sets[0].workspaces.len(); let mut active = self.sets[0].active; - let mut keep = vec![true; len]; - for i in 0..len { + + for i in (0..len).rev() { let has_windows = self.sets.values().any(|s| !s.workspaces[i].is_empty()); + let is_empty = !has_windows && i != active; + let is_last = i == len - 1; + + match self.config.remove_empty { + _ if is_empty && is_last => continue, + RemoveEmpty::All if !is_empty => continue, + RemoveEmpty::Trailing if !is_empty => break, + _ => {} + } - if !has_windows && i != active && i != len - 1 { - for workspace in self.sets.values().map(|s| &s.workspaces[i]) { - workspace_state.remove_workspace(workspace.handle); - } - keep[i] = false; + for workspace in self.sets.values_mut().map(|s| s.workspaces.remove(i)) { + workspace_state.remove_workspace(workspace.handle); + } + + if i < active { + active -= 1; } } - self.sets.values_mut().for_each(|s| { - let mut iter = keep.iter(); - s.workspaces.retain(|_| *iter.next().unwrap()); - }); - active -= keep.iter().take(active + 1).filter(|keep| !**keep).count(); - self.sets.values_mut().for_each(|s| { - s.active = active; - }); + if self.sets[0].workspaces.len() == len { + return; + } - if keep.iter().any(|val| *val == false) { - for set in self.sets.values_mut() { - for (i, workspace) in set.workspaces.iter().enumerate() { - workspace_set_idx( - workspace_state, - i as u8 + 1, - set.idx, - &workspace.handle, - ); - } + for set in self.sets.values_mut() { + set.active = active; + + for (i, workspace) in set.workspaces.iter().enumerate() { + workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); } } } WorkspaceMode::OutputBound => { for set in self.sets.values_mut() { - set.ensure_last_empty(workspace_state); + set.remove_empty(self.config.remove_empty, workspace_state); } } } - - for set in self.sets.values_mut() { - set.refresh(xdg_activation_state) - } } pub fn get(&self, num: usize, output: &Output) -> Option<&Workspace> { @@ -977,7 +1018,7 @@ impl Workspaces { let set = self.sets.get(output).or(self.backup_set.as_ref()).unwrap(); ( set.previously_active - .map(|(idx, start)| (&set.workspaces[idx], start)), + .and_then(|(idx, start)| set.workspaces.get(idx).map(|w| (w, start))), &set.workspaces[set.active], ) }