Files
komorebi/komorebi/src/workspace.rs
T
LGUG2Z 2a45f981e6 feat(stackbar): add stackbar manager module
This commit removes all stackbar-related code from Container, Workspace,
process_command, process_event etc. and centralizes it in the new
stackbar_manager module.

Instead of trying to figure out where in process_event and
process_command we should make stackbar-related changes, a notification
gets sent to a channel that stackbar_manager listens to whenever an
event or command has finished processing.

The stackbar_manager listener, upon receiving a notification, acquires a
lock on the WindowManager instance and updates stackbars for the focused
workspace on every monitor; this allows us to centralize all edge case
handling within the stackbar_manager listener's loop.

Global state related to stackbars has also been moved into the
stackbar_manager module, which also tracks the state of stackbar objects
(STACKBAR_STATE), mappings between stackbars and containers
(STACKBARS_CONTAINERS) and the mappings between stackbars and monitors
(STACKBARS_MONITORS).

A number of edge cases around stackbar behaviour have been addressed in
this commit (re #832), and stackbars now respect the "border_style"
configuration option.
2024-05-19 10:55:40 -07:00

1392 lines
45 KiB
Rust

use std::collections::VecDeque;
use std::num::NonZeroUsize;
use std::sync::atomic::Ordering;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use komorebi_core::Axis;
use komorebi_core::CustomLayout;
use komorebi_core::CycleDirection;
use komorebi_core::DefaultLayout;
use komorebi_core::Layout;
use komorebi_core::OperationDirection;
use komorebi_core::Rect;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::container::Container;
use crate::ring::Ring;
use crate::stackbar_manager;
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
use crate::static_config::WorkspaceConfig;
use crate::window::Window;
use crate::window::WindowDetails;
use crate::windows_api::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
use crate::INITIAL_CONFIGURATION_LOADED;
use crate::NO_TITLEBAR;
use crate::REMOVE_TITLEBARS;
#[allow(clippy::struct_field_names)]
#[derive(
Debug, Clone, Serialize, Deserialize, Getters, CopyGetters, MutGetters, Setters, JsonSchema,
)]
pub struct Workspace {
#[getset(get = "pub", set = "pub")]
name: Option<String>,
containers: Ring<Container>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
monocle_container: Option<Container>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_copy = "pub", set = "pub")]
monocle_container_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
maximized_window: Option<Window>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get_copy = "pub", set = "pub")]
maximized_window_restore_idx: Option<usize>,
#[getset(get = "pub", get_mut = "pub")]
floating_windows: Vec<Window>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
layout: Layout,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
layout_rules: Vec<(usize, Layout)>,
#[getset(get_copy = "pub", set = "pub")]
layout_flip: Option<Axis>,
#[getset(get_copy = "pub", set = "pub")]
workspace_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
container_padding: Option<i32>,
#[getset(get = "pub", set = "pub")]
latest_layout: Vec<Rect>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
resize_dimensions: Vec<Option<Rect>>,
#[getset(get = "pub", set = "pub")]
tile: bool,
}
impl_ring_elements!(Workspace, Container);
impl Default for Workspace {
fn default() -> Self {
Self {
name: None,
containers: Ring::default(),
monocle_container: None,
maximized_window: None,
maximized_window_restore_idx: None,
monocle_container_restore_idx: None,
floating_windows: Vec::default(),
layout: Layout::Default(DefaultLayout::BSP),
layout_rules: vec![],
layout_flip: None,
workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)),
container_padding: Option::from(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)),
latest_layout: vec![],
resize_dimensions: vec![],
tile: true,
}
}
}
impl Workspace {
pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> Result<()> {
self.name = Option::from(config.name.clone());
if config.container_padding.is_some() {
self.set_container_padding(config.container_padding);
}
if config.workspace_padding.is_some() {
self.set_workspace_padding(config.workspace_padding);
}
if let Some(layout) = &config.layout {
self.layout = Layout::Default(*layout);
self.tile = true;
}
if let Some(pathbuf) = &config.custom_layout {
let layout = CustomLayout::from_path(pathbuf)?;
self.layout = Layout::Custom(layout);
self.tile = true;
}
if config.custom_layout.is_none() && config.layout.is_none() {
self.tile = false;
}
if let Some(layout_rules) = &config.layout_rules {
let mut all_rules = vec![];
for (count, rule) in layout_rules {
all_rules.push((*count, Layout::Default(*rule)));
}
self.set_layout_rules(all_rules);
self.tile = true;
}
if let Some(layout_rules) = &config.custom_layout_rules {
let rules = self.layout_rules_mut();
for (count, pathbuf) in layout_rules {
let rule = CustomLayout::from_path(pathbuf)?;
rules.push((*count, Layout::Custom(rule)));
}
self.tile = true;
}
Ok(())
}
pub fn hide(&mut self, omit: Option<isize>) {
for window in self.floating_windows_mut().iter_mut().rev() {
let mut should_hide = omit.is_none();
if !should_hide {
if let Some(omit) = omit {
if omit != window.hwnd {
should_hide = true
}
}
}
if should_hide {
window.hide();
}
}
for container in self.containers_mut() {
container.hide(omit)
}
if let Some(window) = self.maximized_window() {
window.hide();
}
if let Some(container) = self.monocle_container_mut() {
container.hide(omit)
}
}
pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window() {
container.restore();
window.focus(mouse_follows_focus)?;
return Ok(());
}
}
let idx = self.focused_container_idx();
let mut to_focus = None;
for (i, container) in self.containers_mut().iter_mut().enumerate() {
if let Some(window) = container.focused_window_mut() {
if idx == i {
to_focus = Option::from(*window);
}
}
container.restore();
}
for container in self.containers_mut() {
container.restore();
}
for window in self.floating_windows() {
window.restore();
}
if let Some(container) = self.focused_container_mut() {
container.focus_window(container.focused_window_idx());
}
// Do this here to make sure that an error doesn't stop the restoration of other windows
// Maximised windows should always be drawn at the top of the Z order
if let Some(window) = to_focus {
if self.maximized_window().is_none() {
window.focus(mouse_follows_focus)?;
}
}
Ok(())
}
pub fn update(
&mut self,
work_area: &Rect,
work_area_offset: Option<Rect>,
window_based_work_area_offset: (isize, Option<Rect>),
) -> Result<()> {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
return Ok(());
}
let (window_based_work_area_offset_limit, window_based_work_area_offset) =
window_based_work_area_offset;
let container_padding = self.container_padding();
let mut adjusted_work_area = work_area_offset.map_or_else(
|| *work_area,
|offset| {
let mut with_offset = *work_area;
with_offset.left += offset.left;
with_offset.top += offset.top;
with_offset.right -= offset.right;
with_offset.bottom -= offset.bottom;
with_offset
},
);
if self.containers().len() <= window_based_work_area_offset_limit as usize {
adjusted_work_area = window_based_work_area_offset.map_or_else(
|| adjusted_work_area,
|offset| {
let mut with_offset = adjusted_work_area;
with_offset.left += offset.left;
with_offset.top += offset.top;
with_offset.right -= offset.right;
with_offset.bottom -= offset.bottom;
with_offset
},
);
}
adjusted_work_area.add_padding(self.workspace_padding().unwrap_or_default());
self.enforce_resize_constraints();
if !self.layout_rules().is_empty() {
let mut updated_layout = None;
for rule in self.layout_rules() {
if self.containers().len() >= rule.0 {
updated_layout = Option::from(rule.1.clone());
}
}
if let Some(updated_layout) = updated_layout {
if !matches!(updated_layout, Layout::Default(DefaultLayout::BSP)) {
self.set_layout_flip(None);
}
self.set_layout(updated_layout);
}
}
let managed_maximized_window = self.maximized_window().is_some();
if *self.tile() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding.unwrap_or_default());
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
adjusted_work_area.add_padding(border_offset);
let width = BORDER_WIDTH.load(Ordering::SeqCst);
adjusted_work_area.add_padding(width);
}
window.set_position(&adjusted_work_area, true)?;
};
} else if let Some(window) = self.maximized_window_mut() {
window.maximize();
} else if !self.containers().is_empty() {
let mut layouts = self.layout().as_boxed_arrangement().calculate(
&adjusted_work_area,
NonZeroUsize::new(self.containers().len()).ok_or_else(|| {
anyhow!(
"there must be at least one container to calculate a workspace layout"
)
})?,
self.container_padding(),
self.layout_flip(),
self.resize_dimensions(),
);
let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);
let no_titlebar = NO_TITLEBAR.lock().clone();
let container_padding = self.container_padding().unwrap_or(0);
let containers = self.containers_mut();
for (i, container) in containers.iter_mut().enumerate() {
let window_count = container.windows().len();
if let (Some(window), Some(layout)) =
(container.focused_window_mut(), layouts.get_mut(i))
{
if should_remove_titlebars && no_titlebar.contains(&window.exe()?) {
window.remove_title_bar()?;
} else if no_titlebar.contains(&window.exe()?) {
window.add_title_bar()?;
}
// If a window has been unmaximized via toggle-maximize, this block
// will make sure that it is unmaximized via restore_window
if window.is_maximized() && !managed_maximized_window {
WindowsApi::restore_window(window.hwnd());
}
{
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
layout.add_padding(border_offset);
let width = BORDER_WIDTH.load(Ordering::SeqCst);
layout.add_padding(width);
}
if stackbar_manager::should_have_stackbar(window_count) {
let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);
let total_height = tab_height + container_padding;
layout.top += total_height;
layout.bottom -= total_height;
}
window.set_position(&layout, false)?;
}
}
self.set_latest_layout(layouts);
}
}
// Always make sure that the length of the resize dimensions vec is the same as the
// number of layouts / containers. This should never actually truncate as the remove_window
// function takes care of cleaning up resize dimensions when destroying empty containers
let container_count = self.containers().len();
self.resize_dimensions_mut().resize(container_count, None);
Ok(())
}
// focus_changed performs updates in response to the fact that a focus
// change event has occurred. The focus change is assumed to be valid, and
// should not result in a new focus change - the intent here is to update
// focus-reactive elements, such as the stackbar.
pub fn focus_changed(&mut self, hwnd: isize) -> Result<()> {
if !self.tile() {
return Ok(());
}
let containers = self.containers_mut();
for container in containers.iter_mut() {
if let Some(idx) = container.idx_for_window(hwnd) {
container.focus_window(idx);
container.restore();
}
}
Ok(())
}
pub fn reap_orphans(&mut self) -> Result<(usize, usize)> {
let mut hwnds = vec![];
let mut floating_hwnds = vec![];
for window in self.visible_windows_mut().into_iter().flatten() {
if !window.is_window() {
hwnds.push(window.hwnd);
}
}
for window in self.floating_windows() {
if !window.is_window() {
floating_hwnds.push(window.hwnd);
}
}
for hwnd in &hwnds {
tracing::debug!("reaping hwnd: {}", hwnd);
self.remove_window(*hwnd)?;
}
for hwnd in &floating_hwnds {
tracing::debug!("reaping floating hwnd: {}", hwnd);
self.floating_windows_mut()
.retain(|w| !floating_hwnds.contains(&w.hwnd));
}
let mut container_ids = vec![];
for container in self.containers() {
if container.windows().is_empty() {
container_ids.push(container.id().clone());
}
}
self.containers_mut()
.retain(|c| !container_ids.contains(c.id()));
Ok((hwnds.len() + floating_hwnds.len(), container_ids.len()))
}
pub fn container_for_window(&self, hwnd: isize) -> Option<&Container> {
self.containers().get(self.container_idx_for_window(hwnd)?)
}
pub fn focus_container_by_window(&mut self, hwnd: isize) -> Result<()> {
let container_idx = self
.container_idx_for_window(hwnd)
.ok_or_else(|| anyhow!("there is no container/window"))?;
let container = self
.containers_mut()
.get_mut(container_idx)
.ok_or_else(|| anyhow!("there is no container"))?;
let window_idx = container
.idx_for_window(hwnd)
.ok_or_else(|| anyhow!("there is no window"))?;
let mut should_load = false;
if container.focused_window_idx() != window_idx {
should_load = true
}
container.focus_window(window_idx);
if should_load {
container.load_focused_window();
}
self.focus_container(container_idx);
Ok(())
}
pub fn container_idx_from_current_point(&self) -> Option<usize> {
let mut idx = None;
let point = WindowsApi::cursor_pos().ok()?;
for (i, _container) in self.containers().iter().enumerate() {
if let Some(rect) = self.latest_layout().get(i) {
if rect.contains_point((point.x, point.y)) {
idx = Option::from(i);
}
}
}
idx
}
pub fn hwnd_from_exe(&self, exe: &str) -> Option<isize> {
for container in self.containers() {
if let Some(hwnd) = container.hwnd_from_exe(exe) {
return Option::from(hwnd);
}
}
if let Some(window) = self.maximized_window() {
if let Ok(window_exe) = window.exe() {
if exe == window_exe {
return Option::from(window.hwnd);
}
}
}
if let Some(container) = self.monocle_container() {
if let Some(hwnd) = container.hwnd_from_exe(exe) {
return Option::from(hwnd);
}
}
for window in self.floating_windows() {
if let Ok(window_exe) = window.exe() {
if exe == window_exe {
return Option::from(window.hwnd);
}
}
}
None
}
pub fn contains_managed_window(&self, hwnd: isize) -> bool {
for container in self.containers() {
if container.contains_window(hwnd) {
return true;
}
}
if let Some(window) = self.maximized_window() {
if hwnd == window.hwnd {
return true;
}
}
if let Some(container) = self.monocle_container() {
if container.contains_window(hwnd) {
return true;
}
}
false
}
pub fn is_focused_window_monocle_or_maximized(&self) -> Result<bool> {
let hwnd = WindowsApi::foreground_window()?;
if let Some(window) = self.maximized_window() {
if hwnd == window.hwnd {
return Ok(true);
}
}
if let Some(container) = self.monocle_container() {
if container.contains_window(hwnd) {
return Ok(true);
}
}
Ok(false)
}
pub fn contains_window(&self, hwnd: isize) -> bool {
for container in self.containers() {
if container.contains_window(hwnd) {
return true;
}
}
if let Some(window) = self.maximized_window() {
if hwnd == window.hwnd {
return true;
}
}
if let Some(container) = self.monocle_container() {
if container.contains_window(hwnd) {
return true;
}
}
for window in self.floating_windows() {
if hwnd == window.hwnd {
return true;
}
}
false
}
pub fn promote_container(&mut self) -> Result<()> {
let resize = self.resize_dimensions_mut().remove(0);
let container = self
.remove_focused_container()
.ok_or_else(|| anyhow!("there is no container"))?;
let primary_idx = match self.layout() {
Layout::Default(_) => 0,
Layout::Custom(layout) => layout.first_container_idx(
layout
.primary_idx()
.ok_or_else(|| anyhow!("this custom layout does not have a primary column"))?,
),
};
self.containers_mut().insert(primary_idx, container);
self.resize_dimensions_mut().insert(primary_idx, resize);
self.focus_container(primary_idx);
Ok(())
}
pub fn add_container(&mut self, container: Container) {
self.containers_mut().push_back(container);
self.focus_last_container();
}
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) {
self.containers_mut().insert(idx, container);
}
pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {
if idx < self.resize_dimensions().len() {
self.resize_dimensions_mut().remove(idx);
}
if idx < self.containers().len() {
return self.containers_mut().remove(idx);
}
None
}
fn container_idx_for_window(&self, hwnd: isize) -> Option<usize> {
let mut idx = None;
for (i, x) in self.containers().iter().enumerate() {
if x.contains_window(hwnd) {
idx = Option::from(i);
}
}
idx
}
pub fn remove_window(&mut self, hwnd: isize) -> Result<()> {
if self.floating_windows().iter().any(|w| w.hwnd == hwnd) {
self.floating_windows_mut().retain(|w| w.hwnd != hwnd);
return Ok(());
}
if let Some(container) = self.monocle_container_mut() {
if let Some(window_idx) = container
.windows()
.iter()
.position(|window| window.hwnd == hwnd)
{
container
.remove_window_by_idx(window_idx)
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
}
return Ok(());
}
}
if let Some(window) = self.maximized_window() {
if window.hwnd == hwnd {
window.unmaximize();
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
return Ok(());
}
}
let container_idx = self
.container_idx_for_window(hwnd)
.ok_or_else(|| anyhow!("there is no window"))?;
let container = self
.containers_mut()
.get_mut(container_idx)
.ok_or_else(|| anyhow!("there is no container"))?;
let window_idx = container
.windows()
.iter()
.position(|window| window.hwnd == hwnd)
.ok_or_else(|| anyhow!("there is no window"))?;
container
.remove_window_by_idx(window_idx)
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut()
.remove(container_idx)
.ok_or_else(|| anyhow!("there is no container"))?;
// Whenever a container is empty, we need to remove any resize dimensions for it too
if self.resize_dimensions().get(container_idx).is_some() {
self.resize_dimensions_mut().remove(container_idx);
}
self.focus_previous_container();
} else {
container.load_focused_window();
}
Ok(())
}
pub fn remove_focused_container(&mut self) -> Option<Container> {
let focused_idx = self.focused_container_idx();
let container = self.remove_container_by_idx(focused_idx);
self.focus_previous_container();
container
}
pub fn remove_container(&mut self, idx: usize) -> Option<Container> {
let container = self.remove_container_by_idx(idx);
self.focus_previous_container();
container
}
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
let len = NonZeroUsize::new(self.containers().len())?;
direction.destination(
self.layout().as_boxed_direction().as_ref(),
self.layout_flip(),
self.focused_container_idx(),
len,
)
}
pub fn new_idx_for_cycle_direction(&self, direction: CycleDirection) -> Option<usize> {
Option::from(direction.next_idx(
self.focused_container_idx(),
NonZeroUsize::new(self.containers().len())?,
))
}
pub fn move_window_to_container(&mut self, target_container_idx: usize) -> Result<()> {
let focused_idx = self.focused_container_idx();
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
// This is a little messy
let adjusted_target_container_index = if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
if focused_idx < target_container_idx {
target_container_idx - 1
} else {
target_container_idx
}
} else {
container.load_focused_window();
target_container_idx
};
let target_container = self
.containers_mut()
.get_mut(adjusted_target_container_index)
.ok_or_else(|| anyhow!("there is no container"))?;
target_container.add_window(window);
self.focus_container(adjusted_target_container_index);
self.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?
.load_focused_window();
Ok(())
}
pub fn new_container_for_focused_window(&mut self) -> Result<()> {
let focused_container_idx = self.focused_container_idx();
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_container_idx);
self.resize_dimensions_mut().remove(focused_container_idx);
} else {
container.load_focused_window();
}
self.new_container_for_window(window);
let mut container = Container::default();
container.add_window(window);
Ok(())
}
pub fn new_container_for_floating_window(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
let window = self
.remove_focused_floating_window()
.ok_or_else(|| anyhow!("there is no floating window"))?;
let mut container = Container::default();
container.add_window(window);
self.containers_mut().insert(focused_idx, container);
self.resize_dimensions_mut().insert(focused_idx, None);
Ok(())
}
pub fn new_container_for_window(&mut self, window: Window) {
let next_idx = if self.containers().is_empty() {
0
} else {
self.focused_container_idx() + 1
};
let mut container = Container::default();
container.add_window(window);
if next_idx > self.containers().len() {
self.containers_mut().push_back(container);
} else {
self.containers_mut().insert(next_idx, container);
}
if next_idx > self.resize_dimensions().len() {
self.resize_dimensions_mut().push(None);
} else {
self.resize_dimensions_mut().insert(next_idx, None);
}
self.focus_container(next_idx);
}
pub fn new_floating_window(&mut self) -> Result<()> {
let window = if let Some(maximized_window) = self.maximized_window() {
let window = *maximized_window;
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
window
} else if let Some(monocle_container) = self.monocle_container_mut() {
let window = monocle_container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if monocle_container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
} else {
monocle_container.load_focused_window();
}
window
} else {
let focused_idx = self.focused_container_idx();
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
self.resize_dimensions_mut().remove(focused_idx);
if focused_idx == self.containers().len() && focused_idx != 0 {
self.focus_container(focused_idx - 1);
}
} else {
container.load_focused_window();
}
window
};
self.floating_windows_mut().push(window);
Ok(())
}
fn enforce_resize_constraints(&mut self) {
match self.layout {
Layout::Default(DefaultLayout::BSP) => self.enforce_resize_constraints_for_bsp(),
Layout::Default(DefaultLayout::Columns) => self.enforce_resize_for_columns(),
Layout::Default(DefaultLayout::Rows) => self.enforce_resize_for_rows(),
Layout::Default(DefaultLayout::VerticalStack) => {
self.enforce_resize_for_vertical_stack();
}
Layout::Default(DefaultLayout::RightMainVerticalStack) => {
self.enforce_resize_for_right_vertical_stack();
}
Layout::Default(DefaultLayout::HorizontalStack) => {
self.enforce_resize_for_horizontal_stack();
}
Layout::Default(DefaultLayout::UltrawideVerticalStack) => {
self.enforce_resize_for_ultrawide();
}
_ => self.enforce_no_resize(),
}
}
fn enforce_resize_constraints_for_bsp(&mut self) {
for (i, rect) in self.resize_dimensions_mut().iter_mut().enumerate() {
if let Some(rect) = rect {
// Even containers can't be resized to the bottom
if i % 2 == 0 {
rect.bottom = 0;
// Odd containers can't be resized to the right
} else {
rect.right = 0;
}
}
}
// The first container can never be resized to the left or the top
if let Some(Some(first)) = self.resize_dimensions_mut().first_mut() {
first.top = 0;
first.left = 0;
}
// The last container can never be resized to the bottom or the right
if let Some(Some(last)) = self.resize_dimensions_mut().last_mut() {
last.bottom = 0;
last.right = 0;
}
}
fn enforce_resize_for_columns(&mut self) {
let resize_dimensions = self.resize_dimensions_mut();
match resize_dimensions.len() {
0 | 1 => self.enforce_no_resize(),
_ => {
let len = resize_dimensions.len();
for (i, rect) in resize_dimensions.iter_mut().enumerate() {
if let Some(rect) = rect {
rect.top = 0;
rect.bottom = 0;
if i == 0 {
rect.left = 0;
}
if i == len - 1 {
rect.right = 0;
}
}
}
}
}
}
fn enforce_resize_for_rows(&mut self) {
let resize_dimensions = self.resize_dimensions_mut();
match resize_dimensions.len() {
0 | 1 => self.enforce_no_resize(),
_ => {
let len = resize_dimensions.len();
for (i, rect) in resize_dimensions.iter_mut().enumerate() {
if let Some(rect) = rect {
rect.left = 0;
rect.right = 0;
if i == 0 {
rect.top = 0;
}
if i == len - 1 {
rect.bottom = 0;
}
}
}
}
}
}
fn enforce_resize_for_vertical_stack(&mut self) {
let resize_dimensions = self.resize_dimensions_mut();
match resize_dimensions.len() {
// Single window can not be resized at all
0 | 1 => self.enforce_no_resize(),
_ => {
// Zero is actually on the left
if let Some(mut left) = resize_dimensions[0] {
left.top = 0;
left.bottom = 0;
left.left = 0;
}
// Handle stack on the right
let stack_size = resize_dimensions[1..].len();
for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {
if let Some(rect) = rect {
// No containers can resize to the right
rect.right = 0;
// First container in stack cant resize up
if i == 0 {
rect.top = 0;
} else if i == stack_size - 1 {
// Last cant be resized to the bottom
rect.bottom = 0;
}
}
}
}
}
}
fn enforce_resize_for_right_vertical_stack(&mut self) {
let resize_dimensions = self.resize_dimensions_mut();
match resize_dimensions.len() {
// Single window can not be resized at all
0 | 1 => self.enforce_no_resize(),
_ => {
// Zero is actually on the right
if let Some(mut left) = resize_dimensions[1] {
left.top = 0;
left.bottom = 0;
left.right = 0;
}
// Handle stack on the right
let stack_size = resize_dimensions[1..].len();
for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {
if let Some(rect) = rect {
// No containers can resize to the left
rect.left = 0;
// First container in stack cant resize up
if i == 0 {
rect.top = 0;
} else if i == stack_size - 1 {
// Last cant be resized to the bottom
rect.bottom = 0;
}
}
}
}
}
}
fn enforce_resize_for_horizontal_stack(&mut self) {
let resize_dimensions = self.resize_dimensions_mut();
match resize_dimensions.len() {
0 | 1 => self.enforce_no_resize(),
_ => {
if let Some(mut left) = resize_dimensions[0] {
left.top = 0;
left.left = 0;
left.right = 0;
}
let stack_size = resize_dimensions[1..].len();
for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {
if let Some(rect) = rect {
rect.bottom = 0;
if i == 0 {
rect.left = 0;
}
if i == stack_size - 1 {
rect.right = 0;
}
}
}
}
}
}
fn enforce_resize_for_ultrawide(&mut self) {
let resize_dimensions = self.resize_dimensions_mut();
match resize_dimensions.len() {
// Single window can not be resized at all
0 | 1 => self.enforce_no_resize(),
// Two windows can only be resized in the middle
2 => {
// Zero is actually on the right
if let Some(mut right) = resize_dimensions[0] {
right.top = 0;
right.bottom = 0;
right.right = 0;
}
// One is on the left
if let Some(mut left) = resize_dimensions[1] {
left.top = 0;
left.bottom = 0;
left.left = 0;
}
}
// Three or more windows means 0 is in center, 1 is at the left, 2.. are a vertical
// stack on the right
_ => {
// Central can be resized left or right
if let Some(mut right) = resize_dimensions[0] {
right.top = 0;
right.bottom = 0;
}
// Left one can only be resized to the right
if let Some(mut left) = resize_dimensions[1] {
left.top = 0;
left.bottom = 0;
left.left = 0;
}
// Handle stack on the right
let stack_size = resize_dimensions[2..].len();
for (i, rect) in resize_dimensions[2..].iter_mut().enumerate() {
if let Some(rect) = rect {
// No containers can resize to the right
rect.right = 0;
// First container in stack cant resize up
if i == 0 {
rect.top = 0;
} else if i == stack_size - 1 {
// Last cant be resized to the bottom
rect.bottom = 0;
}
}
}
}
}
}
fn enforce_no_resize(&mut self) {
for rect in self.resize_dimensions_mut().iter_mut().flatten() {
rect.left = 0;
rect.right = 0;
rect.top = 0;
rect.bottom = 0;
}
}
pub fn new_monocle_container(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
let container = self
.containers_mut()
.remove(focused_idx)
.ok_or_else(|| anyhow!("there is no container"))?;
// We don't remove any resize adjustments for a monocle, because when this container is
// inevitably reintegrated, it would be weird if it doesn't go back to the dimensions
// it had before
self.set_monocle_container(Option::from(container));
self.set_monocle_container_restore_idx(Option::from(focused_idx));
self.focus_previous_container();
self.monocle_container_mut()
.as_mut()
.ok_or_else(|| anyhow!("there is no monocle container"))?
.load_focused_window();
Ok(())
}
pub fn reintegrate_monocle_container(&mut self) -> Result<()> {
let restore_idx = self
.monocle_container_restore_idx()
.ok_or_else(|| anyhow!("there is no monocle restore index"))?;
let container = self
.monocle_container_mut()
.as_ref()
.ok_or_else(|| anyhow!("there is no monocle container"))?;
let container = container.clone();
if restore_idx >= self.containers().len() {
self.containers_mut()
.resize(restore_idx, Container::default());
}
self.containers_mut().insert(restore_idx, container);
self.focus_container(restore_idx);
self.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?
.load_focused_window();
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
Ok(())
}
pub fn new_maximized_window(&mut self) -> Result<()> {
let focused_idx = self.focused_container_idx();
let foreground_hwnd = WindowsApi::foreground_window()?;
let mut floating_window = None;
if !self.floating_windows().is_empty() {
let mut focused_floating_window_idx = None;
for (i, w) in self.floating_windows().iter().enumerate() {
if w.hwnd == foreground_hwnd {
focused_floating_window_idx = Option::from(i);
}
}
if let Some(idx) = focused_floating_window_idx {
floating_window = Option::from(self.floating_windows_mut().remove(idx));
}
}
if let Some(floating_window) = floating_window {
self.set_maximized_window(Option::from(floating_window));
self.set_maximized_window_restore_idx(Option::from(focused_idx));
if let Some(window) = self.maximized_window() {
window.maximize();
}
return Ok(());
}
let monocle_restore_idx = self.monocle_container_restore_idx();
if let Some(monocle_container) = self.monocle_container_mut() {
let window = monocle_container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if monocle_container.windows().is_empty() {
self.set_monocle_container(None);
self.set_monocle_container_restore_idx(None);
} else {
monocle_container.load_focused_window();
}
self.set_maximized_window(Option::from(window));
self.set_maximized_window_restore_idx(monocle_restore_idx);
if let Some(window) = self.maximized_window() {
window.maximize();
}
return Ok(());
}
let container = self
.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?;
let window = container
.remove_focused_window()
.ok_or_else(|| anyhow!("there is no window"))?;
if container.windows().is_empty() {
self.containers_mut().remove(focused_idx);
if self.resize_dimensions().get(focused_idx).is_some() {
self.resize_dimensions_mut().remove(focused_idx);
}
} else {
container.load_focused_window();
}
self.set_maximized_window(Option::from(window));
self.set_maximized_window_restore_idx(Option::from(focused_idx));
if let Some(window) = self.maximized_window() {
window.maximize();
}
self.focus_previous_container();
Ok(())
}
pub fn reintegrate_maximized_window(&mut self) -> Result<()> {
let restore_idx = self
.maximized_window_restore_idx()
.ok_or_else(|| anyhow!("there is no monocle restore index"))?;
let window = self
.maximized_window()
.as_ref()
.ok_or_else(|| anyhow!("there is no monocle container"))?;
let window = *window;
if !self.containers().is_empty() && restore_idx > self.containers().len() - 1 {
self.containers_mut()
.resize(restore_idx, Container::default());
}
let mut container = Container::default();
container.windows_mut().push_back(window);
self.containers_mut().insert(restore_idx, container);
self.focus_container(restore_idx);
self.focused_container_mut()
.ok_or_else(|| anyhow!("there is no container"))?
.load_focused_window();
self.set_maximized_window(None);
self.set_maximized_window_restore_idx(None);
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn focus_container(&mut self, idx: usize) {
tracing::info!("focusing container");
self.containers.focus(idx);
}
pub fn swap_containers(&mut self, i: usize, j: usize) {
self.containers.swap(i, j);
self.focus_container(j);
}
pub fn remove_focused_floating_window(&mut self) -> Option<Window> {
let hwnd = WindowsApi::foreground_window().ok()?;
let mut idx = None;
for (i, window) in self.floating_windows.iter().enumerate() {
if hwnd == window.hwnd {
idx = Option::from(i);
}
}
match idx {
None => None,
Some(idx) => {
if self.floating_windows.get(idx).is_some() {
Option::from(self.floating_windows_mut().remove(idx))
} else {
None
}
}
}
}
pub fn visible_windows(&self) -> Vec<Option<&Window>> {
let mut vec = vec![];
for container in self.containers() {
vec.push(container.focused_window());
}
vec
}
pub fn visible_window_details(&self) -> Vec<WindowDetails> {
let mut vec: Vec<WindowDetails> = vec![];
for container in self.containers() {
if let Some(focused) = container.focused_window() {
if let Ok(details) = (*focused).try_into() {
vec.push(details);
}
}
}
vec
}
pub fn visible_windows_mut(&mut self) -> Vec<Option<&mut Window>> {
let mut vec = vec![];
for container in self.containers_mut() {
vec.push(container.focused_window_mut());
}
vec
}
pub fn focus_previous_container(&mut self) {
let focused_idx = self.focused_container_idx();
if focused_idx != 0 {
self.focus_container(focused_idx - 1);
}
}
fn focus_last_container(&mut self) {
self.focus_container(self.containers().len() - 1);
}
}