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:
LGUG2Z
2021-08-08 22:47:53 -07:00
parent ca27730b01
commit 8b4ce48a66
13 changed files with 538 additions and 150 deletions

View File

@@ -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
}
}

View File

@@ -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,
}

View File

@@ -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,
},

View File

@@ -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,