feat(wm): float placement configs

This commit adds a few more options to combine with the
`FloatingLayerBehaviour` which determine placement in different
situations:

- `"toggle_float_placement"`: the placement to be used by a floating
  window when it is forced to float with the `toggle-float` command.
- `"floating_layer_placement"`: the placement to be used by a floating
  window when it is spawned on the floating layer and the user has the
  floating layer behaviour set to float.
- `"floating_override_placement"`: the placement to be used by a window
  that is spawned when float override is active.
- `"float_rule_placement"`: the placement to be used by a window that
  matches a 'floating_applications' rule.

Each `Placement` can be one of the following types:

- "None": windows are spawned wherever Windows positions them with
  whatever size they had. Komorebi does not change its size or position.
- "Center": windows are centered without changing their size.
- "CenterAndResize": windows are centered and resized according to the
defined aspect ratio.

By default the placements are as follows:
- `"toggle_float_placement"`: `"CenterAndResize"`
- `"floating_layer_placement"`: `"Center"`
- `"floating_override_placement"`: `"None"`
- `"float_rule_placement"`: `"None"`

This commit also adds the `floating_layer_behaviour` as a global config.
This commit is contained in:
alex-ds13
2025-04-14 12:56:43 +01:00
committed by LGUG2Z
parent c408c1149c
commit 3ee3aac806
7 changed files with 305 additions and 34 deletions

View File

@@ -377,6 +377,21 @@ pub struct WindowManagementBehaviour {
/// that can be later toggled to tiled, when false it will default to
/// `current_behaviour` again.
pub float_override: bool,
/// Determines if a new window should be spawned floating when on the floating layer and the
/// floating layer behaviour is set to float. This value is always calculated when checking for
/// the management behaviour on a specific workspace.
pub floating_layer_override: bool,
/// The floating layer behaviour to be used if the float override is being used
pub floating_layer_behaviour: FloatingLayerBehaviour,
/// The `Placement` to be used when toggling a window to float
pub toggle_float_placement: Placement,
/// The `Placement` to be used when spawning a window on the floating layer with the
/// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float`
pub floating_layer_placement: Placement,
/// The `Placement` to be used when spawning a window with float override active
pub float_override_placement: Placement,
/// The `Placement` to be used when spawning a window that matches a 'floating_applications' rule
pub float_rule_placement: Placement,
}
#[derive(
@@ -396,13 +411,52 @@ pub enum WindowContainerBehaviour {
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum FloatingLayerBehaviour {
/// Tile new windows (unless they match a float rule)
/// Tile new windows (unless they match a float rule or float override is active)
#[default]
Tile,
/// Float new windows
Float,
}
#[derive(
Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Placement {
/// Does not change the size or position of the window
#[default]
None,
/// Center the window without changing the size
Center,
/// Center the window and resize it according to the `AspectRatio`
CenterAndResize,
}
impl FloatingLayerBehaviour {
pub fn should_float(&self) -> bool {
match self {
FloatingLayerBehaviour::Tile => false,
FloatingLayerBehaviour::Float => true,
}
}
}
impl Placement {
pub fn should_center(&self) -> bool {
match self {
Placement::None => false,
Placement::Center | Placement::CenterAndResize => true,
}
}
pub fn should_resize(&self) -> bool {
match self {
Placement::None | Placement::Center => false,
Placement::CenterAndResize => true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum MoveBehaviour {

View File

@@ -346,7 +346,7 @@ impl WindowManager {
}
if proceed {
let mut behaviour = self.window_management_behaviour(
let behaviour = self.window_management_behaviour(
focused_monitor_idx,
focused_workspace_idx,
);
@@ -376,23 +376,32 @@ impl WindowManager {
}
}
behaviour.float_override = behaviour.float_override
|| (should_float
&& !matches!(event, WindowManagerEvent::Manage(_)));
if behaviour.float_override {
// Center floating windows if we are already on the `Floating`
// layer and the window doesn't match a `floating_windows` rule and
// the workspace is not a floating workspace
if behaviour.float_override
|| behaviour.floating_layer_override
|| (should_float && !matches!(event, WindowManagerEvent::Manage(_)))
{
let placement = if behaviour.floating_layer_override {
// Floating layer override placement
behaviour.floating_layer_placement
} else if behaviour.float_override {
// Float override placement
behaviour.float_override_placement
} else {
// Float rule placement
behaviour.float_rule_placement
};
// Center floating windows according to the proper placement if not
// on a floating workspace
let center_spawned_floats =
matches!(workspace.layer, WorkspaceLayer::Floating)
&& !should_float
&& workspace.tile;
placement.should_center() && workspace.tile;
workspace.floating_windows_mut().push_back(window);
workspace.set_layer(WorkspaceLayer::Floating);
if center_spawned_floats {
let mut floating_window = window;
floating_window.center(&workspace.globals().work_area)?;
floating_window.center(
&workspace.globals().work_area,
placement.should_resize(),
)?;
}
self.update_focused_workspace(false, false)?;
} else {
@@ -709,7 +718,7 @@ impl WindowManager {
// If we unmanaged a window, it shouldn't be immediately hidden behind managed windows
if let WindowManagerEvent::Unmanage(mut window) = event {
window.center(&self.focused_monitor_work_area()?)?;
window.center(&self.focused_monitor_work_area()?, true)?;
}
// Update list of known_hwnds and their monitor/workspace index pair

View File

@@ -60,6 +60,7 @@ use crate::AspectRatio;
use crate::Axis;
use crate::CrossBoundaryBehaviour;
use crate::FloatingLayerBehaviour;
use crate::Placement;
use crate::PredefinedAspectRatio;
use crate::ResolvedPathBuf;
use crate::DATA_DIR;
@@ -415,6 +416,25 @@ pub struct StaticConfig {
/// (default: false)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override: Option<bool>,
/// Determines what happens on a new window when on the `FloatingLayer`
/// (default: Tile)
#[serde(skip_serializing_if = "Option::is_none")]
pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,
/// Determines the placement of a new window when toggling to float (default: CenterAndResize)
#[serde(skip_serializing_if = "Option::is_none")]
pub toggle_float_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window on the floating layer with the
/// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float` (default: Center)
#[serde(skip_serializing_if = "Option::is_none")]
pub floating_layer_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window with float override active
/// (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_override_placement: Option<Placement>,
/// Determines the `Placement` to be used when spawning a window that matches a
/// 'floating_applications' rule (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub float_rule_placement: Option<Placement>,
/// Determine what happens when a window is moved across a monitor boundary (default: Swap)
#[serde(skip_serializing_if = "Option::is_none")]
pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
@@ -824,6 +844,21 @@ impl From<&WindowManager> for StaticConfig {
value.window_management_behaviour.current_behaviour,
),
float_override: Option::from(value.window_management_behaviour.float_override),
floating_layer_behaviour: Option::from(
value.window_management_behaviour.floating_layer_behaviour,
),
toggle_float_placement: Option::from(
value.window_management_behaviour.toggle_float_placement,
),
floating_layer_placement: Option::from(
value.window_management_behaviour.floating_layer_placement,
),
float_override_placement: Option::from(
value.window_management_behaviour.float_override_placement,
),
float_rule_placement: Option::from(
value.window_management_behaviour.float_rule_placement,
),
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour),
unmanaged_window_operation_behaviour: Option::from(
@@ -1227,6 +1262,16 @@ impl StaticConfig {
.window_container_behaviour
.unwrap_or(WindowContainerBehaviour::Create),
float_override: value.float_override.unwrap_or_default(),
floating_layer_override: false, // this value is always automatically calculated
floating_layer_behaviour: FloatingLayerBehaviour::default(),
toggle_float_placement: value
.toggle_float_placement
.unwrap_or(Placement::CenterAndResize),
floating_layer_placement: value
.floating_layer_placement
.unwrap_or(Placement::Center),
float_override_placement: value.float_override_placement.unwrap_or(Placement::None),
float_rule_placement: value.float_rule_placement.unwrap_or(Placement::None),
},
cross_monitor_move_behaviour: value
.cross_monitor_move_behaviour
@@ -1626,6 +1671,10 @@ impl StaticConfig {
wm.window_management_behaviour.float_override = val;
}
if let Some(val) = value.floating_layer_behaviour {
wm.window_management_behaviour.floating_layer_behaviour = val;
}
if let Some(val) = value.cross_monitor_move_behaviour {
wm.cross_monitor_move_behaviour = val;
}
@@ -1646,6 +1695,15 @@ impl StaticConfig {
wm.mouse_follows_focus = val;
}
wm.window_management_behaviour.toggle_float_placement = value
.toggle_float_placement
.unwrap_or(Placement::CenterAndResize);
wm.window_management_behaviour.floating_layer_placement =
value.floating_layer_placement.unwrap_or(Placement::Center);
wm.window_management_behaviour.float_override_placement =
value.float_override_placement.unwrap_or(Placement::None);
wm.window_management_behaviour.float_rule_placement =
value.float_rule_placement.unwrap_or(Placement::None);
wm.work_area_offset = value.global_work_area_offset;
match value.focus_follows_mouse {

View File

@@ -411,12 +411,18 @@ impl Window {
Ok(())
}
pub fn center(&mut self, work_area: &Rect) -> Result<()> {
let (aspect_ratio_width, aspect_ratio_height) = FLOATING_WINDOW_TOGGLE_ASPECT_RATIO
.lock()
.width_and_height();
let target_height = work_area.bottom / 2;
let target_width = (target_height * aspect_ratio_width) / aspect_ratio_height;
pub fn center(&mut self, work_area: &Rect, resize: bool) -> Result<()> {
let (target_width, target_height) = if resize {
let (aspect_ratio_width, aspect_ratio_height) = FLOATING_WINDOW_TOGGLE_ASPECT_RATIO
.lock()
.width_and_height();
let target_height = work_area.bottom / 2;
let target_width = (target_height * aspect_ratio_width) / aspect_ratio_height;
(target_width, target_height)
} else {
let current_rect = WindowsApi::window_rect(self.hwnd)?;
(current_rect.right, current_rect.bottom)
};
let x = work_area.left + ((work_area.right - target_width) / 2);
let y = work_area.top + ((work_area.bottom - target_height) / 2);

View File

@@ -80,7 +80,6 @@ use crate::workspace::WorkspaceLayer;
use crate::BorderColours;
use crate::Colour;
use crate::CrossBoundaryBehaviour;
use crate::FloatingLayerBehaviour;
use crate::Rgb;
use crate::CUSTOM_FFM;
use crate::DATA_DIR;
@@ -646,7 +645,7 @@ impl WindowManager {
self.window_management_behaviour.current_behaviour
};
let mut float_override = if let Some(float_override) = workspace.float_override() {
let float_override = if let Some(float_override) = workspace.float_override() {
*float_override
} else {
self.window_management_behaviour.float_override
@@ -656,18 +655,30 @@ impl WindowManager {
if let Some(behaviour) = workspace.floating_layer_behaviour() {
behaviour
} else {
monitor.floating_layer_behaviour().unwrap_or_default()
monitor
.floating_layer_behaviour()
.unwrap_or(self.window_management_behaviour.floating_layer_behaviour)
};
// If the workspace layer is `Floating` and the floating layer behaviour is `Float`,
// then consider it as if it had float override so that new windows spawn as floating
float_override = float_override
|| (matches!(workspace.layer, WorkspaceLayer::Floating)
&& matches!(floating_layer_behaviour, FloatingLayerBehaviour::Float));
// If the workspace layer is `Floating` and the floating layer behaviour should
// float then change floating_layer_override to true so that new windows spawn
// as floating
let floating_layer_override = matches!(workspace.layer, WorkspaceLayer::Floating)
&& floating_layer_behaviour.should_float();
return WindowManagementBehaviour {
current_behaviour,
float_override,
floating_layer_override,
floating_layer_behaviour,
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
floating_layer_placement: self
.window_management_behaviour
.floating_layer_placement,
float_override_placement: self
.window_management_behaviour
.float_override_placement,
float_rule_placement: self.window_management_behaviour.float_rule_placement,
};
}
}
@@ -675,6 +686,12 @@ impl WindowManager {
WindowManagementBehaviour {
current_behaviour: WindowContainerBehaviour::Create,
float_override: self.window_management_behaviour.float_override,
floating_layer_override: self.window_management_behaviour.floating_layer_override,
floating_layer_behaviour: self.window_management_behaviour.floating_layer_behaviour,
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
floating_layer_placement: self.window_management_behaviour.floating_layer_placement,
float_override_placement: self.window_management_behaviour.float_override_placement,
float_rule_placement: self.window_management_behaviour.float_rule_placement,
}
}
@@ -3142,6 +3159,8 @@ impl WindowManager {
let work_area = self.focused_monitor_work_area()?;
let toggle_float_placement = self.window_management_behaviour.toggle_float_placement;
let workspace = self.focused_workspace_mut()?;
workspace.new_floating_window()?;
@@ -3150,7 +3169,9 @@ impl WindowManager {
.back_mut()
.ok_or_else(|| anyhow!("there is no floating window"))?;
window.center(&work_area)?;
if toggle_float_placement.should_center() {
window.center(&work_area, toggle_float_placement.should_resize())?;
}
window.focus(self.mouse_follows_focus)?;
Ok(())

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "KomobarConfig",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.36`",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.37`",
"type": "object",
"required": [
"left_widgets",

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig",
"description": "The `komorebi.json` static configuration file reference for `v0.1.36`",
"description": "The `komorebi.json` static configuration file reference for `v0.1.37`",
"type": "object",
"properties": {
"animation": {
@@ -610,6 +610,58 @@
"description": "Enable or disable float override, which makes it so every new window opens in floating mode (default: false)",
"type": "boolean"
},
"float_override_placement": {
"description": "Determines the `Placement` to be used when spawning a window with float override active (default: None)",
"oneOf": [
{
"description": "Does not change the size or position of the window",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Center the window without changing the size",
"type": "string",
"enum": [
"Center"
]
},
{
"description": "Center the window and resize it according to the `AspectRatio`",
"type": "string",
"enum": [
"CenterAndResize"
]
}
]
},
"float_rule_placement": {
"description": "Determines the `Placement` to be used when spawning a window that matches a 'floating_applications' rule (default: None)",
"oneOf": [
{
"description": "Does not change the size or position of the window",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Center the window without changing the size",
"type": "string",
"enum": [
"Center"
]
},
{
"description": "Center the window and resize it according to the `AspectRatio`",
"type": "string",
"enum": [
"CenterAndResize"
]
}
]
},
"floating_applications": {
"description": "Identify applications which should be managed as floating windows",
"type": "array",
@@ -693,6 +745,51 @@
]
}
},
"floating_layer_behaviour": {
"description": "Determines what happens on a new window when on the `FloatingLayer` (default: Tile)",
"oneOf": [
{
"description": "Tile new windows (unless they match a float rule or float override is active)",
"type": "string",
"enum": [
"Tile"
]
},
{
"description": "Float new windows",
"type": "string",
"enum": [
"Float"
]
}
]
},
"floating_layer_placement": {
"description": "Determines the `Placement` to be used when spawning a window on the floating layer with the `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float` (default: Center)",
"oneOf": [
{
"description": "Does not change the size or position of the window",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Center the window without changing the size",
"type": "string",
"enum": [
"Center"
]
},
{
"description": "Center the window and resize it according to the `AspectRatio`",
"type": "string",
"enum": [
"CenterAndResize"
]
}
]
},
"floating_window_aspect_ratio": {
"description": "Aspect ratio to resize with when toggling floating mode for a window",
"anyOf": [
@@ -1135,7 +1232,7 @@
"description": "Determine what happens to a new window when the Floating workspace layer is active (default: Tile)",
"oneOf": [
{
"description": "Tile new windows (unless they match a float rule)",
"description": "Tile new windows (unless they match a float rule or float override is active)",
"type": "string",
"enum": [
"Tile"
@@ -1512,7 +1609,7 @@
"description": "Determine what happens to a new window when the Floating workspace layer is active (default: Tile)",
"oneOf": [
{
"description": "Tile new windows (unless they match a float rule)",
"description": "Tile new windows (unless they match a float rule or float override is active)",
"type": "string",
"enum": [
"Tile"
@@ -4206,6 +4303,32 @@
}
]
},
"toggle_float_placement": {
"description": "Determines the placement of a new window when toggling to float (default: CenterAndResize)",
"oneOf": [
{
"description": "Does not change the size or position of the window",
"type": "string",
"enum": [
"None"
]
},
{
"description": "Center the window without changing the size",
"type": "string",
"enum": [
"Center"
]
},
{
"description": "Center the window and resize it according to the `AspectRatio`",
"type": "string",
"enum": [
"CenterAndResize"
]
}
]
},
"transparency": {
"description": "Add transparency to unfocused windows (default: false)",
"type": "boolean"