mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-19 07:54:04 +01:00
Use the same `add_container_with_direction` function on `move_container_to_workspace` as it is being used on `move_container_to_monitor` or `move_container_in_direction`. This way we bring parity between all methods and make it easier to change the way a container is added on a monitor workspace when taking the move direction into consideration.
368 lines
12 KiB
Rust
368 lines
12 KiB
Rust
use std::collections::HashMap;
|
|
use std::collections::VecDeque;
|
|
|
|
use color_eyre::eyre::anyhow;
|
|
use color_eyre::eyre::bail;
|
|
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 crate::core::Rect;
|
|
|
|
use crate::container::Container;
|
|
use crate::ring::Ring;
|
|
use crate::workspace::Workspace;
|
|
use crate::DefaultLayout;
|
|
use crate::Layout;
|
|
use crate::OperationDirection;
|
|
use crate::WindowsApi;
|
|
|
|
#[derive(
|
|
Debug,
|
|
Clone,
|
|
Serialize,
|
|
Deserialize,
|
|
Getters,
|
|
CopyGetters,
|
|
MutGetters,
|
|
Setters,
|
|
JsonSchema,
|
|
PartialEq,
|
|
)]
|
|
pub struct Monitor {
|
|
#[getset(get_copy = "pub", set = "pub")]
|
|
id: isize,
|
|
#[getset(get = "pub", set = "pub")]
|
|
name: String,
|
|
#[getset(get = "pub", set = "pub")]
|
|
device: String,
|
|
#[getset(get = "pub", set = "pub")]
|
|
device_id: String,
|
|
#[getset(get = "pub", set = "pub")]
|
|
size: Rect,
|
|
#[getset(get = "pub", set = "pub")]
|
|
work_area_size: Rect,
|
|
#[getset(get_copy = "pub", set = "pub")]
|
|
work_area_offset: Option<Rect>,
|
|
#[getset(get_copy = "pub", set = "pub")]
|
|
window_based_work_area_offset: Option<Rect>,
|
|
#[getset(get_copy = "pub", set = "pub")]
|
|
window_based_work_area_offset_limit: isize,
|
|
workspaces: Ring<Workspace>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[getset(get_copy = "pub", set = "pub")]
|
|
last_focused_workspace: Option<usize>,
|
|
#[getset(get_mut = "pub")]
|
|
workspace_names: HashMap<usize, String>,
|
|
}
|
|
|
|
impl_ring_elements!(Monitor, Workspace);
|
|
|
|
pub fn new(
|
|
id: isize,
|
|
size: Rect,
|
|
work_area_size: Rect,
|
|
name: String,
|
|
device: String,
|
|
device_id: String,
|
|
) -> Monitor {
|
|
let mut workspaces = Ring::default();
|
|
workspaces.elements_mut().push_back(Workspace::default());
|
|
|
|
Monitor {
|
|
id,
|
|
name,
|
|
device,
|
|
device_id,
|
|
size,
|
|
work_area_size,
|
|
work_area_offset: None,
|
|
window_based_work_area_offset: None,
|
|
window_based_work_area_offset_limit: 1,
|
|
workspaces,
|
|
last_focused_workspace: None,
|
|
workspace_names: HashMap::default(),
|
|
}
|
|
}
|
|
|
|
impl Monitor {
|
|
pub fn placeholder() -> Self {
|
|
Self {
|
|
id: 0,
|
|
name: "PLACEHOLDER".to_string(),
|
|
device: "".to_string(),
|
|
device_id: "".to_string(),
|
|
size: Default::default(),
|
|
work_area_size: Default::default(),
|
|
work_area_offset: None,
|
|
window_based_work_area_offset: None,
|
|
window_based_work_area_offset_limit: 0,
|
|
workspaces: Default::default(),
|
|
last_focused_workspace: None,
|
|
workspace_names: Default::default(),
|
|
}
|
|
}
|
|
pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
|
let focused_idx = self.focused_workspace_idx();
|
|
for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {
|
|
if i == focused_idx {
|
|
workspace.restore(mouse_follows_focus)?;
|
|
} else {
|
|
workspace.hide(None);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_container(
|
|
&mut self,
|
|
container: Container,
|
|
workspace_idx: Option<usize>,
|
|
) -> Result<()> {
|
|
let workspace = if let Some(idx) = workspace_idx {
|
|
self.workspaces_mut()
|
|
.get_mut(idx)
|
|
.ok_or_else(|| anyhow!("there is no workspace at index {}", idx))?
|
|
} else {
|
|
self.focused_workspace_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?
|
|
};
|
|
|
|
workspace.add_container_to_back(container);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Adds a container to this `Monitor` using the move direction to calculate if the container
|
|
/// should be added in front of all containers, in the back or in place of the focused
|
|
/// container, moving the rest along. The move direction should be from the origin monitor
|
|
/// towards the target monitor or from the origin workspace towards the target workspace.
|
|
pub fn add_container_with_direction(
|
|
&mut self,
|
|
container: Container,
|
|
workspace_idx: Option<usize>,
|
|
direction: OperationDirection,
|
|
) -> Result<()> {
|
|
let workspace = if let Some(idx) = workspace_idx {
|
|
self.workspaces_mut()
|
|
.get_mut(idx)
|
|
.ok_or_else(|| anyhow!("there is no workspace at index {}", idx))?
|
|
} else {
|
|
self.focused_workspace_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?
|
|
};
|
|
|
|
match direction {
|
|
OperationDirection::Left => {
|
|
// insert the container into the workspace on the monitor at the back (or rightmost position)
|
|
// if we are moving across a boundary to the left (back = right side of the target)
|
|
match workspace.layout() {
|
|
Layout::Default(layout) => match layout {
|
|
DefaultLayout::RightMainVerticalStack => {
|
|
workspace.add_container_to_front(container);
|
|
}
|
|
DefaultLayout::UltrawideVerticalStack => {
|
|
if workspace.containers().len() == 1 {
|
|
workspace.insert_container_at_idx(0, container);
|
|
} else {
|
|
workspace.add_container_to_back(container);
|
|
}
|
|
}
|
|
_ => {
|
|
workspace.add_container_to_back(container);
|
|
}
|
|
},
|
|
Layout::Custom(_) => {
|
|
workspace.add_container_to_back(container);
|
|
}
|
|
}
|
|
}
|
|
OperationDirection::Right => {
|
|
// insert the container into the workspace on the monitor at the front (or leftmost position)
|
|
// if we are moving across a boundary to the right (front = left side of the target)
|
|
match workspace.layout() {
|
|
Layout::Default(layout) => {
|
|
let target_index = layout.leftmost_index(workspace.containers().len());
|
|
|
|
match layout {
|
|
DefaultLayout::RightMainVerticalStack
|
|
| DefaultLayout::UltrawideVerticalStack => {
|
|
if workspace.containers().len() == 1 {
|
|
workspace.add_container_to_back(container);
|
|
} else {
|
|
workspace.insert_container_at_idx(target_index, container);
|
|
}
|
|
}
|
|
_ => {
|
|
workspace.insert_container_at_idx(target_index, container);
|
|
}
|
|
}
|
|
}
|
|
Layout::Custom(_) => {
|
|
workspace.add_container_to_front(container);
|
|
}
|
|
}
|
|
}
|
|
OperationDirection::Up | OperationDirection::Down => {
|
|
// insert the container into the workspace on the monitor at the position
|
|
// where the currently focused container on that workspace is
|
|
workspace.insert_container_at_idx(workspace.focused_container_idx(), container);
|
|
}
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn remove_workspace_by_idx(&mut self, idx: usize) -> Option<Workspace> {
|
|
if idx < self.workspaces().len() {
|
|
return self.workspaces_mut().remove(idx);
|
|
}
|
|
|
|
if idx == 0 {
|
|
self.workspaces_mut().push_back(Workspace::default());
|
|
} else {
|
|
self.focus_workspace(idx.saturating_sub(1)).ok()?;
|
|
};
|
|
|
|
None
|
|
}
|
|
|
|
pub fn ensure_workspace_count(&mut self, ensure_count: usize) {
|
|
if self.workspaces().len() < ensure_count {
|
|
self.workspaces_mut()
|
|
.resize(ensure_count, Workspace::default());
|
|
}
|
|
}
|
|
|
|
pub fn remove_workspaces(&mut self) -> VecDeque<Workspace> {
|
|
self.workspaces_mut().drain(..).collect()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_container_to_workspace(
|
|
&mut self,
|
|
target_workspace_idx: usize,
|
|
follow: bool,
|
|
direction: Option<OperationDirection>,
|
|
) -> Result<()> {
|
|
let workspace = self
|
|
.focused_workspace_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
|
|
|
if workspace.maximized_window().is_some() {
|
|
bail!("cannot move native maximized window to another monitor or workspace");
|
|
}
|
|
|
|
let foreground_hwnd = WindowsApi::foreground_window()?;
|
|
let floating_window_index = workspace
|
|
.floating_windows()
|
|
.iter()
|
|
.position(|w| w.hwnd == foreground_hwnd);
|
|
|
|
if let Some(idx) = floating_window_index {
|
|
let window = workspace.floating_windows_mut().remove(idx);
|
|
|
|
let workspaces = self.workspaces_mut();
|
|
#[allow(clippy::option_if_let_else)]
|
|
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
|
None => {
|
|
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
|
workspaces.get_mut(target_workspace_idx).unwrap()
|
|
}
|
|
Some(workspace) => workspace,
|
|
};
|
|
|
|
target_workspace.floating_windows_mut().push(window);
|
|
} else {
|
|
let container = workspace
|
|
.remove_focused_container()
|
|
.ok_or_else(|| anyhow!("there is no container"))?;
|
|
|
|
let workspaces = self.workspaces_mut();
|
|
|
|
#[allow(clippy::option_if_let_else)]
|
|
let target_workspace = match workspaces.get_mut(target_workspace_idx) {
|
|
None => {
|
|
workspaces.resize(target_workspace_idx + 1, Workspace::default());
|
|
workspaces.get_mut(target_workspace_idx).unwrap()
|
|
}
|
|
Some(workspace) => workspace,
|
|
};
|
|
|
|
if let Some(direction) = direction {
|
|
self.add_container_with_direction(
|
|
container,
|
|
Some(target_workspace_idx),
|
|
direction,
|
|
)?;
|
|
} else {
|
|
target_workspace.add_container_to_back(container);
|
|
}
|
|
}
|
|
|
|
if follow {
|
|
self.focus_workspace(target_workspace_idx)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_workspace(&mut self, idx: usize) -> Result<()> {
|
|
tracing::info!("focusing workspace");
|
|
|
|
{
|
|
let workspaces = self.workspaces_mut();
|
|
|
|
if workspaces.get(idx).is_none() {
|
|
workspaces.resize(idx + 1, Workspace::default());
|
|
}
|
|
|
|
self.workspaces.focus(idx);
|
|
}
|
|
|
|
// Always set the latest known name when creating the workspace for the first time
|
|
{
|
|
let name = { self.workspace_names.get(&idx).cloned() };
|
|
if name.is_some() {
|
|
self.workspaces_mut()
|
|
.get_mut(idx)
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?
|
|
.set_name(name);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn new_workspace_idx(&self) -> usize {
|
|
self.workspaces().len()
|
|
}
|
|
|
|
pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> Result<()> {
|
|
let work_area = *self.work_area_size();
|
|
let window_based_work_area_offset = (
|
|
self.window_based_work_area_offset_limit(),
|
|
self.window_based_work_area_offset(),
|
|
);
|
|
|
|
let offset = if self.work_area_offset().is_some() {
|
|
self.work_area_offset()
|
|
} else {
|
|
offset
|
|
};
|
|
|
|
self.focused_workspace_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?
|
|
.update(&work_area, offset, window_based_work_area_offset)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|