feat(wm): add scrolling layout

This commit adds a new DefaultLayout::Scrolling variant, along with a
new LayoutOptions configuration which will initially be used to allow
the user to declaratively specify the number of visible columns for the
Scrolling layout, and a new komorebic "scrolling-layout-columns" command
to allow the user to modify this value for the focused workspace at
runtime.

The Scrolling layout is inspired by the Niri scrolling window manager,
presenting a workspace as an infinite scrollable horizontal strip with a
viewport which includes the focused window + N other windows in columns.
There is no support for splitting columns into multiple rows.

This layout can currently only be applied to single-monitor setups as
the scrolling would result in layout calculations which push the windows
in the columns moving out of the viewport onto adjacent monitors.

This implementation in the current state is enough to be useable for me
personally, but if others want to iterate on this, make it handle
hiding/restoring windows correctly when scrolling the viewport so that
adjacent monitors don't get impacted etc., patches are always welcome.

resolve #1434
This commit is contained in:
LGUG2Z
2025-05-12 17:13:33 -07:00
parent eec6312a51
commit b4e61b079c
16 changed files with 602 additions and 62 deletions

View File

@@ -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(r, 0.0), c + vec2(r, 0.0)], stroke);
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], 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::Monocle => {}
KomorebiLayout::Floating => { KomorebiLayout::Floating => {

View File

@@ -392,7 +392,7 @@ impl Border {
tracing::error!("failed to update border position {error}"); 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() { if let Some(render_target) = (*border_pointer).render_target.as_ref() {
let border_width = (*border_pointer).width; let border_width = (*border_pointer).width;
let border_offset = (*border_pointer).offset; let border_offset = (*border_pointer).offset;

View File

@@ -356,7 +356,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}; };
if !should_process_notification { if !should_process_notification {
tracing::trace!("monitor state matches latest snapshot, skipping notification"); tracing::debug!("monitor state matches latest snapshot, skipping notification");
continue 'receiver; continue 'receiver;
} }

View File

@@ -12,8 +12,10 @@ use super::custom_layout::ColumnSplitWithCapacity;
use super::CustomLayout; use super::CustomLayout;
use super::DefaultLayout; use super::DefaultLayout;
use super::Rect; use super::Rect;
use crate::default_layout::LayoutOptions;
pub trait Arrangement { pub trait Arrangement {
#[allow(clippy::too_many_arguments)]
fn calculate( fn calculate(
&self, &self,
area: &Rect, area: &Rect,
@@ -21,6 +23,9 @@ pub trait Arrangement {
container_padding: Option<i32>, container_padding: Option<i32>,
layout_flip: Option<Axis>, layout_flip: Option<Axis>,
resize_dimensions: &[Option<Rect>], resize_dimensions: &[Option<Rect>],
focused_idx: usize,
layout_options: Option<LayoutOptions>,
latest_layout: &[Rect],
) -> Vec<Rect>; ) -> Vec<Rect>;
} }
@@ -33,9 +38,110 @@ impl Arrangement for DefaultLayout {
container_padding: Option<i32>, container_padding: Option<i32>,
layout_flip: Option<Axis>, layout_flip: Option<Axis>,
resize_dimensions: &[Option<Rect>], resize_dimensions: &[Option<Rect>],
focused_idx: usize,
layout_options: Option<LayoutOptions>,
latest_layout: &[Rect],
) -> Vec<Rect> { ) -> Vec<Rect> {
let len = usize::from(len); let len = usize::from(len);
let mut dimensions = match self { 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( Self::BSP => recursive_fibonacci(
0, 0,
len, len,
@@ -487,6 +593,9 @@ impl Arrangement for CustomLayout {
container_padding: Option<i32>, container_padding: Option<i32>,
_layout_flip: Option<Axis>, _layout_flip: Option<Axis>,
_resize_dimensions: &[Option<Rect>], _resize_dimensions: &[Option<Rect>],
_focused_idx: usize,
_layout_options: Option<LayoutOptions>,
_latest_layout: &[Rect],
) -> Vec<Rect> { ) -> Vec<Rect> {
let mut dimensions = vec![]; let mut dimensions = vec![];
let container_count = len.get(); let container_count = len.get();
@@ -541,7 +650,7 @@ impl Arrangement for CustomLayout {
}; };
match column { match column {
Column::Primary(Option::Some(_)) => { Column::Primary(Some(_)) => {
let main_column_area = if idx == 0 { let main_column_area = if idx == 0 {
Self::main_column_area(area, primary_right, None) Self::main_column_area(area, primary_right, None)
} else { } else {
@@ -1115,6 +1224,37 @@ fn calculate_ultrawide_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rec
result result
} }
fn calculate_scrolling_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {
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) { fn resize_left(rect: &mut Rect, resize: i32) {
rect.left += resize / 2; rect.left += resize / 2;
rect.right += -resize / 2; rect.right += -resize / 2;

View File

@@ -21,9 +21,24 @@ pub enum DefaultLayout {
UltrawideVerticalStack, UltrawideVerticalStack,
Grid, Grid,
RightMainVerticalStack, RightMainVerticalStack,
Scrolling,
// NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle` // 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<ScrollingLayoutOptions>,
}
#[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 { impl DefaultLayout {
pub fn leftmost_index(&self, len: usize) -> usize { pub fn leftmost_index(&self, len: usize) -> usize {
match self { match self {
@@ -31,6 +46,7 @@ impl DefaultLayout {
n if n > 1 => 1, n if n > 1 => 1,
_ => 0, _ => 0,
}, },
Self::Scrolling => 0,
DefaultLayout::BSP DefaultLayout::BSP
| DefaultLayout::Columns | DefaultLayout::Columns
| DefaultLayout::Rows | DefaultLayout::Rows
@@ -53,6 +69,7 @@ impl DefaultLayout {
_ => len.saturating_sub(1), _ => len.saturating_sub(1),
}, },
DefaultLayout::RightMainVerticalStack => 0, DefaultLayout::RightMainVerticalStack => 0,
DefaultLayout::Scrolling => len.saturating_sub(1),
} }
} }
@@ -75,6 +92,7 @@ impl DefaultLayout {
| Self::RightMainVerticalStack | Self::RightMainVerticalStack
| Self::HorizontalStack | Self::HorizontalStack
| Self::UltrawideVerticalStack | Self::UltrawideVerticalStack
| Self::Scrolling
) { ) {
return None; return None;
}; };
@@ -169,13 +187,15 @@ impl DefaultLayout {
Self::HorizontalStack => Self::UltrawideVerticalStack, Self::HorizontalStack => Self::UltrawideVerticalStack,
Self::UltrawideVerticalStack => Self::Grid, Self::UltrawideVerticalStack => Self::Grid,
Self::Grid => Self::RightMainVerticalStack, Self::Grid => Self::RightMainVerticalStack,
Self::RightMainVerticalStack => Self::BSP, Self::RightMainVerticalStack => Self::Scrolling,
Self::Scrolling => Self::BSP,
} }
} }
#[must_use] #[must_use]
pub const fn cycle_previous(self) -> Self { pub const fn cycle_previous(self) -> Self {
match self { match self {
Self::Scrolling => Self::RightMainVerticalStack,
Self::RightMainVerticalStack => Self::Grid, Self::RightMainVerticalStack => Self::Grid,
Self::Grid => Self::UltrawideVerticalStack, Self::Grid => Self::UltrawideVerticalStack,
Self::UltrawideVerticalStack => Self::HorizontalStack, Self::UltrawideVerticalStack => Self::HorizontalStack,

View File

@@ -102,6 +102,7 @@ impl Direction for DefaultLayout {
Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1, Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => idx > 2, Self::UltrawideVerticalStack => idx > 2,
Self::Grid => !is_grid_edge(op_direction, idx, count), Self::Grid => !is_grid_edge(op_direction, idx, count),
Self::Scrolling => false,
}, },
OperationDirection::Down => match self { OperationDirection::Down => match self {
Self::BSP => idx != count - 1 && idx % 2 != 0, Self::BSP => idx != count - 1 && idx % 2 != 0,
@@ -111,6 +112,7 @@ impl Direction for DefaultLayout {
Self::HorizontalStack => idx == 0, Self::HorizontalStack => idx == 0,
Self::UltrawideVerticalStack => idx > 1 && idx != count - 1, Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,
Self::Grid => !is_grid_edge(op_direction, idx, count), Self::Grid => !is_grid_edge(op_direction, idx, count),
Self::Scrolling => false,
}, },
OperationDirection::Left => match self { OperationDirection::Left => match self {
Self::BSP => idx != 0, Self::BSP => idx != 0,
@@ -120,6 +122,7 @@ impl Direction for DefaultLayout {
Self::HorizontalStack => idx != 0 && idx != 1, Self::HorizontalStack => idx != 0 && idx != 1,
Self::UltrawideVerticalStack => idx != 1, Self::UltrawideVerticalStack => idx != 1,
Self::Grid => !is_grid_edge(op_direction, idx, count), Self::Grid => !is_grid_edge(op_direction, idx, count),
Self::Scrolling => idx != 0,
}, },
OperationDirection::Right => match self { OperationDirection::Right => match self {
Self::BSP => idx % 2 == 0 && idx != count - 1, Self::BSP => idx % 2 == 0 && idx != count - 1,
@@ -133,6 +136,7 @@ impl Direction for DefaultLayout {
_ => idx < 2, _ => idx < 2,
}, },
Self::Grid => !is_grid_edge(op_direction, idx, count), 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::RightMainVerticalStack => idx - 1,
Self::HorizontalStack => 0, Self::HorizontalStack => 0,
Self::Grid => grid_neighbor(op_direction, idx, count), Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Scrolling => unreachable!(),
} }
} }
@@ -176,6 +181,7 @@ impl Direction for DefaultLayout {
Self::Columns => unreachable!(), Self::Columns => unreachable!(),
Self::HorizontalStack => 1, Self::HorizontalStack => 1,
Self::Grid => grid_neighbor(op_direction, idx, count), Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Scrolling => unreachable!(),
} }
} }
@@ -203,6 +209,7 @@ impl Direction for DefaultLayout {
_ => 0, _ => 0,
}, },
Self::Grid => grid_neighbor(op_direction, idx, count), Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Scrolling => idx - 1,
} }
} }
@@ -223,6 +230,7 @@ impl Direction for DefaultLayout {
_ => unreachable!(), _ => unreachable!(),
}, },
Self::Grid => grid_neighbor(op_direction, idx, count), Self::Grid => grid_neighbor(op_direction, idx, count),
Self::Scrolling => idx + 1,
} }
} }
} }

View File

@@ -1,6 +1,7 @@
#![warn(clippy::all)] #![warn(clippy::all)]
#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)] #![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]
use std::num::NonZeroUsize;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
@@ -108,6 +109,7 @@ pub enum SocketMessage {
AdjustWorkspacePadding(Sizing, i32), AdjustWorkspacePadding(Sizing, i32),
ChangeLayout(DefaultLayout), ChangeLayout(DefaultLayout),
CycleLayout(CycleDirection), CycleLayout(CycleDirection),
ScrollingLayoutColumns(NonZeroUsize),
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf), ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
FlipLayout(Axis), FlipLayout(Axis),
ToggleWorkspaceWindowContainerBehaviour, ToggleWorkspaceWindowContainerBehaviour,

View File

@@ -41,6 +41,10 @@ impl Rect {
pub fn is_same_size_as(&self, rhs: &Self) -> bool { pub fn is_same_size_as(&self, rhs: &Self) -> bool {
self.right == rhs.right && self.bottom == rhs.bottom 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 { impl Rect {

View File

@@ -49,6 +49,8 @@ use crate::core::StateQuery;
use crate::core::WindowContainerBehaviour; use crate::core::WindowContainerBehaviour;
use crate::core::WindowKind; use crate::core::WindowKind;
use crate::current_virtual_desktop; use crate::current_virtual_desktop;
use crate::default_layout::LayoutOptions;
use crate::default_layout::ScrollingLayoutOptions;
use crate::monitor::MonitorInformation; use crate::monitor::MonitorInformation;
use crate::notify_subscribers; use crate::notify_subscribers;
use crate::stackbar_manager; use crate::stackbar_manager;
@@ -933,6 +935,27 @@ impl WindowManager {
self.retile_all(true)? self.retile_all(true)?
} }
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, 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::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,
SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?, SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,
SocketMessage::ChangeLayoutCustom(ref path) => { SocketMessage::ChangeLayoutCustom(ref path) => {
@@ -1751,7 +1774,7 @@ Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue
{ {
for config_file_path in &mut *display_bar_configurations { for config_file_path in &mut *display_bar_configurations {
let script = r#"Start-Process "komorebi-bar" '"--config" "CONFIGFILE"' -WindowStyle hidden"# 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) { match powershell_script::run(&script) {
Ok(_) => { Ok(_) => {

View File

@@ -25,6 +25,8 @@ use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi; use crate::windows_api::WindowsApi;
use crate::winevent::WinEvent; use crate::winevent::WinEvent;
use crate::workspace::WorkspaceLayer; use crate::workspace::WorkspaceLayer;
use crate::DefaultLayout;
use crate::Layout;
use crate::Notification; use crate::Notification;
use crate::NotificationEvent; use crate::NotificationEvent;
use crate::State; use crate::State;
@@ -301,7 +303,11 @@ impl WindowManager {
// don't want to trigger the full workspace updates when there are no managed // 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 // containers - this makes floating windows on empty workspaces go into very
// annoying focus change loops which prevents users from interacting with them // 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)?; self.update_focused_workspace(self.mouse_follows_focus, false)?;
} }
@@ -328,6 +334,14 @@ impl WindowManager {
} }
workspace.set_layer(WorkspaceLayer::Tiling); 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) => { Some(idx) => {
if let Some(_window) = workspace.floating_windows().get(idx) { if let Some(_window) = workspace.floating_windows().get(idx) {

View File

@@ -35,6 +35,7 @@ use crate::core::StackbarMode;
use crate::core::WindowContainerBehaviour; use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour; use crate::core::WindowManagementBehaviour;
use crate::current_virtual_desktop; use crate::current_virtual_desktop;
use crate::default_layout::LayoutOptions;
use crate::monitor; use crate::monitor;
use crate::monitor::Monitor; use crate::monitor::Monitor;
use crate::monitor_reconciliator; use crate::monitor_reconciliator;
@@ -191,6 +192,9 @@ pub struct WorkspaceConfig {
/// Layout (default: BSP) /// Layout (default: BSP)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub layout: Option<DefaultLayout>, pub layout: Option<DefaultLayout>,
/// Layout-specific options (default: None)
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_options: Option<LayoutOptions>,
/// END OF LIFE FEATURE: Custom Layout (default: None) /// END OF LIFE FEATURE: Custom Layout (default: None)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<ResolvedPathBuf>")] #[serde_as(as = "Option<ResolvedPathBuf>")]
@@ -286,6 +290,7 @@ impl From<&Workspace> for WorkspaceConfig {
Layout::Custom(_) => None, Layout::Custom(_) => None,
}) })
.flatten(), .flatten(),
layout_options: value.layout_options(),
custom_layout: value custom_layout: value
.workspace_config() .workspace_config()
.as_ref() .as_ref()
@@ -1331,7 +1336,7 @@ impl StaticConfig {
} }
pub fn postload(path: &PathBuf, wm: &Arc<Mutex<WindowManager>>) -> Result<()> { pub fn postload(path: &PathBuf, wm: &Arc<Mutex<WindowManager>>) -> Result<()> {
let value = Self::read(path)?; let mut value = Self::read(path)?;
let mut wm = wm.lock(); let mut wm = wm.lock();
let configs_with_preference: Vec<_> = let configs_with_preference: Vec<_> =
@@ -1342,6 +1347,8 @@ impl StaticConfig {
workspace_matching_rules.clear(); workspace_matching_rules.clear();
drop(workspace_matching_rules); drop(workspace_matching_rules);
let monitor_count = wm.monitors().len();
let offset = wm.work_area_offset; let offset = wm.work_area_offset;
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() { for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
let preferred_config_idx = { let preferred_config_idx = {
@@ -1371,8 +1378,8 @@ impl StaticConfig {
}); });
if let Some(monitor_config) = value if let Some(monitor_config) = value
.monitors .monitors
.as_ref() .as_mut()
.and_then(|ms| idx.and_then(|i| ms.get(i))) .and_then(|ms| idx.and_then(|i| ms.get_mut(i)))
{ {
if let Some(used_config_idx) = idx { if let Some(used_config_idx) = idx {
configs_used.push(used_config_idx); configs_used.push(used_config_idx);
@@ -1395,7 +1402,14 @@ impl StaticConfig {
monitor.update_workspaces_globals(offset); monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() { 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)?; ws.load_static_config(workspace_config)?;
} }
} }

View File

@@ -329,6 +329,7 @@ impl From<&WindowManager> for State {
maximized_window_restore_idx: workspace.maximized_window_restore_idx, maximized_window_restore_idx: workspace.maximized_window_restore_idx,
floating_windows: workspace.floating_windows.clone(), floating_windows: workspace.floating_windows.clone(),
layout: workspace.layout.clone(), layout: workspace.layout.clone(),
layout_options: workspace.layout_options,
layout_rules: workspace.layout_rules.clone(), layout_rules: workspace.layout_rules.clone(),
layout_flip: workspace.layout_flip, layout_flip: workspace.layout_flip,
workspace_padding: workspace.workspace_padding, workspace_padding: workspace.workspace_padding,
@@ -1579,6 +1580,9 @@ impl WindowManager {
workspace.container_padding(), workspace.container_padding(),
workspace.layout_flip(), workspace.layout_flip(),
&[], &[],
workspace.focused_container_idx(),
workspace.layout_options(),
workspace.latest_layout(),
); );
let mut direction = direction; let mut direction = direction;
@@ -3352,8 +3356,16 @@ impl WindowManager {
pub fn change_workspace_layout_default(&mut self, layout: DefaultLayout) -> Result<()> { pub fn change_workspace_layout_default(&mut self, layout: DefaultLayout) -> Result<()> {
tracing::info!("changing layout"); tracing::info!("changing layout");
let monitor_count = self.monitors().len();
let workspace = self.focused_workspace_mut()?; 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() { match workspace.layout() {
Layout::Default(_) => {} Layout::Default(_) => {}
Layout::Custom(layout) => { Layout::Custom(layout) => {

View File

@@ -16,6 +16,7 @@ use crate::core::DefaultLayout;
use crate::core::Layout; use crate::core::Layout;
use crate::core::OperationDirection; use crate::core::OperationDirection;
use crate::core::Rect; use crate::core::Rect;
use crate::default_layout::LayoutOptions;
use crate::locked_deque::LockedDeque; use crate::locked_deque::LockedDeque;
use crate::ring::Ring; use crate::ring::Ring;
use crate::should_act; use crate::should_act;
@@ -70,6 +71,8 @@ pub struct Workspace {
pub floating_windows: Ring<Window>, pub floating_windows: Ring<Window>,
#[getset(get = "pub", get_mut = "pub", set = "pub")] #[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layout: Layout, pub layout: Layout,
#[getset(get_copy = "pub", set = "pub")]
pub layout_options: Option<LayoutOptions>,
#[getset(get = "pub", get_mut = "pub", set = "pub")] #[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layout_rules: Vec<(usize, Layout)>, pub layout_rules: Vec<(usize, Layout)>,
#[getset(get_copy = "pub", set = "pub")] #[getset(get_copy = "pub", set = "pub")]
@@ -139,6 +142,7 @@ impl Default for Workspace {
monocle_container_restore_idx: None, monocle_container_restore_idx: None,
floating_windows: Ring::default(), floating_windows: Ring::default(),
layout: Layout::Default(DefaultLayout::BSP), layout: Layout::Default(DefaultLayout::BSP),
layout_options: None,
layout_rules: vec![], layout_rules: vec![],
layout_flip: None, layout_flip: None,
workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)), 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_layout_flip(config.layout_flip);
self.set_floating_layer_behaviour(config.floating_layer_behaviour); self.set_floating_layer_behaviour(config.floating_layer_behaviour);
self.set_wallpaper(config.wallpaper.clone()); self.set_wallpaper(config.wallpaper.clone());
self.set_layout_options(config.layout_options);
self.set_workspace_config(Some(config.clone())); self.set_workspace_config(Some(config.clone()));
@@ -583,6 +588,9 @@ impl Workspace {
Some(container_padding), Some(container_padding),
self.layout_flip(), self.layout_flip(),
self.resize_dimensions(), self.resize_dimensions(),
self.focused_container_idx(),
self.layout_options(),
self.latest_layout(),
); );
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst); let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
@@ -1194,6 +1202,9 @@ impl Workspace {
Layout::Default(DefaultLayout::UltrawideVerticalStack) => { Layout::Default(DefaultLayout::UltrawideVerticalStack) => {
self.enforce_resize_for_ultrawide(); self.enforce_resize_for_ultrawide();
} }
Layout::Default(DefaultLayout::Scrolling) => {
self.enforce_resize_for_scrolling();
}
_ => self.enforce_no_resize(), _ => 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) { fn enforce_no_resize(&mut self) {
for rect in self.resize_dimensions_mut().iter_mut().flatten() { for rect in self.resize_dimensions_mut().iter_mut().flatten() {
rect.left = 0; rect.left = 0;

View File

@@ -9,6 +9,7 @@ use std::fs::OpenOptions;
use std::io::BufRead; use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::io::Write; use std::io::Write;
use std::num::NonZeroUsize;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@@ -963,6 +964,12 @@ struct EagerFocus {
exe: String, exe: String,
} }
#[derive(Parser)]
struct ScrollingLayoutColumns {
/// Desired number of visible columns
count: NonZeroUsize,
}
#[derive(Parser)] #[derive(Parser)]
#[clap(author, about, version = build::CLAP_LONG_VERSION)] #[clap(author, about, version = build::CLAP_LONG_VERSION)]
struct Opts { struct Opts {
@@ -1202,6 +1209,9 @@ enum SubCommand {
/// Cycle between available layouts /// Cycle between available layouts
#[clap(arg_required_else_help = true)] #[clap(arg_required_else_help = true)]
CycleLayout(CycleLayout), 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 /// 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)]
@@ -2625,6 +2635,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
SubCommand::CycleLayout(arg) => { SubCommand::CycleLayout(arg) => {
send_message(&SocketMessage::CycleLayout(arg.cycle_direction))?; send_message(&SocketMessage::CycleLayout(arg.cycle_direction))?;
} }
SubCommand::ScrollingLayoutColumns(arg) => {
send_message(&SocketMessage::ScrollingLayoutColumns(arg.count))?;
}
SubCommand::LoadCustomLayout(arg) => { SubCommand::LoadCustomLayout(arg) => {
send_message(&SocketMessage::ChangeLayoutCustom(arg.path))?; send_message(&SocketMessage::ChangeLayoutCustom(arg.path))?;
} }

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "KomobarConfig", "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", "type": "object",
"required": [ "required": [
"left_widgets", "left_widgets",
@@ -616,7 +616,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
{ {
@@ -2259,7 +2260,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
{ {
@@ -4294,7 +4296,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -5210,7 +5233,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -5248,7 +5272,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -5361,7 +5386,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -5404,7 +5430,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -10365,7 +10392,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -11281,7 +11329,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -11319,7 +11368,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -11432,7 +11482,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -11475,7 +11526,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -16436,7 +16488,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -17352,7 +17425,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -17390,7 +17464,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -17503,7 +17578,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -17546,7 +17622,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -22507,7 +22584,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -23423,7 +23521,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -23461,7 +23560,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -23574,7 +23674,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -23617,7 +23718,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -28578,7 +28680,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -29494,7 +29617,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -29532,7 +29656,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -29645,7 +29770,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -29688,7 +29814,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -34649,7 +34776,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -35565,7 +35713,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -35603,7 +35752,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -35716,7 +35866,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -35759,7 +35910,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -40720,7 +40872,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -41636,7 +41809,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -41674,7 +41848,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -41787,7 +41962,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -41830,7 +42006,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -46791,7 +46968,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -47707,7 +47905,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -47745,7 +47944,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -47858,7 +48058,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -47901,7 +48102,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -52862,7 +53064,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"type": { "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", "type": "object",
"required": [ "required": [
@@ -53778,7 +54001,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -53816,7 +54040,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -53929,7 +54154,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -53972,7 +54198,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
], ],
@@ -58490,7 +58717,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
{ {

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig", "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", "type": "object",
"properties": { "properties": {
"animation": { "animation": {
@@ -1790,7 +1790,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
}, },
"layout_flip": { "layout_flip": {
@@ -1802,6 +1803,27 @@
"HorizontalAndVertical" "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": { "layout_rules": {
"description": "Layout rules in the format of threshold => layout (default: None)", "description": "Layout rules in the format of threshold => layout (default: None)",
"type": "object", "type": "object",
@@ -1815,7 +1837,8 @@
"HorizontalStack", "HorizontalStack",
"UltrawideVerticalStack", "UltrawideVerticalStack",
"Grid", "Grid",
"RightMainVerticalStack" "RightMainVerticalStack",
"Scrolling"
] ]
} }
}, },