{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Layout.DecorationEx.TabbedGeometry -- Description : Tab-based window decoration geometry -- Copyright : (c) 2007 Andrea Rossato, 2023 Ilya Portnov -- License : BSD-style (see xmonad/LICENSE) -- -- Maintainer : portnov84@rambler.ru -- Stability : unstable -- Portability : unportable -- -- This module defines window decoration geometry based on tabs. -- The tabs can follow horizontally and be placed above or below windows; -- in such case, tabs can occupy full width of the window or be aligned to -- left or right. Or tabs can go vertically near left or right side of -- the window. ----------------------------------------------------------------------------- module XMonad.Layout.DecorationEx.TabbedGeometry ( textTabbed, TabbedGeometry (..), HorizontalTabPlacement (..), VerticalTabPlacement (..), HorizontalTabWidth (..), HorizontalTabsAlignment (..), SingleTabMode (..) ) where import XMonad import qualified XMonad.StackSet as W import XMonad.Prelude import XMonad.Layout.Decoration (ModifiedLayout, Shrinker (..)) import XMonad.Layout.DecorationEx.LayoutModifier import XMonad.Layout.DecorationEx.Common import XMonad.Layout.DecorationEx.Geometry import XMonad.Layout.DecorationEx.Widgets import XMonad.Layout.DecorationEx.TextEngine -- | Placement of tabs when they go horizontally: -- should they be placed above or below the window. data HorizontalTabPlacement = Top | Bottom deriving (Eq, Read, Show) -- | Placement of tabs when they go vertically: -- should they appear at left or at right side of the window. data VerticalTabPlacement = TabsAtLeft | TabsAtRight deriving (Eq, Read, Show) -- | Width of tabs when they go horizontally. data HorizontalTabWidth = AutoWidth -- ^ Define the width automatically by evenly dividing windows' width | FixedWidth !Dimension -- ^ Use fixed width of the tab deriving (Eq, Read, Show) -- | Alignment of tabs when they go horizontally. data HorizontalTabsAlignment = AlignTabsLeft | AlignTabsCenter | AlignTabsRight deriving (Eq, Read, Show) -- | What to do if there is only one tab. data SingleTabMode = ShowTab | HideTab deriving (Eq, Read, Show) data TabbedGeometry a = HorizontalTabs { showIfSingleWindow :: !SingleTabMode -- ^ What to do if there is only one tab , hTabPlacement :: !HorizontalTabPlacement -- ^ Where to place horizontal tabs , hTabAlignment :: !HorizontalTabsAlignment -- ^ How to align horizontal tabs (makes sense with fixed width of tabs). , hTabWidth :: !HorizontalTabWidth -- ^ Width of horizontal tabs , hTabHeight :: !Dimension -- ^ Height of horizontal tabs } | VerticalTabs { showIfSingleWindow :: !SingleTabMode -- ^ What to do if there is only one tab , vTabPlacement :: !VerticalTabPlacement -- ^ Where to place vertical tabs , vTabWidth :: !Dimension -- ^ Width of vertical tabs , vTabHeight :: !Dimension -- ^ Height of vertical tabs } deriving (Show, Read) instance Default (TabbedGeometry a) where def = HorizontalTabs ShowTab Top AlignTabsLeft AutoWidth 20 instance DecorationGeometry TabbedGeometry Window where describeGeometry _ = "Tabbed" pureDecoration tabs _ stack wrs (window, windowRect) = let Rectangle windowX windowY windowWidth windowHeight = windowRect -- windows that are mapped onto the same rectangle as current one are considered to -- be in one tabs group tabbedWindows = filter (`elem` map fst (filter ((==windowRect) . snd) wrs)) (W.integrate stack) mbWindowIndex = window `elemIndex` tabbedWindows numWindows = length tabbedWindows in if numWindows > 1 || (showIfSingleWindow tabs == ShowTab && numWindows > 0) then case tabs of HorizontalTabs {..} -> Just $ case hTabPlacement of Top -> Rectangle decoX windowY effectiveTabWidth hTabHeight Bottom -> Rectangle decoX (windowY + fi (windowHeight - hTabHeight)) effectiveTabWidth hTabHeight where decoX = maybe windowX tabX mbWindowIndex -- If there are too many windows or configured tab width -- is too big, then we have to switch to 'auto' mode. hTabWidth' = case hTabWidth of AutoWidth -> AutoWidth FixedWidth tabWidth | tabWidth * fi numWindows > windowWidth -> AutoWidth | otherwise -> FixedWidth tabWidth effectiveTabWidth = case hTabWidth' of AutoWidth -> fi $ maybe windowX (\i -> tabX (i+1) - tabX i) mbWindowIndex FixedWidth tabWidth -> tabWidth allTabsWidth = case hTabWidth' of AutoWidth -> fi windowWidth FixedWidth _ -> fi $ min windowWidth $ effectiveTabWidth * max 1 (fi numWindows) tabsStartX = case hTabAlignment of AlignTabsLeft -> windowX AlignTabsRight -> windowX + fi windowWidth - allTabsWidth AlignTabsCenter -> windowX + (fi windowWidth - allTabsWidth) `div` 2 -- X coordinate of i'th window in horizontal tabs layout tabX i = tabsStartX + case hTabWidth' of AutoWidth -> fi ((windowWidth * fi i) `div` max 1 (fi numWindows)) FixedWidth _ -> fi effectiveTabWidth * fi i VerticalTabs {..} -> Just $ case vTabPlacement of TabsAtLeft -> fixHeightTab windowX TabsAtRight -> fixHeightTab (windowX + fi (windowWidth - vTabWidth)) where fixHeightLoc i = windowY + fi vTabHeight * fi i fixHeightTab x = Rectangle x (maybe windowY fixHeightLoc mbWindowIndex) vTabWidth vTabHeight else Nothing shrinkWindow tabs (Rectangle _ _ dw dh) (Rectangle x y w h) = case tabs of HorizontalTabs {..} -> case hTabPlacement of Top -> Rectangle x (y + fi dh) w (h - dh) Bottom -> Rectangle x y w (h - dh) VerticalTabs {..} -> case vTabPlacement of TabsAtLeft -> Rectangle (x + fi dw) y (w - dw) h TabsAtRight -> Rectangle x y (w - dw) h -- | Add tabbed decorations (with default settings) with text-based widgets to a layout. textTabbed :: (Shrinker shrinker) => shrinker -- ^ Strings shrinker, e.g. @shrinkText@ -> ThemeEx StandardWidget -- ^ Decoration theme -> l Window -- ^ Layout to be decorated -> ModifiedLayout (DecorationEx TextDecoration StandardWidget TabbedGeometry shrinker) l Window textTabbed shrinker theme = decorationEx shrinker theme TextDecoration def