mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-25 10:08:33 +02:00
feat(wm): add container resizing
The last remaining feature to bring komorebi to feature parity with yatta. Implementing this in komorebi was a lot harder because I had to make sure that resizing worked even when the layout is flipped (in any one of the three possible ways). In yatta, resize dimension information was stored on the window. In komorebi, I initially tried storing this information on the Container itself, but eventually decided to store it for all Containers in the Workspace. There is some additional work required to ensure that this Vec is kept up to date whenever containers are added or destroyed, but it all seems to be working fairly well. I got rid of the iterative fibonacci code that I adapted from leftwm and went back and reworked the recursive code that I was using in yatta (originally from umberwm I think) to integrate layout flipping. At least for me, it is much easier to reason about.
This commit is contained in:
@@ -4,7 +4,9 @@ use serde::Serialize;
|
||||
use strum::Display;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::OperationDirection;
|
||||
use crate::Rect;
|
||||
use crate::Sizing;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
@@ -25,21 +27,125 @@ pub enum LayoutFlip {
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn resize(
|
||||
&self,
|
||||
unaltered: &Rect,
|
||||
resize: &Option<Rect>,
|
||||
edge: OperationDirection,
|
||||
sizing: Sizing,
|
||||
step: Option<i32>,
|
||||
) -> Option<Rect> {
|
||||
let max_divisor = 1.005;
|
||||
let mut r = resize.unwrap_or_default();
|
||||
|
||||
let resize_step = if let Some(step) = step { step } else { 50 };
|
||||
|
||||
match edge {
|
||||
OperationDirection::Left => match sizing {
|
||||
Sizing::Increase => {
|
||||
// Some final checks to make sure the user can't infinitely resize to
|
||||
// the point of pushing other windows out of bounds
|
||||
|
||||
// Note: These checks cannot take into account the changes made to the
|
||||
// edges of adjacent windows at operation time, so it is still possible
|
||||
// to push windows out of bounds by maxing out an Increase Left on a
|
||||
// Window with index 1, and then maxing out a Decrease Right on a Window
|
||||
// with index 0. I don't think it's worth trying to defensively program
|
||||
// against this; if people end up in this situation they are better off
|
||||
// just hitting the retile command
|
||||
let diff = ((r.left + -resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.left += -resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.left - -resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.left -= -resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Up => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.top + resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.top += -resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.top - resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.top -= -resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Right => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.right + resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.right += resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.right - resize_step) as f32).abs();
|
||||
let max = unaltered.right as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.right -= resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
OperationDirection::Down => match sizing {
|
||||
Sizing::Increase => {
|
||||
let diff = ((r.bottom + resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.bottom += resize_step;
|
||||
}
|
||||
}
|
||||
Sizing::Decrease => {
|
||||
let diff = ((r.bottom - resize_step) as f32).abs();
|
||||
let max = unaltered.bottom as f32 / max_divisor;
|
||||
if diff < max {
|
||||
r.bottom -= resize_step;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if !r.eq(&Rect::default()) {
|
||||
Option::from(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate(
|
||||
&self,
|
||||
area: &Rect,
|
||||
count: usize,
|
||||
len: usize,
|
||||
container_padding: Option<i32>,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
resize_dimensions: &[Option<Rect>],
|
||||
) -> Vec<Rect> {
|
||||
let mut dimensions = match self {
|
||||
Layout::BSP => self.fibonacci(area, count, layout_flip),
|
||||
Layout::BSP => recursive_fibonacci(
|
||||
0,
|
||||
len,
|
||||
area,
|
||||
layout_flip,
|
||||
calculate_resize_adjustments(resize_dimensions),
|
||||
),
|
||||
Layout::Columns => {
|
||||
let right = area.right / count as i32;
|
||||
let right = area.right / len as i32;
|
||||
let mut left = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..count {
|
||||
for _ in 0..len {
|
||||
layouts.push(Rect {
|
||||
left: area.left + left,
|
||||
top: area.top,
|
||||
@@ -53,11 +159,11 @@ impl Layout {
|
||||
layouts
|
||||
}
|
||||
Layout::Rows => {
|
||||
let bottom = area.bottom / count as i32;
|
||||
let bottom = area.bottom / len as i32;
|
||||
let mut top = 0;
|
||||
|
||||
let mut layouts: Vec<Rect> = vec![];
|
||||
for _ in 0..count {
|
||||
for _ in 0..len {
|
||||
layouts.push(Rect {
|
||||
left: area.left,
|
||||
top: area.top + top,
|
||||
@@ -78,93 +184,6 @@ impl Layout {
|
||||
|
||||
dimensions
|
||||
}
|
||||
|
||||
pub fn fibonacci(
|
||||
&self,
|
||||
area: &Rect,
|
||||
count: usize,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
) -> Vec<Rect> {
|
||||
let mut dimensions = vec![];
|
||||
|
||||
for _ in 0..count {
|
||||
dimensions.push(Rect::default())
|
||||
}
|
||||
|
||||
let mut left = area.left;
|
||||
let mut top = area.top;
|
||||
let mut bottom = area.bottom;
|
||||
let mut right = area.right;
|
||||
|
||||
for i in 0..count {
|
||||
if i % 2 != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let half_width = right / 2;
|
||||
let half_height = bottom / 2;
|
||||
|
||||
let (main_x, alt_x, new_y, alt_y);
|
||||
|
||||
match layout_flip {
|
||||
Some(flip) => match flip {
|
||||
LayoutFlip::Horizontal => {
|
||||
main_x = left + half_width;
|
||||
alt_x = left;
|
||||
|
||||
new_y = top + half_height;
|
||||
alt_y = top;
|
||||
}
|
||||
LayoutFlip::Vertical => {
|
||||
new_y = top;
|
||||
alt_y = top + half_height;
|
||||
|
||||
main_x = left;
|
||||
alt_x = left + half_width;
|
||||
}
|
||||
LayoutFlip::HorizontalAndVertical => {
|
||||
main_x = left + half_width;
|
||||
alt_x = left;
|
||||
new_y = top;
|
||||
alt_y = top + half_height;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
main_x = left;
|
||||
alt_x = left + half_width;
|
||||
new_y = top + half_height;
|
||||
alt_y = top;
|
||||
}
|
||||
}
|
||||
|
||||
match count - i {
|
||||
1 => {
|
||||
set_dimensions(&mut dimensions[i], left, top, right, bottom);
|
||||
}
|
||||
2 => {
|
||||
set_dimensions(&mut dimensions[i], main_x, top, half_width, bottom);
|
||||
set_dimensions(&mut dimensions[i + 1], alt_x, top, half_width, bottom);
|
||||
}
|
||||
_ => {
|
||||
set_dimensions(&mut dimensions[i], main_x, top, half_width, bottom);
|
||||
set_dimensions(
|
||||
&mut dimensions[i + 1],
|
||||
alt_x,
|
||||
alt_y,
|
||||
half_width,
|
||||
half_height,
|
||||
);
|
||||
|
||||
left = alt_x;
|
||||
top = new_y;
|
||||
right = half_width;
|
||||
bottom = half_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dimensions
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
@@ -185,9 +204,193 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dimensions(rect: &mut Rect, left: i32, top: i32, right: i32, bottom: i32) {
|
||||
rect.bottom = bottom;
|
||||
rect.right = right;
|
||||
rect.left = left;
|
||||
rect.top = top;
|
||||
fn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Option<Rect>> {
|
||||
let mut resize_adjustments = resize_dimensions.to_vec();
|
||||
|
||||
// This needs to be aware of layout flips
|
||||
for (i, opt) in resize_dimensions.iter().enumerate() {
|
||||
if let Some(resize_ref) = opt {
|
||||
if i > 0 {
|
||||
if resize_ref.left != 0 {
|
||||
let range = if i == 1 {
|
||||
0..1
|
||||
} else if i & 1 != 0 {
|
||||
i - 1..i
|
||||
} else {
|
||||
i - 2..i
|
||||
};
|
||||
|
||||
for n in range {
|
||||
let should_adjust = n % 2 == 0;
|
||||
if should_adjust {
|
||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||
adjacent_resize.right += resize_ref.left;
|
||||
} else {
|
||||
resize_adjustments[n] = Option::from(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: resize_ref.left,
|
||||
bottom: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rr) = resize_adjustments[i].as_mut() {
|
||||
rr.left = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if resize_ref.top != 0 {
|
||||
let range = if i == 1 {
|
||||
0..1
|
||||
} else if i & 1 == 0 {
|
||||
i - 1..i
|
||||
} else {
|
||||
i - 2..i
|
||||
};
|
||||
|
||||
for n in range {
|
||||
let should_adjust = n % 2 != 0;
|
||||
if should_adjust {
|
||||
if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {
|
||||
adjacent_resize.bottom += resize_ref.top;
|
||||
} else {
|
||||
resize_adjustments[n] = Option::from(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: resize_ref.top,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Some(resize)) = resize_adjustments.get_mut(i) {
|
||||
resize.top = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cleaned_resize_adjustments: Vec<_> = resize_adjustments
|
||||
.iter()
|
||||
.map(|adjustment| match adjustment {
|
||||
None => None,
|
||||
Some(rect) if rect.eq(&Rect::default()) => None,
|
||||
Some(_) => *adjustment,
|
||||
})
|
||||
.collect();
|
||||
|
||||
cleaned_resize_adjustments
|
||||
}
|
||||
|
||||
fn recursive_fibonacci(
|
||||
idx: usize,
|
||||
count: usize,
|
||||
area: &Rect,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
resize_adjustments: Vec<Option<Rect>>,
|
||||
) -> Vec<Rect> {
|
||||
let mut a = *area;
|
||||
|
||||
let resized = if let Some(Some(r)) = resize_adjustments.get(idx) {
|
||||
a.left += r.left;
|
||||
a.top += r.top;
|
||||
a.right += r.right;
|
||||
a.bottom += r.bottom;
|
||||
a
|
||||
} else {
|
||||
*area
|
||||
};
|
||||
|
||||
let half_width = area.right / 2;
|
||||
let half_height = area.bottom / 2;
|
||||
let half_resized_width = resized.right / 2;
|
||||
let half_resized_height = resized.bottom / 2;
|
||||
|
||||
let (main_x, alt_x, alt_y, main_y);
|
||||
|
||||
match layout_flip {
|
||||
Some(flip) => match flip {
|
||||
LayoutFlip::Horizontal => {
|
||||
main_x = resized.left + half_width + (half_width - half_resized_width);
|
||||
alt_x = resized.left;
|
||||
|
||||
alt_y = resized.top + half_resized_height;
|
||||
main_y = resized.top;
|
||||
}
|
||||
LayoutFlip::Vertical => {
|
||||
main_y = resized.top + half_height + (half_height - half_resized_height);
|
||||
alt_y = resized.top;
|
||||
|
||||
main_x = resized.left;
|
||||
alt_x = resized.left + half_resized_width;
|
||||
}
|
||||
LayoutFlip::HorizontalAndVertical => {
|
||||
main_x = resized.left + half_width + (half_width - half_resized_width);
|
||||
alt_x = resized.left;
|
||||
main_y = resized.top + half_height + (half_height - half_resized_height);
|
||||
alt_y = resized.top;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
main_x = resized.left;
|
||||
alt_x = resized.left + half_resized_width;
|
||||
main_y = resized.top;
|
||||
alt_y = resized.top + half_resized_height;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
vec![]
|
||||
} else if count == 1 {
|
||||
vec![Rect {
|
||||
left: resized.left,
|
||||
top: resized.top,
|
||||
right: resized.right,
|
||||
bottom: resized.bottom,
|
||||
}]
|
||||
} else if idx % 2 != 0 {
|
||||
let mut res = vec![Rect {
|
||||
left: resized.left,
|
||||
top: main_y,
|
||||
right: resized.right,
|
||||
bottom: half_resized_height,
|
||||
}];
|
||||
res.append(&mut recursive_fibonacci(
|
||||
idx + 1,
|
||||
count - 1,
|
||||
&Rect {
|
||||
left: area.left,
|
||||
top: alt_y,
|
||||
right: area.right,
|
||||
bottom: area.bottom - half_resized_height,
|
||||
},
|
||||
layout_flip,
|
||||
resize_adjustments,
|
||||
));
|
||||
res
|
||||
} else {
|
||||
let mut res = vec![Rect {
|
||||
left: main_x,
|
||||
top: resized.top,
|
||||
right: half_resized_width,
|
||||
bottom: resized.bottom,
|
||||
}];
|
||||
res.append(&mut recursive_fibonacci(
|
||||
idx + 1,
|
||||
count - 1,
|
||||
&Rect {
|
||||
left: alt_x,
|
||||
top: area.top,
|
||||
right: area.right - half_resized_width,
|
||||
bottom: area.bottom,
|
||||
},
|
||||
layout_flip,
|
||||
resize_adjustments,
|
||||
));
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ pub enum SocketMessage {
|
||||
FocusWindow(OperationDirection),
|
||||
MoveWindow(OperationDirection),
|
||||
StackWindow(OperationDirection),
|
||||
ResizeWindow(OperationDirection, Sizing),
|
||||
UnstackWindow,
|
||||
CycleStack(CycleDirection),
|
||||
MoveContainerToMonitorNumber(usize),
|
||||
@@ -94,13 +95,3 @@ impl Sizing {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[derive(Clap)]
|
||||
pub enum ResizeEdge {
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
@@ -18,15 +18,12 @@ pub enum OperationDirection {
|
||||
}
|
||||
|
||||
impl OperationDirection {
|
||||
pub fn can_resize(&self, layout: Layout, idx: usize, len: usize) -> bool {
|
||||
match layout {
|
||||
Layout::BSP => match self {
|
||||
Self::Left => len != 0 && idx != 0,
|
||||
Self::Up => len > 2 && idx != 0 && idx != 1,
|
||||
Self::Right => len > 1 && idx % 2 == 0 && idx != len - 1,
|
||||
Self::Down => len > 2 && idx != len - 1 && idx % 2 != 0,
|
||||
},
|
||||
_ => false,
|
||||
pub fn opposite(self) -> Self {
|
||||
match self {
|
||||
OperationDirection::Left => OperationDirection::Right,
|
||||
OperationDirection::Right => OperationDirection::Left,
|
||||
OperationDirection::Up => OperationDirection::Down,
|
||||
OperationDirection::Down => OperationDirection::Up,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +87,7 @@ impl OperationDirection {
|
||||
Layout::Rows => false,
|
||||
},
|
||||
OperationDirection::Right => match layout {
|
||||
Layout::BSP => len > 1 && idx % 2 == 0,
|
||||
Layout::BSP => len > 1 && idx % 2 == 0 && idx != len - 1,
|
||||
Layout::Columns => idx != len - 1,
|
||||
Layout::Rows => false,
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ use serde::Serialize;
|
||||
|
||||
use bindings::Windows::Win32::Foundation::RECT;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Eq, PartialEq)]
|
||||
pub struct Rect {
|
||||
pub left: i32,
|
||||
pub top: i32,
|
||||
|
||||
Reference in New Issue
Block a user