aboutsummaryrefslogtreecommitdiffstats
path: root/XMonad/Layout/Dwindle.hs
blob: a5390c2977b09c9541ce780d0b87a525fafe32c2 (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.Dwindle
-- Copyright   :  (c) Norbert Zeh <norbert.zeh@gmail.com>
-- License     :  BSD3
--
-- Maintainer  :  Norbert Zeh <norbert.zeh@gmail.com>
-- Stability   :  experimental
-- Portability :  portable
--
-- Three layouts: The first, 'Spiral', is a reimplementation of
-- 'XMonad.Layout.Spiral.spiral' with, at least to me, more intuitive semantics.
-- The second, 'Dwindle', is inspired by a similar layout in awesome and
-- produces the same sequence of decreasing window sizes as Spiral but pushes
-- the smallest windows into a screen corner rather than the centre.  The third,
-- 'Squeeze' arranges all windows in one row or in one column, with
-- geometrically decreasing sizes.
--
-----------------------------------------------------------------------------

module XMonad.Layout.Dwindle ( -- * Usage
                               -- $usage
                               Dwindle(..)
                             , Direction2D(..)
                             , Chirality(..)
                             ) where

import Data.List ( unfoldr )
import XMonad
import XMonad.StackSet ( integrate, Stack )
import XMonad.Util.Types ( Direction2D(..) )

-- $usage
-- This module can be used as follows:
--
-- > import XMonad.Layout.Dwindle
--
-- Then add something like this to your layouts:
--
-- > Dwindle R CW 1.5 1.1
--
-- or
--
-- > Spiral L CW 1.5 1.1
--
-- or
--
-- ^ Squeeze D 1.5 1.1
--
-- The first produces a layout that places the second window to the right of
-- the first, the third below the second, the fourth to the right of the third,
-- and so on.  The first window is 1.5 times as wide as the second one, the
-- second is 1.5 times as tall as the third one, and so on.  Thus, the further
-- down the window stack a window is, the smaller it is and the more it is
-- pushed into the bottom-right corner.
--
-- The second produces a layout with the same window sizes but places the second
-- window to the left of the first one, the third above the second one, the
-- fourth to the right of the third one, and so on.
--
-- The third produces a layout that stacks windows vertically top-down with each
-- window being 1.5 times as tall as the next.
--
-- In all three cases, the fourth (third, in the case of 'Squeeze') parameter,
-- 1.1, is the factor by which the third parameter increases or decreases in
-- response to Expand or Shrink messages.
--
-- For more detailed instructions on editing the layoutHook see:
--
-- "XMonad.Doc.Extending#Editing_the_layout_hook"

-- | Layouts with geometrically decreasing window sizes.  'Spiral' and 'Dwindle'
-- split the screen into a rectangle for the first window and a rectangle for
-- the remaining windows, which is split recursively to lay out these windows.
-- Both layouts alternate between horizontal and vertical splits.
--
-- In each recursive step, the split 'Direction2D' determines the placement of the
-- remaining windows relative to the current window: to the left, to the right,
-- above or below.  The split direction of the first split is determined by the
-- first layout parameter.  The split direction of the second step is rotated 90
-- degrees relative to the first split direction according to the second layout
-- parameter of type 'Chirality'.  So, if the first split is 'R' and the second
-- layout parameter is 'CW', then the second split is 'D'.
--
-- For the 'Spiral' layout, the same 'Chirality' is used for computing the split
-- direction of each step from the split direction of the previous step.  For
-- example, parameters 'R' and 'CW' produces the direction sequence 'R', 'D',
-- 'L', 'U', 'R', 'D', 'L', 'U', ...
--
-- For the 'Dwindle' layout, the 'Chirality' alternates between 'CW' and 'CCW' in
-- each step.  For example, parameters 'U' and 'CCW' produce the direction
-- sequence 'U', 'L', 'U', 'L', ... because 'L' is the 'CCW' rotation of 'U' and
-- 'U' is the 'CW' rotation of 'L'.
--
-- In each split, the current rectangle is split so that the ratio between the
-- size of the rectangle allocated to the current window and the size of the
-- rectangle allocated to the remaining windows is the third layout parameter.
-- This ratio can be altered using 'Expand' and 'Shrink' messages.  The former
-- multiplies the ratio by the fourth layout parameter.  The latter divides the
-- ratio by this parameter.
--
-- 'Squeeze' does not alternate between horizontal and vertical splits and
-- simply splits in the direction given as its first argument.
--
-- Parameters for both 'Dwindle' and 'Spiral':
--
-- * First split direction
--
-- * First split chirality
--
-- * Size ratio between rectangle allocated to current window and rectangle 
-- allocated to remaining windows
--
-- * Factor by which the size ratio is changed in response to 'Expand' or 'Shrink'
-- messages
--
-- The parameters for 'Squeeze' are the same, except that there is no 'Chirality'
-- parameter.
data Dwindle a = Dwindle !Direction2D !Chirality !Rational !Rational
               | Spiral  !Direction2D !Chirality !Rational !Rational
               | Squeeze !Direction2D !Rational !Rational
               deriving (Read, Show)

-- | Rotation between consecutive split directions
data Chirality = CW | CCW
               deriving (Read, Show)

instance LayoutClass Dwindle a where
    pureLayout (Dwindle dir rot ratio _) = dwindle alternate dir rot ratio
    pureLayout (Spiral  dir rot ratio _) = dwindle rotate    dir rot ratio
    pureLayout (Squeeze dir     ratio _) = squeeze           dir     ratio
    pureMessage (Dwindle dir rot ratio delta) =
      fmap (\ratio' -> Dwindle dir rot ratio' delta) . changeRatio ratio delta
    pureMessage (Spiral dir rot ratio delta) =
      fmap (\ratio' -> Spiral dir rot ratio' delta) . changeRatio ratio delta
    pureMessage (Squeeze dir ratio delta) =
      fmap (\ratio' -> Squeeze dir ratio' delta) . changeRatio ratio delta

changeRatio :: Rational -> Rational -> SomeMessage -> Maybe Rational
changeRatio ratio delta = fmap f . fromMessage
  where f Expand = ratio * delta
        f Shrink = ratio / delta

dwindle :: AxesGenerator -> Direction2D -> Chirality -> Rational -> Rectangle -> Stack a -> 
           [(a, Rectangle)]
dwindle trans dir rot ratio rect st = unfoldr genRects (integrate st, rect, dirAxes dir, rot)
  where genRects ([],     _, _, _)  = Nothing
        genRects ([w],    r, a, rt) = Just ((w, r),  ([], r,   a,  rt))
        genRects ((w:ws), r, a, rt) = Just ((w, r'), (ws, r'', a', rt'))
          where (r', r'') = splitRect r ratio a
                (a', rt') = trans a rt

squeeze :: Direction2D -> Rational -> Rectangle -> Stack a -> [(a, Rectangle)]
squeeze dir ratio rect st = zip wins rects
  where wins    = integrate st
        nwins   = length wins
        sizes   = take nwins $ unfoldr (\r -> Just (r * ratio, r * ratio)) 1
        totals' = 0 : zipWith (+) sizes totals'
        totals  = tail totals'
        splits  = zip (tail sizes) totals
        ratios  = reverse $ map (\(l, r) -> l / r) splits
        rects   = genRects rect ratios
        genRects r []     = [r]
        genRects r (x:xs) = r' : genRects r'' xs
          where (r', r'') = splitRect r x (dirAxes dir)

splitRect :: Rectangle -> Rational -> Axes -> (Rectangle, Rectangle)
splitRect (Rectangle x y w h) ratio (ax, ay) = (Rectangle x' y' w' h', Rectangle x'' y'' w'' h'')
  where portion = ratio / (ratio + 1)
        w1  = (round $ fi w * portion) :: Int
        w2  = fi w - w1
        h1  = (round $ fi h * portion) :: Int
        h2  = fi h - h1
        x'  = x + fi (negate ax * (1 - ax) * w2 `div` 2)
        y'  = y + fi (negate ay * (1 - ay) * h2 `div` 2)
        w'  = fi $ w1 + (1 - abs ax) * w2
        h'  = fi $ h1 + (1 - abs ay) * h2
        x'' = x + fi (ax * (1 + ax) * w1 `div` 2)
        y'' = y + fi (ay * (1 + ay) * h1 `div` 2)
        w'' = fi $ w2 + (1 - abs ax) * w1
        h'' = fi $ h2 + (1 - abs ay) * h1
        fi :: (Num b, Integral a) => a -> b
        fi  = fromIntegral

type Axes          = (Int, Int)
type AxesGenerator = Axes -> Chirality -> (Axes, Chirality)

dirAxes :: Direction2D -> Axes
dirAxes L = (-1,  0)
dirAxes R = ( 1,  0)
dirAxes U = ( 0, -1)
dirAxes D = ( 0,  1)

alternate :: AxesGenerator
alternate = chDir alt

rotate :: AxesGenerator
rotate = chDir id

chDir :: (Chirality -> Chirality) -> AxesGenerator
chDir f (x, y) r = (a' r, r')
  where a' CW  = (-y,  x)
        a' CCW = ( y, -x)
        r' = f r

alt :: Chirality -> Chirality
alt CW  = CCW
alt CCW = CW