{-# 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 = forall a. [Int] -> Int -> Rational -> Rational -> Int -> MultiCol a
MultiCol (forall a b. (a -> b) -> [a] -> [b]
map (forall a. Ord a => a -> a -> a
max Int
0) [Int]
n) (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
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
showList :: [MultiCol a] -> ShowS
$cshowList :: forall a. [MultiCol a] -> ShowS
show :: MultiCol a -> String
$cshow :: forall a. MultiCol a -> String
showsPrec :: Int -> MultiCol a -> ShowS
$cshowsPrec :: forall a. Int -> MultiCol a -> ShowS
Show,ReadPrec [MultiCol a]
ReadPrec (MultiCol a)
ReadS [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
readListPrec :: ReadPrec [MultiCol a]
$creadListPrec :: forall a. ReadPrec [MultiCol a]
readPrec :: ReadPrec (MultiCol a)
$creadPrec :: forall a. ReadPrec (MultiCol a)
readList :: ReadS [MultiCol a]
$creadList :: forall a. ReadS [MultiCol a]
readsPrec :: Int -> ReadS (MultiCol a)
$creadsPrec :: forall a. Int -> ReadS (MultiCol a)
Read,MultiCol a -> MultiCol a -> Bool
forall a. MultiCol a -> MultiCol a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: MultiCol a -> MultiCol a -> Bool
$c/= :: forall a. MultiCol a -> MultiCol a -> Bool
== :: MultiCol a -> MultiCol a -> Bool
$c== :: forall a. 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 = forall (m :: * -> *) a. Monad m => a -> m a
return (forall {a} {b}. Stack a -> [b] -> [(a, b)]
combine Stack a
s [Rectangle]
rlist, forall {a}. Maybe (MultiCol a)
resl)
        where rlist :: [Rectangle]
rlist = [Int] -> Rational -> Rectangle -> Int -> [Rectangle]
doL (forall a. MultiCol a -> [Int]
multiColNWin forall {a}. MultiCol a
l') (forall a. MultiCol a -> Rational
multiColSize forall {a}. MultiCol a
l') Rectangle
r Int
wlen
              wlen :: Int
wlen = forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ 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 = forall a. MultiCol a -> [Int]
multiColNWin MultiCol a
l forall a. [a] -> [a] -> [a]
++ forall a. a -> [a]
repeat (forall a. MultiCol a -> Int
multiColDefWin MultiCol a
l)
              l' :: MultiCol a
l' = MultiCol a
l { multiColNWin :: [Int]
multiColNWin = forall a. Int -> [a] -> [a]
take (forall a. Ord a => a -> a -> a
max (forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ forall a. MultiCol a -> [Int]
multiColNWin MultiCol a
l) forall a b. (a -> b) -> a -> b
$ Int -> [Int] -> Int
getCol (Int
wlenforall a. Num a => a -> a -> a
-Int
1) [Int]
nw forall a. Num a => a -> a -> a
+ Int
1) [Int]
nw
                     , multiColActive :: Int
multiColActive = Int -> [Int] -> Int
getCol (forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ forall a. Stack a -> [a]
W.up Stack a
s) [Int]
nw
                     }
              -- Only return new layout if it has been modified
              resl :: Maybe (MultiCol a)
resl = if forall {a}. MultiCol a
l'forall a. Eq a => a -> a -> Bool
==MultiCol a
l
                     then forall a. Maybe a
Nothing
                     else forall a. a -> Maybe a
Just forall {a}. MultiCol a
l'
              combine :: Stack a -> [b] -> [(a, b)]
combine (W.Stack a
foc [a]
left [a]
right) [b]
rs = forall a b. [a] -> [b] -> [(a, b)]
zip (a
foc forall a. a -> [a] -> [a]
: forall a. [a] -> [a]
reverse [a]
left forall a. [a] -> [a] -> [a]
++ [a]
right) forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
raiseFocused (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 =
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum [forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall {a}. Resize -> MultiCol a
resize     (forall m. Message m => SomeMessage -> Maybe m
fromMessage SomeMessage
m)
                      ,forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall {a}. IncMasterN -> MultiCol a
incmastern (forall m. Message m => SomeMessage -> Maybe m
fromMessage SomeMessage
m)]
            where resize :: Resize -> MultiCol a
resize Resize
Shrink = MultiCol a
l { multiColSize :: Rational
multiColSize = forall a. Ord a => a -> a -> a
max (-Rational
0.5) forall a b. (a -> b) -> a -> b
$ Rational
sforall a. Num a => a -> a -> a
-Rational
ds }
                  resize Resize
Expand = MultiCol a
l { multiColSize :: Rational
multiColSize = forall a. Ord a => a -> a -> a
min Rational
1 forall a b. (a -> b) -> a -> b
$ Rational
sforall a. Num a => a -> a -> a
+Rational
ds }
                  incmastern :: IncMasterN -> MultiCol a
incmastern (IncMasterN Int
x) = MultiCol a
l { multiColNWin :: [Int]
multiColNWin = forall a. Int -> [a] -> [a]
take Int
a [Int]
n forall a. [a] -> [a] -> [a]
++ [Int
newval] forall a. [a] -> [a] -> [a]
++ forall a. Int -> [a] -> [a]
drop Int
1 [Int]
r }
                      where newval :: Int
newval = forall a. Ord a => a -> a -> a
max Int
0 forall a b. (a -> b) -> a -> b
$ forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
0 (Int
x forall a. Num a => a -> a -> a
+) (forall a. [a] -> Maybe a
listToMaybe [Int]
r)
                            r :: [Int]
r = forall a. Int -> [a] -> [a]
drop Int
a [Int]
n
                  n :: [Int]
n = forall a. MultiCol a -> [Int]
multiColNWin MultiCol a
l
                  ds :: Rational
ds = forall a. MultiCol a -> Rational
multiColDeltaSize MultiCol a
l
                  s :: Rational
s = forall a. MultiCol a -> Rational
multiColSize MultiCol a
l
                  a :: Int
a = 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 forall a. [a] -> [a] -> [a]
++ [a]
before forall a. [a] -> [a] -> [a]
++ [a]
after
    where ([a]
before,[a]
rest) = forall a. Int -> [a] -> ([a], [a])
splitAt Int
n [a]
xs
          ([a]
actual,[a]
after) = 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
nforall a. Ord a => a -> a -> Bool
<Int
1 Bool -> Bool -> Bool
|| Int
w forall a. Ord a => a -> a -> Bool
< Int
n
                  then Int
0
                  else Int
1 forall a. Num a => a -> a -> a
+ Int -> [Int] -> Int
getCol (Int
wforall 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
nforall a. Num a => a -> a -> a
-Int
1) [Int]
nwin forall a. Num a => a -> a -> a
+ Int
1
          -- Compute the actual size
          size :: Int
size = forall a b. (RealFrac a, Integral b) => a -> b
floor forall a b. (a -> b) -> a -> b
$ forall a. Num a => a -> a
abs Rational
s forall a. Num a => a -> a -> a
* 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 = forall a. Int -> [a] -> [a]
take (Int
ncolforall 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 forall a. [a] -> [a] -> [a]
++ [Int
nforall a. Num a => a -> a -> a
-forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Int]
c]
          -- Compute width of columns
          width :: [Int]
width
            | Rational
sforall a. Ord a => a -> a -> Bool
>Rational
0 = if Int
ncolforall a. Eq a => a -> a -> Bool
==Int
1
                    -- Only one window
                    then [forall a b. (Integral a, Num b) => a -> b
fromIntegral 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
sizeforall a. a -> [a] -> [a]
:forall a. Int -> a -> [a]
replicate (Int
ncolforall a. Num a => a -> a -> a
-Int
1) ((forall a b. (Integral a, Num b) => a -> b
fromIntegral (Rectangle -> Dimension
rect_width Rectangle
r) forall a. Num a => a -> a -> a
- Int
size) forall a. Integral a => a -> a -> a
`div` (Int
ncolforall a. Num a => a -> a -> a
-Int
1))
            | forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
ncol forall a. Num a => a -> a -> a
* forall a. Num a => a -> a
abs Rational
s forall a. Ord a => a -> a -> Bool
>= Rational
1 = forall a. Int -> a -> [a]
replicate Int
ncol forall a b. (a -> b) -> a -> b
$ forall a b. (Integral a, Num b) => a -> b
fromIntegral (Rectangle -> Dimension
rect_width Rectangle
r) forall a. Integral a => a -> a -> a
`div` Int
ncol
            | Bool
otherwise = (forall a b. (Integral a, Num b) => a -> b
fromIntegral (Rectangle -> Dimension
rect_width Rectangle
r) forall a. Num a => a -> a -> a
- (Int
ncolforall a. Num a => a -> a -> a
-Int
1)forall a. Num a => a -> a -> a
*Int
size)forall a. a -> [a] -> [a]
:forall a. Int -> a -> [a]
replicate (Int
ncolforall a. Num a => a -> a -> a
-Int
1) Int
size
          -- Compute the horizontal position of columns
          xpos :: [Int]
xpos = forall {t}. Num t => t -> [t] -> [t]
accumEx (forall a b. (Integral a, Num b) => a -> b
fromIntegral 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
aforall a. a -> [a] -> [a]
:t -> [t] -> [t]
accumEx (t
aforall a. Num a => a -> a -> a
+t
x) [t]
xs
          accumEx t
_ [t]
_ = []
          -- Create a rectangle for each column
          cr :: [Rectangle]
cr = forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (\Int
x Int
w -> Rectangle
r { rect_x :: Position
rect_x=forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
x, rect_width :: Dimension
rect_width=forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
w }) [Int]
xpos [Int]
width
          -- Split the columns into the windows
          rlist :: [Rectangle]
rlist = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Int -> Rectangle -> [Rectangle]
splitVertically [Int]
col [Rectangle]
cr