Ilya V. Portnov a5fb7e021a
X.L.DecorationEx: extensible mechanism for window decorations (#857)
* First version of DecorationEx.

* Fixed most warnings.

* Fix build error with ghc-9.8.1.

* Fix title shrinking with text decoration.

* Add convinience re-exports.

* Get rid of orphan instances.

* Fix a couple of warnings.

* Rename X.L.DecorationEx.Types -> X.L.DecorationEx.Common

* Add instance Default StandardCommand.

* Fix some typos and formatting

thanks to @geekosaur

Co-authored-by: brandon s allbery kf8nh <allbery.b@gmail.com>

* Fix reference to xmonad.hs

See also #859

Co-authored-by: brandon s allbery kf8nh <allbery.b@gmail.com>

* Fix reference to xmonad.hs

Co-authored-by: brandon s allbery kf8nh <allbery.b@gmail.com>

* Fix formatting

Co-authored-by: brandon s allbery kf8nh <allbery.b@gmail.com>

* Fix some typos and formatting

thanks to @geekosaur

Co-authored-by: brandon s allbery kf8nh <allbery.b@gmail.com>

* Remove commented code.

* Update CHANGES.md.

* calcWidgetPlace is now allowed to return rectangle with any X,

but that will be ignored.

* More generic instance for DecorationWidget GenericWidget.

* Replace explicit definition of `fi` with import from X.Prelude.

thanks to @slotThe.

* Move fetch-all pattern to the end of definition.

thanks to @slotThe.

* X.L.DecorationEx: Add screenshot

---------

Co-authored-by: brandon s allbery kf8nh <allbery.b@gmail.com>
Co-authored-by: Tony Zorman <soliditsallgood@mailbox.org>
2024-01-21 12:02:08 -05:00

273 lines
11 KiB
Haskell

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Layout.DecorationEx.Common
-- Description : Declaration of types used by DecorationEx module,
-- and commonly used utility functions.
-- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov
-- License : BSD-style (see xmonad/LICENSE)
--
-- Maintainer : portnov84@rambler.ru
-- Stability : unstable
-- Portability : unportable
--
-- This module exposes a number of types which are used by other sub-modules
-- of "XMonad.Layout.DecorationEx" module.
-----------------------------------------------------------------------------
module XMonad.Layout.DecorationEx.Common (
-- * Common types
WindowDecoration (..)
, WindowCommand (..)
, DecorationWidget (..)
, WidgetPlace (..)
, WidgetLayout (..)
, HasWidgets (..)
, ClickHandler (..)
, ThemeAttributes (..)
, XPaintingContext
, BoxBorders (..)
, BorderColors
, ThemeStyleType (..)
, SimpleStyle (..)
, GenericTheme (..)
, ThemeEx
-- * Utilities
, widgetLayout
, windowStyleType
, genericWindowStyle
, themeEx
, borderColor
, shadowBorder
) where
import qualified Data.Map as M
import Data.Bits (testBit)
import XMonad
import qualified XMonad.StackSet as W
import XMonad.Hooks.UrgencyHook
import qualified XMonad.Layout.Decoration as D
-- | Information about decoration of one window
data WindowDecoration = WindowDecoration {
wdOrigWindow :: !Window -- ^ Original window (one being decorated)
, wdOrigWinRect :: !Rectangle -- ^ Rectangle of original window
, wdDecoWindow :: !(Maybe Window) -- ^ Decoration window, or Nothing if this window should not be decorated
, wdDecoRect :: !(Maybe Rectangle) -- ^ Rectangle for decoration window
, wdWidgets :: ![WidgetPlace] -- ^ Places for widgets
}
-- | Type class for window commands (such as maximize or close window)
class (Read cmd, Show cmd) => WindowCommand cmd where
-- | Execute the command
executeWindowCommand :: cmd -> Window -> X Bool
-- | Is the command currently in `checked' state.
-- For example, for 'sticky' command, check if the
-- window is currently sticky.
isCommandChecked :: cmd -> Window -> X Bool
-- | Type class for decoration widgets
class (WindowCommand (WidgetCommand widget), Read widget, Show widget)
=> DecorationWidget widget where
-- | Type of window commands which this type of widgets can execute
type WidgetCommand widget
-- | Get window command which is associated with this widget.
widgetCommand :: widget -> Int -> WidgetCommand widget
-- | Check if the widget is shrinkable, i.e. if it's width
-- can be reduced if there is not enough place in the decoration.
isShrinkable :: widget -> Bool
-- | Layout of widgets
data WidgetLayout a = WidgetLayout {
wlLeft :: ![a] -- ^ Widgets that should be aligned to the left side of decoration
, wlCenter :: ![a] -- ^ Widgets that should be in the center of decoration
, wlRight :: ![a] -- ^ Widgets taht should be aligned to the right side of decoration
}
-- | Data type describing where the decoration widget (e.g. window button)
-- should be placed.
-- All coordinates are relative to decoration rectangle.
data WidgetPlace = WidgetPlace {
wpTextYPosition :: !Position -- ^ Y position of text base line
-- (for widgets like window title or text-based buttons)
, wpRectangle :: !Rectangle -- ^ Rectangle where to place the widget
}
deriving (Show)
-- | Generic data type which is used to
-- describe characteristics of rectangle borders.
data BoxBorders a = BoxBorders {
bxTop :: !a
, bxRight :: !a
, bxBottom :: !a
, bxLeft :: !a
} deriving (Eq, Read, Show)
-- | Convinience data type describing colors of decoration rectangle borders.
type BorderColors = BoxBorders String
-- | Data type describing look of window decoration
-- in particular state (active or inactive)
data SimpleStyle = SimpleStyle {
sBgColor :: !String -- ^ Decoration background color
, sTextColor :: !String -- ^ Text (foreground) color
, sTextBgColor :: !String -- ^ Text background color
, sDecoBorderWidth :: !Dimension -- ^ Width of border of decoration rectangle. Set to 0 to disable the border.
, sDecorationBorders :: !BorderColors -- ^ Colors of borders of decoration rectangle.
}
deriving (Show, Read)
-- | Type class for themes, which claims that
-- the theme contains the list of widgets and their alignments.
class HasWidgets theme widget where
themeWidgets :: theme widget -> WidgetLayout widget
-- | Type class for themes, which claims that
-- the theme can describe how the decoration should respond
-- to clicks on decoration itself (between widgets).
class ClickHandler theme widget where
-- | This is called when the user clicks on the decoration rectangle
-- (not on one of widgets).
onDecorationClick :: theme widget
-> Int -- ^ Mouse button number
-> Maybe (WidgetCommand widget)
-- | Determine if it is possible to drag window by it's decoration
-- with mouse button.
isDraggingEnabled :: theme widget
-> Int -- ^ Mouse button number
-> Bool
-- | Type class for themes, which claims that the theme
-- is responsible for determining looks of decoration.
class (Read theme, Show theme) => ThemeAttributes theme where
-- | Type which describes looks of decoration in one
-- of window states (active, inactive, urgent, etc).
type Style theme
-- | Select style based on window state.
selectWindowStyle :: theme -> Window -> X (Style theme)
-- | Define padding between decoration rectangle and widgets.
widgetsPadding :: theme -> BoxBorders Dimension
-- | Initial background color of decoration rectangle.
-- When decoration widget is created, it is initially filled
-- with this color.
defaultBgColor :: theme -> String
-- | Font name defined in the theme.
themeFontName :: theme -> String
-- | Generic Theme data type. This is used
-- by @TextEngine@ and can be used by other relatively
-- simple decoration engines.
data GenericTheme style widget = GenericTheme {
exActive :: !style -- ^ Decoration style for active (focused) windows
, exInactive :: !style -- ^ Decoration style for inactive (unfocused) windows
, exUrgent :: !style -- ^ Decoration style for urgent windows
, exPadding :: !(BoxBorders Dimension) -- ^ Padding between decoration rectangle and widgets
, exFontName :: !String -- ^ Font name
, exOnDecoClick :: !(M.Map Int (WidgetCommand widget)) -- ^ Correspondence between mouse button number and window command.
, exDragWindowButtons :: ![Int] -- ^ For which mouse buttons dragging is enabled
, exWidgetsLeft :: ![widget] -- ^ Widgets that should appear at the left of decoration rectangle (listed left to right)
, exWidgetsCenter :: ![widget] -- ^ Widgets that should appear in the center of decoration rectangle (listed left to right)
, exWidgetsRight :: ![widget] -- ^ Widgets that should appear at the right of decoration rectangle (listed left to right)
}
deriving instance (Show widget, Show (WidgetCommand widget), Show style) => Show (GenericTheme style widget)
deriving instance (Read widget, Read (WidgetCommand widget), Read style) => Read (GenericTheme style widget)
-- | Convience type for themes used by @TextDecoration@.
type ThemeEx widget = GenericTheme SimpleStyle widget
instance HasWidgets (GenericTheme style) widget where
themeWidgets theme = WidgetLayout (exWidgetsLeft theme) (exWidgetsCenter theme) (exWidgetsRight theme)
-- | Supported states of windows (on which looks of decorations can depend).
data ThemeStyleType = ActiveWindow | UrgentWindow | InactiveWindow
deriving (Eq, Show, Read)
-- | Utility function to convert WidgetLayout to plain list of widgets.
widgetLayout :: WidgetLayout widget -> [widget]
widgetLayout ws = wlLeft ws ++ wlCenter ws ++ wlRight ws
-- | Painting context for decoration engines based on plain X11 calls.
type XPaintingContext = (Display, Pixmap, GC)
instance (Show widget, Read widget, Read (WidgetCommand widget), Show (WidgetCommand widget))
=> ThemeAttributes (ThemeEx widget) where
type Style (ThemeEx widget) = SimpleStyle
selectWindowStyle theme w = genericWindowStyle w theme
defaultBgColor t = sBgColor $ exInactive t
widgetsPadding = exPadding
themeFontName = exFontName
instance ClickHandler (GenericTheme SimpleStyle) widget where
onDecorationClick theme button = M.lookup button (exOnDecoClick theme)
isDraggingEnabled theme button = button `elem` exDragWindowButtons theme
-- | Generic utility function to select style from @GenericTheme@
-- based on current state of the window.
genericWindowStyle :: Window -> GenericTheme style widget -> X style
genericWindowStyle win theme = do
styleType <- windowStyleType win
return $ case styleType of
ActiveWindow -> exActive theme
InactiveWindow -> exInactive theme
UrgentWindow -> exUrgent theme
-- | Detect type of style to be used from current state of the window.
windowStyleType :: Window -> X ThemeStyleType
windowStyleType win = do
mbFocused <- W.peek <$> gets windowset
isWmStateUrgent <- (win `elem`) <$> readUrgents
isUrgencyBitSet <- withDisplay $ \dpy -> do
hints <- io $ getWMHints dpy win
return $ wmh_flags hints `testBit` urgencyHintBit
if isWmStateUrgent || isUrgencyBitSet
then return UrgentWindow
else return $
case mbFocused of
Nothing -> InactiveWindow
Just focused
| focused == win -> ActiveWindow
| otherwise -> InactiveWindow
-- | Convert Theme type from "XMonad.Layout.Decoration" to
-- theme type used by "XMonad.Layout.DecorationEx.TextEngine".
themeEx :: Default (WidgetCommand widget) => D.Theme -> ThemeEx widget
themeEx t =
GenericTheme {
exActive = SimpleStyle (D.activeColor t) (D.activeTextColor t) (D.activeColor t) (D.activeBorderWidth t) (borderColor $ D.activeColor t)
, exInactive = SimpleStyle (D.inactiveColor t) (D.inactiveTextColor t) (D.inactiveColor t) (D.inactiveBorderWidth t) (borderColor $ D.inactiveColor t)
, exUrgent = SimpleStyle (D.urgentColor t) (D.urgentTextColor t) (D.urgentColor t) (D.urgentBorderWidth t) (borderColor $ D.urgentColor t)
, exPadding = BoxBorders 0 4 0 4
, exFontName = D.fontName t
, exOnDecoClick = M.fromList [(1, def)]
, exDragWindowButtons = [1]
, exWidgetsLeft = []
, exWidgetsCenter = []
, exWidgetsRight = []
}
instance Default (WidgetCommand widget) => Default (ThemeEx widget) where
def = themeEx (def :: D.Theme)
borderColor :: String -> BorderColors
borderColor c = BoxBorders c c c c
shadowBorder :: String -> String -> BorderColors
shadowBorder highlight shadow = BoxBorders highlight shadow shadow highlight