mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-13 12:23:38 +01:00
feat(komorebic): pan the Scrolling layout to the left or the right
komorebic pan <left|right> moves the view along the row of the Scrolling layout by a column without moving focus first, and lets it come to rest with a column against an edge of the screen. Focus goes to the nearest column on the screen if the focused one has gone off it. It does nothing in the other layouts. The way that the view goes is the way that focus goes for the commands that have directions: panning to the left brings what is to the left of the screen into view, which is to move the row to the right. This is a command that a wheel can be bound to, as it is with whkd: alt + wheelup : komorebic pan left.
This commit is contained in:
1 parent
dd961f50f6
commit
a63057c232
12 files changed
+261
No files matched your search
@@ -0,0 +1,16 @@
|
|||||||
|
```
|
||||||
|
Move the view of the Scrolling layout a column to the left or the right, which does nothing in any other layout
|
||||||
|
|
||||||
|
Usage: pan <DIRECTION>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<DIRECTION>
|
||||||
|
The way that the view goes
|
||||||
|
|
||||||
|
[possible values: left, right]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
|
||||||
|
```
|
||||||
@@ -132,6 +132,20 @@ and the screen scrolls back to where it was. If you have, focus goes to the wind
|
|||||||
on the right, or to the one before it if there is none. A window that is closed without having focus does not take focus
|
on the right, or to the one before it if there is none. A window that is closed without having focus does not take focus
|
||||||
with it.
|
with it.
|
||||||
|
|
||||||
|
## Panning
|
||||||
|
|
||||||
|
[`komorebic pan`](../cli/pan.md) moves the view along the row without moving focus first. `pan left` brings what is to the
|
||||||
|
left of the screen into view, and `pan right` what is to the right, by a column. The row
|
||||||
|
comes to rest with a column against an edge of the screen, and focus goes to the nearest column on the screen if the
|
||||||
|
focused one has gone off it.
|
||||||
|
|
||||||
|
```
|
||||||
|
alt + wheelup : komorebic pan left
|
||||||
|
alt + wheeldown : komorebic pan right
|
||||||
|
```
|
||||||
|
|
||||||
|
It does nothing in the other layouts.
|
||||||
|
|
||||||
## Resizing
|
## Resizing
|
||||||
|
|
||||||
[`komorebic resize-edge`](../cli/resize-edge.md) and [`komorebic resize-axis`](../cli/resize-axis.md) change the width of
|
[`komorebic resize-edge`](../cli/resize-edge.md) and [`komorebic resize-axis`](../cli/resize-axis.md) change the width of
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ pub use komorebi::core::MonocleFocusBehaviour;
|
|||||||
pub use komorebi::core::MoveBehaviour;
|
pub use komorebi::core::MoveBehaviour;
|
||||||
pub use komorebi::core::OperationBehaviour;
|
pub use komorebi::core::OperationBehaviour;
|
||||||
pub use komorebi::core::OperationDirection;
|
pub use komorebi::core::OperationDirection;
|
||||||
|
pub use komorebi::core::PanDirection;
|
||||||
pub use komorebi::core::PathExt;
|
pub use komorebi::core::PathExt;
|
||||||
pub use komorebi::core::Rect;
|
pub use komorebi::core::Rect;
|
||||||
pub use komorebi::core::Sizing;
|
pub use komorebi::core::Sizing;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub mod default_layout;
|
|||||||
pub mod direction;
|
pub mod direction;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod operation_direction;
|
pub mod operation_direction;
|
||||||
|
pub mod pan_direction;
|
||||||
pub mod rect;
|
pub mod rect;
|
||||||
pub mod scrolling;
|
pub mod scrolling;
|
||||||
pub mod sizing;
|
pub mod sizing;
|
||||||
@@ -27,5 +28,6 @@ pub use default_layout::*;
|
|||||||
pub use direction::*;
|
pub use direction::*;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
pub use operation_direction::*;
|
pub use operation_direction::*;
|
||||||
|
pub use pan_direction::*;
|
||||||
pub use rect::*;
|
pub use rect::*;
|
||||||
pub use sizing::*;
|
pub use sizing::*;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use clap::ValueEnum;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use strum::Display;
|
||||||
|
use strum::EnumString;
|
||||||
|
|
||||||
|
/// Which way the view goes when the strip of the Scrolling layout is panned: to the left is to
|
||||||
|
/// see what is to the left of the screen, which is to move the strip to the right
|
||||||
|
#[derive(
|
||||||
|
Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Display, EnumString, ValueEnum,
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
pub enum PanDirection {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanDirection {
|
||||||
|
/// How far the strip is moved on the screen to pan `distance` pixels this way, in pixels,
|
||||||
|
/// to the right if it is positive
|
||||||
|
#[must_use]
|
||||||
|
pub fn strip_offset(self, distance: u32) -> f64 {
|
||||||
|
match self {
|
||||||
|
Self::Left => f64::from(distance),
|
||||||
|
Self::Right => -f64::from(distance),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_strip_goes_the_other_way_to_the_view() {
|
||||||
|
assert!((PanDirection::Left.strip_offset(400) - 400.0).abs() < f64::EPSILON);
|
||||||
|
assert!((PanDirection::Right.strip_offset(400) + 400.0).abs() < f64::EPSILON);
|
||||||
|
assert!(PanDirection::Left.strip_offset(0).abs() < f64::EPSILON);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directions_have_the_names_that_the_others_have() {
|
||||||
|
assert_eq!(
|
||||||
|
<PanDirection as FromStr>::from_str("Left").unwrap(),
|
||||||
|
PanDirection::Left
|
||||||
|
);
|
||||||
|
assert_eq!(PanDirection::Right.to_string(), "Right");
|
||||||
|
assert!(<PanDirection as FromStr>::from_str("Up").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ pub use komorebi_layouts::MAX_RATIO;
|
|||||||
pub use komorebi_layouts::MAX_RATIOS;
|
pub use komorebi_layouts::MAX_RATIOS;
|
||||||
pub use komorebi_layouts::MIN_RATIO;
|
pub use komorebi_layouts::MIN_RATIO;
|
||||||
pub use komorebi_layouts::OperationDirection;
|
pub use komorebi_layouts::OperationDirection;
|
||||||
|
pub use komorebi_layouts::PanDirection;
|
||||||
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;
|
||||||
@@ -132,6 +133,7 @@ pub enum SocketMessage {
|
|||||||
Consume(OperationDirection),
|
Consume(OperationDirection),
|
||||||
Expel,
|
Expel,
|
||||||
ToggleColumnStack,
|
ToggleColumnStack,
|
||||||
|
Pan(PanDirection),
|
||||||
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
|
ChangeLayoutCustom(#[serde_as(as = "ResolvedPathBuf")] PathBuf),
|
||||||
FlipLayout(Axis),
|
FlipLayout(Axis),
|
||||||
ToggleWorkspaceWindowContainerBehaviour,
|
ToggleWorkspaceWindowContainerBehaviour,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod live_resize;
|
|||||||
pub mod lockable_sequence;
|
pub mod lockable_sequence;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
pub mod monitor_reconciliator;
|
pub mod monitor_reconciliator;
|
||||||
|
pub mod pan;
|
||||||
pub mod process_command;
|
pub mod process_command;
|
||||||
pub mod process_event;
|
pub mod process_event;
|
||||||
pub mod process_movement;
|
pub mod process_movement;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//! Moving the strip of the Scrolling layout across the screen without changing which window has
|
||||||
|
//! focus, by a step, which is what this has been asked for so far.
|
||||||
|
|
||||||
|
use crate::core::PanDirection;
|
||||||
|
use crate::window_manager::WindowManager;
|
||||||
|
use color_eyre::eyre;
|
||||||
|
|
||||||
|
impl WindowManager {
|
||||||
|
/// Move the view of the focused workspace one step, a column, to the left or the right, and
|
||||||
|
/// let it come to rest with a column against an edge of the screen. Focus goes to the nearest column in view if the focused one is
|
||||||
|
/// not. Nothing happens in a layout that is not Scrolling.
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub fn pan(&mut self, direction: PanDirection) -> eyre::Result<()> {
|
||||||
|
let workspace = self.focused_workspace_mut()?;
|
||||||
|
|
||||||
|
if workspace.begin_scrolling_pan().is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let step = workspace
|
||||||
|
.scrolling_pan_step()
|
||||||
|
.and_then(|step| u32::try_from(step).ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
workspace.pan_scrolling(direction.strip_offset(step));
|
||||||
|
let focus_moved = workspace.end_scrolling_pan(0.0);
|
||||||
|
|
||||||
|
self.update_focused_workspace(focus_moved && self.mouse_follows_focus, focus_moved)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -929,6 +929,7 @@ impl WindowManager {
|
|||||||
self.focused_workspace_mut()?.expel_scrolling();
|
self.focused_workspace_mut()?.expel_scrolling();
|
||||||
self.update_focused_workspace(false, false)?;
|
self.update_focused_workspace(false, false)?;
|
||||||
}
|
}
|
||||||
|
SocketMessage::Pan(direction) => self.pan(direction)?,
|
||||||
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) => {
|
SocketMessage::LayoutRatios(ref columns, ref rows) => {
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ use crate::workspace::Workspace;
|
|||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
use komorebi_layouts::scrolling;
|
use komorebi_layouts::scrolling;
|
||||||
use komorebi_layouts::scrolling::ScrollingPlacement;
|
use komorebi_layouts::scrolling::ScrollingPlacement;
|
||||||
|
use komorebi_scrolling::ColumnWidth;
|
||||||
use komorebi_scrolling::Options;
|
use komorebi_scrolling::Options;
|
||||||
|
use komorebi_scrolling::ScrollingSpace;
|
||||||
use komorebi_scrolling::Visibility;
|
use komorebi_scrolling::Visibility;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
@@ -712,6 +714,70 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start moving the strip across the screen by hand, and give back the strip as it is, for
|
||||||
|
/// putting it back if the pan is given up. Only the Scrolling layout has a strip to move, and
|
||||||
|
/// not before a layout has made it.
|
||||||
|
pub(crate) fn begin_scrolling_pan(&mut self) -> Option<ScrollingSpace<String>> {
|
||||||
|
if !self.is_scrolling() || self.scrolling.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sync_scrolling_containers();
|
||||||
|
|
||||||
|
let space = self.scrolling.as_mut()?;
|
||||||
|
let saved = space.clone();
|
||||||
|
space.begin_pan();
|
||||||
|
|
||||||
|
Some(saved)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the strip by `dx` pixels on the screen, to the right if it is positive. Nothing else
|
||||||
|
/// changes: not which container has focus, nor the width of any.
|
||||||
|
pub(crate) fn pan_scrolling(&mut self, dx: f64) {
|
||||||
|
if let Some(space) = self.scrolling.as_mut() {
|
||||||
|
space.pan_by(dx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Let go of the strip, going at `velocity` pixels a second on the screen, to the right if it
|
||||||
|
/// is positive, and let it come to rest, which is with a column against an edge of the screen.
|
||||||
|
/// Focus goes to the nearest column that is fully in view if the focused one is not, and
|
||||||
|
/// this tells whether it moved.
|
||||||
|
pub(crate) fn end_scrolling_pan(&mut self, velocity: f64) -> bool {
|
||||||
|
let Some(space) = self.scrolling.as_mut() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if space.pan_end(velocity).is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let focused = space.active_tile_index();
|
||||||
|
let before = self.focused_container_idx();
|
||||||
|
|
||||||
|
match focused {
|
||||||
|
Some(idx) if idx != before => {
|
||||||
|
self.focus_container(idx);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How far one step of a pan goes, if nothing says: a column of the width that a new one
|
||||||
|
/// is given.
|
||||||
|
pub(crate) fn scrolling_pan_step(&self) -> Option<i32> {
|
||||||
|
let space = self.scrolling.as_ref()?;
|
||||||
|
let viewport = f64::from(space.viewport().width);
|
||||||
|
|
||||||
|
let step = match space.options().default_width {
|
||||||
|
ColumnWidth::Proportion(share) => viewport * share,
|
||||||
|
ColumnWidth::Fixed(width) => f64::from(width),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(step.round() as i32)
|
||||||
|
}
|
||||||
|
|
||||||
/// Centre the focused column on the screen, this once.
|
/// Centre the focused column on the screen, this once.
|
||||||
pub(crate) fn center_scrolling_column(&mut self) {
|
pub(crate) fn center_scrolling_column(&mut self) {
|
||||||
if self.focused_scrolling_column().is_some()
|
if self.focused_scrolling_column().is_some()
|
||||||
@@ -1046,4 +1112,65 @@ mod tests {
|
|||||||
|
|
||||||
assert!(workspace.scrolling.is_none());
|
assert!(workspace.scrolling.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn panning_moves_the_strip_and_leaves_focus_where_it_is_until_it_is_let_go() {
|
||||||
|
let mut workspace = workspace(3, 6);
|
||||||
|
workspace.layout_scrolling(&AREA, 0);
|
||||||
|
workspace.focus_container(0);
|
||||||
|
let before = workspace.layout_scrolling(&AREA, 0);
|
||||||
|
|
||||||
|
assert!(workspace.begin_scrolling_pan().is_some());
|
||||||
|
workspace.pan_scrolling(-300.0);
|
||||||
|
let panned = workspace.layout_scrolling(&AREA, 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
panned[1].rect.left,
|
||||||
|
before[1].rect.left - 300,
|
||||||
|
"the strip moved"
|
||||||
|
);
|
||||||
|
assert_eq!(workspace.focused_container_idx(), 0, "and focus did not");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_strip_that_is_let_go_comes_to_rest_and_focus_goes_to_a_column_in_view() {
|
||||||
|
let mut workspace = workspace(3, 6);
|
||||||
|
workspace.layout_scrolling(&AREA, 0);
|
||||||
|
workspace.focus_container(0);
|
||||||
|
|
||||||
|
assert!(workspace.begin_scrolling_pan().is_some());
|
||||||
|
// three columns to the left of the screen, which is where the first three are
|
||||||
|
workspace.pan_scrolling(-1200.0);
|
||||||
|
assert!(workspace.end_scrolling_pan(0.0), "focus moved");
|
||||||
|
|
||||||
|
let rested = workspace.layout_scrolling(&AREA, 0);
|
||||||
|
let focused = workspace.focused_container_idx();
|
||||||
|
|
||||||
|
assert!(focused >= 3, "to a column that is in view, not {focused}");
|
||||||
|
assert_eq!(rested[focused].visibility, Visibility::Visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn there_is_no_strip_to_pan_in_another_layout_or_before_there_is_one() {
|
||||||
|
let mut workspace = workspace(3, 6);
|
||||||
|
assert!(workspace.begin_scrolling_pan().is_none(), "no layout yet");
|
||||||
|
assert!(!workspace.end_scrolling_pan(0.0));
|
||||||
|
|
||||||
|
workspace.layout_scrolling(&AREA, 0);
|
||||||
|
workspace.layout = Layout::Default(DefaultLayout::BSP);
|
||||||
|
assert!(workspace.begin_scrolling_pan().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_step_of_a_pan_is_a_column_of_the_width_that_a_new_one_has() {
|
||||||
|
let mut workspace = workspace(3, 6);
|
||||||
|
assert_eq!(workspace.scrolling_pan_step(), None, "no layout yet");
|
||||||
|
|
||||||
|
workspace.layout_scrolling(&AREA, 0);
|
||||||
|
assert_eq!(workspace.scrolling_pan_step(), Some(400));
|
||||||
|
|
||||||
|
workspace.set_scrolling_columns(4);
|
||||||
|
workspace.layout_scrolling(&AREA, 0);
|
||||||
|
assert_eq!(workspace.scrolling_pan_step(), Some(300));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,7 @@ use komorebi_client::HidingBehaviour;
|
|||||||
use komorebi_client::MoveBehaviour;
|
use komorebi_client::MoveBehaviour;
|
||||||
use komorebi_client::OperationBehaviour;
|
use komorebi_client::OperationBehaviour;
|
||||||
use komorebi_client::OperationDirection;
|
use komorebi_client::OperationDirection;
|
||||||
|
use komorebi_client::PanDirection;
|
||||||
use komorebi_client::Rect;
|
use komorebi_client::Rect;
|
||||||
use komorebi_client::Sizing;
|
use komorebi_client::Sizing;
|
||||||
use komorebi_client::SocketMessage;
|
use komorebi_client::SocketMessage;
|
||||||
@@ -1009,6 +1010,13 @@ struct ScrollingLayoutColumns {
|
|||||||
count: NonZeroUsize,
|
count: NonZeroUsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Pan {
|
||||||
|
/// The way that the view goes
|
||||||
|
#[clap(value_enum)]
|
||||||
|
direction: PanDirection,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct LayoutRatios {
|
struct LayoutRatios {
|
||||||
/// Column width ratios (space-separated values between 0.1 and 0.9)
|
/// Column width ratios (space-separated values between 0.1 and 0.9)
|
||||||
@@ -1302,6 +1310,10 @@ enum SubCommand {
|
|||||||
/// Stack the windows of the focused column of the Scrolling layout, or spread the focused
|
/// Stack the windows of the focused column of the Scrolling layout, or spread the focused
|
||||||
/// stack out into a column with a window in each slot
|
/// stack out into a column with a window in each slot
|
||||||
ToggleColumnStack,
|
ToggleColumnStack,
|
||||||
|
/// Move the view of the Scrolling layout a column to the left or the right, which does
|
||||||
|
/// nothing in any other layout
|
||||||
|
#[clap(arg_required_else_help = true)]
|
||||||
|
Pan(Pan),
|
||||||
/// Set the layout column and row ratios for the focused workspace
|
/// Set the layout column and row ratios for the focused workspace
|
||||||
LayoutRatios(LayoutRatios),
|
LayoutRatios(LayoutRatios),
|
||||||
/// Load a custom layout from file for the focused workspace
|
/// Load a custom layout from file for the focused workspace
|
||||||
@@ -2992,6 +3004,9 @@ if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
|||||||
SubCommand::ToggleColumnStack => {
|
SubCommand::ToggleColumnStack => {
|
||||||
send_message(&SocketMessage::ToggleColumnStack)?;
|
send_message(&SocketMessage::ToggleColumnStack)?;
|
||||||
}
|
}
|
||||||
|
SubCommand::Pan(args) => {
|
||||||
|
send_message(&SocketMessage::Pan(args.direction))?;
|
||||||
|
}
|
||||||
SubCommand::LayoutRatios(args) => {
|
SubCommand::LayoutRatios(args) => {
|
||||||
if args.columns.is_none() && args.rows.is_none() {
|
if args.columns.is_none() && args.rows.is_none() {
|
||||||
println!(
|
println!(
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ nav:
|
|||||||
- cli/scrolling-layout-full-width.md
|
- cli/scrolling-layout-full-width.md
|
||||||
- cli/center-focus.md
|
- cli/center-focus.md
|
||||||
- cli/expand.md
|
- cli/expand.md
|
||||||
|
- cli/pan.md
|
||||||
- cli/consume.md
|
- cli/consume.md
|
||||||
- cli/expel.md
|
- cli/expel.md
|
||||||
- cli/toggle-column-stack.md
|
- cli/toggle-column-stack.md
|
||||||
|
|||||||
Reference in new issue
Block a user