diff --git a/docs/cli/toggle-workspace-layer.md b/docs/cli/toggle-workspace-layer.md new file mode 100644 index 00000000..1758b2bd --- /dev/null +++ b/docs/cli/toggle-workspace-layer.md @@ -0,0 +1,12 @@ +# toggle-workspace-layer + +``` +Toggle between the Tiling and Floating layers on the focused workspace + +Usage: komorebic.exe toggle-workspace-layer + +Options: + -h, --help + Print help + +``` diff --git a/komorebi-bar/src/bar.rs b/komorebi-bar/src/bar.rs index cca94605..092b0a42 100644 --- a/komorebi-bar/src/bar.rs +++ b/komorebi-bar/src/bar.rs @@ -365,16 +365,10 @@ impl Komobar { &SocketMessage::MonitorWorkAreaOffset(monitor_index, new_rect), ) { tracing::error!( - "error applying work area offset to monitor '{}': {}", - monitor_index, - error, + "error applying work area offset to monitor '{monitor_index}': {error}" ); } else { - tracing::info!( - "work area offset applied to monitor: {}\n, {:#?}", - monitor_index, - new_rect - ); + tracing::info!("work area offset applied to monitor: {monitor_index}",); } } } @@ -631,10 +625,10 @@ impl Komobar { let window = komorebi_client::Window::from(hwnd); match window.set_position(&self.size_rect, false) { Ok(_) => { - tracing::info!("updated bar position: {:#?}", &self.size_rect); + tracing::info!("updated bar position"); } Err(error) => { - tracing::error!("{}", error.to_string()) + tracing::error!("{error}") } } } diff --git a/komorebi-bar/src/komorebi.rs b/komorebi-bar/src/komorebi.rs index 43b08240..4b4830f7 100644 --- a/komorebi-bar/src/komorebi.rs +++ b/komorebi-bar/src/komorebi.rs @@ -33,6 +33,7 @@ use komorebi_client::Rect; use komorebi_client::SocketMessage; use komorebi_client::Window; use komorebi_client::Workspace; +use komorebi_client::WorkspaceLayer; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -49,6 +50,8 @@ pub struct KomorebiConfig { pub workspaces: Option, /// Configure the Layout widget pub layout: Option, + /// Configure the Workspace Layer widget + pub workspace_layer: Option, /// Configure the Focused Window widget pub focused_window: Option, /// Configure the Configuration Switcher widget @@ -75,6 +78,12 @@ pub struct KomorebiLayoutConfig { pub display: Option, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct KomorebiWorkspaceLayerConfig { + /// Enable the Komorebi Workspace Layer widget + pub enable: bool, +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct KomorebiFocusedWindowConfig { /// Enable the Komorebi Focused Window widget @@ -127,6 +136,7 @@ impl From<&KomorebiConfig> for Komorebi { workspaces: value.workspaces, layout: value.layout.clone(), focused_window: value.focused_window, + workspace_layer: value.workspace_layer, configuration_switcher, } } @@ -138,6 +148,7 @@ pub struct Komorebi { pub workspaces: Option, pub layout: Option, pub focused_window: Option, + pub workspace_layer: Option, pub configuration_switcher: Option, } @@ -154,7 +165,7 @@ impl BarWidget for Komorebi { let format = workspaces.display.unwrap_or(DisplayFormat::Text); config.apply_on_widget(false, ui, |ui| { - for (i, (ws, container_information)) in + for (i, (ws, container_information, _)) in komorebi_notification_state.workspaces.iter().enumerate() { if SelectableFrame::new( @@ -281,6 +292,42 @@ impl BarWidget for Komorebi { } } + if let Some(layer_config) = &self.workspace_layer { + if layer_config.enable { + let layer = komorebi_notification_state + .workspaces + .iter() + .find(|o| komorebi_notification_state.selected_workspace.eq(&o.0)) + .map(|(_, _, layer)| layer); + + if let Some(layer) = layer { + let name = layer.to_string(); + config.apply_on_widget(false, ui, |ui| { + if SelectableFrame::new(false) + .show(ui, |ui| ui.add(Label::new(name).selectable(false))) + .clicked() + && komorebi_client::send_batch([ + SocketMessage::MouseFollowsFocus(false), + SocketMessage::ToggleWorkspaceLayer, + SocketMessage::MouseFollowsFocus( + komorebi_notification_state.mouse_follows_focus, + ), + ]) + .is_err() + { + tracing::error!( + "could not send the following batch of messages to komorebi:\n\ + MouseFollowsFocus(false), + ToggleWorkspaceLayer, + MouseFollowsFocus({})", + komorebi_notification_state.mouse_follows_focus, + ); + } + }); + } + } + } + if let Some(layout_config) = &self.layout { if layout_config.enable { let workspace_idx: Option = komorebi_notification_state @@ -476,7 +523,11 @@ fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle { #[derive(Clone, Debug)] pub struct KomorebiNotificationState { - pub workspaces: Vec<(String, KomorebiNotificationStateContainerInformation)>, + pub workspaces: Vec<( + String, + KomorebiNotificationStateContainerInformation, + WorkspaceLayer, + )>, pub selected_workspace: String, pub focused_container_information: KomorebiNotificationStateContainerInformation, pub layout: KomorebiLayout, @@ -592,6 +643,7 @@ impl KomorebiNotificationState { workspaces.push(( ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)), ws.into(), + ws.layer().to_owned(), )); } } diff --git a/komorebi-client/src/lib.rs b/komorebi-client/src/lib.rs index cb26551e..adbc33ec 100644 --- a/komorebi-client/src/lib.rs +++ b/komorebi-client/src/lib.rs @@ -48,6 +48,7 @@ pub use komorebi::ring::Ring; pub use komorebi::window::Window; pub use komorebi::window_manager_event::WindowManagerEvent; pub use komorebi::workspace::Workspace; +pub use komorebi::workspace::WorkspaceLayer; pub use komorebi::AnimationsConfig; pub use komorebi::AspectRatio; pub use komorebi::BorderColours; diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index b0479d90..c9fd4c11 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -149,6 +149,7 @@ pub enum SocketMessage { NamedWorkspaceLayoutCustomRule(String, usize, PathBuf), ClearWorkspaceLayoutRules(usize, usize), ClearNamedWorkspaceLayoutRules(String), + ToggleWorkspaceLayer, // Configuration ReloadConfiguration, ReplaceConfiguration(PathBuf), diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index b14c7b51..500cd839 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -66,6 +66,7 @@ use crate::window_manager; use crate::window_manager::WindowManager; use crate::windows_api::WindowsApi; use crate::winevent_listener; +use crate::workspace::WorkspaceLayer; use crate::workspace::WorkspaceWindowLocation; use crate::GlobalState; use crate::Notification; @@ -291,13 +292,37 @@ impl WindowManager { } } SocketMessage::FocusWindow(direction) => { - self.focus_container_in_direction(direction)?; + let focused_workspace = self.focused_workspace()?; + match focused_workspace.layer() { + WorkspaceLayer::Tiling => { + self.focus_container_in_direction(direction)?; + } + WorkspaceLayer::Floating => { + self.focus_floating_window_in_direction(direction)?; + } + } } SocketMessage::MoveWindow(direction) => { - self.move_container_in_direction(direction)?; + let focused_workspace = self.focused_workspace()?; + match focused_workspace.layer() { + WorkspaceLayer::Tiling => { + self.move_container_in_direction(direction)?; + } + WorkspaceLayer::Floating => { + self.move_floating_window_in_direction(direction)?; + } + } } SocketMessage::CycleFocusWindow(direction) => { - self.focus_container_in_cycle_direction(direction)?; + let focused_workspace = self.focused_workspace()?; + match focused_workspace.layer() { + WorkspaceLayer::Tiling => { + self.focus_container_in_cycle_direction(direction)?; + } + WorkspaceLayer::Floating => { + self.focus_floating_window_in_cycle_direction(direction)?; + } + } } SocketMessage::CycleMoveWindow(direction) => { self.move_container_in_cycle_direction(direction)?; @@ -1020,6 +1045,29 @@ impl WindowManager { self.focus_workspace(workspace_idx)?; } } + SocketMessage::ToggleWorkspaceLayer => { + let mouse_follows_focus = self.mouse_follows_focus; + let workspace = self.focused_workspace_mut()?; + + match workspace.layer() { + WorkspaceLayer::Tiling => { + workspace.set_layer(WorkspaceLayer::Floating); + + if let Some(first) = workspace.floating_windows().first() { + first.focus(mouse_follows_focus)?; + } + } + WorkspaceLayer::Floating => { + workspace.set_layer(WorkspaceLayer::Tiling); + + if let Some(container) = workspace.focused_container() { + if let Some(window) = container.focused_window() { + window.focus(mouse_follows_focus)?; + } + } + } + }; + } SocketMessage::Stop => { self.stop(false)?; } diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index b71f3bb4..44d56566 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -27,6 +27,7 @@ use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent::WinEvent; +use crate::workspace::WorkspaceLayer; use crate::workspace_reconciliator; use crate::workspace_reconciliator::ALT_TAB_HWND; use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT; @@ -282,10 +283,13 @@ impl WindowManager { } else { workspace.focus_container_by_window(window.hwnd)?; } + + workspace.set_layer(WorkspaceLayer::Tiling); } Some(idx) => { if let Some(window) = workspace.floating_windows().get(idx) { window.focus(false)?; + workspace.set_layer(WorkspaceLayer::Floating); } } } @@ -394,11 +398,13 @@ impl WindowManager { if behaviour.float_override { workspace.floating_windows_mut().push(window); + workspace.set_layer(WorkspaceLayer::Floating); self.update_focused_workspace(false, false)?; } else { match behaviour.current_behaviour { WindowContainerBehaviour::Create => { workspace.new_container_for_window(window); + workspace.set_layer(WorkspaceLayer::Tiling); self.update_focused_workspace(false, false)?; } WindowContainerBehaviour::Append => { @@ -408,6 +414,7 @@ impl WindowManager { anyhow!("there is no focused container") })? .add_window(window); + workspace.set_layer(WorkspaceLayer::Tiling); self.update_focused_workspace(true, false)?; stackbar_manager::send_notification(); } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 89f7d4ec..ab0add8b 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -48,6 +48,8 @@ use crate::core::WindowContainerBehaviour; use crate::core::WindowManagementBehaviour; use crate::border_manager; +use crate::border_manager::BORDER_OFFSET; +use crate::border_manager::BORDER_WIDTH; use crate::border_manager::STYLE; use crate::config_generation::WorkspaceMatchingRule; use crate::container::Container; @@ -74,6 +76,7 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent_listener; use crate::workspace::Workspace; +use crate::workspace::WorkspaceLayer; use crate::BorderColours; use crate::Colour; use crate::CrossBoundaryBehaviour; @@ -331,6 +334,7 @@ impl From<&WindowManager> for State { .window_container_behaviour_rules .clone(), float_override: workspace.float_override, + layer: workspace.layer, workspace_config: None, }) .collect::>(); @@ -1390,86 +1394,168 @@ impl WindowManager { delta: i32, update: bool, ) -> Result<()> { - let work_area = self.focused_monitor_work_area()?; + let mouse_follows_focus = self.mouse_follows_focus; + let mut focused_monitor_work_area = self.focused_monitor_work_area()?; let workspace = self.focused_workspace_mut()?; - match workspace.layout() { - Layout::Default(layout) => { - tracing::info!("resizing window"); - let len = NonZeroUsize::new(workspace.containers().len()) - .ok_or_else(|| anyhow!("there must be at least one container"))?; - let focused_idx = workspace.focused_container_idx(); - let focused_idx_resize = workspace - .resize_dimensions() - .get(focused_idx) - .ok_or_else(|| anyhow!("there is no resize adjustment for this container"))?; + match workspace.layer() { + WorkspaceLayer::Floating => { + let workspace = self.focused_workspace()?; + let focused_hwnd = WindowsApi::foreground_window()?; - if direction - .destination( - workspace.layout().as_boxed_direction().as_ref(), - workspace.layout_flip(), - focused_idx, - len, - ) - .is_some() - { - let unaltered = layout.calculate( - &work_area, - len, - workspace.container_padding(), - workspace.layout_flip(), - &[], - ); + let border_offset = BORDER_OFFSET.load(Ordering::SeqCst); + let border_width = BORDER_WIDTH.load(Ordering::SeqCst); + focused_monitor_work_area.left += border_offset; + focused_monitor_work_area.left += border_width; + focused_monitor_work_area.top += border_offset; + focused_monitor_work_area.top += border_width; + focused_monitor_work_area.right -= border_offset; + focused_monitor_work_area.right -= border_width; + focused_monitor_work_area.bottom -= border_offset; + focused_monitor_work_area.bottom -= border_width; - let mut direction = direction; - - // We only ever want to operate on the unflipped Rect positions when resizing, then we - // can flip them however they need to be flipped once the resizing has been done - if let Some(flip) = workspace.layout_flip() { - match flip { - Axis::Horizontal => { - if matches!(direction, OperationDirection::Left) - || matches!(direction, OperationDirection::Right) - { - direction = direction.opposite(); + for window in workspace.floating_windows().iter() { + if window.hwnd == focused_hwnd { + let mut rect = WindowsApi::window_rect(window.hwnd)?; + match (direction, sizing) { + (OperationDirection::Left, Sizing::Increase) => { + if rect.left - delta < focused_monitor_work_area.left { + rect.left = focused_monitor_work_area.left; + } else { + rect.left -= delta; } } - Axis::Vertical => { - if matches!(direction, OperationDirection::Up) - || matches!(direction, OperationDirection::Down) + (OperationDirection::Left, Sizing::Decrease) => { + rect.left += delta; + } + (OperationDirection::Right, Sizing::Increase) => { + if rect.left + rect.right + delta * 2 + > focused_monitor_work_area.right { - direction = direction.opposite(); + rect.right = focused_monitor_work_area.right - rect.left; + } else { + rect.right += delta * 2; } } - Axis::HorizontalAndVertical => direction = direction.opposite(), + (OperationDirection::Right, Sizing::Decrease) => { + rect.right -= delta * 2; + } + (OperationDirection::Up, Sizing::Increase) => { + if rect.top - delta < focused_monitor_work_area.top { + rect.top = focused_monitor_work_area.top; + } else { + rect.top -= delta; + } + } + (OperationDirection::Up, Sizing::Decrease) => { + rect.top += delta; + } + (OperationDirection::Down, Sizing::Increase) => { + if rect.top + rect.bottom + delta * 2 + > focused_monitor_work_area.bottom + { + rect.bottom = focused_monitor_work_area.bottom - rect.top; + } else { + rect.bottom += delta * 2; + } + } + (OperationDirection::Down, Sizing::Decrease) => { + rect.bottom -= delta * 2; + } } + + WindowsApi::position_window(window.hwnd, &rect, false)?; + if mouse_follows_focus { + WindowsApi::center_cursor_in_rect(&rect)?; + } + + break; } - - let resize = layout.resize( - unaltered - .get(focused_idx) - .ok_or_else(|| anyhow!("there is no last layout"))?, - focused_idx_resize, - direction, - sizing, - delta, - ); - - workspace.resize_dimensions_mut()[focused_idx] = resize; - - return if update { - self.update_focused_workspace(false, false) - } else { - Ok(()) - }; } - - tracing::warn!("cannot resize container in this direction"); } - Layout::Custom(_) => { - tracing::warn!("containers cannot be resized when using custom layouts"); + WorkspaceLayer::Tiling => { + match workspace.layout() { + Layout::Default(layout) => { + tracing::info!("resizing window"); + let len = NonZeroUsize::new(workspace.containers().len()) + .ok_or_else(|| anyhow!("there must be at least one container"))?; + let focused_idx = workspace.focused_container_idx(); + let focused_idx_resize = workspace + .resize_dimensions() + .get(focused_idx) + .ok_or_else(|| { + anyhow!("there is no resize adjustment for this container") + })?; + + if direction + .destination( + workspace.layout().as_boxed_direction().as_ref(), + workspace.layout_flip(), + focused_idx, + len, + ) + .is_some() + { + let unaltered = layout.calculate( + &focused_monitor_work_area, + len, + workspace.container_padding(), + workspace.layout_flip(), + &[], + ); + + let mut direction = direction; + + // We only ever want to operate on the unflipped Rect positions when resizing, then we + // can flip them however they need to be flipped once the resizing has been done + if let Some(flip) = workspace.layout_flip() { + match flip { + Axis::Horizontal => { + if matches!(direction, OperationDirection::Left) + || matches!(direction, OperationDirection::Right) + { + direction = direction.opposite(); + } + } + Axis::Vertical => { + if matches!(direction, OperationDirection::Up) + || matches!(direction, OperationDirection::Down) + { + direction = direction.opposite(); + } + } + Axis::HorizontalAndVertical => direction = direction.opposite(), + } + } + + let resize = layout.resize( + unaltered + .get(focused_idx) + .ok_or_else(|| anyhow!("there is no last layout"))?, + focused_idx_resize, + direction, + sizing, + delta, + ); + + workspace.resize_dimensions_mut()[focused_idx] = resize; + + return if update { + self.update_focused_workspace(false, false) + } else { + Ok(()) + }; + } + + tracing::warn!("cannot resize container in this direction"); + } + Layout::Custom(_) => { + tracing::warn!("containers cannot be resized when using custom layouts"); + } + } } } + Ok(()) } @@ -1858,6 +1944,56 @@ impl WindowManager { self.update_focused_workspace(mouse_follows_focus, true) } + #[tracing::instrument(skip(self))] + pub fn focus_floating_window_in_direction( + &mut self, + direction: OperationDirection, + ) -> Result<()> { + let mouse_follows_focus = self.mouse_follows_focus; + let focused_workspace = self.focused_workspace()?; + + let mut target_idx = None; + let len = focused_workspace.floating_windows().len(); + + if len > 1 { + let focused_hwnd = WindowsApi::foreground_window()?; + for (idx, window) in focused_workspace.floating_windows().iter().enumerate() { + if window.hwnd == focused_hwnd { + match direction { + OperationDirection::Left => {} + OperationDirection::Right => {} + OperationDirection::Up => { + if idx == len - 1 { + target_idx = Some(0) + } else { + target_idx = Some(idx + 1) + } + } + OperationDirection::Down => { + if idx == 0 { + target_idx = Some(len - 1) + } else { + target_idx = Some(idx - 1) + } + } + } + } + } + + if target_idx.is_none() { + target_idx = Some(0); + } + } + + if let Some(idx) = target_idx { + if let Some(window) = focused_workspace.floating_windows().get(idx) { + window.focus(mouse_follows_focus)?; + } + } + + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn focus_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> { self.handle_unmanaged_window_behaviour()?; @@ -2003,6 +2139,75 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn move_floating_window_in_direction( + &mut self, + direction: OperationDirection, + ) -> Result<()> { + let mouse_follows_focus = self.mouse_follows_focus; + + let mut focused_monitor_work_area = self.focused_monitor_work_area()?; + let border_offset = BORDER_OFFSET.load(Ordering::SeqCst); + let border_width = BORDER_WIDTH.load(Ordering::SeqCst); + focused_monitor_work_area.left += border_offset; + focused_monitor_work_area.left += border_width; + focused_monitor_work_area.top += border_offset; + focused_monitor_work_area.top += border_width; + focused_monitor_work_area.right -= border_offset; + focused_monitor_work_area.right -= border_width; + focused_monitor_work_area.bottom -= border_offset; + focused_monitor_work_area.bottom -= border_width; + + let focused_workspace = self.focused_workspace()?; + let delta = self.resize_delta; + + let focused_hwnd = WindowsApi::foreground_window()?; + for window in focused_workspace.floating_windows().iter() { + if window.hwnd == focused_hwnd { + let mut rect = WindowsApi::window_rect(window.hwnd)?; + match direction { + OperationDirection::Left => { + if rect.left - delta < focused_monitor_work_area.left { + rect.left = focused_monitor_work_area.left; + } else { + rect.left -= delta; + } + } + OperationDirection::Right => { + if rect.left + delta + rect.right > focused_monitor_work_area.right { + rect.left = focused_monitor_work_area.right - rect.right; + } else { + rect.left += delta; + } + } + OperationDirection::Up => { + if rect.top - delta < focused_monitor_work_area.top { + rect.top = focused_monitor_work_area.top; + } else { + rect.top -= delta; + } + } + OperationDirection::Down => { + if rect.top + delta + rect.bottom > focused_monitor_work_area.bottom { + rect.top = focused_monitor_work_area.bottom - rect.bottom; + } else { + rect.top += delta; + } + } + } + + WindowsApi::position_window(window.hwnd, &rect, false)?; + if mouse_follows_focus { + WindowsApi::center_cursor_in_rect(&rect)?; + } + + break; + } + } + + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn move_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> { self.handle_unmanaged_window_behaviour()?; @@ -2179,6 +2384,54 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn focus_floating_window_in_cycle_direction( + &mut self, + direction: CycleDirection, + ) -> Result<()> { + let mouse_follows_focus = self.mouse_follows_focus; + let focused_workspace = self.focused_workspace()?; + + let mut target_idx = None; + let len = focused_workspace.floating_windows().len(); + + if len > 1 { + let focused_hwnd = WindowsApi::foreground_window()?; + for (idx, window) in focused_workspace.floating_windows().iter().enumerate() { + if window.hwnd == focused_hwnd { + match direction { + CycleDirection::Previous => { + if idx == 0 { + target_idx = Some(len - 1) + } else { + target_idx = Some(idx - 1) + } + } + CycleDirection::Next => { + if idx == len - 1 { + target_idx = Some(0) + } else { + target_idx = Some(idx - 1) + } + } + } + } + } + + if target_idx.is_none() { + target_idx = Some(0); + } + } + + if let Some(idx) = target_idx { + if let Some(window) = focused_workspace.floating_windows().get(idx) { + window.focus(mouse_follows_focus)?; + } + } + + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> { self.handle_unmanaged_window_behaviour()?; @@ -2526,8 +2779,10 @@ impl WindowManager { } if is_floating_window { + workspace.set_layer(WorkspaceLayer::Tiling); self.unfloat_window()?; } else { + workspace.set_layer(WorkspaceLayer::Floating); self.float_window()?; } diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index e91dfdb4..25ec9fd9 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -1,4 +1,6 @@ use std::collections::VecDeque; +use std::fmt::Display; +use std::fmt::Formatter; use std::num::NonZeroUsize; use std::sync::atomic::Ordering; @@ -92,11 +94,29 @@ pub struct Workspace { pub window_container_behaviour_rules: Option>, #[getset(get = "pub", get_mut = "pub", set = "pub")] pub float_override: Option, + #[getset(get = "pub", get_mut = "pub", set = "pub")] + pub layer: WorkspaceLayer, #[serde(skip_serializing_if = "Option::is_none")] #[getset(get = "pub", set = "pub")] pub workspace_config: Option, } +#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub enum WorkspaceLayer { + #[default] + Tiling, + Floating, +} + +impl Display for WorkspaceLayer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + WorkspaceLayer::Tiling => write!(f, "Tiling"), + WorkspaceLayer::Floating => write!(f, "Floating"), + } + } +} + impl_ring_elements!(Workspace, Container); impl Default for Workspace { @@ -122,6 +142,7 @@ impl Default for Workspace { window_container_behaviour_rules: None, float_override: None, workspace_config: None, + layer: Default::default(), } } } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index e2f4124e..1ad1cef4 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -1269,6 +1269,8 @@ enum SubCommand { /// mode, for the currently focused workspace. If there was no override value set for the /// workspace previously it takes the opposite of the global value. ToggleWorkspaceFloatOverride, + /// Toggle between the Tiling and Floating layers on the focused workspace + ToggleWorkspaceLayer, /// Toggle window tiling on the focused workspace TogglePause, /// Toggle window tiling on the focused workspace @@ -2854,6 +2856,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) { SubCommand::ToggleWorkspaceFloatOverride => { send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?; } + SubCommand::ToggleWorkspaceLayer => { + send_message(&SocketMessage::ToggleWorkspaceLayer)?; + } SubCommand::WindowHidingBehaviour(arg) => { send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?; } diff --git a/mkdocs.yml b/mkdocs.yml index 2e584b56..972a1d15 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -177,6 +177,7 @@ nav: - cli/toggle-float-override.md - cli/toggle-workspace-window-container-behaviour.md - cli/toggle-workspace-float-override.md + - cli/toggle-workspace-layer.md - cli/toggle-pause.md - cli/toggle-tiling.md - cli/toggle-float.md diff --git a/schema.bar.json b/schema.bar.json index 41a66dd8..d0ecce6c 100644 --- a/schema.bar.json +++ b/schema.bar.json @@ -504,6 +504,19 @@ } } }, + "workspace_layer": { + "description": "Configure the Workspace Layer widget", + "type": "object", + "required": [ + "enable" + ], + "properties": { + "enable": { + "description": "Enable the Komorebi Workspace Layer widget", + "type": "boolean" + } + } + }, "workspaces": { "description": "Configure the Workspaces widget", "type": "object", @@ -1811,6 +1824,19 @@ } } }, + "workspace_layer": { + "description": "Configure the Workspace Layer widget", + "type": "object", + "required": [ + "enable" + ], + "properties": { + "enable": { + "description": "Enable the Komorebi Workspace Layer widget", + "type": "boolean" + } + } + }, "workspaces": { "description": "Configure the Workspaces widget", "type": "object", @@ -3051,6 +3077,19 @@ } } }, + "workspace_layer": { + "description": "Configure the Workspace Layer widget", + "type": "object", + "required": [ + "enable" + ], + "properties": { + "enable": { + "description": "Enable the Komorebi Workspace Layer widget", + "type": "boolean" + } + } + }, "workspaces": { "description": "Configure the Workspaces widget", "type": "object",