ymonad-0.1.0.0: src/YMonad/Core/Layout.hs
module YMonad.Core.Layout (
Border (..),
Layout (..),
Resize (..),
IncMasterN (..),
ChangeLayout (..),
CLR (..),
LayoutMessage (..),
tall,
full,
mirror,
spacingRaw,
spacing,
spacingWithEdge,
smartSpacing,
smartSpacingWithEdge,
(|||),
description,
applyLayoutMessage,
layoutViews,
mirrorRect,
splitVertically,
splitHorizontally,
splitHorizontallyBy,
splitVerticallyBy,
tile,
) where
import Data.Ratio ((%))
import YMonad.Core.Common (Rect (..))
import YMonad.Core.StackSet (Stack (..), integrate)
data Border = Border
{ borderTop :: Int
, borderBottom :: Int
, borderRight :: Int
, borderLeft :: Int
}
deriving stock (Eq, Ord, Show, Read)
data Resize = Shrink | Expand
deriving stock (Eq, Ord, Show, Read)
newtype IncMasterN = IncMasterN Int
deriving stock (Eq, Ord, Show, Read)
data ChangeLayout = FirstLayout | NextLayout
deriving stock (Eq, Ord, Show, Read)
data CLR = CL | CR
deriving stock (Eq, Ord, Show, Read)
data LayoutMessage
= MsgResize Resize
| MsgIncMasterN IncMasterN
| MsgChangeLayout ChangeLayout
| MsgJumpToLayout Text
deriving stock (Eq, Ord, Show, Read)
data Layout
= Full
| Tall
{ tallNMaster :: Int
, tallRatioIncrement :: Rational
, tallRatio :: Rational
}
| Mirror Layout
| Choose CLR Layout Layout
| Spacing
{ spacingSmartBorder :: Bool
, spacingScreenBorder :: Border
, spacingScreenBorderEnabled :: Bool
, spacingWindowBorder :: Border
, spacingWindowBorderEnabled :: Bool
, spacingInnerLayout :: Layout
}
deriving stock (Eq, Ord, Show, Read)
full :: Layout
full = Full
tall :: Layout
tall = Tall 1 (3 % 100) (1 % 2)
mirror :: Layout -> Layout
mirror = Mirror
spacingRaw :: Bool -> Border -> Bool -> Border -> Bool -> Layout -> Layout
spacingRaw = Spacing
spacing :: Int -> Layout -> Layout
spacing px = spacingRaw False zeroBorder False (uniformBorder px) True
spacingWithEdge :: Int -> Layout -> Layout
spacingWithEdge px = spacingRaw False (uniformBorder px) True (uniformBorder px) True
smartSpacing :: Int -> Layout -> Layout
smartSpacing px = spacingRaw True zeroBorder False (uniformBorder px) True
smartSpacingWithEdge :: Int -> Layout -> Layout
smartSpacingWithEdge px = spacingRaw True (uniformBorder px) True (uniformBorder px) True
(|||) :: Layout -> Layout -> Layout
(|||) = Choose CL
infixr 5 |||
description :: Layout -> Text
description layout = case layout of
Full -> "Full"
Tall {} -> "Tall"
Mirror inner -> "Mirror " <> description inner
Choose CL left _ -> description left
Choose CR _ right -> description right
Spacing {spacingInnerLayout = inner} -> description inner
applyLayoutMessage :: LayoutMessage -> Layout -> Layout
applyLayoutMessage message layout =
case message of
MsgResize resize -> applyResize resize layout
MsgIncMasterN delta -> applyIncMaster delta layout
MsgChangeLayout FirstLayout -> firstLayout layout
MsgChangeLayout NextLayout -> nextLayout layout
MsgJumpToLayout name -> fromMaybe layout (jumpToLayout name layout)
layoutViews :: Layout -> Rect -> Maybe (Stack a) -> [(a, Rect)]
layoutViews _ _ Nothing = []
layoutViews layout rect (Just stack) =
case layout of
Full -> [(stackFocus stack, rect)]
Tall nmaster _ frac ->
let windows = integrate stack
in zip windows (tile frac rect nmaster (length windows))
Mirror inner -> map (second mirrorRect) (layoutViews inner (mirrorRect rect) (Just stack))
Choose CL left _ -> layoutViews left rect (Just stack)
Choose CR _ right -> layoutViews right rect (Just stack)
Spacing smart screenBorder screenEnabled windowBorder windowEnabled inner ->
let windows = integrate stack
applyBorders = not smart || length windows >= 2
parentRect =
if applyBorders && screenEnabled
then insetRect screenBorder rect
else rect
childViews = layoutViews inner parentRect (Just stack)
in if applyBorders && windowEnabled
then map (second (insetRect windowBorder)) childViews
else childViews
applyResize :: Resize -> Layout -> Layout
applyResize resize layout = case layout of
Tall nmaster delta frac ->
Tall nmaster delta (clampRatio (case resize of Shrink -> frac - delta; Expand -> frac + delta))
Mirror inner -> Mirror (applyResize resize inner)
Choose CL left right -> Choose CL (applyResize resize left) right
Choose CR left right -> Choose CR left (applyResize resize right)
Spacing smart screenBorder screenEnabled windowBorder windowEnabled inner ->
Spacing smart screenBorder screenEnabled windowBorder windowEnabled (applyResize resize inner)
Full -> Full
applyIncMaster :: IncMasterN -> Layout -> Layout
applyIncMaster (IncMasterN delta) layout = case layout of
Tall nmaster ratioIncrement frac -> Tall (max 0 (nmaster + delta)) ratioIncrement frac
Mirror inner -> Mirror (applyIncMaster (IncMasterN delta) inner)
Choose CL left right -> Choose CL (applyIncMaster (IncMasterN delta) left) right
Choose CR left right -> Choose CR left (applyIncMaster (IncMasterN delta) right)
Spacing smart screenBorder screenEnabled windowBorder windowEnabled inner ->
Spacing smart screenBorder screenEnabled windowBorder windowEnabled (applyIncMaster (IncMasterN delta) inner)
Full -> Full
firstLayout :: Layout -> Layout
firstLayout layout = case layout of
Choose _ left right -> Choose CL (firstLayout left) (firstLayout right)
Mirror inner -> Mirror (firstLayout inner)
Spacing smart screenBorder screenEnabled windowBorder windowEnabled inner ->
Spacing smart screenBorder screenEnabled windowBorder windowEnabled (firstLayout inner)
_ -> layout
nextLayout :: Layout -> Layout
nextLayout layout = fromMaybe (firstLayout layout) (nextLayoutNoWrap layout)
nextLayoutNoWrap :: Layout -> Maybe Layout
nextLayoutNoWrap layout = case layout of
Full -> Nothing
Tall {} -> Nothing
Mirror inner -> Mirror <$> nextLayoutNoWrap inner
Choose CL left right ->
case nextLayoutNoWrap left of
Just left' -> Just (Choose CL left' right)
Nothing -> Just (Choose CR (firstLayout left) (firstLayout right))
Choose CR left right ->
Choose CR left <$> nextLayoutNoWrap right
Spacing smart screenBorder screenEnabled windowBorder windowEnabled inner ->
Spacing smart screenBorder screenEnabled windowBorder windowEnabled <$> nextLayoutNoWrap inner
jumpToLayout :: Text -> Layout -> Maybe Layout
jumpToLayout name layout
| description layout == name = Just layout
| otherwise =
case layout of
Mirror inner -> Mirror <$> jumpToLayout name inner
Choose _ left right ->
let left' = fromMaybe left (jumpToLayout name left)
right' = fromMaybe right (jumpToLayout name right)
in if description left' == name
then Just (Choose CL left' right')
else
if description right' == name
then Just (Choose CR left' right')
else Nothing
Spacing smart screenBorder screenEnabled windowBorder windowEnabled inner ->
Spacing smart screenBorder screenEnabled windowBorder windowEnabled <$> jumpToLayout name inner
_ -> Nothing
clampRatio :: Rational -> Rational
clampRatio = max 0 . min 1
tile :: Rational -> Rect -> Int -> Int -> [Rect]
tile frac rect nmaster n
| n <= 0 = []
| n <= nmaster || nmaster == 0 = splitVertically n rect
| otherwise = splitVertically nmaster masterRect ++ splitVertically (n - nmaster) stackRect
where
(masterRect, stackRect) = splitHorizontallyBy (clampRatio frac) rect
splitVertically :: Int -> Rect -> [Rect]
splitVertically n rect
| n <= 0 = []
| n < 2 = [rect]
| otherwise = firstRect : splitVertically (n - 1) remainderRect
where
smallHeight = rectHeight rect `div` n
firstRect =
Rect
{ rectX = rectX rect
, rectY = rectY rect
, rectWidth = rectWidth rect
, rectHeight = smallHeight
}
remainderRect =
Rect
{ rectX = rectX rect
, rectY = rectY rect + smallHeight
, rectWidth = rectWidth rect
, rectHeight = rectHeight rect - smallHeight
}
splitHorizontally :: Int -> Rect -> [Rect]
splitHorizontally n = map mirrorRect . splitVertically n . mirrorRect
splitHorizontallyBy :: Rational -> Rect -> (Rect, Rect)
splitHorizontallyBy frac rect =
( Rect
{ rectX = rectX rect
, rectY = rectY rect
, rectWidth = leftWidth
, rectHeight = rectHeight rect
}
, Rect
{ rectX = rectX rect + leftWidth
, rectY = rectY rect
, rectWidth = rectWidth rect - leftWidth
, rectHeight = rectHeight rect
}
)
where
leftWidth = floor (fromIntegral (rectWidth rect) * frac)
splitVerticallyBy :: Rational -> Rect -> (Rect, Rect)
splitVerticallyBy frac = both mirrorRect . splitHorizontallyBy frac . mirrorRect
mirrorRect :: Rect -> Rect
mirrorRect rect =
Rect
{ rectX = rectY rect
, rectY = rectX rect
, rectWidth = rectHeight rect
, rectHeight = rectWidth rect
}
both :: (a -> b) -> (a, a) -> (b, b)
both f (x, y) = (f x, f y)
uniformBorder :: Int -> Border
uniformBorder px = Border px px px px
zeroBorder :: Border
zeroBorder = uniformBorder 0
insetRect :: Border -> Rect -> Rect
insetRect border rect =
Rect
{ rectX = rectX rect + left
, rectY = rectY rect + top
, rectWidth = max 0 (rectWidth rect - left - right)
, rectHeight = max 0 (rectHeight rect - top - bottom)
}
where
top = max 0 (borderTop border)
bottom = max 0 (borderBottom border)
right = max 0 (borderRight border)
left = max 0 (borderLeft border)