mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-11 22:12:53 +01: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
|
||||
|
||||
```
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -149,6 +149,7 @@ pub enum SocketMessage {
|
||||
NamedWorkspaceLayoutCustomRule(String, usize, PathBuf),
|
||||
ClearWorkspaceLayoutRules(usize, usize),
|
||||
ClearNamedWorkspaceLayoutRules(String),
|
||||
ToggleWorkspaceLayer,
|
||||
// Configuration
|
||||
ReloadConfiguration,
|
||||
ReplaceConfiguration(PathBuf),
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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()?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))?;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user