feat: resize tiled windows directionally
This commit is contained in:
1 parent
0d9bbe49f7
commit
4e4a44e210
3 files changed
+162
-3
No files matched your search
@@ -34,7 +34,10 @@ impl Ferese {
|
|||||||
return FilterResult::Intercept(());
|
return FilterResult::Intercept(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let direction = if modifiers.logo && !modifiers.ctrl && !modifiers.alt {
|
let supported_modifiers = modifiers.logo
|
||||||
|
&& !modifiers.alt
|
||||||
|
&& !(modifiers.ctrl && modifiers.shift);
|
||||||
|
let direction = if supported_modifiers {
|
||||||
match keysym.modified_sym().raw() {
|
match keysym.modified_sym().raw() {
|
||||||
keysyms::KEY_h | keysyms::KEY_H => Some(Direction::Left),
|
keysyms::KEY_h | keysyms::KEY_H => Some(Direction::Left),
|
||||||
keysyms::KEY_j | keysyms::KEY_J => Some(Direction::Down),
|
keysyms::KEY_j | keysyms::KEY_J => Some(Direction::Down),
|
||||||
@@ -49,9 +52,12 @@ impl Ferese {
|
|||||||
if let Some(direction) = direction {
|
if let Some(direction) = direction {
|
||||||
if state == KeyState::Pressed {
|
if state == KeyState::Pressed {
|
||||||
data.intercepted_keys.insert(keycode);
|
data.intercepted_keys.insert(keycode);
|
||||||
if modifiers.shift {
|
|
||||||
|
if modifiers.ctrl && !modifiers.shift {
|
||||||
|
data.resize_direction(direction);
|
||||||
|
} else if modifiers.shift && !modifiers.ctrl {
|
||||||
data.move_direction(direction);
|
data.move_direction(direction);
|
||||||
} else {
|
} else if !modifiers.ctrl && !modifiers.shift {
|
||||||
data.focus_direction(direction);
|
data.focus_direction(direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,6 +293,20 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resize_direction(&mut self, direction: Direction) {
|
||||||
|
const RESIZE_STEP: f64 = 0.05;
|
||||||
|
|
||||||
|
let Some(current) = self.focused_window else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.layout.resize_window(current, direction, RESIZE_STEP) {
|
||||||
|
Ok(true) => self.relayout(),
|
||||||
|
Ok(false) => {}
|
||||||
|
Err(error) => tracing::error!(%error, ?current, "failed to resize tiled window"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn output_bounds(&self) -> Option<Rect> {
|
fn output_bounds(&self) -> Option<Rect> {
|
||||||
let output = self.space.outputs().next()?;
|
let output = self.space.outputs().next()?;
|
||||||
let geometry = self.space.output_geometry(output)?;
|
let geometry = self.space.output_geometry(output)?;
|
||||||
|
|||||||
@@ -371,6 +371,70 @@ impl LayoutTree {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resize_window(
|
||||||
|
&mut self,
|
||||||
|
window: WindowId,
|
||||||
|
direction: Direction,
|
||||||
|
amount: f64,
|
||||||
|
) -> Result<bool, LayoutError> {
|
||||||
|
let mut child = self
|
||||||
|
.windows
|
||||||
|
.get(&window)
|
||||||
|
.copied()
|
||||||
|
.ok_or(LayoutError::UnknownWindow(window))?;
|
||||||
|
let amount = valid_resize_amount(amount);
|
||||||
|
|
||||||
|
if amount == 0.0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(parent) = self.parents.get(&child).copied() {
|
||||||
|
let adjustment = match self.nodes.get(&parent) {
|
||||||
|
Some(Node::Split {
|
||||||
|
axis: Axis::Horizontal,
|
||||||
|
first,
|
||||||
|
..
|
||||||
|
}) if direction == Direction::Right && *first == child => Some(amount),
|
||||||
|
Some(Node::Split {
|
||||||
|
axis: Axis::Horizontal,
|
||||||
|
second,
|
||||||
|
..
|
||||||
|
}) if direction == Direction::Left && *second == child => Some(-amount),
|
||||||
|
Some(Node::Split {
|
||||||
|
axis: Axis::Vertical,
|
||||||
|
first,
|
||||||
|
..
|
||||||
|
}) if direction == Direction::Down && *first == child => Some(amount),
|
||||||
|
Some(Node::Split {
|
||||||
|
axis: Axis::Vertical,
|
||||||
|
second,
|
||||||
|
..
|
||||||
|
}) if direction == Direction::Up && *second == child => Some(-amount),
|
||||||
|
Some(Node::Split { .. } | Node::Stack { .. }) => None,
|
||||||
|
Some(Node::Window(_)) => {
|
||||||
|
return Err(LayoutError::InvalidTree("window cannot be a parent"));
|
||||||
|
}
|
||||||
|
None => return Err(LayoutError::InvalidTree("parent node is missing")),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(adjustment) = adjustment {
|
||||||
|
let Some(Node::Split { ratio, .. }) = self.nodes.get_mut(&parent) else {
|
||||||
|
return Err(LayoutError::InvalidTree("resize target is not a split"));
|
||||||
|
};
|
||||||
|
let resized = normalized_ratio(*ratio + adjustment);
|
||||||
|
let changed = resized != *ratio;
|
||||||
|
*ratio = resized;
|
||||||
|
|
||||||
|
debug_assert!(self.validate().is_ok());
|
||||||
|
return Ok(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
child = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn validate(&self) -> Result<(), LayoutError> {
|
pub fn validate(&self) -> Result<(), LayoutError> {
|
||||||
let Some(root) = self.root else {
|
let Some(root) = self.root else {
|
||||||
return if self.nodes.is_empty() && self.parents.is_empty() && self.windows.is_empty() {
|
return if self.nodes.is_empty() && self.parents.is_empty() && self.windows.is_empty() {
|
||||||
@@ -541,6 +605,14 @@ fn valid_gap(gap: f64) -> f64 {
|
|||||||
if gap.is_finite() { gap.max(0.0) } else { 0.0 }
|
if gap.is_finite() { gap.max(0.0) } else { 0.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn valid_resize_amount(amount: f64) -> f64 {
|
||||||
|
if amount.is_finite() {
|
||||||
|
amount.abs()
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn nearly_equal(left: f64, right: f64) -> bool {
|
fn nearly_equal(left: f64, right: f64) -> bool {
|
||||||
(left - right).abs() < f64::EPSILON * left.abs().max(right.abs()).max(1.0)
|
(left - right).abs() < f64::EPSILON * left.abs().max(right.abs()).max(1.0)
|
||||||
}
|
}
|
||||||
@@ -777,4 +849,71 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directional_resize_changes_the_nearest_matching_split() {
|
||||||
|
let mut tree = LayoutTree::default();
|
||||||
|
tree.insert(WindowId(1), None, Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
tree.insert(WindowId(2), Some(WindowId(1)), Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
tree.insert(WindowId(3), Some(WindowId(1)), Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
let bounds = Rect::new(0.0, 0.0, 100.0, 100.0);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
tree.resize_window(WindowId(1), Direction::Right, 0.1)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let geometry = tree.geometry(bounds).unwrap();
|
||||||
|
assert_eq!(geometry[&WindowId(1)].width, 30.0);
|
||||||
|
assert_eq!(geometry[&WindowId(3)].x, 30.0);
|
||||||
|
assert_eq!(geometry[&WindowId(2)].x, 50.0);
|
||||||
|
assert!(tree.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directional_resize_walks_to_an_ancestor_and_clamps() {
|
||||||
|
let mut tree = LayoutTree::default();
|
||||||
|
tree.insert(WindowId(1), None, Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
tree.insert(WindowId(2), Some(WindowId(1)), Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
tree.insert(WindowId(3), Some(WindowId(1)), Axis::Vertical, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
let bounds = Rect::new(0.0, 0.0, 100.0, 100.0);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
tree.resize_window(WindowId(3), Direction::Right, 1.0)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!tree
|
||||||
|
.resize_window(WindowId(3), Direction::Right, 1.0)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let geometry = tree.geometry(bounds).unwrap();
|
||||||
|
assert_eq!(geometry[&WindowId(2)].x, 95.0);
|
||||||
|
assert!(tree.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directional_resize_at_an_outer_edge_is_a_noop() {
|
||||||
|
let mut tree = LayoutTree::default();
|
||||||
|
tree.insert(WindowId(1), None, Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!tree
|
||||||
|
.resize_window(WindowId(1), Direction::Left, 0.05)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!tree
|
||||||
|
.resize_window(WindowId(1), Direction::Right, f64::NAN)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user