use crate::core::{
client::Client,
data_types::{Change, Region, ResizeAction},
xconnection::Xid,
};
use std::{cmp, fmt};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct LayoutConf {
pub floating: bool,
pub gapless: bool,
pub follow_focus: bool,
pub allow_wrapping: bool,
}
impl Default for LayoutConf {
fn default() -> Self {
Self {
floating: false,
gapless: false,
follow_focus: false,
allow_wrapping: true,
}
}
}
pub type LayoutFunc = fn(&[&Client], Option<Xid>, &Region, u32, f32) -> Vec<ResizeAction>;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone)]
pub struct Layout {
pub(crate) conf: LayoutConf,
pub(crate) symbol: String,
max_main: u32,
ratio: f32,
#[cfg_attr(feature = "serde", serde(skip))]
f: Option<LayoutFunc>,
}
impl cmp::PartialEq<Layout> for Layout {
fn eq(&self, other: &Layout) -> bool {
self.conf == other.conf
&& self.symbol == other.symbol
&& self.max_main == other.max_main
&& self.ratio == other.ratio
}
}
impl fmt::Debug for Layout {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Layout")
.field("kind", &self.conf)
.field("symbol", &self.symbol)
.field("max_main", &self.max_main)
.field("ratio", &self.ratio)
.field("f", &stringify!(&self.f))
.finish()
}
}
pub fn floating(_: &[&Client], _: Option<Xid>, _: &Region, _: u32, _: f32) -> Vec<ResizeAction> {
vec![]
}
impl Layout {
pub fn new(
symbol: impl Into<String>,
conf: LayoutConf,
f: LayoutFunc,
max_main: u32,
ratio: f32,
) -> Self {
Self {
symbol: symbol.into(),
conf,
max_main,
ratio,
f: Some(f),
}
}
pub fn floating(symbol: impl Into<String>) -> Self {
Self {
symbol: symbol.into(),
conf: LayoutConf {
floating: true,
gapless: false,
follow_focus: false,
allow_wrapping: true,
},
f: Some(floating),
max_main: 1,
ratio: 1.0,
}
}
#[cfg(feature = "serde")]
pub(crate) fn set_layout_function(&mut self, f: LayoutFunc) {
self.f = Some(f);
}
pub fn arrange(
&self,
clients: &[&Client],
focused: Option<Xid>,
r: &Region,
) -> Vec<ResizeAction> {
(self.f.expect("missing layout function"))(clients, focused, r, self.max_main, self.ratio)
}
pub fn update_max_main(&mut self, change: Change) {
match change {
Change::More => self.max_main += 1,
Change::Less => {
if self.max_main > 0 {
self.max_main -= 1;
}
}
}
}
pub fn update_main_ratio(&mut self, change: Change, step: f32) {
match change {
Change::More => self.ratio += step,
Change::Less => self.ratio -= step,
}
if self.ratio < 0.0 {
self.ratio = 0.0
} else if self.ratio > 1.0 {
self.ratio = 1.0;
}
}
}
pub fn client_breakdown<T>(clients: &[T], n_main: u32) -> (u32, u32) {
let n = clients.len() as u32;
if n <= n_main {
(n, 0)
} else {
(n_main, n - n_main)
}
}
#[cfg(test)]
pub(crate) fn mock_layout(
clients: &[&Client],
_: Option<Xid>,
region: &Region,
_: u32,
_: f32,
) -> Vec<ResizeAction> {
clients
.iter()
.enumerate()
.map(|(i, c)| {
let (x, y, w, h) = region.values();
let _k = i as u32;
(c.id(), Some(Region::new(x + _k, y + _k, w - _k, h - _k)))
})
.collect()
}
pub fn side_stack(
clients: &[&Client],
_: Option<Xid>,
monitor_region: &Region,
max_main: u32,
ratio: f32,
) -> Vec<ResizeAction> {
let n = clients.len() as u32;
if n <= max_main || max_main == 0 {
return monitor_region
.as_rows(n)
.iter()
.zip(clients)
.map(|(r, c)| (c.id(), Some(*r)))
.collect();
}
let split = ((monitor_region.w as f32) * ratio) as u32;
let (main, stack) = monitor_region.split_at_width(split).unwrap();
main.as_rows(max_main)
.into_iter()
.chain(stack.as_rows(n.saturating_sub(max_main)))
.zip(clients)
.map(|(r, c)| (c.id(), Some(r)))
.collect()
}
pub fn bottom_stack(
clients: &[&Client],
_: Option<Xid>,
monitor_region: &Region,
max_main: u32,
ratio: f32,
) -> Vec<ResizeAction> {
let n = clients.len() as u32;
if n <= max_main || max_main == 0 {
return monitor_region
.as_columns(n)
.iter()
.zip(clients)
.map(|(r, c)| (c.id(), Some(*r)))
.collect();
}
let split = ((monitor_region.h as f32) * ratio) as u32;
let (main, stack) = monitor_region.split_at_height(split).unwrap();
main.as_columns(max_main)
.into_iter()
.chain(stack.as_columns(n.saturating_sub(max_main)))
.zip(clients)
.map(|(r, c)| (c.id(), Some(r)))
.collect()
}
pub fn monocle(
clients: &[&Client],
focused: Option<Xid>,
monitor_region: &Region,
_: u32,
_: f32,
) -> Vec<ResizeAction> {
if let Some(fid) = focused {
let (mx, my, mw, mh) = monitor_region.values();
clients
.iter()
.map(|c| {
let cid = c.id();
if cid == fid {
(cid, Some(Region::new(mx, my, mw, mh)))
} else {
(cid, None)
}
})
.collect()
} else {
Vec::new()
}
}