mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-22 16:48:33 +02:00
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:
12
docs/cli/toggle-workspace-layer.md
Normal file
12
docs/cli/toggle-workspace-layer.md
Normal 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
|
||||||
|
|
||||||
|
```
|
||||||
@@ -369,12 +369,6 @@ impl Komobar {
|
|||||||
monitor_index,
|
monitor_index,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
tracing::info!(
|
|
||||||
"work area offset applied to monitor: {}\n, {:#?}",
|
|
||||||
monitor_index,
|
|
||||||
new_rect
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -629,13 +623,8 @@ impl Komobar {
|
|||||||
pub fn position_bar(&self) {
|
pub fn position_bar(&self) {
|
||||||
if let Some(hwnd) = self.hwnd {
|
if let Some(hwnd) = self.hwnd {
|
||||||
let window = komorebi_client::Window::from(hwnd);
|
let window = komorebi_client::Window::from(hwnd);
|
||||||
match window.set_position(&self.size_rect, false) {
|
if let Err(error) = window.set_position(&self.size_rect, false) {
|
||||||
Ok(_) => {
|
tracing::error!("{}", error.to_string())
|
||||||
tracing::info!("updated bar position: {:#?}", &self.size_rect);
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!("{}", error.to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ use komorebi_client::Rect;
|
|||||||
use komorebi_client::SocketMessage;
|
use komorebi_client::SocketMessage;
|
||||||
use komorebi_client::Window;
|
use komorebi_client::Window;
|
||||||
use komorebi_client::Workspace;
|
use komorebi_client::Workspace;
|
||||||
|
use komorebi_client::WorkspaceLayer;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -49,6 +50,8 @@ pub struct KomorebiConfig {
|
|||||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||||
/// Configure the Layout widget
|
/// Configure the Layout widget
|
||||||
pub layout: Option<KomorebiLayoutConfig>,
|
pub layout: Option<KomorebiLayoutConfig>,
|
||||||
|
/// Configure the Workspace Layer widget
|
||||||
|
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||||
/// Configure the Focused Window widget
|
/// Configure the Focused Window widget
|
||||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||||
/// Configure the Configuration Switcher widget
|
/// Configure the Configuration Switcher widget
|
||||||
@@ -75,6 +78,12 @@ pub struct KomorebiLayoutConfig {
|
|||||||
pub display: Option<DisplayFormat>,
|
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)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct KomorebiFocusedWindowConfig {
|
pub struct KomorebiFocusedWindowConfig {
|
||||||
/// Enable the Komorebi Focused Window widget
|
/// Enable the Komorebi Focused Window widget
|
||||||
@@ -127,6 +136,7 @@ impl From<&KomorebiConfig> for Komorebi {
|
|||||||
workspaces: value.workspaces,
|
workspaces: value.workspaces,
|
||||||
layout: value.layout.clone(),
|
layout: value.layout.clone(),
|
||||||
focused_window: value.focused_window,
|
focused_window: value.focused_window,
|
||||||
|
workspace_layer: value.workspace_layer,
|
||||||
configuration_switcher,
|
configuration_switcher,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,6 +148,7 @@ pub struct Komorebi {
|
|||||||
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
pub workspaces: Option<KomorebiWorkspacesConfig>,
|
||||||
pub layout: Option<KomorebiLayoutConfig>,
|
pub layout: Option<KomorebiLayoutConfig>,
|
||||||
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
pub focused_window: Option<KomorebiFocusedWindowConfig>,
|
||||||
|
pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,
|
||||||
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +165,7 @@ impl BarWidget for Komorebi {
|
|||||||
let format = workspaces.display.unwrap_or(DisplayFormat::Text);
|
let format = workspaces.display.unwrap_or(DisplayFormat::Text);
|
||||||
|
|
||||||
config.apply_on_widget(false, ui, |ui| {
|
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()
|
komorebi_notification_state.workspaces.iter().enumerate()
|
||||||
{
|
{
|
||||||
if SelectableFrame::new(
|
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 let Some(layout_config) = &self.layout {
|
||||||
if layout_config.enable {
|
if layout_config.enable {
|
||||||
let workspace_idx: Option<usize> = komorebi_notification_state
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KomorebiNotificationState {
|
pub struct KomorebiNotificationState {
|
||||||
pub workspaces: Vec<(String, KomorebiNotificationStateContainerInformation)>,
|
pub workspaces: Vec<(
|
||||||
|
String,
|
||||||
|
KomorebiNotificationStateContainerInformation,
|
||||||
|
WorkspaceLayer,
|
||||||
|
)>,
|
||||||
pub selected_workspace: String,
|
pub selected_workspace: String,
|
||||||
pub focused_container_information: KomorebiNotificationStateContainerInformation,
|
pub focused_container_information: KomorebiNotificationStateContainerInformation,
|
||||||
pub layout: KomorebiLayout,
|
pub layout: KomorebiLayout,
|
||||||
@@ -592,6 +643,7 @@ impl KomorebiNotificationState {
|
|||||||
workspaces.push((
|
workspaces.push((
|
||||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||||
ws.into(),
|
ws.into(),
|
||||||
|
ws.layer().to_owned(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ pub use komorebi::ring::Ring;
|
|||||||
pub use komorebi::window::Window;
|
pub use komorebi::window::Window;
|
||||||
pub use komorebi::window_manager_event::WindowManagerEvent;
|
pub use komorebi::window_manager_event::WindowManagerEvent;
|
||||||
pub use komorebi::workspace::Workspace;
|
pub use komorebi::workspace::Workspace;
|
||||||
|
pub use komorebi::workspace::WorkspaceLayer;
|
||||||
pub use komorebi::AnimationsConfig;
|
pub use komorebi::AnimationsConfig;
|
||||||
pub use komorebi::AspectRatio;
|
pub use komorebi::AspectRatio;
|
||||||
pub use komorebi::BorderColours;
|
pub use komorebi::BorderColours;
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ pub enum SocketMessage {
|
|||||||
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
|
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
|
||||||
ClearWorkspaceLayoutRules(usize, usize),
|
ClearWorkspaceLayoutRules(usize, usize),
|
||||||
ClearNamedWorkspaceLayoutRules(String),
|
ClearNamedWorkspaceLayoutRules(String),
|
||||||
|
ToggleWorkspaceLayer,
|
||||||
// Configuration
|
// Configuration
|
||||||
ReloadConfiguration,
|
ReloadConfiguration,
|
||||||
ReplaceConfiguration(PathBuf),
|
ReplaceConfiguration(PathBuf),
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ use crate::window_manager;
|
|||||||
use crate::window_manager::WindowManager;
|
use crate::window_manager::WindowManager;
|
||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::winevent_listener;
|
use crate::winevent_listener;
|
||||||
|
use crate::workspace::WorkspaceLayer;
|
||||||
use crate::workspace::WorkspaceWindowLocation;
|
use crate::workspace::WorkspaceWindowLocation;
|
||||||
use crate::GlobalState;
|
use crate::GlobalState;
|
||||||
use crate::Notification;
|
use crate::Notification;
|
||||||
@@ -291,13 +292,37 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketMessage::FocusWindow(direction) => {
|
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) => {
|
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) => {
|
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) => {
|
SocketMessage::CycleMoveWindow(direction) => {
|
||||||
self.move_container_in_cycle_direction(direction)?;
|
self.move_container_in_cycle_direction(direction)?;
|
||||||
@@ -1020,6 +1045,29 @@ impl WindowManager {
|
|||||||
self.focus_workspace(workspace_idx)?;
|
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 => {
|
SocketMessage::Stop => {
|
||||||
self.stop(false)?;
|
self.stop(false)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ use crate::core::WindowContainerBehaviour;
|
|||||||
use crate::core::WindowManagementBehaviour;
|
use crate::core::WindowManagementBehaviour;
|
||||||
|
|
||||||
use crate::border_manager;
|
use crate::border_manager;
|
||||||
|
use crate::border_manager::BORDER_OFFSET;
|
||||||
|
use crate::border_manager::BORDER_WIDTH;
|
||||||
use crate::border_manager::STYLE;
|
use crate::border_manager::STYLE;
|
||||||
use crate::config_generation::WorkspaceMatchingRule;
|
use crate::config_generation::WorkspaceMatchingRule;
|
||||||
use crate::container::Container;
|
use crate::container::Container;
|
||||||
@@ -75,6 +77,7 @@ use crate::window_manager_event::WindowManagerEvent;
|
|||||||
use crate::windows_api::WindowsApi;
|
use crate::windows_api::WindowsApi;
|
||||||
use crate::winevent_listener;
|
use crate::winevent_listener;
|
||||||
use crate::workspace::Workspace;
|
use crate::workspace::Workspace;
|
||||||
|
use crate::workspace::WorkspaceLayer;
|
||||||
use crate::BorderColours;
|
use crate::BorderColours;
|
||||||
use crate::Colour;
|
use crate::Colour;
|
||||||
use crate::CrossBoundaryBehaviour;
|
use crate::CrossBoundaryBehaviour;
|
||||||
@@ -333,6 +336,7 @@ impl From<&WindowManager> for State {
|
|||||||
.window_container_behaviour_rules
|
.window_container_behaviour_rules
|
||||||
.clone(),
|
.clone(),
|
||||||
float_override: workspace.float_override,
|
float_override: workspace.float_override,
|
||||||
|
layer: workspace.layer,
|
||||||
workspace_config: None,
|
workspace_config: None,
|
||||||
})
|
})
|
||||||
.collect::<VecDeque<_>>();
|
.collect::<VecDeque<_>>();
|
||||||
@@ -1392,86 +1396,168 @@ impl WindowManager {
|
|||||||
delta: i32,
|
delta: i32,
|
||||||
update: bool,
|
update: bool,
|
||||||
) -> Result<()> {
|
) -> 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()?;
|
let workspace = self.focused_workspace_mut()?;
|
||||||
|
|
||||||
match workspace.layout() {
|
match workspace.layer() {
|
||||||
Layout::Default(layout) => {
|
WorkspaceLayer::Floating => {
|
||||||
tracing::info!("resizing window");
|
let workspace = self.focused_workspace()?;
|
||||||
let len = NonZeroUsize::new(workspace.containers().len())
|
let focused_hwnd = WindowsApi::foreground_window()?;
|
||||||
.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
|
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
||||||
.destination(
|
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
|
||||||
workspace.layout().as_boxed_direction().as_ref(),
|
focused_monitor_work_area.left += border_offset;
|
||||||
workspace.layout_flip(),
|
focused_monitor_work_area.left += border_width;
|
||||||
focused_idx,
|
focused_monitor_work_area.top += border_offset;
|
||||||
len,
|
focused_monitor_work_area.top += border_width;
|
||||||
)
|
focused_monitor_work_area.right -= border_offset;
|
||||||
.is_some()
|
focused_monitor_work_area.right -= border_width;
|
||||||
{
|
focused_monitor_work_area.bottom -= border_offset;
|
||||||
let unaltered = layout.calculate(
|
focused_monitor_work_area.bottom -= border_width;
|
||||||
&work_area,
|
|
||||||
len,
|
|
||||||
workspace.container_padding(),
|
|
||||||
workspace.layout_flip(),
|
|
||||||
&[],
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut direction = direction;
|
for window in workspace.floating_windows().iter() {
|
||||||
|
if window.hwnd == focused_hwnd {
|
||||||
// We only ever want to operate on the unflipped Rect positions when resizing, then we
|
let mut rect = WindowsApi::window_rect(window.hwnd)?;
|
||||||
// can flip them however they need to be flipped once the resizing has been done
|
match (direction, sizing) {
|
||||||
if let Some(flip) = workspace.layout_flip() {
|
(OperationDirection::Left, Sizing::Increase) => {
|
||||||
match flip {
|
if rect.left - delta < focused_monitor_work_area.left {
|
||||||
Axis::Horizontal => {
|
rect.left = focused_monitor_work_area.left;
|
||||||
if matches!(direction, OperationDirection::Left)
|
} else {
|
||||||
|| matches!(direction, OperationDirection::Right)
|
rect.left -= delta;
|
||||||
{
|
|
||||||
direction = direction.opposite();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Vertical => {
|
(OperationDirection::Left, Sizing::Decrease) => {
|
||||||
if matches!(direction, OperationDirection::Up)
|
rect.left += delta;
|
||||||
|| matches!(direction, OperationDirection::Down)
|
}
|
||||||
|
(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(_) => {
|
WorkspaceLayer::Tiling => {
|
||||||
tracing::warn!("containers cannot be resized when using custom layouts");
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1860,6 +1946,56 @@ impl WindowManager {
|
|||||||
self.update_focused_workspace(mouse_follows_focus, true)
|
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))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn focus_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
pub fn focus_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
||||||
self.handle_unmanaged_window_behaviour()?;
|
self.handle_unmanaged_window_behaviour()?;
|
||||||
@@ -2005,6 +2141,75 @@ impl WindowManager {
|
|||||||
Ok(())
|
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))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn move_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
pub fn move_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
||||||
self.handle_unmanaged_window_behaviour()?;
|
self.handle_unmanaged_window_behaviour()?;
|
||||||
@@ -2181,6 +2386,54 @@ impl WindowManager {
|
|||||||
Ok(())
|
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))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
||||||
self.handle_unmanaged_window_behaviour()?;
|
self.handle_unmanaged_window_behaviour()?;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::fmt::Formatter;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
@@ -92,11 +94,29 @@ pub struct Workspace {
|
|||||||
pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,
|
pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,
|
||||||
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
pub float_override: Option<bool>,
|
pub float_override: Option<bool>,
|
||||||
|
#[getset(get = "pub", get_mut = "pub", set = "pub")]
|
||||||
|
pub layer: WorkspaceLayer,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[getset(get = "pub", set = "pub")]
|
#[getset(get = "pub", set = "pub")]
|
||||||
pub workspace_config: Option<WorkspaceConfig>,
|
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_ring_elements!(Workspace, Container);
|
||||||
|
|
||||||
impl Default for Workspace {
|
impl Default for Workspace {
|
||||||
@@ -122,6 +142,7 @@ impl Default for Workspace {
|
|||||||
window_container_behaviour_rules: None,
|
window_container_behaviour_rules: None,
|
||||||
float_override: None,
|
float_override: None,
|
||||||
workspace_config: None,
|
workspace_config: None,
|
||||||
|
layer: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1269,6 +1269,8 @@ enum SubCommand {
|
|||||||
/// mode, for the currently focused workspace. If there was no override value set for the
|
/// 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.
|
/// workspace previously it takes the opposite of the global value.
|
||||||
ToggleWorkspaceFloatOverride,
|
ToggleWorkspaceFloatOverride,
|
||||||
|
/// Toggle between the Tiling and Floating layers on the focused workspace
|
||||||
|
ToggleWorkspaceLayer,
|
||||||
/// Toggle window tiling on the focused workspace
|
/// Toggle window tiling on the focused workspace
|
||||||
TogglePause,
|
TogglePause,
|
||||||
/// Toggle window tiling on the focused workspace
|
/// Toggle window tiling on the focused workspace
|
||||||
@@ -2854,6 +2856,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
|||||||
SubCommand::ToggleWorkspaceFloatOverride => {
|
SubCommand::ToggleWorkspaceFloatOverride => {
|
||||||
send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;
|
send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;
|
||||||
}
|
}
|
||||||
|
SubCommand::ToggleWorkspaceLayer => {
|
||||||
|
send_message(&SocketMessage::ToggleWorkspaceLayer)?;
|
||||||
|
}
|
||||||
SubCommand::WindowHidingBehaviour(arg) => {
|
SubCommand::WindowHidingBehaviour(arg) => {
|
||||||
send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?;
|
send_message(&SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour))?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ nav:
|
|||||||
- cli/toggle-float-override.md
|
- cli/toggle-float-override.md
|
||||||
- cli/toggle-workspace-window-container-behaviour.md
|
- cli/toggle-workspace-window-container-behaviour.md
|
||||||
- cli/toggle-workspace-float-override.md
|
- cli/toggle-workspace-float-override.md
|
||||||
|
- cli/toggle-workspace-layer.md
|
||||||
- cli/toggle-pause.md
|
- cli/toggle-pause.md
|
||||||
- cli/toggle-tiling.md
|
- cli/toggle-tiling.md
|
||||||
- cli/toggle-float.md
|
- cli/toggle-float.md
|
||||||
|
|||||||
@@ -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": {
|
"workspaces": {
|
||||||
"description": "Configure the Workspaces widget",
|
"description": "Configure the Workspaces widget",
|
||||||
"type": "object",
|
"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": {
|
"workspaces": {
|
||||||
"description": "Configure the Workspaces widget",
|
"description": "Configure the Workspaces widget",
|
||||||
"type": "object",
|
"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": {
|
"workspaces": {
|
||||||
"description": "Configure the Workspaces widget",
|
"description": "Configure the Workspaces widget",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
Reference in New Issue
Block a user