{-# LANGUAGE PatternGuards, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, ParallelListComp, DeriveDataTypeable #-}
module XMonad.Layout.AvoidFloats (
avoidFloats,
avoidFloats',
AvoidFloatMsg(..),
AvoidFloatItemMsg(..),
) where
import XMonad
import XMonad.Layout.LayoutModifier
import qualified XMonad.StackSet as W
import Data.List
import Data.Ord
import Data.Maybe
import qualified Data.Map as M
import qualified Data.Set as S
avoidFloats
:: l a
-> ModifiedLayout AvoidFloats l a
avoidFloats = avoidFloats' 100 100 False
avoidFloats'
:: Int
-> Int
-> Bool
-> l a
-> ModifiedLayout AvoidFloats l a
avoidFloats' w h act = ModifiedLayout (AvoidFloats Nothing S.empty w h act)
data AvoidFloats a = AvoidFloats
{ cache :: Maybe ((M.Map a W.RationalRect, Rectangle), Rectangle)
, chosen :: S.Set a
, minw :: Int
, minh :: Int
, avoidAll :: Bool
} deriving (Read, Show)
data AvoidFloatMsg
= AvoidFloatToggle
| AvoidFloatSet Bool
| AvoidFloatClearItems
deriving (Typeable)
data AvoidFloatItemMsg a
= AvoidFloatAddItem a
| AvoidFloatRemoveItem a
| AvoidFloatToggleItem a
deriving (Typeable)
instance Message AvoidFloatMsg
instance Typeable a => Message (AvoidFloatItemMsg a)
instance LayoutModifier AvoidFloats Window where
modifyLayoutWithUpdate lm w r = withDisplay $ \d -> do
floating <- gets $ W.floating . windowset
case cache lm of
Just (key, mer) | key == (floating,r) -> flip (,) Nothing `fmap` runLayout w mer
_ -> do rs <- io $ map toRect `fmap` mapM (getWindowAttributes d) (filter shouldAvoid $ M.keys floating)
let mer = maximumBy (comparing area) $ filter bigEnough $ maxEmptyRectangles r rs
flip (,) (Just $ pruneWindows $ lm { cache = Just ((floating,r),mer) }) `fmap` runLayout w mer
where
toRect :: WindowAttributes -> Rectangle
toRect wa = let b = fi $ wa_border_width wa
in Rectangle (fi $ wa_x wa) (fi $ wa_y wa) (fi $ wa_width wa + 2*b) (fi $ wa_height wa + 2*b)
bigEnough :: Rectangle -> Bool
bigEnough rect = rect_width rect >= fi (minw lm) && rect_height rect >= fi (minh lm)
shouldAvoid a = avoidAll lm || a `S.member` chosen lm
pureMess lm m
| Just (AvoidFloatToggle) <- fromMessage m = Just $ lm { avoidAll = not (avoidAll lm), cache = Nothing }
| Just (AvoidFloatSet s) <- fromMessage m, s /= avoidAll lm = Just $ lm { avoidAll = s, cache = Nothing }
| Just (AvoidFloatClearItems) <- fromMessage m = Just $ lm { chosen = S.empty, cache = Nothing }
| Just (AvoidFloatAddItem a) <- fromMessage m, a `S.notMember` chosen lm = Just $ lm { chosen = S.insert a (chosen lm), cache = Nothing }
| Just (AvoidFloatRemoveItem a) <- fromMessage m, a `S.member` chosen lm = Just $ lm { chosen = S.delete a (chosen lm), cache = Nothing }
| Just (AvoidFloatToggleItem a) <- fromMessage m = let op = if a `S.member` chosen lm then S.delete else S.insert
in Just $ lm { chosen = op a (chosen lm), cache = Nothing }
| otherwise = Nothing
pruneWindows :: AvoidFloats Window -> AvoidFloats Window
pruneWindows lm = case cache lm of
Nothing -> lm
Just ((floating,_),_) -> lm { chosen = S.filter (flip M.member floating) (chosen lm) }
maxEmptyRectangles :: Rectangle -> [Rectangle] -> [Rectangle]
maxEmptyRectangles br rectangles = filter (\a -> area a > 0) $ upAndDownEdge ++ noneOrUpEdge ++ downEdge
where
upAndDownEdge = findGaps br rectangles
noneOrUpEdge = concat $ map (everyLower br bottoms) bottoms
downEdge = concat $ map maybeToList $ map (bottomEdge br bottoms) bottoms
bottoms = sortBy (comparing bottom) $ splitContainers rectangles
everyLower :: Rectangle -> [Rectangle] -> Rectangle -> [Rectangle]
everyLower br bottoms r = let (rs, boundLeft, boundRight, boundRects) = foldr (everyUpper r) ([], left br, right br, reverse bottoms) bottoms
(boundLeft', boundRight', _) = shrinkBounds boundLeft boundRight boundRects r (top br)
in mkRect boundLeft' boundRight' (top br) (top r) ?: rs
everyUpper
:: Rectangle
-> Rectangle
-> ([Rectangle],Int,Int,[Rectangle])
-> ([Rectangle],Int,Int,[Rectangle])
everyUpper lower upper (rs, boundLeft, boundRight, boundRects) = (r?:rs, boundLeft', boundRight', boundRects')
where
r = mkRect boundLeft' boundRight' (bottom upper) (top lower)
(boundLeft', boundRight', boundRects') = shrinkBounds boundLeft boundRight boundRects lower (bottom upper)
shrinkBounds :: Int -> Int -> [Rectangle] -> Rectangle -> Int -> (Int, Int, [Rectangle])
shrinkBounds boundLeft boundRight boundRects lower upperLimit = (boundLeft', boundRight', boundRects')
where
(shrinkers, boundRects') = span (\a -> bottom a > upperLimit) boundRects
(boundLeft', boundRight') = foldr (shrinkBounds' lower) (boundLeft, boundRight) $ filter (\a -> top a < top lower) shrinkers
shrinkBounds' :: Rectangle -> Rectangle -> (Int, Int) -> (Int, Int)
shrinkBounds' mr r (boundLeft, boundRight)
| right r < right mr = (max boundLeft $ right r, boundRight)
| left r > left mr = (boundLeft, min boundRight $ left r)
| otherwise = (right r, left r)
bottomEdge :: Rectangle -> [Rectangle] -> Rectangle -> Maybe Rectangle
bottomEdge br bottoms r = let rs = filter (\a -> bottom r < bottom a && top a < bottom br) bottoms
boundLeft = maximum $ left br : (filter (< right r) $ map right rs)
boundRight = minimum $ right br : (filter (> left r) $ map left rs)
in if any (\a -> left a <= left r && right r <= right a) rs
then Nothing
else mkRect boundLeft boundRight (bottom r) (bottom br)
splitContainers :: [Rectangle] -> [Rectangle]
splitContainers rects = splitContainers' [] $ sortBy (comparing rect_width) rects
where
splitContainers' :: [Rectangle] -> [Rectangle] -> [Rectangle]
splitContainers' res [] = res
splitContainers' res (r:rs) = splitContainers' (r:res) $ concat $ map (doSplit r) rs
doSplit :: Rectangle -> Rectangle -> [Rectangle]
doSplit guide r
| left guide <= left r || right r <= right guide = [r]
| otherwise = let w0 = fi (rect_x guide - rect_x r) + (rect_width guide `div` 2)
w1 = rect_width r - w0
in [ Rectangle (rect_x r) (rect_y r) w0 (rect_height r)
, Rectangle (rect_x r + fi w0) (rect_y r) w1 (rect_height r)
]
findGaps
:: Rectangle
-> [Rectangle]
-> [Rectangle]
findGaps br rs = let (gaps,end) = foldr findGaps' ([], left br) $ sortBy (flip $ comparing left) $ filter inBounds rs
lastgap = mkRect end (right br) (top br) (bottom br)
in lastgap?:gaps
where
findGaps' :: Rectangle -> ([Rectangle], Int) -> ([Rectangle], Int)
findGaps' r (gaps, end) = let gap = mkRect end (left r) (top br) (bottom br)
in (gap?:gaps, max end (right r))
inBounds :: Rectangle -> Bool
inBounds r = left r < right br && left br < right r
fi :: (Integral a, Num b) => a -> b
fi x = fromIntegral x
(?:) :: Maybe a -> [a] -> [a]
Just x ?: xs = x:xs
_ ?: xs = xs
left, right, top, bottom, area :: Rectangle -> Int
left r = fi (rect_x r)
right r = fi (rect_x r) + fi (rect_width r)
top r = fi (rect_y r)
bottom r = fi (rect_y r) + fi (rect_height r)
area r = fi (rect_width r * rect_height r)
mkRect :: Int -> Int -> Int -> Int -> Maybe Rectangle
mkRect l r t b = let rect = Rectangle (fi l) (fi t) (fi $ max 0 $ r-l) (fi $ max 0 $ b-t)
in if area rect > 0
then Just rect
else Nothing