feat: move tiled windows directionally
This commit is contained in:
1 parent
8c1184ddda
commit
0d9bbe49f7
3 files changed
+87
-6
No files matched your search
@@ -34,11 +34,7 @@ impl Ferese {
|
|||||||
return FilterResult::Intercept(());
|
return FilterResult::Intercept(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let direction = if modifiers.logo
|
let direction = if modifiers.logo && !modifiers.ctrl && !modifiers.alt {
|
||||||
&& !modifiers.ctrl
|
|
||||||
&& !modifiers.alt
|
|
||||||
&& !modifiers.shift
|
|
||||||
{
|
|
||||||
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),
|
||||||
@@ -53,7 +49,11 @@ 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);
|
||||||
data.focus_direction(direction);
|
if modifiers.shift {
|
||||||
|
data.move_direction(direction);
|
||||||
|
} else {
|
||||||
|
data.focus_direction(direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterResult::Intercept(())
|
FilterResult::Intercept(())
|
||||||
|
|||||||
@@ -278,6 +278,21 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_direction(&mut self, direction: Direction) {
|
||||||
|
let Some(current) = self.focused_window else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(bounds) = self.output_bounds() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.layout.move_window(current, direction, bounds) {
|
||||||
|
Ok(true) => self.relayout(),
|
||||||
|
Ok(false) => {}
|
||||||
|
Err(error) => tracing::error!(%error, ?current, "failed to move 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)?;
|
||||||
|
|||||||
@@ -342,6 +342,35 @@ impl LayoutTree {
|
|||||||
.map(|(candidate, _, _, _)| candidate))
|
.map(|(candidate, _, _, _)| candidate))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_window(
|
||||||
|
&mut self,
|
||||||
|
window: WindowId,
|
||||||
|
direction: Direction,
|
||||||
|
bounds: Rect,
|
||||||
|
) -> Result<bool, LayoutError> {
|
||||||
|
let Some(neighbor) = self.directional_neighbor(window, direction, bounds)? else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
let window_node = self
|
||||||
|
.windows
|
||||||
|
.get(&window)
|
||||||
|
.copied()
|
||||||
|
.ok_or(LayoutError::UnknownWindow(window))?;
|
||||||
|
let neighbor_node = self
|
||||||
|
.windows
|
||||||
|
.get(&neighbor)
|
||||||
|
.copied()
|
||||||
|
.ok_or(LayoutError::UnknownWindow(neighbor))?;
|
||||||
|
|
||||||
|
self.nodes.insert(window_node, Node::Window(neighbor));
|
||||||
|
self.nodes.insert(neighbor_node, Node::Window(window));
|
||||||
|
self.windows.insert(window, neighbor_node);
|
||||||
|
self.windows.insert(neighbor, window_node);
|
||||||
|
|
||||||
|
debug_assert!(self.validate().is_ok());
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
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() {
|
||||||
@@ -711,4 +740,41 @@ mod tests {
|
|||||||
None
|
None
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directional_move_swaps_window_leaves() {
|
||||||
|
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();
|
||||||
|
let bounds = Rect::new(0.0, 0.0, 100.0, 80.0);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
tree.move_window(WindowId(1), Direction::Right, bounds)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let geometry = tree.geometry(bounds).unwrap();
|
||||||
|
assert_eq!(geometry[&WindowId(2)].x, 0.0);
|
||||||
|
assert_eq!(geometry[&WindowId(1)].x, 50.0);
|
||||||
|
assert!(tree.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directional_move_at_boundary_is_a_noop() {
|
||||||
|
let mut tree = LayoutTree::default();
|
||||||
|
tree.insert(WindowId(1), None, Axis::Horizontal, 0.5)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!tree
|
||||||
|
.move_window(
|
||||||
|
WindowId(1),
|
||||||
|
Direction::Left,
|
||||||
|
Rect::new(0.0, 0.0, 100.0, 80.0),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user