feat(wm): focus floats depending on direction

This commit tries to focus the floating windows depending on the
direction used. It takes into account the top left corner of each window
and uses that to look for the closest top left corner in the direction
provided.

If there are no windows found in that direction, it then checks if it
can focus across monitor.
This commit is contained in:
alex-ds13
2025-03-21 15:47:26 +00:00
committed by LGUG2Z
parent 5cc688dc6b
commit 4bfd7febb4

View File

@@ -1985,38 +1985,244 @@ impl WindowManager {
if len > 1 {
let focused_hwnd = WindowsApi::foreground_window()?;
for (idx, window) in focused_workspace.floating_windows().iter().enumerate() {
if window.hwnd == focused_hwnd {
match direction {
OperationDirection::Left => {}
OperationDirection::Right => {}
OperationDirection::Up => {
if idx == len - 1 {
target_idx = Some(0)
} else {
target_idx = Some(idx + 1)
}
}
OperationDirection::Down => {
if idx == 0 {
target_idx = Some(len - 1)
} else {
target_idx = Some(idx - 1)
}
}
let focused_rect = WindowsApi::window_rect(focused_hwnd)?;
match direction {
OperationDirection::Left => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.left < focused_rect.left)
.then_some((idx, i32::abs(r.left - focused_rect.left)))
})
.collect::<Vec<_>>();
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
}
OperationDirection::Right => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.left > focused_rect.left)
.then_some((idx, i32::abs(r.left - focused_rect.left)))
})
.collect::<Vec<_>>();
if target_idx.is_none() {
target_idx = Some(0);
}
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
OperationDirection::Up => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.top < focused_rect.top)
.then_some((idx, i32::abs(r.top - focused_rect.top)))
})
.collect::<Vec<_>>();
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
OperationDirection::Down => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.top > focused_rect.top)
.then_some((idx, i32::abs(r.top - focused_rect.top)))
})
.collect::<Vec<_>>();
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
};
}
if let Some(idx) = target_idx {
focused_workspace.floating_windows.focus(idx);
if let Some(window) = focused_workspace.floating_windows().get(idx) {
window.focus(mouse_follows_focus)?;
}
return Ok(());
}
let mut cross_monitor_monocle_or_max = false;
let workspace_idx = self.focused_workspace_idx()?;
// this is for when we are scrolling across workspaces like PaperWM
if matches!(
self.cross_boundary_behaviour,
CrossBoundaryBehaviour::Workspace
) && matches!(
direction,
OperationDirection::Left | OperationDirection::Right
) {
let workspace_count = if let Some(monitor) = self.focused_monitor() {
monitor.workspaces().len()
} else {
1
};
let next_idx = match direction {
OperationDirection::Left => match workspace_idx {
0 => workspace_count - 1,
n => n - 1,
},
OperationDirection::Right => match workspace_idx {
n if n == workspace_count - 1 => 0,
n => n + 1,
},
_ => workspace_idx,
};
self.focus_workspace(next_idx)?;
if let Ok(focused_workspace) = self.focused_workspace_mut() {
if focused_workspace.monocle_container().is_none() {
match direction {
OperationDirection::Left => match focused_workspace.layout() {
Layout::Default(layout) => {
let target_index =
layout.rightmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(
focused_workspace.containers().len().saturating_sub(1),
);
}
},
OperationDirection::Right => match focused_workspace.layout() {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(0);
}
},
_ => {}
};
}
}
return Ok(());
}
// if there is no floating_window in that direction for this workspace
let monitor_idx = self
.monitor_idx_in_direction(direction)
.ok_or_else(|| anyhow!("there is no container or monitor in this direction"))?;
self.focus_monitor(monitor_idx)?;
let mouse_follows_focus = self.mouse_follows_focus;
if let Ok(focused_workspace) = self.focused_workspace_mut() {
if let Some(window) = focused_workspace.maximized_window() {
window.focus(mouse_follows_focus)?;
cross_monitor_monocle_or_max = true;
} else if let Some(monocle) = focused_workspace.monocle_container() {
if let Some(window) = monocle.focused_window() {
window.focus(mouse_follows_focus)?;
cross_monitor_monocle_or_max = true;
}
} else if focused_workspace.layer() == &WorkspaceLayer::Tiling {
match direction {
OperationDirection::Left => match focused_workspace.layout() {
Layout::Default(layout) => {
let target_index =
layout.rightmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(
focused_workspace.containers().len().saturating_sub(1),
);
}
},
OperationDirection::Right => match focused_workspace.layout() {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(0);
}
},
_ => {}
};
}
}
if !cross_monitor_monocle_or_max {
let ws = self.focused_workspace_mut()?;
if ws.is_empty() {
// This is to remove focus from the previous monitor
let desktop_window = Window::from(WindowsApi::desktop_window()?);
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
Ok(()) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
}
}
} else if ws.layer() == &WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {
if let Some(window) = ws.focused_floating_window() {
window.focus(self.mouse_follows_focus)?;
}
} else {
ws.set_layer(WorkspaceLayer::Tiling);
if let Ok(focused_window) = self.focused_window() {
focused_window.focus(self.mouse_follows_focus)?;
}
}
}
Ok(())