use crate::{
builtin::layout::messages::{ExpandMain, IncMain, Mirror, Rotate, ShrinkMain},
core::layout::{Layout, Message},
pure::{geometry::Rect, Stack},
Xid,
};
pub mod messages;
pub mod transformers;
#[cfg(test)]
pub mod quickcheck_tests;
#[derive(Debug, Clone, Copy)]
enum StackPosition {
Side,
Bottom,
}
impl StackPosition {
fn rotate(&self) -> Self {
match self {
StackPosition::Side => StackPosition::Bottom,
StackPosition::Bottom => StackPosition::Side,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MainAndStack {
pos: StackPosition,
max_main: u32,
ratio: f32,
ratio_step: f32,
mirrored: bool,
}
impl Default for MainAndStack {
fn default() -> Self {
Self {
pos: StackPosition::Side,
max_main: 1,
ratio: 0.6,
ratio_step: 0.1,
mirrored: false,
}
}
}
impl MainAndStack {
pub fn boxed_default() -> Box<dyn Layout> {
Box::<Self>::default()
}
pub fn boxed_default_rotated() -> Box<dyn Layout> {
let mut l = Self::default();
l.rotate();
Box::new(l)
}
pub fn side(max_main: u32, ratio: f32, ratio_step: f32) -> Box<dyn Layout> {
Box::new(Self::side_unboxed(max_main, ratio, ratio_step, false))
}
pub fn side_mirrored(max_main: u32, ratio: f32, ratio_step: f32) -> Box<dyn Layout> {
Box::new(Self::side_unboxed(max_main, ratio, ratio_step, true))
}
pub fn side_unboxed(max_main: u32, ratio: f32, ratio_step: f32, mirrored: bool) -> Self {
Self {
pos: StackPosition::Side,
max_main,
ratio,
ratio_step,
mirrored,
}
}
pub fn bottom(max_main: u32, ratio: f32, ratio_step: f32) -> Box<dyn Layout> {
Box::new(Self::bottom_unboxed(max_main, ratio, ratio_step, false))
}
pub fn top(max_main: u32, ratio: f32, ratio_step: f32) -> Box<dyn Layout> {
Box::new(Self::bottom_unboxed(max_main, ratio, ratio_step, true))
}
pub fn bottom_unboxed(max_main: u32, ratio: f32, ratio_step: f32, mirrored: bool) -> Self {
Self {
pos: StackPosition::Bottom,
max_main,
ratio,
ratio_step,
mirrored,
}
}
pub fn rotate(&mut self) {
self.pos = self.pos.rotate();
}
fn ratio(&self) -> f32 {
if self.mirrored {
1.0 - self.ratio
} else {
self.ratio
}
}
fn all_windows_in_single_stack(&self, n: u32) -> bool {
n <= self.max_main || self.max_main == 0 || self.ratio == 1.0 || self.ratio == 0.0
}
fn layout_side(&self, s: &Stack<Xid>, r: Rect) -> Vec<(Xid, Rect)> {
let n = s.len() as u32;
if self.all_windows_in_single_stack(n) {
r.as_rows(n).iter().zip(s).map(|(r, c)| (*c, *r)).collect()
} else {
let (mut main, mut stack) = r
.split_at_width_perc(self.ratio())
.expect("split point to be valid");
if self.mirrored {
(main, stack) = (stack, main);
}
main.as_rows(self.max_main)
.into_iter()
.chain(stack.as_rows(n.saturating_sub(self.max_main)))
.zip(s)
.map(|(r, c)| (*c, r))
.collect()
}
}
fn layout_bottom(&self, s: &Stack<Xid>, r: Rect) -> Vec<(Xid, Rect)> {
let n = s.len() as u32;
if self.all_windows_in_single_stack(n) {
r.as_columns(n)
.iter()
.zip(s)
.map(|(r, c)| (*c, *r))
.collect()
} else {
let (mut main, mut stack) = r
.split_at_height_perc(self.ratio())
.expect("split point to be valid");
if self.mirrored {
(main, stack) = (stack, main);
}
main.as_columns(self.max_main)
.into_iter()
.chain(stack.as_columns(n.saturating_sub(self.max_main)))
.zip(s)
.map(|(r, c)| (*c, r))
.collect()
}
}
}
impl Layout for MainAndStack {
fn name(&self) -> String {
match (self.pos, self.mirrored) {
(StackPosition::Side, false) => "Side".to_owned(),
(StackPosition::Side, true) => "Mirror".to_owned(),
(StackPosition::Bottom, false) => "Bottom".to_owned(),
(StackPosition::Bottom, true) => "Top".to_owned(),
}
}
fn boxed_clone(&self) -> Box<dyn Layout> {
Box::new(*self)
}
fn layout(&mut self, s: &Stack<Xid>, r: Rect) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>) {
let positions = match self.pos {
StackPosition::Side => self.layout_side(s, r),
StackPosition::Bottom => self.layout_bottom(s, r),
};
(None, positions)
}
fn handle_message(&mut self, m: &Message) -> Option<Box<dyn Layout>> {
if let Some(&ExpandMain) = m.downcast_ref() {
self.ratio += self.ratio_step;
if self.ratio > 1.0 {
self.ratio = 1.0;
}
} else if let Some(&ShrinkMain) = m.downcast_ref() {
self.ratio -= self.ratio_step;
if self.ratio < 0.0 {
self.ratio = 0.0;
}
} else if let Some(&IncMain(n)) = m.downcast_ref() {
if n < 0 {
self.max_main = self.max_main.saturating_sub((-n) as u32);
} else {
self.max_main += n as u32;
}
} else if let Some(&Mirror) = m.downcast_ref() {
self.mirrored = !self.mirrored;
} else if let Some(&Rotate) = m.downcast_ref() {
self.rotate();
}
None
}
}
#[derive(Debug, Clone, Copy)]
pub struct CenteredMain {
pos: StackPosition,
max_main: u32,
ratio: f32,
ratio_step: f32,
}
impl Default for CenteredMain {
fn default() -> Self {
Self {
pos: StackPosition::Side,
max_main: 1,
ratio: 0.6,
ratio_step: 0.1,
}
}
}
impl CenteredMain {
pub fn boxed_default() -> Box<dyn Layout> {
Box::<Self>::default()
}
pub fn boxed_default_rotated() -> Box<dyn Layout> {
let mut l = Self::default();
l.rotate();
Box::new(l)
}
pub fn vertical(max_main: u32, ratio: f32, ratio_step: f32) -> Box<dyn Layout> {
Box::new(Self::vertical_unboxed(max_main, ratio, ratio_step))
}
pub fn vertical_unboxed(max_main: u32, ratio: f32, ratio_step: f32) -> Self {
Self {
pos: StackPosition::Side,
max_main,
ratio,
ratio_step,
}
}
pub fn horizontal(max_main: u32, ratio: f32, ratio_step: f32) -> Box<dyn Layout> {
Box::new(Self::horizontal_unboxed(max_main, ratio, ratio_step))
}
pub fn horizontal_unboxed(max_main: u32, ratio: f32, ratio_step: f32) -> Self {
Self {
pos: StackPosition::Bottom,
max_main,
ratio,
ratio_step,
}
}
pub fn rotate(&mut self) {
self.pos = self.pos.rotate();
}
fn single_stack(&self, n: u32) -> bool {
n <= self.max_main || self.ratio == 1.0 || self.ratio == 0.0
}
fn layout_vertical(&self, s: &Stack<Xid>, r: Rect) -> Vec<(Xid, Rect)> {
let n = s.len() as u32;
if self.single_stack(n) {
r.as_rows(n).iter().zip(s).map(|(r, c)| (*c, *r)).collect()
} else if n.saturating_sub(self.max_main) == 1 {
let (main, stack) = r
.split_at_width_perc(self.ratio)
.expect("split point to be valid");
main.as_rows(self.max_main)
.into_iter()
.chain(std::iter::once(stack))
.zip(s)
.map(|(r, c)| (*c, r))
.collect()
} else {
let n_right = n.saturating_sub(self.max_main) / 2;
let n_left = n.saturating_sub(n_right).saturating_sub(self.max_main);
let (left_and_main, right) = r
.split_at_width_perc(0.5 + self.ratio / 2.0)
.expect("split point to be valid");
let (left, main) = left_and_main
.split_at_width(right.w)
.expect("split point to be valid");
main.as_rows(self.max_main)
.into_iter()
.chain(left.as_rows(n_left))
.chain(right.as_rows(n_right))
.zip(s)
.map(|(r, c)| (*c, r))
.collect()
}
}
fn layout_horizontal(&self, s: &Stack<Xid>, r: Rect) -> Vec<(Xid, Rect)> {
let n = s.len() as u32;
if self.single_stack(n) {
r.as_columns(n)
.iter()
.zip(s)
.map(|(r, c)| (*c, *r))
.collect()
} else if n.saturating_sub(self.max_main) == 1 {
let (main, stack) = r
.split_at_height_perc(1.0 - self.ratio)
.expect("split point to be valid");
main.as_columns(self.max_main)
.into_iter()
.chain(std::iter::once(stack))
.zip(s)
.map(|(r, c)| (*c, r))
.collect()
} else {
let n_top = n.saturating_sub(self.max_main) / 2;
let n_bottom = n.saturating_sub(n_top).saturating_sub(self.max_main);
let (top_and_main, bottom) = r
.split_at_height_perc(0.5 + self.ratio / 2.0)
.expect("split point to be valid");
let (top, main) = top_and_main
.split_at_height(bottom.h)
.expect("split point to be valid");
main.as_columns(self.max_main)
.into_iter()
.chain(top.as_columns(n_top))
.chain(bottom.as_columns(n_bottom))
.zip(s)
.map(|(r, c)| (*c, r))
.collect()
}
}
}
impl Layout for CenteredMain {
fn name(&self) -> String {
match self.pos {
StackPosition::Side => "Center|".to_owned(),
StackPosition::Bottom => "Center-".to_owned(),
}
}
fn boxed_clone(&self) -> Box<dyn Layout> {
Box::new(*self)
}
fn layout(&mut self, s: &Stack<Xid>, r: Rect) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>) {
let positions = match self.pos {
StackPosition::Side => self.layout_vertical(s, r),
StackPosition::Bottom => self.layout_horizontal(s, r),
};
(None, positions)
}
fn handle_message(&mut self, m: &Message) -> Option<Box<dyn Layout>> {
if let Some(&ExpandMain) = m.downcast_ref() {
self.ratio += self.ratio_step;
if self.ratio > 1.0 {
self.ratio = 1.0;
}
} else if let Some(&ShrinkMain) = m.downcast_ref() {
self.ratio -= self.ratio_step;
if self.ratio < 0.0 {
self.ratio = 0.0;
}
} else if let Some(&IncMain(n)) = m.downcast_ref() {
if n < 0 {
self.max_main = self.max_main.saturating_sub((-n) as u32);
} else {
self.max_main += n as u32;
}
} else if let Some(&Rotate) = m.downcast_ref() {
self.rotate();
}
None
}
}
#[derive(Debug, Clone, Copy)]
pub struct Monocle;
impl Monocle {
pub fn boxed() -> Box<dyn Layout> {
Box::new(Monocle)
}
}
impl Layout for Monocle {
fn name(&self) -> String {
"Mono".to_owned()
}
fn boxed_clone(&self) -> Box<dyn Layout> {
Self::boxed()
}
fn layout(&mut self, s: &Stack<Xid>, r: Rect) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>) {
(None, vec![(s.focus, r)])
}
fn handle_message(&mut self, _: &Message) -> Option<Box<dyn Layout>> {
None
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct Grid;
impl Grid {
pub fn boxed() -> Box<dyn Layout> {
Box::new(Grid)
}
}
impl Layout for Grid {
fn name(&self) -> String {
"Grid".to_string()
}
fn boxed_clone(&self) -> Box<dyn Layout> {
Self::boxed()
}
fn layout(&mut self, s: &Stack<Xid>, r: Rect) -> (Option<Box<dyn Layout>>, Vec<(Xid, Rect)>) {
let n = s.len();
let n_cols = (1..).find(|&i| (i * i) >= n).unwrap_or(1);
let n_rows = if n_cols * (n_cols - 1) >= n {
n_cols - 1
} else {
n_cols
};
let rects = r
.as_rows(n_rows as u32)
.into_iter()
.flat_map(|row| row.as_columns(n_cols as u32));
let positions = s.iter().zip(rects).map(|(&id, r)| (id, r)).collect();
(None, positions)
}
fn handle_message(&mut self, _: &Message) -> Option<Box<dyn Layout>> {
None
}
}
#[cfg(test)]
mod tests {
use crate::{
builtin::layout::{messages::IncMain, *},
core::layout::IntoMessage,
};
#[test]
fn message_handling() {
let mut l = MainAndStack::side_unboxed(1, 0.6, 0.1, false);
l.handle_message(&IncMain(2).into_message());
assert_eq!(l.max_main, 3);
}
}