aboutsummaryrefslogtreecommitdiffstats
path: root/XMonad/Layout/HintedTile.hs
blob: 70ebc74dc9ce8fc83e77cd039e869e6f48ce9c0d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.HintedTile
-- Copyright   :  (c) Peter De Wachter <pdewacht@gmail.com>
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Peter De Wachter <pdewacht@gmail.com>
--                Andrea Rossato <andrea.rossato@unibz.it>
-- Stability   :  unstable
-- Portability :  unportable
--
-- A gapless tiled layout that attempts to obey window size hints,
-- rather than simply ignoring them.
--
-----------------------------------------------------------------------------

module XMonad.Layout.HintedTile (
                                 -- * Usage
                                 -- $usage
                                 tall, wide ) where

import XMonad
import XMonad.Layouts    ( Resize(..), IncMasterN(..) )
import XMonad.Operations ( applySizeHints             )
import qualified XMonad.StackSet as W
import Graphics.X11.Xlib
import Graphics.X11.Xlib.Extras
import Control.Monad.Reader

-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Layout.HintedTile
--
-- Then edit your @layoutHook@ by adding the HintedTile layout:
--
-- > mylayout = tall 1 0.1 0.5 ||| Full ||| etc..
-- > main = xmonad dafaultConfig { layoutHook = mylayouts }
--
-- For more detailed instructions on editing the layoutHook see:
-- "XMonad.Doc.Extending#Editing_the_layout_hook"

data HintedTile a =
    HT { nmaster     :: Int
       , delta, frac :: Rational
       , orientation :: Orientation
       } deriving ( Show, Read )

data Orientation = Wide | Tall deriving ( Show, Read )

tall, wide :: Int -> Rational -> Rational -> HintedTile Window
wide n d f = HT {nmaster = n, delta = d, frac = f, orientation = Tall }
tall n d f = HT {nmaster = n, delta = d, frac = f, orientation = Wide }

instance LayoutClass HintedTile Window where
    doLayout c rect w' = let w = W.integrate w'
                         in do { hints <- sequence (map getHints w)
                               ; b <- asks (borderWidth . config)
                               ; return (zip w (tiler b (frac c) rect `uncurry` splitAt (nmaster c) hints)
                                        , Nothing) }
        where
          (split, divide) =
              case orientation c of
                Wide -> (splitHorizontally, divideHorizontally)
                Tall -> (splitVertically,   divideVertically  )
          tiler b f r masters slaves =
              if null masters || null slaves
              then divide b (masters ++ slaves) r
              else split f r (divide b masters) (divide b slaves)

    pureMessage c m = fmap resize (fromMessage m) `mplus`
                      fmap incmastern (fromMessage m)
        where
          resize Shrink = c { frac = max 0 $ frac c - delta c }
          resize Expand = c { frac = min 1 $ frac c + delta c }
          incmastern (IncMasterN d) = c { nmaster = max 0 $ nmaster c + d }

    description l = "HintedTile " ++ show (orientation l)

addBorder, substractBorder :: Dimension -> (Dimension, Dimension) -> (Dimension, Dimension)
addBorder       b (w, h) = (w + 2 * b, h + 2 * b)
substractBorder b (w, h) = (w - 2 * b, h - 2 * b)

getHints :: Window -> X SizeHints
getHints w = withDisplay $ \d -> io $ getWMNormalHints d w

-- Divide the screen vertically (horizontally) into n subrectangles
divideVertically, divideHorizontally :: Dimension -> [SizeHints] -> Rectangle -> [Rectangle]
divideVertically _ [] _ = [] -- there's a fold here, struggling to get out
divideVertically b (hints:rest) (Rectangle sx sy sw sh) = (Rectangle sx sy w h) :
      (divideVertically b rest (Rectangle sx (sy + fromIntegral h) sw (sh - h)))
    where (w, h) = addBorder b $ applySizeHints hints $ substractBorder b
                   (sw, sh `div` fromIntegral (1 + (length rest)))

divideHorizontally _ [] _ = []
divideHorizontally b (hints:rest) (Rectangle sx sy sw sh) = (Rectangle sx sy w h) :
      (divideHorizontally b rest (Rectangle (sx + fromIntegral w) sy (sw - w) sh))
    where (w, h) = addBorder b $ applySizeHints hints $ substractBorder b
                   (sw `div` fromIntegral (1 + (length rest)), sh)

-- Split the screen into two rectangles, using a rational to specify the ratio
splitHorizontally, splitVertically :: Rational -> Rectangle -> (Rectangle -> [Rectangle]) -> (Rectangle -> [Rectangle]) -> [Rectangle]
splitHorizontally f (Rectangle sx sy sw sh) left right = leftRects ++ rightRects
    where leftw = floor $ fromIntegral sw * f
          leftRects = left $ Rectangle sx sy leftw sh
          rightx = (maximum . map rect_width) leftRects
          rightRects = right $ Rectangle (sx + fromIntegral rightx) sy (sw - rightx) sh

splitVertically f (Rectangle sx sy sw sh) top bottom = topRects ++ bottomRects
    where toph = floor $ fromIntegral sh * f
          topRects = top $ Rectangle sx sy sw toph
          bottomy = (maximum . map rect_height) topRects
          bottomRects = bottom $ Rectangle sx (sy + fromIntegral bottomy) sw (sh - bottomy)