content: {-# 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
  