{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, MultiParamTypeClasses #-}
----------------------------------------------------------------------
-- |
-- Module : XMonad.Layout.GridVariants
-- Copyright : (c) Norbert Zeh
-- License : BSD-style (see LICENSE)
--
-- Maintainer : nzeh@cs.dal.ca
-- Stability : unstable
-- Portability : unportable
--
-- Two layouts: one is a variant of the Grid layout that allows the
-- desired aspect ratio of windows to be specified. The other is like
-- Tall but places a grid with fixed number of rows and columns in the
-- master area and uses an aspect-ratio-specified layout for the
-- slaves.
----------------------------------------------------------------------
module XMonad.Layout.GridVariants ( -- * Usage
-- $usage
ChangeMasterGridGeom(..)
, ChangeGridGeom(..)
, Grid(..)
, TallGrid(..)
, SplitGrid(..)
, Orientation(..)
) where
import Control.Monad
import XMonad
import qualified XMonad.StackSet as W
-- $usage
-- This module can be used as follows:
--
-- > import XMonad.Layout.GridVariants
--
-- Then add something like this to your layouts:
--
-- > Grid (16/10)
--
-- for a 16:10 aspect ratio grid, or
--
-- > SplitGrid L 2 3 (2/3) (16/10) (5/100)
--
-- for a layout with a 2x3 master grid that uses 2/3 of the screen,
-- and a 16:10 aspect ratio slave grid to its right. The last
-- parameter is again the percentage by which the split between master
-- and slave area changes in response to Expand/Shrink messages.
--
-- To be able to change the geometry of the master grid, add something
-- like this to your keybindings:
--
-- > ((modm .|. shiftMask, xK_equal), sendMessage $ IncMasterCols 1),
-- > ((modm .|. shiftMask, xK_minus), sendMessage $ IncMasterCols (-1)),
-- > ((modm .|. controlMask, xK_equal), sendMessage $ IncMasterRows 1),
-- > ((modm .|. controlMask, xK_minus), sendMessage $ IncMasterRows (-1))
-- | Grid layout. The parameter is the desired x:y aspect ratio of windows
data Grid a = Grid !Rational
deriving (Read, Show)
instance LayoutClass Grid a where
pureLayout (Grid aspect) rect st = zip wins rects
where
wins = W.integrate st
nwins = length wins
rects = arrangeAspectGrid rect nwins aspect
pureMessage layout msg = fmap (changeGridAspect layout) (fromMessage msg)
description _ = "Grid"
changeGridAspect :: Grid a -> ChangeGridGeom -> Grid a
changeGridAspect (Grid _) (SetGridAspect aspect) = Grid aspect
changeGridAspect (Grid aspect) (ChangeGridAspect delta) =
Grid (max 0.00001 (aspect + delta))
-- |Geometry change messages understood by Grid and SplitGrid
data ChangeGridGeom
= SetGridAspect !Rational
| ChangeGridAspect !Rational
deriving Typeable
instance Message ChangeGridGeom
-- |SplitGrid layout. Parameters are
--
-- - side where the master is
-- - number of master rows
-- - number of master columns
-- - portion of screen used for master grid
-- - x:y aspect ratio of slave windows
-- - increment for resize messages
data SplitGrid a = SplitGrid Orientation !Int !Int !Rational !Rational !Rational
deriving (Read, Show)
-- |Type to specify the side of the screen that holds
-- the master area of a SplitGrid.
data Orientation = T | B | L | R
deriving (Eq, Read, Show)
instance LayoutClass SplitGrid a where
pureLayout (SplitGrid o mrows mcols mfrac saspect _) rect st = zip wins rects
where
wins = W.integrate st
nwins = length wins
rects = arrangeSplitGrid rect o nwins mrows mcols mfrac saspect
pureMessage layout msg =
msum [ fmap (resizeMaster layout) (fromMessage msg)
, fmap (changeMasterGrid layout) (fromMessage msg)
, fmap (changeSlaveGridAspect layout) (fromMessage msg)
]
description _ = "SplitGrid"
-- |The geometry change message understood by the master grid
data ChangeMasterGridGeom
= IncMasterRows !Int -- ^Change the number of master rows
| IncMasterCols !Int -- ^Change the number of master columns
| SetMasterRows !Int -- ^Set the number of master rows to absolute value
| SetMasterCols !Int -- ^Set the number of master columns to absolute value
| SetMasterFraction !Rational -- ^Set the fraction of the screen used by the master grid
deriving Typeable
instance Message ChangeMasterGridGeom
arrangeSplitGrid :: Rectangle -> Orientation -> Int -> Int -> Int -> Rational -> Rational -> [Rectangle]
arrangeSplitGrid rect@(Rectangle rx ry rw rh) o nwins mrows mcols mfrac saspect
| nwins <= mwins = arrangeMasterGrid rect nwins mcols
| mwins == 0 = arrangeAspectGrid rect nwins saspect
| otherwise = (arrangeMasterGrid mrect mwins mcols) ++
(arrangeAspectGrid srect swins saspect)
where
mwins = mrows * mcols
swins = nwins - mwins
mrect = Rectangle mx my mw mh
srect = Rectangle sx sy sw sh
(mh, sh, mw, sw) = if o `elem` [T, B] then
(ceiling (fromIntegral rh * mfrac), rh - mh, rw, rw)
else
(rh, rh, ceiling (fromIntegral rw * mfrac), rw - mw)
mx = fromIntegral rx + if o == R then fromIntegral sw else 0
my = fromIntegral ry + if o == B then fromIntegral sh else 0
sx = fromIntegral rx + if o == L then fromIntegral mw else 0
sy = fromIntegral ry + if o == T then fromIntegral mh else 0
arrangeMasterGrid :: Rectangle -> Int -> Int -> [Rectangle]
arrangeMasterGrid rect nwins mcols = arrangeGrid rect nwins (min nwins mcols)
arrangeAspectGrid :: Rectangle -> Int -> Rational -> [Rectangle]
arrangeAspectGrid rect@(Rectangle _ _ rw rh) nwins aspect =
arrangeGrid rect nwins (min nwins ncols)
where
scr_a = fromIntegral rw / fromIntegral rh
fcols = sqrt ( fromRational $ scr_a * fromIntegral nwins / aspect ) :: Double
cols1 = floor fcols :: Int
cols2 = ceiling fcols :: Int
rows1 = ceiling ( fromIntegral nwins / fromIntegral cols1 :: Rational ) :: Int
rows2 = floor ( fromIntegral nwins / fromIntegral cols2 :: Rational ) :: Int
a1 = scr_a * fromIntegral rows1 / fromIntegral cols1
a2 = scr_a * fromIntegral rows2 / fromIntegral cols2
ncols | cols1 == 0 = cols2
| rows2 == 0 = cols1
| a1 / aspect < aspect / a2 = cols1
| otherwise = cols2
arrangeGrid :: Rectangle -> Int -> Int -> [Rectangle]
arrangeGrid (Rectangle rx ry rw rh) nwins ncols =
[Rectangle (fromIntegral x + rx) (fromIntegral y + ry) (fromIntegral w) (fromIntegral h)
| (x, y, w, h) <- rects]
where
nrows_in_cols = listDifference $ splitEvenly nwins ncols
x_slabs = splitIntoSlabs (fromIntegral rw) ncols
y_slabs = [splitIntoSlabs (fromIntegral rh) nrows | nrows <- nrows_in_cols]
rects_in_cols = [[(x, y, w, h) | (y, h) <- lst]
| ((x, w), lst) <- zip x_slabs y_slabs]
rects = foldr (++) [] rects_in_cols
splitIntoSlabs :: Int -> Int -> [(Int, Int)]
splitIntoSlabs width nslabs = zip (0:xs) widths
where
xs = splitEvenly width nslabs
widths = listDifference xs
listDifference :: [Int] -> [Int]
listDifference lst = [cur-pre | (cur,pre) <- zip lst (0:lst)]
splitEvenly :: Int -> Int -> [Int]
splitEvenly n parts = [ sz-off | (sz,off) <- zip sizes offsets]
where
size = ceiling ( (fromIntegral n / fromIntegral parts) :: Double )
extra = size*parts - n
sizes = [i*size | i <- [1..parts]]
offsets = (take (fromIntegral extra) [1..]) ++ [extra,extra..]
resizeMaster :: SplitGrid a -> Resize -> SplitGrid a
resizeMaster (SplitGrid o mrows mcols mfrac saspect delta) Shrink =
SplitGrid o mrows mcols (max 0 (mfrac - delta)) saspect delta
resizeMaster (SplitGrid o mrows mcols mfrac saspect delta) Expand =
SplitGrid o mrows mcols (min 1 (mfrac + delta)) saspect delta
changeMasterGrid :: SplitGrid a -> ChangeMasterGridGeom -> SplitGrid a
changeMasterGrid (SplitGrid o mrows mcols mfrac saspect delta) (IncMasterRows d) =
SplitGrid o (max 0 (mrows + d)) mcols mfrac saspect delta
changeMasterGrid (SplitGrid o mrows mcols mfrac saspect delta) (IncMasterCols d) =
SplitGrid o mrows (max 0 (mcols + d)) mfrac saspect delta
changeMasterGrid (SplitGrid o _ mcols mfrac saspect delta) (SetMasterRows mrows) =
SplitGrid o (max 0 mrows) mcols mfrac saspect delta
changeMasterGrid (SplitGrid o mrows _ mfrac saspect delta) (SetMasterCols mcols) =
SplitGrid o mrows (max 0 mcols) mfrac saspect delta
changeMasterGrid (SplitGrid o mrows mcols _ saspect delta) (SetMasterFraction mfrac) =
SplitGrid o mrows mcols mfrac saspect delta
changeSlaveGridAspect :: SplitGrid a -> ChangeGridGeom -> SplitGrid a
changeSlaveGridAspect (SplitGrid o mrows mcols mfrac _ delta) (SetGridAspect saspect) =
SplitGrid o mrows mcols mfrac saspect delta
changeSlaveGridAspect (SplitGrid o mrows mcols mfrac saspect delta) (ChangeGridAspect sdelta) =
SplitGrid o mrows mcols mfrac (max 0.00001 (saspect + sdelta)) delta
-- | TallGrid layout. Parameters are
--
-- - number of master rows
-- - number of master columns
-- - portion of screen used for master grid
-- - x:y aspect ratio of slave windows
-- - increment for resize messages
--
-- This exists mostly because it was introduced in an earlier version.
-- It's a fairly thin wrapper around "SplitGrid L".
data TallGrid a = TallGrid !Int !Int !Rational !Rational !Rational
deriving (Read, Show)
instance LayoutClass TallGrid a where
pureLayout (TallGrid mrows mcols mfrac saspect _) rect st = zip wins rects
where
wins = W.integrate st
nwins = length wins
rects = arrangeSplitGrid rect L nwins mrows mcols mfrac saspect
pureMessage layout msg =
msum [ fmap ((tallGridAdapter resizeMaster) layout) (fromMessage msg)
, fmap ((tallGridAdapter changeMasterGrid) layout) (fromMessage msg) ]
description _ = "TallGrid"
tallGridAdapter :: (SplitGrid a -> b -> SplitGrid a) -> TallGrid a -> b -> TallGrid a
tallGridAdapter f (TallGrid mrows mcols mfrac saspect delta) msg =
TallGrid mrows' mcols' mfrac' saspect' delta'
where
SplitGrid _ mrows' mcols' mfrac' saspect' delta' =
f (SplitGrid L mrows mcols mfrac saspect delta) msg