feat(wm): add tiling and floating ws layers

This commit introduces an implementation of workspace layers to
komorebi.

Workspace layers change the kinds of windows that certain commands
operate on. This implementation features two variants,
WorkspaceLayer::Tiling and WorkspaceLayer::Floating.

The default behaviour until now has been WorkspaceLayer::Tiling.

When the user sets WorkspaceLayer::Floating, either through the
'toggle-workspace-layer' command or the new bar widget, the 'move',
'focus', 'cycle-focus' and 'resize-axis' commands will operate on
floating windows, if the currently focused window is a floating window.

As I don't have 'cycle-focus' bound to anything, 'focus up' and 'focus
down' double as incrementing and decrementing cycle focus commands,
iterating focus through the floating windows assigned to a workspace.

Floating windows in komorebi belong to specific workspaces, therefore
commands such as 'move' and 'resize-axis' will restrict movement and
resizing to the bounds of their workspace's work area (or more
accurately, the work area of the monitor that the workspace belongs to,
as floating windows are never constrained by workspace-specific work
area restrictions).
This commit is contained in:
LGUG2Z
2025-02-22 11:54:24 -08:00
parent 1756983978
commit 9d41a293f6
12 changed files with 516 additions and 80 deletions

View File

@@ -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
```

View File

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

View File

@@ -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<KomorebiWorkspacesConfig>,
/// Configure the Layout widget
pub layout: Option<KomorebiLayoutConfig>,
/// Configure the Workspace Layer widget
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
/// Configure the Focused Window widget
pub focused_window: Option<KomorebiFocusedWindowConfig>,
/// Configure the Configuration Switcher widget
@@ -75,6 +78,12 @@ pub struct KomorebiLayoutConfig {
pub display: Option<DisplayFormat>,
}
#[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<KomorebiWorkspacesConfig>,
pub layout: Option<KomorebiLayoutConfig>,
pub focused_window: Option<KomorebiFocusedWindowConfig>,
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
}
@@ -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<usize> = 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(),
));
}
}

View File

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

View File

@@ -149,6 +149,7 @@ pub enum SocketMessage {
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
ClearWorkspaceLayoutRules(usize, usize),
ClearNamedWorkspaceLayoutRules(String),
ToggleWorkspaceLayer,
// Configuration
ReloadConfiguration,
ReplaceConfiguration(PathBuf),

View File

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

View File

@@ -26,6 +26,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;
@@ -280,10 +281,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);
}
}
}
@@ -393,11 +397,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 => {
@@ -407,6 +413,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();
}

View File

@@ -49,6 +49,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;
@@ -75,6 +77,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;
@@ -333,6 +336,7 @@ impl From<&WindowManager> for State {
.window_container_behaviour_rules
.clone(),
float_override: workspace.float_override,
layer: workspace.layer,
workspace_config: None,
})
.collect::<VecDeque<_>>();
@@ -1392,86 +1396,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(())
}
@@ -1860,6 +1946,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()?;
@@ -2005,6 +2141,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()?;
@@ -2181,6 +2386,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()?;
@@ -2528,8 +2781,10 @@ impl WindowManager {
}
if is_floating_window {
workspace.set_layer(WorkspaceLayer::Tiling);
self.unfloat_window()?;
} else {
workspace.set_layer(WorkspaceLayer::Floating);
self.float_window()?;
}

View File

@@ -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<Vec<(usize, WindowContainerBehaviour)>>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub float_override: Option<bool>,
#[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<WorkspaceConfig>,
}
#[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(),
}
}
}

View File

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

View File

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

View File

@@ -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",