mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-08-13 03:05:57 -07:00
MosaicAlt take 2
This commit is contained in:
129
MosaicAlt.hs
129
MosaicAlt.hs
@@ -21,6 +21,8 @@ module XMonadContrib.MosaicAlt (
|
|||||||
MosaicAlt(..)
|
MosaicAlt(..)
|
||||||
, shrinkWindowAlt
|
, shrinkWindowAlt
|
||||||
, expandWindowAlt
|
, expandWindowAlt
|
||||||
|
, tallWindowAlt
|
||||||
|
, wideWindowAlt
|
||||||
, resetAlt
|
, resetAlt
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@@ -45,6 +47,8 @@ import Graphics.X11.Types ( Window )
|
|||||||
-- > keys = ...
|
-- > keys = ...
|
||||||
-- > , ((modMask .|. shiftMask, xK_a), withFocused (sendMessage . expandWindowAlt))
|
-- > , ((modMask .|. shiftMask, xK_a), withFocused (sendMessage . expandWindowAlt))
|
||||||
-- > , ((modMask .|. shiftMask, xK_z), withFocused (sendMessage . shrinkWindowAlt))
|
-- > , ((modMask .|. shiftMask, xK_z), withFocused (sendMessage . shrinkWindowAlt))
|
||||||
|
-- > , ((modMask .|. shiftMask, xK_s), withFocused (sendMessage . tallWindowAlt))
|
||||||
|
-- > , ((modMask .|. shiftMask, xK_d), withFocused (sendMessage . wideWindowAlt))
|
||||||
-- > , ((modMask .|. controlMask, xK_space), sendMessage resetAlt)
|
-- > , ((modMask .|. controlMask, xK_space), sendMessage resetAlt)
|
||||||
-- > ...
|
-- > ...
|
||||||
|
|
||||||
@@ -54,83 +58,104 @@ import Graphics.X11.Types ( Window )
|
|||||||
data HandleWindowAlt =
|
data HandleWindowAlt =
|
||||||
ShrinkWindowAlt Window
|
ShrinkWindowAlt Window
|
||||||
| ExpandWindowAlt Window
|
| ExpandWindowAlt Window
|
||||||
|
| TallWindowAlt Window
|
||||||
|
| WideWindowAlt Window
|
||||||
| ResetAlt
|
| ResetAlt
|
||||||
deriving ( Typeable, Eq )
|
deriving ( Typeable, Eq )
|
||||||
instance Message HandleWindowAlt
|
instance Message HandleWindowAlt
|
||||||
shrinkWindowAlt, expandWindowAlt :: Window -> HandleWindowAlt
|
shrinkWindowAlt, expandWindowAlt :: Window -> HandleWindowAlt
|
||||||
|
tallWindowAlt, wideWindowAlt :: Window -> HandleWindowAlt
|
||||||
shrinkWindowAlt = ShrinkWindowAlt
|
shrinkWindowAlt = ShrinkWindowAlt
|
||||||
expandWindowAlt = ExpandWindowAlt
|
expandWindowAlt = ExpandWindowAlt
|
||||||
|
tallWindowAlt = TallWindowAlt
|
||||||
|
wideWindowAlt = WideWindowAlt
|
||||||
resetAlt :: HandleWindowAlt
|
resetAlt :: HandleWindowAlt
|
||||||
resetAlt = ResetAlt
|
resetAlt = ResetAlt
|
||||||
|
|
||||||
type Areas = M.Map Window Rational
|
data Param = Param { area, aspect :: Rational } deriving ( Show, Read )
|
||||||
data MosaicAlt a = MosaicAlt Areas deriving ( Show, Read )
|
type Params = M.Map Window Param
|
||||||
|
data MosaicAlt a = MosaicAlt Params deriving ( Show, Read )
|
||||||
|
|
||||||
instance LayoutClass MosaicAlt Window where
|
instance LayoutClass MosaicAlt Window where
|
||||||
description _ = "MosaicAlt"
|
description _ = "MosaicAlt"
|
||||||
doLayout (MosaicAlt areas) rect stack =
|
doLayout (MosaicAlt params) rect stack =
|
||||||
return (arrange rect stack areas', Just $ MosaicAlt areas')
|
return (arrange rect stack params', Just $ MosaicAlt params')
|
||||||
where
|
where
|
||||||
areas' = ins (W.up stack) $ ins (W.down stack) $ ins [W.focus stack] areas
|
params' = ins (W.up stack) $ ins (W.down stack) $ ins [W.focus stack] params
|
||||||
ins wins as = foldl M.union as $ map (`M.singleton` 1) wins
|
ins wins as = foldl M.union as $ map (`M.singleton` (Param 1 1.5)) wins
|
||||||
|
|
||||||
handleMessage (MosaicAlt areas) msg = return $ case fromMessage msg of
|
handleMessage (MosaicAlt params) msg = return $ case fromMessage msg of
|
||||||
Just (ShrinkWindowAlt w) -> Just $ MosaicAlt $ alter areas w (4 % 5)
|
Just (ShrinkWindowAlt w) -> Just $ MosaicAlt $ alter params w (4 % 5) 1
|
||||||
Just (ExpandWindowAlt w) -> Just $ MosaicAlt $ alter areas w (6 % 5)
|
Just (ExpandWindowAlt w) -> Just $ MosaicAlt $ alter params w (6 % 5) 1
|
||||||
|
Just (TallWindowAlt w) -> Just $ MosaicAlt $ alter params w 1 (3 % 4)
|
||||||
|
Just (WideWindowAlt w) -> Just $ MosaicAlt $ alter params w 1 (5 % 4)
|
||||||
Just ResetAlt -> Just $ MosaicAlt M.empty
|
Just ResetAlt -> Just $ MosaicAlt M.empty
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
-- Layout algorithm entry point.
|
-- Change requested params for a window.
|
||||||
arrange :: Rectangle -> W.Stack Window -> Areas -> [(Window, Rectangle)]
|
alter :: Params -> Window -> Rational -> Rational -> Params
|
||||||
arrange rect stack areas = tree rect (sortBy areaCompare winList) totalArea areas
|
alter params win arDelta asDelta = case M.lookup win params of
|
||||||
where
|
Just (Param ar as) -> M.insert win (Param (ar * arDelta) (as * asDelta)) params
|
||||||
winList = reverse (W.up stack) ++ W.focus stack : W.down stack
|
Nothing -> M.insert win (Param arDelta (1.5 * asDelta)) params
|
||||||
totalArea = areaSum areas winList
|
|
||||||
areaCompare a b = or1 b `compare` or1 a
|
|
||||||
or1 w = maybe 1 id $ M.lookup w areas
|
|
||||||
|
|
||||||
-- Selects a horizontal or vertical split to get the best aspect ratio.
|
-- Layout algorithm entry point.
|
||||||
-- FIXME: Give the user more dynamic control.
|
arrange :: Rectangle -> W.Stack Window -> Params -> [(Window, Rectangle)]
|
||||||
splitBest :: Rational -> Rectangle -> (Rectangle, Rectangle)
|
arrange rect stack params = r
|
||||||
splitBest ratio rect =
|
|
||||||
if (w % h) < cutoff then splitVerticallyBy ratio rect
|
|
||||||
else splitHorizontallyBy ratio rect
|
|
||||||
where
|
where
|
||||||
-- Prefer wide windows to tall ones, mainly because it makes xterms more usable.
|
(_, r) = findSplits 3 rect tree params
|
||||||
cutoff = if w > 1000 then 1.25
|
tree = makeTree (sortBy areaCompare wins) params
|
||||||
else if w < 500 then 2.25
|
wins = reverse (W.up stack) ++ W.focus stack : W.down stack
|
||||||
else 2.25 - (w - 500) % 500
|
areaCompare a b = or1 b `compare` or1 a
|
||||||
w = rect_width rect
|
or1 w = maybe 1 area $ M.lookup w params
|
||||||
h = rect_height rect
|
|
||||||
|
|
||||||
-- Recursively group windows into a binary tree. Aim to balance the tree
|
-- Recursively group windows into a binary tree. Aim to balance the tree
|
||||||
-- according to the total requested area in each branch.
|
-- according to the total requested area in each branch.
|
||||||
tree :: Rectangle -> [Window] -> Rational -> Areas -> [(Window, Rectangle)]
|
data Tree = Node (Rational, Tree) (Rational, Tree) | Leaf Window | None
|
||||||
tree rect winList totalArea areas = case winList of
|
makeTree :: [Window] -> Params -> Tree
|
||||||
[] -> []
|
makeTree wins params = case wins of
|
||||||
[x] -> [(x, rect)]
|
[] -> None
|
||||||
_ -> tree aRect aWins aArea areas ++ tree bRect bWins bArea areas
|
[x] -> Leaf x
|
||||||
where
|
_ -> Node (aArea, makeTree aWins params) (bArea, makeTree bWins params)
|
||||||
(aRect, bRect) = splitBest (aArea / (aArea + bArea)) rect
|
where ((aWins, aArea), (bWins, bArea)) = areaSplit params wins
|
||||||
((aWins, aArea), (bWins, bArea)) = areaSplit areas winList totalArea
|
|
||||||
|
|
||||||
-- Sum the requested areas of a bunch of windows.
|
|
||||||
areaSum :: Areas -> [Window] -> Rational
|
|
||||||
areaSum areas = sum . map (maybe 1 id . flip M.lookup areas)
|
|
||||||
|
|
||||||
-- Split a list of windows in half by area.
|
-- Split a list of windows in half by area.
|
||||||
areaSplit :: Areas -> [Window] -> Rational -> (([Window], Rational), ([Window], Rational))
|
areaSplit :: Params -> [Window] -> (([Window], Rational), ([Window], Rational))
|
||||||
areaSplit areas wins totalArea = ((reverse aWins, aArea), (bWins, bArea))
|
areaSplit params wins = gather [] 0 [] 0 wins
|
||||||
where
|
where
|
||||||
((aWins, aArea), (bWins, bArea)) = gather [] wins 0
|
gather a aa b ba (r : rs) =
|
||||||
gather a b t = if t >= (totalArea / 2) then ((a, t), (b, totalArea - t))
|
if aa <= ba
|
||||||
else gather (head b : a) (tail b) (t + or1 (head b))
|
then gather (r : a) (aa + or1 r) b ba rs
|
||||||
or1 w = maybe 1 id $ M.lookup w areas
|
else gather a aa (r : b) (ba + or1 r) rs
|
||||||
|
gather a aa b ba [] = ((reverse a, aa), (b, ba))
|
||||||
|
or1 w = maybe 1 area $ M.lookup w params
|
||||||
|
|
||||||
-- Change requested area for a window.
|
-- Figure out which ways to split the space, by exhaustive search.
|
||||||
alter :: Areas -> Window -> Rational -> Areas
|
-- Complexity is quadratic in the number of windows.
|
||||||
alter areas win delta = case M.lookup win areas of
|
findSplits :: Int -> Rectangle -> Tree -> Params -> (Double, [(Window, Rectangle)])
|
||||||
Just v -> M.insert win (v * delta) areas
|
findSplits _ _ None _ = (0, [])
|
||||||
Nothing -> M.insert win delta areas
|
findSplits _ rect (Leaf w) params = (aspectBadness rect w params, [(w, rect)])
|
||||||
|
findSplits depth rect (Node (aArea, aTree) (bArea, bTree)) params =
|
||||||
|
if hBadness < vBadness then (hBadness, hList) else (vBadness, vList)
|
||||||
|
where
|
||||||
|
(hBadness, hList) = trySplit splitHorizontallyBy
|
||||||
|
(vBadness, vList) = trySplit splitVerticallyBy
|
||||||
|
trySplit splitBy =
|
||||||
|
(aBadness + bBadness, aList ++ bList)
|
||||||
|
where
|
||||||
|
(aBadness, aList) = findSplits (depth - 1) aRect aTree params
|
||||||
|
(bBadness, bList) = findSplits (depth - 1) bRect bTree params
|
||||||
|
(aRect, bRect) = splitBy ratio rect
|
||||||
|
ratio = aArea / (aArea + bArea)
|
||||||
|
|
||||||
|
-- Decide how much we like this rectangle.
|
||||||
|
aspectBadness :: Rectangle -> Window -> Params -> Double
|
||||||
|
aspectBadness rect win params =
|
||||||
|
(if a < 1 then tall else wide) * sqrt(w * h)
|
||||||
|
where
|
||||||
|
tall = if w < 700 then ((1 / a) * (700 / w)) else 1 / a
|
||||||
|
wide = if w < 700 then a else (a * w / 700)
|
||||||
|
a = (w / h) / fromRational (maybe 1.5 aspect $ M.lookup win params)
|
||||||
|
w = fromIntegral $ rect_width rect
|
||||||
|
h = fromIntegral $ rect_height rect
|
||||||
|
|
||||||
-- vim: sw=4:et
|
-- vim: sw=4:et
|
||||||
|
Reference in New Issue
Block a user