diff --git a/komorebi-bar/src/widgets/komorebi_layout.rs b/komorebi-bar/src/widgets/komorebi_layout.rs index b8361097..906767ba 100644 --- a/komorebi-bar/src/widgets/komorebi_layout.rs +++ b/komorebi-bar/src/widgets/komorebi_layout.rs @@ -188,6 +188,12 @@ impl KomorebiLayout { painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke); painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke); } + // TODO: @CtByte can you think of a nice icon to draw here? + komorebi_client::DefaultLayout::Scrolling => { + painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke); + painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke); + painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke); + } }, KomorebiLayout::Monocle => {} KomorebiLayout::Floating => { diff --git a/komorebi/src/border_manager/border.rs b/komorebi/src/border_manager/border.rs index ef1d4bce..f45fb417 100644 --- a/komorebi/src/border_manager/border.rs +++ b/komorebi/src/border_manager/border.rs @@ -392,7 +392,7 @@ impl Border { tracing::error!("failed to update border position {error}"); } - if !rect.is_same_size_as(&old_rect) { + if !rect.is_same_size_as(&old_rect) || !rect.has_same_position_as(&old_rect) { if let Some(render_target) = (*border_pointer).render_target.as_ref() { let border_width = (*border_pointer).width; let border_offset = (*border_pointer).offset; diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 39f8e4dc..20db7dd1 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -356,7 +356,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result }; if !should_process_notification { - tracing::trace!("monitor state matches latest snapshot, skipping notification"); + tracing::debug!("monitor state matches latest snapshot, skipping notification"); continue 'receiver; } diff --git a/komorebi/src/core/arrangement.rs b/komorebi/src/core/arrangement.rs index 7e750e1f..360336ea 100644 --- a/komorebi/src/core/arrangement.rs +++ b/komorebi/src/core/arrangement.rs @@ -12,8 +12,10 @@ use super::custom_layout::ColumnSplitWithCapacity; use super::CustomLayout; use super::DefaultLayout; use super::Rect; +use crate::default_layout::LayoutOptions; pub trait Arrangement { + #[allow(clippy::too_many_arguments)] fn calculate( &self, area: &Rect, @@ -21,6 +23,9 @@ pub trait Arrangement { container_padding: Option, layout_flip: Option, resize_dimensions: &[Option], + focused_idx: usize, + layout_options: Option, + latest_layout: &[Rect], ) -> Vec; } @@ -33,9 +38,110 @@ impl Arrangement for DefaultLayout { container_padding: Option, layout_flip: Option, resize_dimensions: &[Option], + focused_idx: usize, + layout_options: Option, + latest_layout: &[Rect], ) -> Vec { let len = usize::from(len); let mut dimensions = match self { + Self::Scrolling => { + let column_count = layout_options + .and_then(|o| o.scrolling.map(|s| s.columns)) + .unwrap_or(3); + + let column_width = area.right / column_count as i32; + let mut layouts = Vec::with_capacity(len); + + match len { + // treat < 3 windows the same as the columns layout + len if len < 3 => { + layouts = columns(area, len); + + let adjustment = calculate_columns_adjustment(resize_dimensions); + layouts.iter_mut().zip(adjustment.iter()).for_each( + |(layout, adjustment)| { + layout.top += adjustment.top; + layout.bottom += adjustment.bottom; + layout.left += adjustment.left; + layout.right += adjustment.right; + }, + ); + + if matches!( + layout_flip, + Some(Axis::Horizontal | Axis::HorizontalAndVertical) + ) { + if let 2.. = len { + columns_reverse(&mut layouts); + } + } + } + // treat >= column_count as scrolling + len => { + let visible_columns = area.right / column_width; + let first_visible: isize = if focused_idx == 0 { + // if focused idx is 0, we are at the beginning of the scrolling strip + 0 + } else { + let previous_first_visible = if latest_layout.is_empty() { + 0 + } else { + // previous first_visible based on the left position of the first visible window + let left_edge = area.left; + latest_layout + .iter() + .position(|rect| rect.left >= left_edge) + .unwrap_or(0) as isize + }; + + let focused_idx = focused_idx as isize; + + if focused_idx < previous_first_visible { + // focused window is off the left edge, we need to scroll left + focused_idx + } else if focused_idx + >= previous_first_visible + visible_columns as isize + { + // focused window is off the right edge, we need to scroll right + // and make sure it's the last visible window + (focused_idx + 1 - visible_columns as isize).max(0) + } else { + // focused window is already visible, we don't need to scroll + previous_first_visible + } + .min( + (len as isize) + .saturating_sub(visible_columns as isize) + .max(0), + ) + }; + + for i in 0..len { + let position = (i as isize) - first_visible; + let left = area.left + (position as i32 * column_width); + + layouts.push(Rect { + left, + top: area.top, + right: column_width, + bottom: area.bottom, + }); + } + + let adjustment = calculate_scrolling_adjustment(resize_dimensions); + layouts.iter_mut().zip(adjustment.iter()).for_each( + |(layout, adjustment)| { + layout.top += adjustment.top; + layout.bottom += adjustment.bottom; + layout.left += adjustment.left; + layout.right += adjustment.right; + }, + ); + } + } + + layouts + } Self::BSP => recursive_fibonacci( 0, len, @@ -487,6 +593,9 @@ impl Arrangement for CustomLayout { container_padding: Option, _layout_flip: Option, _resize_dimensions: &[Option], + _focused_idx: usize, + _layout_options: Option, + _latest_layout: &[Rect], ) -> Vec { let mut dimensions = vec![]; let container_count = len.get(); @@ -541,7 +650,7 @@ impl Arrangement for CustomLayout { }; match column { - Column::Primary(Option::Some(_)) => { + Column::Primary(Some(_)) => { let main_column_area = if idx == 0 { Self::main_column_area(area, primary_right, None) } else { @@ -1115,6 +1224,37 @@ fn calculate_ultrawide_adjustment(resize_dimensions: &[Option]) -> Vec]) -> Vec { + let len = resize_dimensions.len(); + let mut result = vec![Rect::default(); len]; + + if len <= 1 { + return result; + } + + for (i, rect) in resize_dimensions.iter().enumerate() { + if let Some(rect) = rect { + let is_leftmost = i == 0; + let is_rightmost = i == len - 1; + + resize_left(&mut result[i], rect.left); + resize_right(&mut result[i], rect.right); + resize_top(&mut result[i], rect.top); + resize_bottom(&mut result[i], rect.bottom); + + if !is_leftmost && rect.left != 0 { + resize_right(&mut result[i - 1], rect.left); + } + + if !is_rightmost && rect.right != 0 { + resize_left(&mut result[i + 1], rect.right); + } + } + } + + result +} + fn resize_left(rect: &mut Rect, resize: i32) { rect.left += resize / 2; rect.right += -resize / 2; diff --git a/komorebi/src/core/default_layout.rs b/komorebi/src/core/default_layout.rs index edbf9374..6eea2fbf 100644 --- a/komorebi/src/core/default_layout.rs +++ b/komorebi/src/core/default_layout.rs @@ -21,9 +21,24 @@ pub enum DefaultLayout { UltrawideVerticalStack, Grid, RightMainVerticalStack, + Scrolling, // NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle` } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct LayoutOptions { + /// Options related to the Scrolling layout + pub scrolling: Option, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct ScrollingLayoutOptions { + /// Desired number of visible columns (default: 3) + pub columns: usize, +} + impl DefaultLayout { pub fn leftmost_index(&self, len: usize) -> usize { match self { @@ -31,6 +46,7 @@ impl DefaultLayout { n if n > 1 => 1, _ => 0, }, + Self::Scrolling => 0, DefaultLayout::BSP | DefaultLayout::Columns | DefaultLayout::Rows @@ -53,6 +69,7 @@ impl DefaultLayout { _ => len.saturating_sub(1), }, DefaultLayout::RightMainVerticalStack => 0, + DefaultLayout::Scrolling => len.saturating_sub(1), } } @@ -75,6 +92,7 @@ impl DefaultLayout { | Self::RightMainVerticalStack | Self::HorizontalStack | Self::UltrawideVerticalStack + | Self::Scrolling ) { return None; }; @@ -169,13 +187,15 @@ impl DefaultLayout { Self::HorizontalStack => Self::UltrawideVerticalStack, Self::UltrawideVerticalStack => Self::Grid, Self::Grid => Self::RightMainVerticalStack, - Self::RightMainVerticalStack => Self::BSP, + Self::RightMainVerticalStack => Self::Scrolling, + Self::Scrolling => Self::BSP, } } #[must_use] pub const fn cycle_previous(self) -> Self { match self { + Self::Scrolling => Self::RightMainVerticalStack, Self::RightMainVerticalStack => Self::Grid, Self::Grid => Self::UltrawideVerticalStack, Self::UltrawideVerticalStack => Self::HorizontalStack, diff --git a/komorebi/src/core/direction.rs b/komorebi/src/core/direction.rs index 37f95bcc..fc723c55 100644 --- a/komorebi/src/core/direction.rs +++ b/komorebi/src/core/direction.rs @@ -102,6 +102,7 @@ impl Direction for DefaultLayout { Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1, Self::UltrawideVerticalStack => idx > 2, Self::Grid => !is_grid_edge(op_direction, idx, count), + Self::Scrolling => false, }, OperationDirection::Down => match self { Self::BSP => idx != count - 1 && idx % 2 != 0, @@ -111,6 +112,7 @@ impl Direction for DefaultLayout { Self::HorizontalStack => idx == 0, Self::UltrawideVerticalStack => idx > 1 && idx != count - 1, Self::Grid => !is_grid_edge(op_direction, idx, count), + Self::Scrolling => false, }, OperationDirection::Left => match self { Self::BSP => idx != 0, @@ -120,6 +122,7 @@ impl Direction for DefaultLayout { Self::HorizontalStack => idx != 0 && idx != 1, Self::UltrawideVerticalStack => idx != 1, Self::Grid => !is_grid_edge(op_direction, idx, count), + Self::Scrolling => idx != 0, }, OperationDirection::Right => match self { Self::BSP => idx % 2 == 0 && idx != count - 1, @@ -133,6 +136,7 @@ impl Direction for DefaultLayout { _ => idx < 2, }, Self::Grid => !is_grid_edge(op_direction, idx, count), + Self::Scrolling => idx != count - 1, }, } } @@ -158,6 +162,7 @@ impl Direction for DefaultLayout { | Self::RightMainVerticalStack => idx - 1, Self::HorizontalStack => 0, Self::Grid => grid_neighbor(op_direction, idx, count), + Self::Scrolling => unreachable!(), } } @@ -176,6 +181,7 @@ impl Direction for DefaultLayout { Self::Columns => unreachable!(), Self::HorizontalStack => 1, Self::Grid => grid_neighbor(op_direction, idx, count), + Self::Scrolling => unreachable!(), } } @@ -203,6 +209,7 @@ impl Direction for DefaultLayout { _ => 0, }, Self::Grid => grid_neighbor(op_direction, idx, count), + Self::Scrolling => idx - 1, } } @@ -223,6 +230,7 @@ impl Direction for DefaultLayout { _ => unreachable!(), }, Self::Grid => grid_neighbor(op_direction, idx, count), + Self::Scrolling => idx + 1, } } } diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 30ce6673..394864b5 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -1,6 +1,7 @@ #![warn(clippy::all)] #![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)] +use std::num::NonZeroUsize; use std::path::PathBuf; use std::str::FromStr; @@ -108,6 +109,7 @@ pub enum SocketMessage { AdjustWorkspacePadding(Sizing, i32), ChangeLayout(DefaultLayout), CycleLayout(CycleDirection), + ScrollingLayoutColumns(NonZeroUsize), ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf), FlipLayout(Axis), ToggleWorkspaceWindowContainerBehaviour, diff --git a/komorebi/src/core/rect.rs b/komorebi/src/core/rect.rs index 285b7d00..04ad653d 100644 --- a/komorebi/src/core/rect.rs +++ b/komorebi/src/core/rect.rs @@ -41,6 +41,10 @@ impl Rect { pub fn is_same_size_as(&self, rhs: &Self) -> bool { self.right == rhs.right && self.bottom == rhs.bottom } + + pub fn has_same_position_as(&self, rhs: &Self) -> bool { + self.left == rhs.left && self.top == rhs.top + } } impl Rect { diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index d92cc97e..13d41ca7 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -49,6 +49,8 @@ use crate::core::StateQuery; use crate::core::WindowContainerBehaviour; use crate::core::WindowKind; use crate::current_virtual_desktop; +use crate::default_layout::LayoutOptions; +use crate::default_layout::ScrollingLayoutOptions; use crate::monitor::MonitorInformation; use crate::notify_subscribers; use crate::stackbar_manager; @@ -933,6 +935,27 @@ impl WindowManager { self.retile_all(true)? } SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, + SocketMessage::ScrollingLayoutColumns(count) => { + let focused_workspace = self.focused_workspace_mut()?; + + let options = match focused_workspace.layout_options() { + Some(mut opts) => { + if let Some(scrolling) = &mut opts.scrolling { + scrolling.columns = count.into(); + } + + opts + } + None => LayoutOptions { + scrolling: Some(ScrollingLayoutOptions { + columns: count.into(), + }), + }, + }; + + focused_workspace.set_layout_options(Some(options)); + self.update_focused_workspace(false, false)?; + } SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?, SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?, SocketMessage::ChangeLayoutCustom(ref path) => { @@ -1751,7 +1774,7 @@ Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue { for config_file_path in &mut *display_bar_configurations { let script = r#"Start-Process "komorebi-bar" '"--config" "CONFIGFILE"' -WindowStyle hidden"# - .replace("CONFIGFILE", &config_file_path.to_string_lossy()); + .replace("CONFIGFILE", &config_file_path.to_string_lossy()); match powershell_script::run(&script) { Ok(_) => { diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 91699632..fda05f20 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -25,6 +25,8 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent::WinEvent; use crate::workspace::WorkspaceLayer; +use crate::DefaultLayout; +use crate::Layout; use crate::Notification; use crate::NotificationEvent; use crate::State; @@ -301,7 +303,11 @@ impl WindowManager { // don't want to trigger the full workspace updates when there are no managed // containers - this makes floating windows on empty workspaces go into very // annoying focus change loops which prevents users from interacting with them - if !self.focused_workspace()?.containers().is_empty() { + if !matches!( + self.focused_workspace()?.layout(), + Layout::Default(DefaultLayout::Scrolling) + ) && !self.focused_workspace()?.containers().is_empty() + { self.update_focused_workspace(self.mouse_follows_focus, false)?; } @@ -328,6 +334,14 @@ impl WindowManager { } workspace.set_layer(WorkspaceLayer::Tiling); + + if matches!( + self.focused_workspace()?.layout(), + Layout::Default(DefaultLayout::Scrolling) + ) && !self.focused_workspace()?.containers().is_empty() + { + self.update_focused_workspace(self.mouse_follows_focus, false)?; + } } Some(idx) => { if let Some(_window) = workspace.floating_windows().get(idx) { diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 58313387..737103c1 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -35,6 +35,7 @@ use crate::core::StackbarMode; use crate::core::WindowContainerBehaviour; use crate::core::WindowManagementBehaviour; use crate::current_virtual_desktop; +use crate::default_layout::LayoutOptions; use crate::monitor; use crate::monitor::Monitor; use crate::monitor_reconciliator; @@ -191,6 +192,9 @@ pub struct WorkspaceConfig { /// Layout (default: BSP) #[serde(skip_serializing_if = "Option::is_none")] pub layout: Option, + /// Layout-specific options (default: None) + #[serde(skip_serializing_if = "Option::is_none")] + pub layout_options: Option, /// END OF LIFE FEATURE: Custom Layout (default: None) #[serde(skip_serializing_if = "Option::is_none")] #[serde_as(as = "Option")] @@ -286,6 +290,7 @@ impl From<&Workspace> for WorkspaceConfig { Layout::Custom(_) => None, }) .flatten(), + layout_options: value.layout_options(), custom_layout: value .workspace_config() .as_ref() @@ -1331,7 +1336,7 @@ impl StaticConfig { } pub fn postload(path: &PathBuf, wm: &Arc>) -> Result<()> { - let value = Self::read(path)?; + let mut value = Self::read(path)?; let mut wm = wm.lock(); let configs_with_preference: Vec<_> = @@ -1342,6 +1347,8 @@ impl StaticConfig { workspace_matching_rules.clear(); drop(workspace_matching_rules); + let monitor_count = wm.monitors().len(); + let offset = wm.work_area_offset; for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() { let preferred_config_idx = { @@ -1371,8 +1378,8 @@ impl StaticConfig { }); if let Some(monitor_config) = value .monitors - .as_ref() - .and_then(|ms| idx.and_then(|i| ms.get(i))) + .as_mut() + .and_then(|ms| idx.and_then(|i| ms.get_mut(i))) { if let Some(used_config_idx) = idx { configs_used.push(used_config_idx); @@ -1395,7 +1402,14 @@ impl StaticConfig { monitor.update_workspaces_globals(offset); for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() { - if let Some(workspace_config) = monitor_config.workspaces.get(j) { + if let Some(workspace_config) = monitor_config.workspaces.get_mut(j) { + if monitor_count > 1 + && matches!(workspace_config.layout, Some(DefaultLayout::Scrolling)) + { + tracing::warn!("scrolling layout is only supported for a single monitor; falling back to columns layout"); + workspace_config.layout = Some(DefaultLayout::Columns); + } + ws.load_static_config(workspace_config)?; } } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 6df2c179..4b7d71dc 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -329,6 +329,7 @@ impl From<&WindowManager> for State { maximized_window_restore_idx: workspace.maximized_window_restore_idx, floating_windows: workspace.floating_windows.clone(), layout: workspace.layout.clone(), + layout_options: workspace.layout_options, layout_rules: workspace.layout_rules.clone(), layout_flip: workspace.layout_flip, workspace_padding: workspace.workspace_padding, @@ -1579,6 +1580,9 @@ impl WindowManager { workspace.container_padding(), workspace.layout_flip(), &[], + workspace.focused_container_idx(), + workspace.layout_options(), + workspace.latest_layout(), ); let mut direction = direction; @@ -3352,8 +3356,16 @@ impl WindowManager { pub fn change_workspace_layout_default(&mut self, layout: DefaultLayout) -> Result<()> { tracing::info!("changing layout"); + let monitor_count = self.monitors().len(); let workspace = self.focused_workspace_mut()?; + if monitor_count > 1 && matches!(layout, DefaultLayout::Scrolling) { + tracing::warn!( + "scrolling layout is only supported for a single monitor; not changing layout" + ); + return Ok(()); + } + match workspace.layout() { Layout::Default(_) => {} Layout::Custom(layout) => { diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 10f980df..944000d8 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -16,6 +16,7 @@ use crate::core::DefaultLayout; use crate::core::Layout; use crate::core::OperationDirection; use crate::core::Rect; +use crate::default_layout::LayoutOptions; use crate::locked_deque::LockedDeque; use crate::ring::Ring; use crate::should_act; @@ -70,6 +71,8 @@ pub struct Workspace { pub floating_windows: Ring, #[getset(get = "pub", get_mut = "pub", set = "pub")] pub layout: Layout, + #[getset(get_copy = "pub", set = "pub")] + pub layout_options: Option, #[getset(get = "pub", get_mut = "pub", set = "pub")] pub layout_rules: Vec<(usize, Layout)>, #[getset(get_copy = "pub", set = "pub")] @@ -139,6 +142,7 @@ impl Default for Workspace { monocle_container_restore_idx: None, floating_windows: Ring::default(), layout: Layout::Default(DefaultLayout::BSP), + layout_options: None, layout_rules: vec![], layout_flip: None, workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)), @@ -267,6 +271,7 @@ impl Workspace { self.set_layout_flip(config.layout_flip); self.set_floating_layer_behaviour(config.floating_layer_behaviour); self.set_wallpaper(config.wallpaper.clone()); + self.set_layout_options(config.layout_options); self.set_workspace_config(Some(config.clone())); @@ -583,6 +588,9 @@ impl Workspace { Some(container_padding), self.layout_flip(), self.resize_dimensions(), + self.focused_container_idx(), + self.layout_options(), + self.latest_layout(), ); let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst); @@ -1194,6 +1202,9 @@ impl Workspace { Layout::Default(DefaultLayout::UltrawideVerticalStack) => { self.enforce_resize_for_ultrawide(); } + Layout::Default(DefaultLayout::Scrolling) => { + self.enforce_resize_for_scrolling(); + } _ => self.enforce_no_resize(), } } @@ -1421,6 +1432,28 @@ impl Workspace { } } + fn enforce_resize_for_scrolling(&mut self) { + let resize_dimensions = self.resize_dimensions_mut(); + match resize_dimensions.len() { + 0 | 1 => self.enforce_no_resize(), + _ => { + let len = resize_dimensions.len(); + + for (i, rect) in resize_dimensions.iter_mut().enumerate() { + if let Some(rect) = rect { + rect.top = 0; + rect.bottom = 0; + + if i == 0 { + rect.left = 0; + } else if i == len - 1 { + rect.right = 0; + } + } + } + } + } + } fn enforce_no_resize(&mut self) { for rect in self.resize_dimensions_mut().iter_mut().flatten() { rect.left = 0; diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index aef6545e..268ef29d 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -9,6 +9,7 @@ use std::fs::OpenOptions; use std::io::BufRead; use std::io::BufReader; use std::io::Write; +use std::num::NonZeroUsize; use std::path::PathBuf; use std::process::Command; use std::sync::atomic::AtomicBool; @@ -963,6 +964,12 @@ struct EagerFocus { exe: String, } +#[derive(Parser)] +struct ScrollingLayoutColumns { + /// Desired number of visible columns + count: NonZeroUsize, +} + #[derive(Parser)] #[clap(author, about, version = build::CLAP_LONG_VERSION)] struct Opts { @@ -1202,6 +1209,9 @@ enum SubCommand { /// Cycle between available layouts #[clap(arg_required_else_help = true)] CycleLayout(CycleLayout), + /// Set the number of visible columns for the Scrolling layout on the focused workspace + #[clap(arg_required_else_help = true)] + ScrollingLayoutColumns(ScrollingLayoutColumns), /// Load a custom layout from file for the focused workspace #[clap(hide = true)] #[clap(arg_required_else_help = true)] @@ -2625,6 +2635,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) { SubCommand::CycleLayout(arg) => { send_message(&SocketMessage::CycleLayout(arg.cycle_direction))?; } + SubCommand::ScrollingLayoutColumns(arg) => { + send_message(&SocketMessage::ScrollingLayoutColumns(arg.count))?; + } SubCommand::LoadCustomLayout(arg) => { send_message(&SocketMessage::ChangeLayoutCustom(arg.path))?; } diff --git a/schema.bar.json b/schema.bar.json index 92c32c88..8117fb36 100644 --- a/schema.bar.json +++ b/schema.bar.json @@ -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.37`", + "description": "The `komorebi.bar.json` configuration file reference for `v0.1.38`", "type": "object", "required": [ "left_widgets", @@ -616,7 +616,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, { @@ -2259,7 +2260,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, { @@ -4294,7 +4296,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -4327,6 +4330,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -5210,7 +5233,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -5248,7 +5272,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -5361,7 +5386,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -5404,7 +5430,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -10365,7 +10392,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -10398,6 +10426,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -11281,7 +11329,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -11319,7 +11368,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -11432,7 +11482,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -11475,7 +11526,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -16436,7 +16488,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -16469,6 +16522,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -17352,7 +17425,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -17390,7 +17464,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -17503,7 +17578,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -17546,7 +17622,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -22507,7 +22584,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -22540,6 +22618,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -23423,7 +23521,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -23461,7 +23560,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -23574,7 +23674,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -23617,7 +23718,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -28578,7 +28680,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -28611,6 +28714,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -29494,7 +29617,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -29532,7 +29656,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -29645,7 +29770,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -29688,7 +29814,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -34649,7 +34776,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -34682,6 +34810,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -35565,7 +35713,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -35603,7 +35752,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -35716,7 +35866,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -35759,7 +35910,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -40720,7 +40872,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -40753,6 +40906,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -41636,7 +41809,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -41674,7 +41848,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -41787,7 +41962,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -41830,7 +42006,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -46791,7 +46968,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -46824,6 +47002,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -47707,7 +47905,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -47745,7 +47944,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -47858,7 +48058,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -47901,7 +48102,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -52862,7 +53064,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "type": { @@ -52895,6 +53098,26 @@ } } }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "integer", + "format": "uint", + "minimum": 1.0 + }, + "type": { + "type": "string", + "enum": [ + "ScrollingLayoutColumns" + ] + } + } + }, { "type": "object", "required": [ @@ -53778,7 +54001,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -53816,7 +54040,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -53929,7 +54154,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -53972,7 +54198,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } ], @@ -58490,7 +58717,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, { diff --git a/schema.json b/schema.json index a85a6e07..83c44896 100644 --- a/schema.json +++ b/schema.json @@ -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.37`", + "description": "The `komorebi.json` static configuration file reference for `v0.1.38`", "type": "object", "properties": { "animation": { @@ -1790,7 +1790,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] }, "layout_flip": { @@ -1802,6 +1803,27 @@ "HorizontalAndVertical" ] }, + "layout_options": { + "description": "Layout-specific options (default: None)", + "type": "object", + "properties": { + "scrolling": { + "description": "Options related to the Scrolling layout", + "type": "object", + "required": [ + "columns" + ], + "properties": { + "columns": { + "description": "Desired number of visible columns (default: 3)", + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + } + } + } + }, "layout_rules": { "description": "Layout rules in the format of threshold => layout (default: None)", "type": "object", @@ -1815,7 +1837,8 @@ "HorizontalStack", "UltrawideVerticalStack", "Grid", - "RightMainVerticalStack" + "RightMainVerticalStack", + "Scrolling" ] } },