use crate::{
core::{
client::Client,
data_types::{Change, Region, ResizeAction},
layout::{Layout, LayoutConf},
ring::{Direction, InsertPoint, Ring, Selector},
xconnection::Xid,
},
Result,
};
#[cfg(feature = "serde")]
use crate::{core::layout::LayoutFunc, PenroseError};
#[cfg(feature = "serde")]
use std::collections::HashMap;
pub(crate) struct ArrangeActions {
pub(crate) actions: Vec<ResizeAction>,
pub(crate) floating: Vec<Xid>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct Workspace {
name: String,
clients: Ring<Xid>,
layouts: Ring<Layout>,
}
impl Workspace {
pub fn new(name: impl Into<String>, layouts: Vec<Layout>) -> Self {
if layouts.is_empty() {
panic!("{}: require at least one layout function", name.into());
}
Self {
name: name.into(),
clients: Ring::new(Vec::new()),
layouts: Ring::new(layouts),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub(crate) fn set_name(&mut self, name: impl Into<String>) {
self.name = name.into();
}
#[cfg(feature = "serde")]
pub(crate) fn restore_layout_functions(
&mut self,
layout_funcs: &HashMap<&str, LayoutFunc>,
) -> Result<()> {
self.layouts.iter_mut().try_for_each(|layout| {
let s = &layout.symbol;
match layout_funcs.get(s.as_str()) {
Some(f) => {
layout.set_layout_function(*f);
Ok(())
}
None => Err(PenroseError::HydrationState(format!(
"'{}' is not a known layout symbol: {:?}",
layout.symbol,
layout_funcs.keys()
))),
}
})
}
pub fn len(&self) -> usize {
self.clients.len()
}
pub fn is_empty(&self) -> bool {
self.clients.len() == 0
}
pub fn iter(&self) -> std::collections::vec_deque::Iter<'_, Xid> {
self.clients.iter()
}
pub fn client_ids(&self) -> Vec<Xid> {
self.clients.as_vec()
}
pub fn focused_client(&self) -> Option<Xid> {
self.clients.focused().copied()
}
pub fn add_client(&mut self, id: Xid, ip: &InsertPoint) -> Result<()> {
let existing = self.clients.element(&Selector::Condition(&|c| *c == id));
if existing.is_some() {
return Err(perror!("{} is already in this workspace", id));
}
self.clients.insert_at(ip, id);
Ok(())
}
pub fn focus_client(&mut self, id: Xid) -> Option<Xid> {
let prev = self.clients.focused().copied();
self.clients.focus(&Selector::Condition(&|c| *c == id));
prev
}
pub fn remove_client(&mut self, id: Xid) -> Option<Xid> {
self.clients.remove(&Selector::Condition(&|c| *c == id))
}
pub fn remove_focused_client(&mut self) -> Option<Xid> {
self.clients.remove(&Selector::Focused)
}
pub(crate) fn arrange(
&self,
screen_region: Region,
managed_workspace_clients: &[&Client],
) -> ArrangeActions {
if self.clients.len() > 0 {
let layout = self.layouts.focused_unchecked();
let (floating, tiled): (Vec<&Client>, Vec<&Client>) =
managed_workspace_clients.iter().partition(|c| c.floating);
debug!(
layout = ?layout.symbol,
n_clients = tiled.len(),
name = ?self.name,
"applying layout",
);
ArrangeActions {
actions: layout.arrange(&tiled, self.focused_client(), &screen_region),
floating: floating.iter().map(|c| c.id()).collect(),
}
} else {
ArrangeActions {
actions: vec![],
floating: vec![],
}
}
}
pub fn try_set_layout(&mut self, symbol: &str) -> Option<&Layout> {
self.layouts
.focus(&Selector::Condition(&|l| l.symbol == symbol))
.map(|(_, layout)| layout)
}
pub fn cycle_layout(&mut self, direction: Direction) -> &str {
self.layouts.cycle_focus(direction);
self.layout_symbol()
}
pub fn layout_symbol(&self) -> &str {
&self.layouts.focused_unchecked().symbol
}
pub fn layout_conf(&self) -> LayoutConf {
self.layouts.focused_unchecked().conf
}
pub fn cycle_client(&mut self, direction: Direction) -> Option<(Xid, Xid)> {
if self.clients.len() < 2 {
return None; }
if !self.layout_conf().allow_wrapping && self.clients.would_wrap(direction) {
return None;
}
let prev = *self.clients.focused()?;
let new = *self.clients.cycle_focus(direction)?;
if prev != new {
Some((prev, new))
} else {
None
}
}
pub fn drag_client(&mut self, direction: Direction) -> Option<Xid> {
if !self.layout_conf().allow_wrapping && self.clients.would_wrap(direction) {
return None;
}
self.clients.drag_focused(direction).copied()
}
pub fn rotate_clients(&mut self, direction: Direction) {
self.clients.rotate(direction)
}
pub fn update_max_main(&mut self, change: Change) {
if let Some(layout) = self.layouts.focused_mut() {
layout.update_max_main(change);
}
}
pub fn update_main_ratio(&mut self, change: Change, step: f32) {
if let Some(layout) = self.layouts.focused_mut() {
layout.update_main_ratio(change, step);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{layout::*, ring::Direction, xconnection::MockXConn};
fn test_layouts() -> Vec<Layout> {
vec![Layout::new("t", LayoutConf::default(), mock_layout, 1, 0.6)]
}
fn add_n_clients(ws: &mut Workspace, n: usize) {
for i in 0..n {
let k = ((i + 1) * 10) as u32; ws.add_client(k, &InsertPoint::First).unwrap();
}
}
#[test]
fn ref_to_focused_client_when_empty() {
let ws = Workspace::new("test", test_layouts());
assert_eq!(ws.focused_client(), None);
}
#[test]
fn ref_to_focused_client_when_populated() {
let mut ws = Workspace::new("test", test_layouts());
ws.clients = Ring::new(vec![42, 123]);
let c = ws.focused_client().expect("should have had a client for 0");
assert_eq!(c, 42);
ws.clients.cycle_focus(Direction::Forward);
let c = ws.focused_client().expect("should have had a client for 1");
assert_eq!(c, 123);
}
#[test]
fn removing_a_client_when_present() {
let mut ws = Workspace::new("test", test_layouts());
ws.clients = Ring::new(vec![13, 42]);
let removed = ws
.remove_client(42)
.expect("should have had a client for id=42");
assert_eq!(removed, 42);
}
#[test]
fn removing_a_client_when_not_present() {
let mut ws = Workspace::new("test", test_layouts());
ws.clients = Ring::new(vec![13]);
let removed = ws.remove_client(42);
assert_eq!(removed, None, "got a client by the wrong ID");
}
#[test]
fn adding_a_client() {
let mut ws = Workspace::new("test", test_layouts());
add_n_clients(&mut ws, 3);
let ids: Vec<Xid> = ws.clients.iter().copied().collect();
assert_eq!(ids, vec![30, 20, 10], "not pushing at the top of the stack")
}
#[test]
fn applying_a_layout_gives_one_action_per_client() {
let mut ws = Workspace::new("test", test_layouts());
let conn = MockXConn::new(vec![], vec![], vec![]);
ws.clients = Ring::new(vec![1, 2, 3]);
let clients = vec![
Client::new(&conn, 1, 0, &[]),
Client::new(&conn, 2, 0, &[]),
Client::new(&conn, 3, 0, &[]),
];
let refs: Vec<&Client> = clients.iter().collect();
let res = ws.arrange(Region::new(0, 0, 2000, 1000), &refs[..]);
assert_eq!(res.actions.len(), 3, "actions are not 1-1 for clients")
}
#[test]
fn dragging_a_client_forward() {
let mut ws = Workspace::new("test", test_layouts());
ws.clients = Ring::new(vec![1, 2, 3, 4]);
assert_eq!(ws.focused_client(), Some(1));
assert_eq!(ws.drag_client(Direction::Forward), Some(1));
assert_eq!(ws.clients.as_vec(), vec![2, 1, 3, 4]);
assert_eq!(ws.drag_client(Direction::Forward), Some(1));
assert_eq!(ws.clients.as_vec(), vec![2, 3, 1, 4]);
assert_eq!(ws.drag_client(Direction::Forward), Some(1));
assert_eq!(ws.clients.as_vec(), vec![2, 3, 4, 1]);
assert_eq!(ws.drag_client(Direction::Forward), Some(1));
assert_eq!(ws.clients.as_vec(), vec![1, 2, 3, 4]);
assert_eq!(ws.focused_client(), Some(1));
}
#[test]
fn dragging_non_index_0_client_backward() {
let mut ws = Workspace::new("test", test_layouts());
ws.clients = Ring::new(vec![1, 2, 3, 4]);
ws.focus_client(3);
assert_eq!(ws.focused_client(), Some(3));
assert_eq!(ws.drag_client(Direction::Backward), Some(3));
assert_eq!(ws.clients.as_vec(), vec![1, 3, 2, 4]);
assert_eq!(ws.drag_client(Direction::Backward), Some(3));
assert_eq!(ws.clients.as_vec(), vec![3, 1, 2, 4]);
assert_eq!(ws.drag_client(Direction::Backward), Some(3));
assert_eq!(ws.clients.as_vec(), vec![1, 2, 4, 3]);
assert_eq!(ws.drag_client(Direction::Backward), Some(3));
assert_eq!(ws.clients.as_vec(), vec![1, 2, 3, 4]);
assert_eq!(ws.focused_client(), Some(3));
}
}