{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Layout.Mosaic -- Copyright : (c) 2009 Adam Vogt, 2007 James Webb -- License : BSD-style (see xmonad/LICENSE) -- -- Maintainer : vogt.adamgmail.com -- Stability : unstable -- Portability : unportable -- -- Based on MosaicAlt, but aspect ratio messages always change the aspect -- ratios, and rearranging the window stack changes the window sizes. -- ----------------------------------------------------------------------------- module XMonad.Layout.Mosaic ( -- * Usage -- $usage Mosaic(Mosaic) ,Aspect(..) ,shallower ,steeper ,growMaster ,shrinkMaster ,changeMaster ) where import Prelude hiding (sum) import XMonad(Typeable, LayoutClass(doLayout , pureMessage, description), Message, fromMessage, splitHorizontallyBy, splitVerticallyBy, Rectangle) import XMonad.StackSet(integrate) import Data.Foldable(Foldable(foldMap), sum) import Data.Monoid(Monoid(mappend, mempty)) -- $usage -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: -- -- > import XMonad.Layout.Mosaic -- -- Then edit your @layoutHook@ by adding the Mosaic layout: -- -- > myLayouts = Mosaic (take 5 $ iterate (*0.7) 1) ||| Mosaic [3,1,1,1,1,1] ||| Full ||| etc.. -- > main = xmonad defaultConfig { layoutHook = myLayouts } -- -- Unfortunately, infinite lists break serialization, so don't use them. -- -- To change the choice in aspect ratio and the relative sizes of windows, add -- to your keybindings: -- -- > , ((modMask, xK_a), sendMessage Taller) -- > , ((modMask, xK_z), sendMessage Wider) -- > , ((modMask, xK_h), sendMessage Shrink >> sendMessage (SlopeMod shallower)) -- > , ((modMask, xK_l), sendMessage Expand >> sendMessage (SlopeMod steeper)) -- -- > , ((modMask, xK_r), sendMessage Reset) -- -- For more detailed instructions on editing the layoutHook see: -- -- "XMonad.Doc.Extending#Editing_the_layout_hook" data Aspect = Taller | Wider | Reset | SlopeMod ([Rational] -> [Rational]) deriving (Typeable) instance Message Aspect data Mosaic a {- | The relative magnitudes (the sign is ignored) of the rational numbers - provided determine the relative areas that the windows receive. The - first number represents the size of the master window, the second is for - the next window in the stack, and so on. Windows without a list element - are hidden. -} = Mosaic [Rational] -- override the aspect? current index, maximum index | MosaicSt Bool Rational Int [Rational] deriving (Read, Show) instance LayoutClass Mosaic a where description = const "Mosaic" pureMessage (Mosaic _ss) _ms = Nothing pureMessage (MosaicSt _ ix mix ss) ms = fromMessage ms >>= ixMod where ixMod Taller | rix >= mix = Nothing | otherwise = Just $ MosaicSt False (succ ix) mix ss ixMod Wider | rix <= 0 = Nothing | otherwise = Just $ MosaicSt False (pred ix) mix ss ixMod Reset = Just $ Mosaic ss ixMod (SlopeMod f) = Just $ MosaicSt False ix mix (f ss) rix = round ix doLayout (Mosaic ss) r st = return (zip (integrate st) rect, newLayout) where rects = splits (length $ integrate st) r ss lrects = length rects rect = rects !! (lrects `div` 2) newLayout = Just $ MosaicSt True (fromIntegral lrects / 2) (pred lrects) ss doLayout (MosaicSt override ix mix ss) r st = return (zip (integrate st) rect, newLayout) where rects = splits (length $ integrate st) r ss lrects = length rects nix = if mix == 0 || override then fromIntegral $ lrects `div` 2 else max 0 $ min (fromIntegral $ pred lrects) $ fromIntegral (pred lrects) * ix / fromIntegral mix rect = rects !! round nix newLayout = Just $ MosaicSt override nix (pred lrects) ss -- | These sample functions are meant to be applied to the list of window sizes -- through the 'SlopeMod' message. -- -- Steeper and shallower scale the ratios of successive windows. -- -- growMaster and shrinkMaster just increase and decrease the size of the first -- element, and thus they change the layout very similarily to the standard -- 'Expand' or 'Shrink' for the 'Tall' layout. -- -- It may be possible to resize the specific focused window; however the same -- result could probably be achieved by promoting it, or moving it to a higher -- place in the list of windows; when you have a decreasing list of window -- sizes, the change in position will also result in a change in size. steeper :: [Rational] -> [Rational] steeper [] = [] steeper xs = map (subtract (minimum xs*0.8)) xs shallower :: [Rational] -> [Rational] shallower [] = [] shallower xs = map (+(minimum xs*2)) xs growMaster :: [Rational] -> [Rational] growMaster = changeMaster 2 shrinkMaster :: [Rational] -> [Rational] shrinkMaster = changeMaster 0.5 -- | Multiply the area of the current master by a specified ratio changeMaster :: Rational -> [Rational] -> [Rational] changeMaster _ [] = [] changeMaster f (x:xs) = f*x:xs splits :: Int -> Rectangle -> [Rational] -> [[Rectangle]] splits num rect = splitsL rect . makeTree . normalize . map abs . reverse . take num -- recursively enumerate splits splitsL :: Rectangle -> Tree Rational -> [[Rectangle]] splitsL _rect Empty = [] splitsL rect (Leaf _) = [[rect]] splitsL rect (Branch l r) = do let mkSplit f = f (sum l / (sum l + sum r)) rect (rl,rr) <- map mkSplit [splitVerticallyBy,splitHorizontallyBy] splitsL rl l `interleave` splitsL rr r -- like zipWith (++), but when one list is shorter, its elements are duplicated -- so that they match interleave :: [[a]] -> [[a]] -> [[a]] interleave xs ys | lx > ly = zc xs (extend lx ys) | otherwise = zc (extend ly xs) ys where lx = length xs ly = length ys zc = zipWith (++) extend :: Int -> [a] -> [a] extend n pat = do (p,e) <- zip pat $ replicate m True ++ repeat False [p | e] ++ replicate d p where (d,m) = n `divMod` length pat normalize :: Fractional a => [a] -> [a] normalize x = let s = sum x in map (/s) x data Tree a = Branch (Tree a) (Tree a) | Leaf a | Empty deriving (Show) instance Foldable Tree where foldMap _f Empty = mempty foldMap f (Leaf x) = f x foldMap f (Branch l r) = foldMap f l `mappend` foldMap f r instance Monoid (Tree a) where mempty = Empty mappend Empty x = x mappend x Empty = x mappend x y = Branch x y makeTree :: [Rational] -> Tree Rational makeTree [] = Empty makeTree [x] = Leaf x makeTree xs = Branch (makeTree a) (makeTree b) where ((a,b),_) = foldr w (([],[]),(0,0)) xs w n ((ls,rs),(l,r)) = if l > r then ((ls,n:rs),(l,n+r)) else ((n:ls,rs),(n+l,r))