mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-07-11 23:52:47 +02:00
feat(cli): new layout-ratios command
The implementation adds a new layout-ratio CLI command to komorebi that allows users to dynamically set column and row ratios for layouts at runtime via komorebic layout-ratio --columns 0.3 0.4 --rows 0.5. A public validate_ratios function was added to komorebi-layouts that clamps ratio values between 0.1 and 0.9, truncates when the cumulative sum would reach or exceed 1.0, and limits to a maximum of 5 ratios. This function is shared between config file deserialization and the new CLI command, ensuring consistent validation behavior. The SocketMessage enum in komorebi/src/core/mod.rs has a new LayoutRatios variant. The handler in process_command.rs uses the shared validate_ratios function to process the ratios before applying them to the focused workspace's layout options. When the CLI command is called without any arguments, it prints a helpful message instead of returning an error.
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
*.json text diff
|
||||||
@@ -23,6 +23,36 @@ pub const DEFAULT_RATIO: f32 = 0.5;
|
|||||||
/// Default secondary ratio value for UltrawideVerticalStack layout
|
/// Default secondary ratio value for UltrawideVerticalStack layout
|
||||||
pub const DEFAULT_SECONDARY_RATIO: f32 = 0.25;
|
pub const DEFAULT_SECONDARY_RATIO: f32 = 0.25;
|
||||||
|
|
||||||
|
/// Validates and converts a Vec of ratios into a fixed-size array.
|
||||||
|
/// - Clamps values to MIN_RATIO..MAX_RATIO range
|
||||||
|
/// - Truncates when cumulative sum reaches or exceeds 1.0
|
||||||
|
/// - Limits to MAX_RATIOS values
|
||||||
|
#[must_use]
|
||||||
|
pub fn validate_ratios(ratios: &[f32]) -> [Option<f32>; MAX_RATIOS] {
|
||||||
|
let mut arr = [None; MAX_RATIOS];
|
||||||
|
let mut cumulative_sum = 0.0_f32;
|
||||||
|
|
||||||
|
for (i, &val) in ratios.iter().take(MAX_RATIOS).enumerate() {
|
||||||
|
let clamped_val = val.clamp(MIN_RATIO, MAX_RATIO);
|
||||||
|
|
||||||
|
// Only add this ratio if cumulative sum stays below 1.0
|
||||||
|
if cumulative_sum + clamped_val < 1.0 {
|
||||||
|
arr[i] = Some(clamped_val);
|
||||||
|
cumulative_sum += clamped_val;
|
||||||
|
} else {
|
||||||
|
// Stop adding ratios - cumulative sum would reach or exceed 1.0
|
||||||
|
tracing::debug!(
|
||||||
|
"Truncating ratios at index {} - cumulative sum {} + {} would reach/exceed 1.0",
|
||||||
|
i,
|
||||||
|
cumulative_sum,
|
||||||
|
clamped_val
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arr
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
|
Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,
|
||||||
)]
|
)]
|
||||||
@@ -137,30 +167,7 @@ where
|
|||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let opt: Option<Vec<f32>> = Option::deserialize(deserializer)?;
|
let opt: Option<Vec<f32>> = Option::deserialize(deserializer)?;
|
||||||
Ok(opt.map(|vec| {
|
Ok(opt.map(|vec| validate_ratios(&vec)))
|
||||||
let mut arr = [None; MAX_RATIOS];
|
|
||||||
let mut cumulative_sum = 0.0_f32;
|
|
||||||
|
|
||||||
for (i, &val) in vec.iter().take(MAX_RATIOS).enumerate() {
|
|
||||||
let clamped_val = val.clamp(MIN_RATIO, MAX_RATIO);
|
|
||||||
|
|
||||||
// Only add this ratio if cumulative sum stays below 1.0
|
|
||||||
if cumulative_sum + clamped_val < 1.0 {
|
|
||||||
arr[i] = Some(clamped_val);
|
|
||||||
cumulative_sum += clamped_val;
|
|
||||||
} else {
|
|
||||||
// Stop adding ratios - cumulative sum would reach or exceed 1.0
|
|
||||||
tracing::debug!(
|
|
||||||
"Truncating ratios at index {} - cumulative sum {} + {} would reach/exceed 1.0",
|
|
||||||
i,
|
|
||||||
cumulative_sum,
|
|
||||||
clamped_val
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arr
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to serialize [Option<f32>; MAX_RATIOS] as a compact array (without trailing nulls)
|
/// Helper to serialize [Option<f32>; MAX_RATIOS] as a compact array (without trailing nulls)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub use komorebi_layouts::OperationDirection;
|
|||||||
pub use komorebi_layouts::Rect;
|
pub use komorebi_layouts::Rect;
|
||||||
pub use komorebi_layouts::ScrollingLayoutOptions;
|
pub use komorebi_layouts::ScrollingLayoutOptions;
|
||||||
pub use komorebi_layouts::Sizing;
|
pub use komorebi_layouts::Sizing;
|
||||||
|
pub use komorebi_layouts::validate_ratios;
|
||||||
|
|
||||||
// Local modules and exports
|
// Local modules and exports
|
||||||
pub use animation::AnimationStyle;
|
pub use animation::AnimationStyle;
|
||||||
@@ -118,6 +119,7 @@ pub enum SocketMessage {
|
|||||||
AdjustWorkspacePadding(Sizing, i32),
|
AdjustWorkspacePadding(Sizing, i32),
|
||||||
ChangeLayout(DefaultLayout),
|
ChangeLayout(DefaultLayout),
|
||||||
CycleLayout(CycleDirection),
|
CycleLayout(CycleDirection),
|
||||||
|
LayoutRatios(Option<Vec<f32>>, Option<Vec<f32>>),
|
||||||
ScrollingLayoutColumns(NonZeroUsize),
|
ScrollingLayoutColumns(NonZeroUsize),
|
||||||
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
|
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
|
||||||
FlipLayout(Axis),
|
FlipLayout(Axis),
|
||||||
|
|||||||
@@ -957,6 +957,29 @@ impl WindowManager {
|
|||||||
}
|
}
|
||||||
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
|
SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
|
||||||
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
|
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
|
||||||
|
SocketMessage::LayoutRatios(ref columns, ref rows) => {
|
||||||
|
use crate::core::validate_ratios;
|
||||||
|
|
||||||
|
let focused_workspace = self.focused_workspace_mut()?;
|
||||||
|
|
||||||
|
let mut options = focused_workspace.layout_options.unwrap_or(LayoutOptions {
|
||||||
|
scrolling: None,
|
||||||
|
grid: None,
|
||||||
|
column_ratios: None,
|
||||||
|
row_ratios: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(cols) = columns {
|
||||||
|
options.column_ratios = Some(validate_ratios(cols));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rws) = rows {
|
||||||
|
options.row_ratios = Some(validate_ratios(rws));
|
||||||
|
}
|
||||||
|
|
||||||
|
focused_workspace.layout_options = Some(options);
|
||||||
|
self.update_focused_workspace(false, false)?;
|
||||||
|
}
|
||||||
SocketMessage::ChangeLayoutCustom(ref path) => {
|
SocketMessage::ChangeLayoutCustom(ref path) => {
|
||||||
self.change_workspace_custom_layout(path)?;
|
self.change_workspace_custom_layout(path)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1001,6 +1001,16 @@ struct ScrollingLayoutColumns {
|
|||||||
count: NonZeroUsize,
|
count: NonZeroUsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct LayoutRatios {
|
||||||
|
/// Column width ratios (space-separated values between 0.1 and 0.9)
|
||||||
|
#[clap(short, long, num_args = 1..)]
|
||||||
|
columns: Option<Vec<f32>>,
|
||||||
|
/// Row height ratios (space-separated values between 0.1 and 0.9)
|
||||||
|
#[clap(short, long, num_args = 1..)]
|
||||||
|
rows: Option<Vec<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct License {
|
struct License {
|
||||||
/// Email address associated with an Individual Commercial Use License
|
/// Email address associated with an Individual Commercial Use License
|
||||||
@@ -1267,6 +1277,8 @@ enum SubCommand {
|
|||||||
/// Set the number of visible columns for the Scrolling layout on the focused workspace
|
/// Set the number of visible columns for the Scrolling layout on the focused workspace
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
ScrollingLayoutColumns(ScrollingLayoutColumns),
|
ScrollingLayoutColumns(ScrollingLayoutColumns),
|
||||||
|
/// Set the layout column and row ratios for the focused workspace
|
||||||
|
LayoutRatios(LayoutRatios),
|
||||||
/// Load a custom layout from file for the focused workspace
|
/// Load a custom layout from file for the focused workspace
|
||||||
#[clap(hide = true)]
|
#[clap(hide = true)]
|
||||||
#[clap(arg_required_else_help = true)]
|
#[clap(arg_required_else_help = true)]
|
||||||
@@ -2934,6 +2946,15 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
|||||||
SubCommand::ScrollingLayoutColumns(args) => {
|
SubCommand::ScrollingLayoutColumns(args) => {
|
||||||
send_message(&SocketMessage::ScrollingLayoutColumns(args.count))?;
|
send_message(&SocketMessage::ScrollingLayoutColumns(args.count))?;
|
||||||
}
|
}
|
||||||
|
SubCommand::LayoutRatios(args) => {
|
||||||
|
if args.columns.is_none() && args.rows.is_none() {
|
||||||
|
println!(
|
||||||
|
"No ratios provided, nothing to change. Use --columns or --rows to specify ratios."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
send_message(&SocketMessage::LayoutRatios(args.columns, args.rows))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
SubCommand::LoadCustomLayout(args) => {
|
SubCommand::LoadCustomLayout(args) => {
|
||||||
send_message(&SocketMessage::ChangeLayoutCustom(args.path))?;
|
send_message(&SocketMessage::ChangeLayoutCustom(args.path))?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5417,6 +5417,46 @@
|
|||||||
"content"
|
"content"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2,
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "float"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "float"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "LayoutRatios"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
Reference in New Issue
Block a user