{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.MultiColumns
-- Description :  A layout that tiles windows in a growing number of columns.
-- Copyright   :  (c) Anders Engstrom <ankaan@gmail.com>
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Anders Engstrom <ankaan@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- This layout tiles windows in a growing number of columns. The number of
-- windows in each column can be controlled by messages.
-----------------------------------------------------------------------------

module XMonad.Layout.MultiColumns (
                              -- * Usage
                              -- $usage

                              multiCol,
                              MultiCol,
                             ) where

import XMonad
import qualified XMonad.StackSet as W

import XMonad.Prelude

-- $usage
-- You can use this module with the following in your @xmonad.hs@:
--
-- > import XMonad.Layout.MultiColumns
--
-- Then edit your @layoutHook@ by adding the multiCol layout:
--
-- > myLayouts = multiCol [1] 4 0.01 0.5 ||| etc..
-- > main = xmonad def { layoutHook = myLayouts }
--
-- Or alternatively:
--
-- > myLayouts = Mirror (multiCol [1] 2 0.01 (-0.25)) ||| etc..
-- > main = xmonad def { layoutHook = myLayouts }
--
-- The maximum number of windows in a column can be controlled using the
-- IncMasterN messages and the column containing the focused window will be
-- modified. If the value is 0, all remaining windows will be placed in that
-- column when all columns before that has been filled.
--
-- The size can be set to between 1 and -0.5. If the value is positive, the
-- master column will be of that size. The rest of the screen is split among
-- the other columns. But if the size is negative, it instead indicates the
-- size of all non-master columns and the master column will cover the rest of
-- the screen. If the master column would become smaller than the other
-- columns, the screen is instead split equally among all columns. Therefore,
-- if equal size among all columns are desired, set the size to -0.5.
--
-- For more detailed instructions on editing the layoutHook see
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial> and
-- "XMonad.Doc.Extending#Editing_the_layout_hook".

-- | Layout constructor.
multiCol
  :: [Int]    -- ^ Windows in each column, starting with master. Set to 0 to catch the rest.
  -> Int      -- ^ Default value for all following columns.
  -> Rational -- ^ How much to change size each time.
  -> Rational -- ^ Initial size of master area, or column area if the size is negative.
  -> MultiCol a
multiCol :: forall a. [Int] -> Int -> Rational -> Rational -> MultiCol a
multiCol [Int]
n Int
defn Rational
ds Rational
s = [Int] -> Int -> Rational -> Rational -> Int -> MultiCol a
forall a. [Int] -> Int -> Rational -> Rational -> Int -> MultiCol a
MultiCol ((Int -> Int) -> [Int] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0) [Int]
n) (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 Int
defn) Rational
ds Rational
s Int
0

data MultiCol a = MultiCol
  { forall a. MultiCol a -> [Int]
multiColNWin      :: ![Int]
  , forall a. MultiCol a -> Int
multiColDefWin    :: !Int
  , forall a. MultiCol a -> Rational
multiColDeltaSize :: !Rational
  , forall a. MultiCol a -> Rational
multiColSize      :: !Rational
  , forall a. MultiCol a -> Int
multiColActive    :: !Int
  } deriving (Int -> MultiCol a -> ShowS
[MultiCol a] -> ShowS
MultiCol a -> String
(Int -> MultiCol a -> ShowS)
-> (MultiCol a -> String)
-> ([MultiCol a] -> ShowS)
-> Show (MultiCol a)
forall a. Int -> MultiCol a -> ShowS
forall a. [MultiCol a] -> ShowS
forall a. MultiCol a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: forall a. Int -> MultiCol a -> ShowS
showsPrec :: Int -> MultiCol a -> ShowS
$cshow :: forall a. MultiCol a -> String
show :: MultiCol a -> String
$cshowList :: forall a. [MultiCol a] -> ShowS
showList :: [MultiCol a] -> ShowS
Show,ReadPrec [MultiCol a]
ReadPrec (MultiCol a)
Int -> ReadS (MultiCol a)
ReadS [MultiCol a]
(Int -> ReadS (MultiCol a))
-> ReadS [MultiCol a]
-> ReadPrec (MultiCol a)
-> ReadPrec [MultiCol a]
-> Read (MultiCol a)
forall a. ReadPrec [MultiCol a]
forall a. ReadPrec (MultiCol a)
forall a. Int -> ReadS (MultiCol a)
forall a. ReadS [MultiCol a]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: forall a. Int -> ReadS (MultiCol a)
readsPrec :: Int -> ReadS (MultiCol a)
$creadList :: forall a. ReadS [MultiCol a]
readList :: ReadS [MultiCol a]
$creadPrec :: forall a. ReadPrec (MultiCol a)
readPrec :: ReadPrec (MultiCol a)
$creadListPrec :: forall a. ReadPrec [MultiCol a]
readListPrec :: ReadPrec [MultiCol a]
Read,MultiCol a -> MultiCol a -> Bool
(MultiCol a -> MultiCol a -> Bool)
-> (MultiCol a -> MultiCol a -> Bool) -> Eq (MultiCol a)
forall a. MultiCol a -> MultiCol a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: forall a. MultiCol a -> MultiCol a -> Bool
== :: MultiCol a -> MultiCol a -> Bool
$c/= :: forall a. MultiCol a -> MultiCol a -> Bool
/= :: MultiCol a -> MultiCol a -> Bool
Eq)

instance LayoutClass MultiCol a where
    doLayout :: MultiCol a
-> Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (MultiCol a))
doLayout MultiCol a
l Rectangle
r Stack a
s = ([(a, Rectangle)], Maybe (MultiCol a))
-> X ([(a, Rectangle)], Maybe (MultiCol a))
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (Stack a -> [Rectangle] -> [(a, Rectangle)]
forall {a} {b}. Stack a -> [b] -> [(a, b)]
combine Stack a
s [Rectangle]
rlist, Maybe (MultiCol a)
forall {a}. Maybe (MultiCol a)
resl)
        where rlist :: [Rectangle]
rlist = [Int] -> Rational -> Rectangle -> Int -> [Rectangle]
doL (MultiCol Any -> [Int]
forall a. MultiCol a -> [Int]
multiColNWin MultiCol Any
forall {a}. MultiCol a
l') (MultiCol Any -> Rational
forall a. MultiCol a -> Rational
multiColSize MultiCol Any
forall {a}. MultiCol a
l') Rectangle
r Int
wlen
              wlen :: Int
wlen = [a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([a] -> Int) -> [a] -> Int
forall a b. (a -> b) -> a -> b
$ Stack a -> [a]
forall a. Stack a -> [a]
W.integrate Stack a
s
              -- Make sure the list of columns is big enough and update active column
              nw :: [Int]
nw = MultiCol a -> [Int]
forall a. MultiCol a -> [Int]
multiColNWin MultiCol a
l [Int] -> [Int] -> [Int]
forall a. [a] -> [a] -> [a]
++ Int -> [Int]
forall a. a -> [a]
repeat (MultiCol a -> Int
forall a. MultiCol a -> Int
multiColDefWin MultiCol a
l)
              l' :: MultiCol a
l' = MultiCol a
l { multiColNWin = take (max (length $ multiColNWin l) $ getCol (wlen-1) nw + 1) nw
                     , multiColActive = getCol (length $ W.up s) nw
                     }
              -- Only return new layout if it has been modified
              resl :: Maybe (MultiCol a)
resl = if MultiCol a
forall {a}. MultiCol a
l'MultiCol a -> MultiCol a -> Bool
forall a. Eq a => a -> a -> Bool
==MultiCol a
l
                     then Maybe (MultiCol a)
forall a. Maybe a
Nothing
                     else MultiCol a -> Maybe (MultiCol a)
forall a. a -> Maybe a
Just MultiCol a
forall {a}. MultiCol a
l'
              combine :: Stack a -> [b] -> [(a, b)]
combine (W.Stack a
foc [a]
left [a]
right) [b]
rs = [a] -> [b] -> [(a, b)]
forall a b. [a] -> [b] -> [(a, b)]
zip (a
foc a -> [a] -> [a]
forall a. a -> [a] -> [a]
: [a] -> [a]
forall a. [a] -> [a]
reverse [a]
left [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a]
right) ([b] -> [(a, b)]) -> [b] -> [(a, b)]
forall a b. (a -> b) -> a -> b
$ Int -> [b] -> [b]
forall a. Int -> [a] -> [a]
raiseFocused ([a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
left) [b]
rs
    handleMessage :: MultiCol a -> SomeMessage -> X (Maybe (MultiCol a))
handleMessage MultiCol a
l SomeMessage
m =
        Maybe (MultiCol a) -> X (Maybe (MultiCol a))
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe (MultiCol a) -> X (Maybe (MultiCol a)))
-> Maybe (MultiCol a) -> X (Maybe (MultiCol a))
forall a b. (a -> b) -> a -> b
$ [Maybe (MultiCol a)] -> Maybe (MultiCol a)
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum [(Resize -> MultiCol a) -> Maybe Resize -> Maybe (MultiCol a)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Resize -> MultiCol a
forall {a}. Resize -> MultiCol a
resize     (SomeMessage -> Maybe Resize
forall m. Message m => SomeMessage -> Maybe m
fromMessage SomeMessage
m)
                      ,(IncMasterN -> MultiCol a)
-> Maybe IncMasterN -> Maybe (MultiCol a)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap IncMasterN -> MultiCol a
forall {a}. IncMasterN -> MultiCol a
incmastern (SomeMessage -> Maybe IncMasterN
forall m. Message m => SomeMessage -> Maybe m
fromMessage SomeMessage
m)]
            where resize :: Resize -> MultiCol a
resize Resize
Shrink = MultiCol a
l { multiColSize = max (-0.5) $ s-ds }
                  resize Resize
Expand = MultiCol a
l { multiColSize = min 1 $ s+ds }
                  incmastern :: IncMasterN -> MultiCol a
incmastern (IncMasterN Int
x) = MultiCol a
l { multiColNWin = take a n ++ [newval] ++ drop 1 r }
                      where newval :: Int
newval = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ Int -> (Int -> Int) -> Maybe Int -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
0 (Int
x Int -> Int -> Int
forall a. Num a => a -> a -> a
+) ([Int] -> Maybe Int
forall a. [a] -> Maybe a
listToMaybe [Int]
r)
                            r :: [Int]
r = Int -> [Int] -> [Int]
forall a. Int -> [a] -> [a]
drop Int
a [Int]
n
                  n :: [Int]
n = MultiCol a -> [Int]
forall a. MultiCol a -> [Int]
multiColNWin MultiCol a
l
                  ds :: Rational
ds = MultiCol a -> Rational
forall a. MultiCol a -> Rational
multiColDeltaSize MultiCol a
l
                  s :: Rational
s = MultiCol a -> Rational
forall a. MultiCol a -> Rational
multiColSize MultiCol a
l
                  a :: Int
a = MultiCol a -> Int
forall a. MultiCol a -> Int
multiColActive MultiCol a
l
    description :: MultiCol a -> String
description MultiCol a
_ = String
"MultiCol"

raiseFocused :: Int -> [a] -> [a]
raiseFocused :: forall a. Int -> [a] -> [a]
raiseFocused Int
n [a]
xs = [a]
actual [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a]
before [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a]
after
    where ([a]
before,[a]
rest) = Int -> [a] -> ([a], [a])
forall a. Int -> [a] -> ([a], [a])
splitAt Int
n [a]
xs
          ([a]
actual,[a]
after) = Int -> [a] -> ([a], [a])
forall a. Int -> [a] -> ([a], [a])
splitAt Int
1 [a]
rest

-- | Get which column a window is in, starting at 0.
getCol :: Int -> [Int] -> Int
getCol :: Int -> [Int] -> Int
getCol Int
w (Int
n:[Int]
ns) = if Int
nInt -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<Int
1 Bool -> Bool -> Bool
|| Int
w Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
n
                  then Int
0
                  else Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int -> [Int] -> Int
getCol (Int
wInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
n) [Int]
ns
-- Should never occur...
getCol Int
_ [Int]
_ = -Int
1

doL :: [Int] -> Rational -> Rectangle -> Int -> [Rectangle]
doL :: [Int] -> Rational -> Rectangle -> Int -> [Rectangle]
doL [Int]
nwin Rational
s Rectangle
r Int
n = [Rectangle]
rlist
    where -- Number of columns to tile
          ncol :: Int
ncol = Int -> [Int] -> Int
getCol (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) [Int]
nwin Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
          -- Compute the actual size
          size :: Int
size = Rational -> Int
forall b. Integral b => Rational -> b
forall a b. (RealFrac a, Integral b) => a -> b
floor (Rational -> Int) -> Rational -> Int
forall a b. (a -> b) -> a -> b
$ Rational -> Rational
forall a. Num a => a -> a
abs Rational
s Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Dimension -> Rational
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Rectangle -> Dimension
rect_width Rectangle
r)
          -- Extract all but last column to tile
          c :: [Int]
c = Int -> [Int] -> [Int]
forall a. Int -> [a] -> [a]
take (Int
ncolInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) [Int]
nwin
          -- Compute number of windows in last column and add it to the others
          col :: [Int]
col = [Int]
c [Int] -> [Int] -> [Int]
forall a. [a] -> [a] -> [a]
++ [Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
-[Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Int]
c]
          -- Compute width of columns
          width :: [Int]
width
            | Rational
sRational -> Rational -> Bool
forall a. Ord a => a -> a -> Bool
>Rational
0 = if Int
ncolInt -> Int -> Bool
forall a. Eq a => a -> a -> Bool
==Int
1
                    -- Only one window
                    then [Dimension -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Dimension -> Int) -> Dimension -> Int
forall a b. (a -> b) -> a -> b
$ Rectangle -> Dimension
rect_width Rectangle
r]
                    -- Give the master it's space and split the rest equally for the other columns
                    else Int
sizeInt -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:Int -> Int -> [Int]
forall a. Int -> a -> [a]
replicate (Int
ncolInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) ((Dimension -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Rectangle -> Dimension
rect_width Rectangle
r) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
size) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` (Int
ncolInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1))
            | Int -> Rational
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
ncol Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational -> Rational
forall a. Num a => a -> a
abs Rational
s Rational -> Rational -> Bool
forall a. Ord a => a -> a -> Bool
>= Rational
1 = Int -> Int -> [Int]
forall a. Int -> a -> [a]
replicate Int
ncol (Int -> [Int]) -> Int -> [Int]
forall a b. (a -> b) -> a -> b
$ Dimension -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Rectangle -> Dimension
rect_width Rectangle
r) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
ncol
            | Bool
otherwise = (Dimension -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Rectangle -> Dimension
rect_width Rectangle
r) Int -> Int -> Int
forall a. Num a => a -> a -> a
- (Int
ncolInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1)Int -> Int -> Int
forall a. Num a => a -> a -> a
*Int
size)Int -> [Int] -> [Int]
forall a. a -> [a] -> [a]
:Int -> Int -> [Int]
forall a. Int -> a -> [a]
replicate (Int
ncolInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1) Int
size
          -- Compute the horizontal position of columns
          xpos :: [Int]
xpos = Int -> [Int] -> [Int]
forall {t}. Num t => t -> [t] -> [t]
accumEx (Position -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Position -> Int) -> Position -> Int
forall a b. (a -> b) -> a -> b
$ Rectangle -> Position
rect_x Rectangle
r) [Int]
width
          -- Exclusive accumulation
          accumEx :: t -> [t] -> [t]
accumEx t
a (t
x:[t]
xs) = t
at -> [t] -> [t]
forall a. a -> [a] -> [a]
:t -> [t] -> [t]
accumEx (t
at -> t -> t
forall a. Num a => a -> a -> a
+t
x) [t]
xs
          accumEx t
_ [t]
_ = []
          -- Create a rectangle for each column
          cr :: [Rectangle]
cr = (Int -> Int -> Rectangle) -> [Int] -> [Int] -> [Rectangle]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (\Int
x Int
w -> Rectangle
r { rect_x=fromIntegral x, rect_width=fromIntegral w }) [Int]
xpos [Int]
width
          -- Split the columns into the windows
          rlist :: [Rectangle]
rlist = [[Rectangle]] -> [Rectangle]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[Rectangle]] -> [Rectangle]) -> [[Rectangle]] -> [Rectangle]
forall a b. (a -> b) -> a -> b
$ (Int -> Rectangle -> [Rectangle])
-> [Int] -> [Rectangle] -> [[Rectangle]]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Int -> Rectangle -> [Rectangle]
splitVertically [Int]
col [Rectangle]
cr