mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 03:20:21 -07:00
Many instance declarations can now be derived either by DerivingVia or GeneralizedNewtypeDeriving.
204 lines
7.5 KiB
Haskell
204 lines
7.5 KiB
Haskell
{-# LANGUAGE DeriveFoldable #-}
|
|
{-# LANGUAGE DeriveFunctor #-}
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
{-# LANGUAGE PatternGuards #-}
|
|
-----------------------------------------------------------------------------
|
|
-- |
|
|
-- Module : XMonad.Layout.Mosaic
|
|
-- Description : Give each window a specified amount of screen space relative to the others.
|
|
-- Copyright : (c) 2009 Adam Vogt, 2007 James Webb
|
|
-- License : BSD-style (see xmonad/LICENSE)
|
|
--
|
|
-- Maintainer : vogt.adam<at>gmail.com
|
|
-- Stability : unstable
|
|
-- Portability : unportable
|
|
--
|
|
-- Based on MosaicAlt, but aspect ratio messages always change the aspect
|
|
-- ratios, and rearranging the window stack changes the window sizes.
|
|
--
|
|
-----------------------------------------------------------------------------
|
|
|
|
module XMonad.Layout.Mosaic (
|
|
-- * Usage
|
|
-- $usage
|
|
Aspect(..)
|
|
,mosaic
|
|
,changeMaster
|
|
,changeFocused
|
|
|
|
,Mosaic
|
|
)
|
|
where
|
|
|
|
import Prelude hiding (sum)
|
|
|
|
import XMonad(LayoutClass(doLayout, handleMessage, pureMessage, description),
|
|
Message, X, fromMessage, withWindowSet, Resize(..),
|
|
splitHorizontallyBy, splitVerticallyBy, sendMessage, Rectangle)
|
|
import XMonad.Prelude (mplus, on, sortBy, sum)
|
|
import qualified XMonad.StackSet as W
|
|
import Control.Arrow(second, first)
|
|
|
|
-- $usage
|
|
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
|
|
--
|
|
-- > import XMonad.Layout.Mosaic
|
|
--
|
|
-- Then edit your @layoutHook@ by adding the Mosaic layout:
|
|
--
|
|
-- > myLayout = mosaic 2 [3,2] ||| Full ||| etc..
|
|
-- > main = xmonad $ def { layoutHook = myLayout }
|
|
--
|
|
-- Unfortunately, infinite lists break serialization, so don't use them. And if
|
|
-- the list is too short, it is extended with @++ repeat 1@, which covers the
|
|
-- main use case.
|
|
--
|
|
-- To change the choice in aspect ratio and the relative sizes of windows, add
|
|
-- to your keybindings:
|
|
--
|
|
-- > , ((modm, xK_a), sendMessage Taller)
|
|
-- > , ((modm, xK_z), sendMessage Wider)
|
|
--
|
|
-- > , ((modm, xK_r), sendMessage Reset)
|
|
--
|
|
-- For more detailed instructions on editing the layoutHook see
|
|
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial> and
|
|
-- "XMonad.Doc.Extending#Editing_the_layout_hook".
|
|
|
|
data Aspect
|
|
= Taller
|
|
| Wider
|
|
| Reset
|
|
| SlopeMod ([Rational] -> [Rational])
|
|
|
|
instance Message Aspect
|
|
|
|
-- | The relative magnitudes (the sign is ignored) of the rational numbers in
|
|
-- the second argument determine the relative areas that the windows receive.
|
|
-- The first number represents the size of the master window, the second is for
|
|
-- the next window in the stack, and so on.
|
|
--
|
|
-- The list is extended with @++ repeat 1@, so @mosaic 1.5 []@ is like a
|
|
-- resizable grid.
|
|
--
|
|
-- The first parameter is the multiplicative factor to use when responding to
|
|
-- the 'Expand' message.
|
|
mosaic :: Rational -> [Rational] -> Mosaic a
|
|
mosaic = Mosaic Nothing
|
|
|
|
data Mosaic a = -- | True to override the aspect, current index, maximum index
|
|
Mosaic (Maybe(Bool,Rational,Int)) Rational [Rational] deriving (Read,Show)
|
|
|
|
instance LayoutClass Mosaic a where
|
|
description = const "Mosaic"
|
|
|
|
pureMessage (Mosaic Nothing _ _) _ = Nothing
|
|
pureMessage (Mosaic (Just(_,ix,mix)) delta ss) ms = fromMessage ms >>= ixMod
|
|
where ixMod Taller | round ix >= mix = Nothing
|
|
| otherwise = Just $ Mosaic (Just(False,succ ix,mix)) delta ss
|
|
ixMod Wider | round ix <= (0::Integer) = Nothing
|
|
| otherwise = Just $ Mosaic (Just(False,pred ix,mix)) delta ss
|
|
ixMod Reset = Just $ Mosaic Nothing delta ss
|
|
ixMod (SlopeMod f) = Just $ Mosaic (Just(False,ix,mix)) delta (f ss)
|
|
|
|
handleMessage l@(Mosaic _ delta _) ms
|
|
| Just Expand <- fromMessage ms = changeFocused (*delta) >> return Nothing
|
|
| Just Shrink <- fromMessage ms = changeFocused (/delta) >> return Nothing
|
|
| otherwise = return $ pureMessage l ms
|
|
|
|
doLayout (Mosaic state delta ss) r st = let
|
|
ssExt = zipWith const (ss ++ repeat 1) $ W.integrate st
|
|
rects = splits r ssExt
|
|
nls = length rects
|
|
fi = fromIntegral
|
|
nextIx (ov,ix,mix)
|
|
| mix <= 0 || ov = fromIntegral $ nls `div` 2
|
|
| otherwise = max 0 $ (*fi (pred nls)) $ min 1 $ ix / fi mix
|
|
rect = rects !! maybe (nls `div` 2) (round . nextIx) state
|
|
state' = fmap (\x@(ov,_,_) -> (ov,nextIx x,pred nls)) state
|
|
`mplus` Just (True,fromIntegral nls / 2,pred nls)
|
|
ss' = maybe ss (const ss `either` const ssExt) $ zipRemain ss ssExt
|
|
in return (zip (W.integrate st) rect, Just $ Mosaic state' delta ss')
|
|
|
|
zipRemain :: [a] -> [b] -> Maybe (Either [a] [b])
|
|
zipRemain (_:xs) (_:ys) = zipRemain xs ys
|
|
zipRemain [] [] = Nothing
|
|
zipRemain [] y = Just (Right y)
|
|
zipRemain x [] = Just (Left x)
|
|
|
|
-- | These sample functions are meant to be applied to the list of window sizes
|
|
-- through the 'SlopeMod' message.
|
|
changeMaster :: (Rational -> Rational) -> X ()
|
|
changeMaster = sendMessage . SlopeMod . onHead
|
|
|
|
-- | Apply a function to the Rational that represents the currently focused
|
|
-- window.
|
|
--
|
|
-- 'Expand' and 'Shrink' messages are responded to with @changeFocused
|
|
-- (*delta)@ or @changeFocused (delta/)@ where @delta@ is the first argument to
|
|
-- 'mosaic'.
|
|
--
|
|
-- This is exported because other functions (ex. @const 1@, @(+1)@) may be
|
|
-- useful to apply to the current area.
|
|
changeFocused :: (Rational -> Rational) -> X ()
|
|
changeFocused f = withWindowSet $ sendMessage . SlopeMod
|
|
. maybe id (mulIx . length . W.up)
|
|
. W.stack . W.workspace . W.current
|
|
where mulIx i = uncurry (++) . second (onHead f) . splitAt i
|
|
|
|
onHead :: (a -> a) -> [a] -> [a]
|
|
onHead f = uncurry (++) . first (fmap f) . splitAt 1
|
|
|
|
splits :: Rectangle -> [Rational] -> [[Rectangle]]
|
|
splits rect = map (reverse . map snd . sortBy (compare `on` fst))
|
|
. splitsL rect . makeTree snd . zip [1..]
|
|
. normalize . reverse . map abs
|
|
|
|
splitsL :: Rectangle -> Tree (Int,Rational) -> [[(Int,Rectangle)]]
|
|
splitsL _rect Empty = []
|
|
splitsL rect (Leaf (x,_)) = [[(x,rect)]]
|
|
splitsL rect (Branch l r) = do
|
|
let mkSplit f = f ((sumSnd l /) $ sumSnd l + sumSnd r) rect
|
|
sumSnd = sum . fmap snd
|
|
(rl,rr) <- map mkSplit [splitVerticallyBy,splitHorizontallyBy]
|
|
splitsL rl l `interleave` splitsL rr r
|
|
|
|
-- like zipWith (++), but when one list is shorter, its elements are duplicated
|
|
-- so that they match
|
|
interleave :: [[a]] -> [[a]] -> [[a]]
|
|
interleave xs ys | lx > ly = zc xs (extend lx ys)
|
|
| otherwise = zc (extend ly xs) ys
|
|
where lx = length xs
|
|
ly = length ys
|
|
zc = zipWith (++)
|
|
|
|
extend :: Int -> [a] -> [a]
|
|
extend n pat = do
|
|
(p,e) <- zip pat $ replicate m True ++ repeat False
|
|
[p | e] ++ replicate d p
|
|
where (d,m) = n `divMod` length pat
|
|
|
|
normalize :: Fractional a => [a] -> [a]
|
|
normalize x = let s = sum x in map (/s) x
|
|
|
|
data Tree a = Branch (Tree a) (Tree a) | Leaf a | Empty
|
|
deriving (Functor, Show, Foldable)
|
|
|
|
instance Semigroup (Tree a) where
|
|
Empty <> x = x
|
|
x <> Empty = x
|
|
x <> y = Branch x y
|
|
|
|
instance Monoid (Tree a) where
|
|
mempty = Empty
|
|
|
|
makeTree :: (Num a1, Ord a1) => (a -> a1) -> [a] -> Tree a
|
|
makeTree _ [] = Empty
|
|
makeTree _ [x] = Leaf x
|
|
makeTree f xs = Branch (makeTree f a) (makeTree f b)
|
|
where ((a,b),_) = foldr go (([],[]),(0,0)) xs
|
|
go n ((ls,rs),(l,r))
|
|
| l > r = ((ls,n:rs),(l,f n+r))
|
|
| otherwise = ((n:ls,rs),(f n+l,r))
|