Skip to content

Commit

Permalink
Merge pull request #848 from portnov/master
Browse files Browse the repository at this point in the history
Add new module: X.L.CircleEx: advanced version of Circle layout
  • Loading branch information
slotThe authored Dec 21, 2023
2 parents 75d67cf + 4f2a5c7 commit c01cd3a
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 79 deletions.
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
- Deprecated the entire module, use `XMonad.Actions.WithAll`
instead.

* `XMonad.Layout.Circle`:

- Deprecated the entire module, use the `circle` function from
`XMonad.Layout.CircleEx` instead.

* `XMonad.Hooks.EwmhDesktops`

- `_NET_CLIENT_LIST_STACKING` puts windows in the current workspace at the
Expand Down Expand Up @@ -135,6 +140,11 @@
There's both an action to be bound to a key, and hooks that plug into
`XMonad.Hooks.EwmhDesktops`.

* `XMonad.Layout.CircleEx`:

- A new window layout, similar to X.L.Circle, but with more
possibilities for customisation.

### Bug Fixes and Minor Changes

* `XMonad.Layout.Magnifier`
Expand Down
51 changes: 10 additions & 41 deletions XMonad/Layout/Circle.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms #-}

-----------------------------------------------------------------------------
-- |
Expand All @@ -15,15 +16,14 @@
--
-----------------------------------------------------------------------------

module XMonad.Layout.Circle (
-- * Usage
-- $usage
Circle (..)
) where -- actually it's an ellipse
module XMonad.Layout.Circle {-# DEPRECATED "Use XMonad.Layout.CircleEx instead" #-}
( -- * Usage
-- $usage
pattern Circle
) where -- actually it's an ellipse

import XMonad.Prelude
import XMonad
import XMonad.StackSet (integrate, peek)
import GHC.Real (Ratio(..))
import XMonad.Layout.CircleEx

-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
Expand All @@ -39,37 +39,6 @@ import XMonad.StackSet (integrate, peek)
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial> and
-- "XMonad.Doc.Extending#Editing_the_layout_hook".

data Circle a = Circle deriving ( Read, Show )
pattern Circle :: CircleEx a
pattern Circle = CircleEx 1 (70 :% 99) (2 :% 5) 1 0

instance LayoutClass Circle Window where
doLayout Circle r s = do layout <- raiseFocus $ circleLayout r $ integrate s
return (layout, Nothing)

circleLayout :: Rectangle -> [a] -> [(a, Rectangle)]
circleLayout _ [] = []
circleLayout r (w:ws) = master : rest
where master = (w, center r)
rest = zip ws $ map (satellite r) [0, pi * 2 / fromIntegral (length ws) ..]

raiseFocus :: [(Window, Rectangle)] -> X [(Window, Rectangle)]
raiseFocus xs = do focused <- withWindowSet (return . peek)
return $ case find ((== focused) . Just . fst) xs of
Just x -> x : delete x xs
Nothing -> xs

center :: Rectangle -> Rectangle
center (Rectangle sx sy sw sh) = Rectangle x y w h
where s = sqrt 2 :: Double
w = round (fromIntegral sw / s)
h = round (fromIntegral sh / s)
x = sx + fromIntegral (sw - w) `div` 2
y = sy + fromIntegral (sh - h) `div` 2

satellite :: Rectangle -> Double -> Rectangle
satellite (Rectangle sx sy sw sh) a = Rectangle (sx + round (rx + rx * cos a))
(sy + round (ry + ry * sin a))
w h
where rx = fromIntegral (sw - w) / 2
ry = fromIntegral (sh - h) / 2
w = sw * 10 `div` 25
h = sh * 10 `div` 25
189 changes: 189 additions & 0 deletions XMonad/Layout/CircleEx.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Layout.CircleEx
-- Description : An elliptical, overlapping layout—extended version.
-- Copyright : (c) Peter De Wachter, Ilya V. Portnov
-- License : BSD-style (see LICENSE)
--
-- Maintainer : Ilya V. Portnov <[email protected]>
-- Stability : unstable
-- Portability : unportable
--
-- Circle is an elliptical, overlapping layout. Original code by Peter De Wachter,
-- extended by Ilya Porntov.
-----------------------------------------------------------------------------

module XMonad.Layout.CircleEx (
-- * Usage
-- $usage
CircleEx (..), circle, circleEx,
CircleExMsg (..)
)
where

import Data.Ratio

import XMonad
import XMonad.StackSet (Stack)
import XMonad.Prelude
import qualified XMonad.StackSet as W

-- $usage
--
-- The layout puts the first N windows (called master) into the center of
-- screen. All others (called secondary, or stack) are organized in a circle
-- (well, ellipse). When opening a new secondary window, its size will be
-- slightly smaller than that of its predecessor (this is configurable). If
-- the number of master windows is set to zero, all windows will be arranged
-- in a circle. If there is more than one master window, they will be stacked
-- in the center on top of each other. The size of each additional master
-- window will again be slightly smaller than that of the former.
--
-- Since a picture says more than a thousand words, you see one
-- <https://github.com/xmonad/xmonad-contrib/assets/50166980/90ef1273-5201-4380-8b94-9e62d3c98e1c here>.
--
-- You can use this module with the following in your @xmonad.hs@:
--
-- > import XMonad.Layout.CircleEx
--
-- Then edit your @layoutHook@ by adding the 'CircleEx' layout:
--
-- > myCircle = circleEx {cDelta = -3*pi/4}
-- > myLayout = myCircle ||| Full ||| etc..
-- > main = xmonad def { layoutHook = myLayout }
--
-- This layout understands standard messages:
--
-- * 'IncMasterN': increase or decrease the number of master windows.
-- * 'Shrink' and 'Expand': change the size of master windows.
--
-- More layout-specific messages are also supported, see 'CircleExMsg' below.
--
-- For more detailed instructions on editing the layoutHook see:
-- "XMonad.Doc.Extending#Editing_the_layout_hook"

-- | The layout data type. It is recommended to not use the 'CircleEx' data
-- constructor directly, and instead rely on record update syntax; for
-- example: @circleEx {cMasterRatio = 4%5}@. In this way you can avoid nasty
-- surprises if one day additional fields are added to @CircleEx@.
data CircleEx a = CircleEx
{ cNMaster :: !Int -- ^ Number of master windows. Default value is 1.
, cMasterRatio :: !Rational -- ^ Size of master window in relation to screen size.
-- Default value is @4%5@.
, cStackRatio :: !Rational -- ^ Size of first secondary window in relation to screen size.
-- Default value is @3%5@.
, cMultiplier :: !Rational -- ^ Coefficient used to calculate the sizes of subsequent secondary
-- windows. The size of the next window is calculated as the
-- size of the previous one multiplied by this value.
-- This value is also used to scale master windows, in case
-- there is more than one.
-- Default value is @5%6@. Set this to 1 if you want all secondary
-- windows to have the same size.
, cDelta :: !Double -- ^ Angle of rotation of the whole circle layout. Usual values
-- are from 0 to 2π, although it will work outside
-- this range as well. Default value of 0 means that the first
-- secondary window will be placed at the right side of screen.
} deriving (Eq, Show, Read)

-- | Circle layout with default settings:
--
-- * Number of master windows is set to 1
-- * @cMasterRatio@ is set to @70/99@, which is nearly @1/sqrt(2)@
-- * @cStackRatio@ is set to @2/5@
-- * @cMultiplier@ is set to 1, which means all secondary windows
-- will have the same size
--
-- This can be used as a drop-in replacement for "XMonad.Layout.Circle".
circle :: CircleEx a
circle = CircleEx 1 (70%99) (2%5) 1 0

-- | Another variant of default settings for circle layout:
--
-- * Number of master windows is set to 1
-- * @cMasterRatio@ is set to @4/5@
-- * @cStackRatio@ is set to @3/5@
-- * @cMultiplier@ is set to @5/6@
--
circleEx :: CircleEx a
circleEx = CircleEx 1 (4%5) (3%5) (5%6) 0

-- | Specific messages understood by CircleEx layout.
data CircleExMsg
= Rotate !Double -- ^ Rotate secondary windows by specific angle
| IncStackRatio !Rational -- ^ Increase (or decrease, with negative value) sizes of secondary windows
| IncMultiplier !Rational -- ^ Increase 'cMultiplier'.
deriving (Eq, Show, Typeable)

instance Message CircleExMsg

instance LayoutClass CircleEx Window where
doLayout :: CircleEx Window -> Rectangle -> Stack Window -> X ([(Window, Rectangle)], Maybe (CircleEx Window))
doLayout layout rect stack = do
result <- raiseFocus $ circleLayout layout rect $ W.integrate stack
return (result, Nothing)

pureMessage :: CircleEx Window -> SomeMessage -> Maybe (CircleEx Window)
pureMessage layout m =
msum [changeMasterN <$> fromMessage m,
resize <$> fromMessage m,
specific <$> fromMessage m]
where
deltaSize = 11 % 10

resize :: Resize -> CircleEx a
resize Shrink = layout {cMasterRatio = max 0.1 $ min 1.0 $ cMasterRatio layout / deltaSize}
resize Expand = layout {cMasterRatio = max 0.1 $ min 1.0 $ cMasterRatio layout * deltaSize}

changeMasterN :: IncMasterN -> CircleEx a
changeMasterN (IncMasterN d) = layout {cNMaster = max 0 (cNMaster layout + d)}

specific :: CircleExMsg -> CircleEx a
specific (Rotate delta) = layout {cDelta = delta + cDelta layout}
specific (IncStackRatio delta) = layout {cStackRatio = max 0.1 $ min 2.0 $ delta + cStackRatio layout}
specific (IncMultiplier delta) = layout {cMultiplier = max 0.1 $ min 2.0 $ delta + cMultiplier layout}

circleLayout :: CircleEx a -> Rectangle -> [a] -> [(a, Rectangle)]
circleLayout _ _ [] = []
circleLayout (CircleEx {..}) rectangle wins =
master (take cNMaster wins) ++ rest (drop cNMaster wins)
where
master :: [a] -> [(a, Rectangle)]
master ws = zip ws $ map (placeCenter cMasterRatio cMultiplier rectangle)
[cNMaster-1, cNMaster-2 .. 0]
rest :: [a] -> [(a, Rectangle)]
rest ws = zip ws $ zipWith (placeSatellite cStackRatio cMultiplier rectangle)
(map (+ cDelta) [0, pi*2 / fromIntegral (length ws) ..])
[0 ..]


raiseFocus :: [(Window, Rectangle)] -> X [(Window, Rectangle)]
raiseFocus wrs = do
focused <- withWindowSet (return . W.peek)
return $ case find ((== focused) . Just . fst) wrs of
Just x -> x : delete x wrs
Nothing -> wrs

placeCenter :: Rational -> Rational -> Rectangle -> Int -> Rectangle
placeCenter ratio multiplier (Rectangle x y width height) n = Rectangle x' y' width' height'
where
m = ratio * multiplier ^ n
width' = round (m * fromIntegral width)
height' = round (m * fromIntegral height)
x' = x + fromIntegral (width - width') `div` 2
y' = y + fromIntegral (height - height') `div` 2

placeSatellite :: Rational -> Rational -> Rectangle -> Double -> Int -> Rectangle
placeSatellite ratio multiplier (Rectangle x y width height) alpha n =
Rectangle x' y' width' height'
where
m = ratio * multiplier ^ n
x' = x + round (rx + rx * cos alpha)
y' = y + round (ry + ry * sin alpha)
rx = fromIntegral (width - width') / 2
ry = fromIntegral (height - height') / 2
width' = round (fromIntegral width * m)
height' = round (fromIntegral height * m)
Loading

0 comments on commit c01cd3a

Please sign in to comment.