packages feed

nano-ui-0.1.0.0: lib/NanoUI/Style.hs

{-# LANGUAGE StrictData #-}

module NanoUI.Style
  ( Sizing (..)
  , Direction (..)
  , AlignX (..)
  , AlignY (..)
  , Padding (..)
  , Layout (..)
  , defaultLayout
  , Style (..)
  , Theme (..)
  , defaultTheme
  , tomorrowNightMinDarkTheme
  , tomorrowMinLightTheme
  , tomorrowMidnightMinDarkTheme
  , Base16 (..)
  , themeFromBase16
  , themeFromBase16Dark
  , themeFromBase16Light
  , base16TomorrowNight
  , base16TomorrowLight
  -- * Style modifiers
  , background
  , foreground
  , borderColor
  , borderWidth
  , cornerRadius
  , hoverBackground
  , pressBackground
  , fillColor
  -- * Theme modifiers
  , buttonStyle
  , inputStyle
  , panelStyle
  , windowStyle
  , everyStyle
  , accentColor
  , textColor
  , mutedColor
  , linkColor
  , selectionColor
  , windowColor
  , rounded
  , tinted
  , primary
  , destructive
  , success
  , subtle
  , readableOn
  , disabledTheme
  , themeSeries
  , separatorTrackColor
  , scrollBarTrackColor
  , scrollBarThumbColor
  , fadeAlpha
  , windowPad
  , windowMargin
  , padAll
  , padXY
  , gap
  , fillW
  , fillH
  , grow
  , minW
  , maxW
  , fixedW
  , minH
  , maxH
  , fixedH
  , fixedWH
  , alignMid
  , alignEnd
  , tight
  , percent
  , gridMinColW
  , fixedAspectW
  , fixedAspectH
  , gridCols
  , FontVariant (..)
  , FontWeight (..)
  , FontStyle (..)
  , TextDecoration (..)
  , LayoutModifier
  , fontRegular
  , fontHeading
  , fontMuted
  , fontMono
  , fontDanger
  , fontSize
  , fontSizeScale
  , fontColor
  , fontWeight
  , fontBold
  , fontLight
  , fontMedium
  , fontSemiBold
  , fontExtraBold
  , fontBlack
  , fontStyle
  , fontItalic
  , fontOblique
  , textDecoration
  , fontUnderline
  , fontStrike
  , alignStart
  , alignCenter
  , alignTop
  , alignBottom
  ) where

import Data.Bits ((.&.), (.|.))
import Data.Word (Word8)
import NanoUI.Types (Color (..), colorA, colorLuminance, colorRGBA, contrastRatio, lerpColor)

data Sizing
  = Fixed Float
  | Fit
  | Grow Float
  | Shrink Float
  | Percent Float
  deriving (Eq, Show)

data Direction = Row | Column
  deriving (Eq, Show, Enum, Bounded)

data AlignX = AlignStart | AlignCenter | AlignEnd
  deriving (Eq, Show, Enum, Bounded)

data AlignY = AlignTop | AlignMiddle | AlignBottom
  deriving (Eq, Show, Enum, Bounded)

data Padding = Padding
  { padL :: {-# UNPACK #-} !Float
  , padR :: {-# UNPACK #-} !Float
  , padT :: {-# UNPACK #-} !Float
  , padB :: {-# UNPACK #-} !Float
  }
  deriving (Eq, Show)

-- Floating window chrome. The body sits one side-pad below the chrome and one
-- side-pad above the window's bottom edge (the window's own column gap fills
-- the top; see 'NanoUI.Widgets.Overlay').
windowPad :: Padding
windowPad = Padding 10 10 0 10

-- Screen inset for floating window/modal max size and default placement.
windowMargin :: Float
windowMargin = 14

data FontVariant
  = FontRegular
  | FontHeading
  | FontMuted
  | FontMono
  | FontDanger
  deriving (Eq, Show, Enum, Bounded, Ord)

data FontWeight
  = WeightNormal
  | WeightBold
  | WeightLight
  | WeightMedium
  | WeightSemiBold
  | WeightExtraBold
  | WeightBlack
  deriving (Eq, Show, Enum, Bounded, Ord)

data FontStyle
  = FontStyleNormal
  | FontStyleItalic
  | FontStyleOblique
  deriving (Eq, Show, Enum, Bounded, Ord)

data TextDecoration
  = DecorationNone
  | DecorationUnderline
  | DecorationStrikethrough
  | DecorationUnderlineStrike
  deriving (Eq, Show, Enum, Bounded, Ord)

type LayoutModifier = Layout -> Layout

data Layout = Layout
  { layoutDirection :: !Direction
  , layoutWidth :: !Sizing
  , layoutHeight :: !Sizing
  , layoutPadding :: !Padding
  , layoutGap :: {-# UNPACK #-} !Float
  , layoutAlignX :: !AlignX
  , layoutAlignY :: !AlignY
  , layoutMinW :: {-# UNPACK #-} !Float
  , layoutMinH :: {-# UNPACK #-} !Float
  , layoutMaxW :: {-# UNPACK #-} !Float
  , layoutMaxH :: {-# UNPACK #-} !Float
  , layoutFontVariant :: !FontVariant
  , layoutGridCols :: {-# UNPACK #-} !Int
  , layoutGridMinColW :: {-# UNPACK #-} !Float
  , layoutFontSize :: {-# UNPACK #-} !Float
  , layoutFontColor :: !(Maybe Color)
  , layoutFontWeight :: !FontWeight
  , layoutFontStyle :: !FontStyle
  , layoutTextDecoration :: !TextDecoration
  }
  deriving (Eq, Show)

defaultLayout :: Layout
defaultLayout =
  Layout
    { layoutDirection = Column
    , layoutWidth = Fit
    , layoutHeight = Fit
    , layoutPadding = Padding 3 3 3 3
    , layoutGap = 8
    , layoutAlignX = AlignStart
    , layoutAlignY = AlignTop
    , layoutMinW = 0
    , layoutMinH = 0
    , layoutMaxW = 1e9
    , layoutMaxH = 1e9
    , layoutFontVariant = FontRegular
    , layoutGridCols = 0
    , layoutGridMinColW = 0
    , layoutFontSize = 0
    , layoutFontColor = Nothing
    , layoutFontWeight = WeightNormal
    , layoutFontStyle = FontStyleNormal
    , layoutTextDecoration = DecorationNone
    }

padAll :: Float -> Layout -> Layout
padAll n l = l {layoutPadding = Padding n n n n}

padXY :: Float -> Float -> Layout -> Layout
padXY x y l = l {layoutPadding = Padding x x y y}

gap :: Float -> Layout -> Layout
gap n l = l {layoutGap = n}

fillW :: Layout -> Layout
fillW l = l {layoutWidth = Grow 1}

fillH :: Layout -> Layout
fillH l = l {layoutHeight = Grow 1}

grow :: Layout -> Layout
grow = fillW . fillH

minW :: Float -> Layout -> Layout
minW n l = l {layoutMinW = n}

maxW :: Float -> Layout -> Layout
maxW n l = l {layoutMaxW = n}

fixedW :: Float -> Layout -> Layout
fixedW n l = l {layoutWidth = Fixed n, layoutMinW = n, layoutMaxW = n}

minH :: Float -> Layout -> Layout
minH n l = l {layoutMinH = n}

maxH :: Float -> Layout -> Layout
maxH n l = l {layoutMaxH = n}

fixedH :: Float -> Layout -> Layout
fixedH n l = l {layoutHeight = Fixed n}

fixedWH :: Float -> Float -> Layout -> Layout
fixedWH w h l = l {layoutWidth = Fixed w, layoutHeight = Fixed h}

alignMid :: Layout -> Layout
alignMid l = l {layoutAlignY = AlignMiddle}

alignEnd :: Layout -> Layout
alignEnd l = l {layoutAlignX = AlignEnd}

tight :: Layout -> Layout
tight l = l {layoutPadding = Padding 0 0 0 0}

percent :: Float -> Layout -> Layout
percent p l = l {layoutWidth = Percent p}

gridMinColW :: Float -> Layout -> Layout
gridMinColW w l = l {layoutGridMinColW = max 0 w}

fixedAspectW :: Float -> Float -> Layout -> Layout
fixedAspectW w ratio = fixedWH w (w / ratio)

fixedAspectH :: Float -> Float -> Layout -> Layout
fixedAspectH h ratio = fixedWH (h * ratio) h

gridCols :: Int -> Layout -> Layout
gridCols n l = l {layoutGridCols = max 0 n}

fontRegular :: Layout -> Layout
fontRegular l = l {layoutFontVariant = FontRegular}

fontHeading :: Layout -> Layout
fontHeading l = l {layoutFontVariant = FontHeading}

fontMuted :: Layout -> Layout
fontMuted l = l {layoutFontVariant = FontMuted}

fontMono :: Layout -> Layout
fontMono l = l {layoutFontVariant = FontMono}

fontDanger :: Layout -> Layout
fontDanger l = l {layoutFontVariant = FontDanger}

fontSize :: Float -> Layout -> Layout
fontSize sz l = l {layoutFontSize = max 0 sz}

fontSizeScale :: Float -> Layout -> Layout
fontSizeScale s l =
  let cur = layoutFontSize l
      sz = if cur > 0 then cur * s else 16 * s
   in l {layoutFontSize = max 0 sz}

fontColor :: Color -> Layout -> Layout
fontColor col l = l {layoutFontColor = Just col}

fontWeight :: FontWeight -> Layout -> Layout
fontWeight w l = l {layoutFontWeight = w}

fontBold :: Layout -> Layout
fontBold = fontWeight WeightBold

fontLight :: Layout -> Layout
fontLight = fontWeight WeightLight

fontMedium :: Layout -> Layout
fontMedium = fontWeight WeightMedium

fontSemiBold :: Layout -> Layout
fontSemiBold = fontWeight WeightSemiBold

fontExtraBold :: Layout -> Layout
fontExtraBold = fontWeight WeightExtraBold

fontBlack :: Layout -> Layout
fontBlack = fontWeight WeightBlack

fontStyle :: FontStyle -> Layout -> Layout
fontStyle s l = l {layoutFontStyle = s}

fontItalic :: Layout -> Layout
fontItalic = fontStyle FontStyleItalic

fontOblique :: Layout -> Layout
fontOblique = fontStyle FontStyleOblique

textDecoration :: TextDecoration -> Layout -> Layout
textDecoration d l = l {layoutTextDecoration = d}

fontUnderline :: Layout -> Layout
fontUnderline l =
  let newDeco = case layoutTextDecoration l of
        DecorationStrikethrough -> DecorationUnderlineStrike
        DecorationUnderlineStrike -> DecorationUnderlineStrike
        _ -> DecorationUnderline
   in l {layoutTextDecoration = newDeco}

fontStrike :: Layout -> Layout
fontStrike l =
  let newDeco = case layoutTextDecoration l of
        DecorationUnderline -> DecorationUnderlineStrike
        DecorationUnderlineStrike -> DecorationUnderlineStrike
        _ -> DecorationStrikethrough
   in l {layoutTextDecoration = newDeco}

alignStart :: Layout -> Layout
alignStart l = l {layoutAlignX = AlignStart}

alignCenter :: Layout -> Layout
alignCenter l = l {layoutAlignX = AlignCenter}

alignTop :: Layout -> Layout
alignTop l = l {layoutAlignY = AlignTop}

alignBottom :: Layout -> Layout
alignBottom l = l {layoutAlignY = AlignBottom}

data Style = Style
  { styleBg :: {-# UNPACK #-} !Color
  , styleFg :: {-# UNPACK #-} !Color
  , styleBorder :: {-# UNPACK #-} !Color
  , styleBorderWidth :: {-# UNPACK #-} !Float
  , styleCornerRadius :: {-# UNPACK #-} !Float
  , styleHoverBg :: {-# UNPACK #-} !Color
  , styleActiveBg :: {-# UNPACK #-} !Color
  }
  deriving (Eq, Show)

data Theme = Theme
  { themeWindow :: {-# UNPACK #-} !Color
  , themePanel :: !Style
  , themeFloatingWindow :: !Style
  , themeButton :: !Style
  , themeInput :: !Style
  , themeSeparator :: {-# UNPACK #-} !Color
  , themeAccent :: {-# UNPACK #-} !Color
  , themeMuted :: {-# UNPACK #-} !Color
  , themeRed :: {-# UNPACK #-} !Color
  , themeOrange :: {-# UNPACK #-} !Color
  , themeYellow :: {-# UNPACK #-} !Color
  , themeGreen :: {-# UNPACK #-} !Color
  , themePurple :: {-# UNPACK #-} !Color
  , themeOverlayDim :: {-# UNPACK #-} !Color
  , themeOnAccent :: {-# UNPACK #-} !Color
  -- ^ Text and marks drawn on an accent fill: a checked box, an active tab,
  -- a primary button.
  , themeSelection :: {-# UNPACK #-} !Color
  -- ^ Selected text's highlight, drawn under the text. Usually translucent.
  , themeFocusRing :: {-# UNPACK #-} !Color
  , themeLink :: {-# UNPACK #-} !Color
  , themeShadow :: {-# UNPACK #-} !Color
  -- ^ Offset shadow under menus, dropdowns and floating windows. A zero alpha
  -- draws none.
  , themeDisabledFade :: {-# UNPACK #-} !Float
  -- ^ How far a disabled widget's colours fade toward the window colour, from
  -- 0 (not at all) to 1 (invisible).
  }
  deriving (Eq, Show)

-- -----------------------------------------------------------------------------
-- Style and theme modifiers
-- -----------------------------------------------------------------------------

-- $modifiers
-- Styles and themes change the way layouts do: through functions that
-- compose with @(.)@. A @Style -> Style@ edits one surface, and a
-- @Theme -> Theme@ edits the theme a part of the view is drawn with (see
-- @styled@ in "NanoUI"):
--
-- > styled (buttonStyle (cornerRadius 8) . accentColor teal) $ do ...
-- > styled primary (button "Save")

background :: Color -> Style -> Style
background c s = s {styleBg = c}

foreground :: Color -> Style -> Style
foreground c s = s {styleFg = c}

borderColor :: Color -> Style -> Style
borderColor c s = s {styleBorder = c}

borderWidth :: Float -> Style -> Style
borderWidth w s = s {styleBorderWidth = max 0 w}

cornerRadius :: Float -> Style -> Style
cornerRadius r s = s {styleCornerRadius = max 0 r}

hoverBackground :: Color -> Style -> Style
hoverBackground c s = s {styleHoverBg = c}

pressBackground :: Color -> Style -> Style
pressBackground c s = s {styleActiveBg = c}

-- | A background with hover and press shades derived from it: hovering mixes
-- in some of the foreground, pressing darkens.
fillColor :: Color -> Style -> Style
fillColor c s =
  s
    { styleBg = c
    , styleHoverBg = lerpColor c (styleFg s) 0.12
    , styleActiveBg = lerpColor c (colorRGBA 0 0 0 (colorA c)) 0.18
    }

buttonStyle :: (Style -> Style) -> Theme -> Theme
buttonStyle f t = t {themeButton = f (themeButton t)}

-- | Text fields, text areas, sliders' wells and scroller wells.
inputStyle :: (Style -> Style) -> Theme -> Theme
inputStyle f t = t {themeInput = f (themeInput t)}

-- | Panels, cards, menus, and label text.
panelStyle :: (Style -> Style) -> Theme -> Theme
panelStyle f t = t {themePanel = f (themePanel t)}

-- | Floating windows.
windowStyle :: (Style -> Style) -> Theme -> Theme
windowStyle f t = t {themeFloatingWindow = f (themeFloatingWindow t)}

everyStyle :: (Style -> Style) -> Theme -> Theme
everyStyle f = buttonStyle f . inputStyle f . panelStyle f . windowStyle f

accentColor :: Color -> Theme -> Theme
accentColor c t = t {themeAccent = c, themeFocusRing = c, themeSelection = fadeAlpha c (colorA (themeSelection t))}

-- | The foreground of every surface.
textColor :: Color -> Theme -> Theme
textColor c = everyStyle (foreground c)

mutedColor :: Color -> Theme -> Theme
mutedColor c t = t {themeMuted = c}

linkColor :: Color -> Theme -> Theme
linkColor c t = t {themeLink = c}

selectionColor :: Color -> Theme -> Theme
selectionColor c t = t {themeSelection = c}

-- | The backdrop behind everything, which disabled widgets also fade toward.
windowColor :: Color -> Theme -> Theme
windowColor c t = t {themeWindow = c}

-- | The corner radius of every surface.
rounded :: Float -> Theme -> Theme
rounded r = everyStyle (cornerRadius r)

-- | Buttons filled with a colour picked from the theme, with a readable label.
--
-- > styled (tinted themePurple) (button "Tag")
tinted :: (Theme -> Color) -> Theme -> Theme
tinted pick t =
  let c = pick t
      label = readableOn t c
   in buttonStyle
        ( \s ->
            s
              { styleBg = c
              , styleFg = label
              , styleBorder = c
              , styleHoverBg = lerpColor c label 0.14
              , styleActiveBg = lerpColor c (themeWindow t) 0.22
              }
        )
        t

-- | Buttons in the accent colour, for the action a view is for.
primary :: Theme -> Theme
primary = tinted themeAccent

-- | Buttons in the theme's red, for destructive actions.
destructive :: Theme -> Theme
destructive = tinted themeRed

success :: Theme -> Theme
success = tinted themeGreen

-- | Buttons without a fill or border until hovered, for toolbars and
-- secondary actions.
subtle :: Theme -> Theme
subtle =
  buttonStyle $ \s ->
    s
      { styleBg = clear
      , styleBorder = clear
      , styleBorderWidth = 0
      , styleHoverBg = fadeAlpha (styleFg s) 30
      , styleActiveBg = fadeAlpha (styleFg s) 48
      }
  where
    clear = colorRGBA 0 0 0 0

-- | Whichever of the theme's text colours reads best on @c@.
readableOn :: Theme -> Color -> Color
readableOn t c =
  let candidates = [themeOnAccent t, styleFg (themePanel t), themeWindow t]
      best a b = if contrastRatio a c >= contrastRatio b c then a else b
   in foldr1 best candidates

-- | The theme disabled widgets are drawn with: every colour faded toward the
-- window colour by 'themeDisabledFade', and no hover or press feedback.
disabledTheme :: Theme -> Theme
disabledTheme t =
  let f = themeDisabledFade t
      fade c
        | colorA c == 0 = c
        | otherwise = fadeAlpha (lerpColor c (themeWindow t) f) (colorA c)
      fadeStyle s =
        let bg = fade (styleBg s)
         in s {styleBg = bg, styleFg = fade (styleFg s), styleBorder = fade (styleBorder s), styleHoverBg = bg, styleActiveBg = bg}
   in t
        { themePanel = fadeStyle (themePanel t)
        , themeFloatingWindow = fadeStyle (themeFloatingWindow t)
        , themeButton = fadeStyle (themeButton t)
        , themeInput = fadeStyle (themeInput t)
        , themeSeparator = fade (themeSeparator t)
        , themeAccent = fade (themeAccent t)
        , themeMuted = fade (themeMuted t)
        , themeRed = fade (themeRed t)
        , themeOrange = fade (themeOrange t)
        , themeYellow = fade (themeYellow t)
        , themeGreen = fade (themeGreen t)
        , themePurple = fade (themePurple t)
        , themeOnAccent = fade (themeOnAccent t)
        , themeFocusRing = fade (themeFocusRing t)
        , themeLink = fade (themeLink t)
        }

-- | Flat widget style: bg/fg/border plus hover and active fills.
-- Border width 1 and corner radius 2, as the built-in themes use.
flatStyle :: Color -> Color -> Color -> Color -> Color -> Style
flatStyle bg fg border hoverBg activeBg =
  Style
    { styleBg = bg
    , styleFg = fg
    , styleBorder = border
    , styleBorderWidth = 1
    , styleCornerRadius = 2
    , styleHoverBg = hoverBg
    , styleActiveBg = activeBg
    }

-- | Neutral charcoal surfaces, warm text, and a blue selection accent.
-- Keep structural edges quiet; interactive borders and focus carry contrast.
defaultTheme :: Theme
defaultTheme =
  let panelSurface =
        flatStyle
          (colorRGBA 34 34 38 255)
          (colorRGBA 236 234 230 255)
          (colorRGBA 54 54 62 255)
          (colorRGBA 34 34 38 255)
          (colorRGBA 30 30 34 255)
   in Theme
        { themeWindow = colorRGBA 24 24 27 255
        , themePanel = panelSurface
        , themeFloatingWindow = panelSurface
        , themeButton =
            flatStyle
              (colorRGBA 52 52 58 255)
              (colorRGBA 248 247 245 255)
              (colorRGBA 74 76 84 255)
              (colorRGBA 68 70 78 255)
              (colorRGBA 42 42 48 255)
        , themeInput =
            flatStyle
              (colorRGBA 18 18 21 255)
              (colorRGBA 236 234 230 255)
              (colorRGBA 70 72 80 255)
              (colorRGBA 24 24 28 255)
              (colorRGBA 14 14 17 255)
        , themeSeparator = colorRGBA 62 64 72 255
        , themeAccent = colorRGBA 88 156 246 255
        , themeMuted = colorRGBA 176 172 164 255
        , themeRed = colorRGBA 252 165 165 255
        , themeOrange = colorRGBA 216 140 72 255
        , themeYellow = colorRGBA 212 176 88 255
        , themeGreen = colorRGBA 104 168 124 255
        , themePurple = colorRGBA 176 140 220 255
        , themeOverlayDim = colorRGBA 8 8 10 176
        , themeOnAccent = colorRGBA 255 255 255 255
        , themeSelection = fadeAlpha (colorRGBA 88 156 246 255) 115
        , themeFocusRing = colorRGBA 88 156 246 255
        , themeLink = colorRGBA 124 178 250 255
        , themeShadow = colorRGBA 0 0 0 72
        , themeDisabledFade = 0.55
        }

-- Status and series colours in hue order, then accent.
themeSeries :: Theme -> [Color]
themeSeries t =
  [ themeRed t
  , themeOrange t
  , themeYellow t
  , themeGreen t
  , themeAccent t
  , themePurple t
  ]

-- | Opaque tint of a base surface toward the separator color: the track color
-- for scrollbar tracks, divider strips, and similar hairline chrome.
separatorTrackColor :: Style -> Theme -> Color
separatorTrackColor base theme =
  lerpColor (styleBg base) (themeSeparator theme) 0.28

-- Scroll track/thumb tints. The track is an opaque mix of the surface it sits
-- on (the scroller well / floating window body) toward the separator colour, so
-- the lane reads against that surface on every theme. The thumb stays a
-- translucent foreground mix so the track shows through it.
scrollBarTrackColor :: Style -> Theme -> Color
scrollBarTrackColor base theme =
  separatorTrackColor base theme

scrollBarThumbColor :: Style -> Theme -> Color
scrollBarThumbColor base theme =
  let solid = lerpColor (themeSeparator theme) (styleFg base) 0.58
   in fadeAlpha solid 130

-- | Replaces the alpha channel of a color.
fadeAlpha :: Color -> Word8 -> Color
fadeAlpha (Color w) a = Color ((w .&. 0xFFFFFF00) .|. fromIntegral a)

-- | Ported from "Tomorrow Night Min" in https://github.com/biaqat/tomorrow-min-theme-zed
tomorrowNightMinDarkTheme :: Theme
tomorrowNightMinDarkTheme =
  let panelSurface =
        flatStyle
          (colorRGBA 30 31 33 255)  -- base.bg #1E1F21 (elevated panel canvas)
          (colorRGBA 234 234 234 255)  -- bright.fg #EAEAEA
          edgeCol
          (colorRGBA 52 54 62 255)  -- #34363E
          (colorRGBA 26 27 29 255)  -- #1A1B1D
   in Theme
        { themeWindow = colorRGBA 23 24 26 255         -- #17181A (dark root window backdrop)
        , themePanel = panelSurface
        , themeFloatingWindow = panelSurface
        , themeButton =
            flatStyle
              (colorRGBA 44 46 51 255)  -- elevated button surface
              (colorRGBA 245 245 245 255)  -- bright.fg / white
              edgeCol
              (colorRGBA 69 74 83 255)  -- #454A53
              (colorRGBA 28 29 32 255)  -- depressed on click
        , themeInput =
            flatStyle
              (colorRGBA 23 24 26 255)  -- #17181A (recessed into #1E1F21 panel)
              (colorRGBA 234 234 234 255)  -- bright.fg #EAEAEA
              edgeCol
              (colorRGBA 29 30 33 255)
              (colorRGBA 19 20 22 255)
        , themeSeparator = sepCol
        , themeAccent = accentCol
        , themeMuted = colorRGBA 150 152 150 255       -- comment #969896
        , themeRed = colorRGBA 204 102 102 255         -- base.red #CC6666
        , themeOrange = colorRGBA 222 147 95 255       -- base.orange #DE935F
        , themeYellow = colorRGBA 240 198 116 255      -- base.yellow #F0C674
        , themeGreen = colorRGBA 181 189 104 255       -- base.green #B5BD68
        , themePurple = colorRGBA 178 148 187 255      -- base.purple #B294BB
        , themeOverlayDim = colorRGBA 0 0 0 160
        , themeOnAccent = colorRGBA 255 255 255 255
        , themeSelection = fadeAlpha accentCol 115
        , themeFocusRing = accentCol
        , themeLink = accentCol
        , themeShadow = colorRGBA 0 0 0 72
        , themeDisabledFade = 0.55
        }
  where
  edgeCol    = colorRGBA 77 80 87 255              -- window #4D5057 (touch brighter crisp border)
  sepCol = colorRGBA 55 59 65 255              -- base.selection #373B41 (subtle divider)
  accentCol    = colorRGBA 103 150 230 255           -- vscode.cornflower_blue #6796E6

-- | Ported from "Tomorrow Min" in https://github.com/biaqat/tomorrow-min-theme-zed
tomorrowMinLightTheme :: Theme
tomorrowMinLightTheme =
  let panelSurface =
        flatStyle
          (colorRGBA 242 242 242 255)  -- #F2F2F2
          (colorRGBA 55 59 65 255)  -- #373B41
          (colorRGBA 222 222 222 255)  -- #DEDEDE
          (colorRGBA 231 231 231 255)  -- #E7E7E7 (darker than #F2F2F2 so hover reads)
          (colorRGBA 219 219 219 255)  -- #DBDBDB
   in Theme
        { themeWindow = colorRGBA 255 255 255 255     -- #FFFFFF
        , themePanel = panelSurface
        , themeFloatingWindow = panelSurface
        , themeButton =
            flatStyle
              (colorRGBA 232 232 232 255)  -- #E8E8E8 (step down from panel for zebra rows)
              (colorRGBA 55 59 65 255)  -- #373B41
              (colorRGBA 214 214 214 255)  -- #D6D6D6
              (colorRGBA 214 214 214 255)  -- #D6D6D6
              (colorRGBA 196 196 196 255)  -- #C4C4C4
        , themeInput =
            flatStyle
              (colorRGBA 255 255 255 255)  -- #FFFFFF
              (colorRGBA 55 59 65 255)
              (colorRGBA 210 210 210 255)
              (colorRGBA 243 243 243 255)  -- #F3F3F3 (darker than white so hover reads)
              (colorRGBA 255 255 255 255)  -- focus keeps the normal white bg; accent border signals focus
        , themeSeparator = colorRGBA 222 222 222 255  -- #DEDEDE
        , themeAccent = colorRGBA 82 134 188 255      -- #5286BC (Tomorrow Blue)
        , themeMuted = colorRGBA 140 140 140 255      -- #8C8C8C
        , themeRed = colorRGBA 197 78 82 255          -- Tomorrow Red #C54E52
        , themeOrange = colorRGBA 231 140 69 255      -- Tomorrow Orange #E78C45
        , themeYellow = colorRGBA 231 197 71 255      -- Tomorrow Yellow #E7C547
        , themeGreen = colorRGBA 113 140 0 255        -- Tomorrow Green #718C00
        , themePurple = colorRGBA 137 91 144 255      -- Tomorrow Purple #895B90
        , themeOverlayDim = colorRGBA 0 0 0 100
        , themeOnAccent = colorRGBA 255 255 255 255
        , themeSelection = fadeAlpha (colorRGBA 82 134 188 255) 80
        , themeFocusRing = colorRGBA 82 134 188 255
        , themeLink = colorRGBA 66 113 174 255
        , themeShadow = colorRGBA 0 0 0 36
        , themeDisabledFade = 0.55
        }

-- | Ported from "Tomorrow at Midnight Min" in https://github.com/biaqat/tomorrow-min-theme-zed
tomorrowMidnightMinDarkTheme :: Theme
tomorrowMidnightMinDarkTheme =
  let panelSurface =
        flatStyle
          (colorRGBA 16 17 20 255)  -- #101114 (elevated panel canvas)
          (colorRGBA 238 238 238 255)  -- #EEEEEE
          edgeCol
          (colorRGBA 46 48 56 255)  -- #2E3038
          (colorRGBA 12 13 15 255)  -- #0C0D0F
   in Theme
        { themeWindow = colorRGBA 0 0 0 255           -- #000000 (pitch black root window backdrop)
        , themePanel = panelSurface
        , themeFloatingWindow = panelSurface
        , themeButton =
            flatStyle
              (colorRGBA 26 27 34 255)  -- #1A1B22
              (colorRGBA 238 238 238 255)  -- #EEEEEE
              edgeCol
              (colorRGBA 54 58 72 255)  -- #363A48
              (colorRGBA 56 60 81 255)  -- #383C51
        , themeInput =
            flatStyle
              (colorRGBA 13 14 18 255)  -- #0D0E12 (recessed into panel)
              (colorRGBA 238 238 238 255)
              edgeCol
              (colorRGBA 21 22 28 255)
              (colorRGBA 8 9 11 255)
        , themeSeparator = sepCol
        , themeAccent = accentCol
        , themeMuted = colorRGBA 128 132 150 255       -- #808496
        , themeRed = colorRGBA 213 78 83 255           -- bright.red #D54E53
        , themeOrange = colorRGBA 231 140 69 255       -- bright.orange #E78C45
        , themeYellow = colorRGBA 231 197 71 255       -- bright.yellow #E7C547
        , themeGreen = colorRGBA 185 202 74 255        -- bright.green #B9CA4A
        , themePurple = colorRGBA 195 151 216 255      -- bright.purple #C397D8
        , themeOverlayDim = colorRGBA 0 0 0 160
        , themeOnAccent = colorRGBA 255 255 255 255
        , themeSelection = fadeAlpha accentCol 115
        , themeFocusRing = accentCol
        , themeLink = accentCol
        , themeShadow = colorRGBA 0 0 0 96
        , themeDisabledFade = 0.55
        }
  where
  edgeCol    = colorRGBA 48 52 70 255              -- #303446
  sepCol = colorRGBA 48 52 70 255              -- #303446
  accentCol    = colorRGBA 140 182 226 255           -- #8CB6E2

-- -----------------------------------------------------------------------------
-- Base16 Colorschemes
-- -----------------------------------------------------------------------------

-- | Standard Base16 palette containing 16 styling tones and syntax colours
-- following Chris Kempson's Base16 specification.
data Base16 = Base16
  { base00 :: {-# UNPACK #-} !Color -- ^ Default Background
  , base01 :: {-# UNPACK #-} !Color -- ^ Lighter Background (status bars, line numbers, panel backgrounds)
  , base02 :: {-# UNPACK #-} !Color -- ^ Selection Background (active elements, subtle highlights)
  , base03 :: {-# UNPACK #-} !Color -- ^ Comments, Invisibles, Line Highlighting (muted text, borders)
  , base04 :: {-# UNPACK #-} !Color -- ^ Dark Foreground (status bar foreground, secondary text)
  , base05 :: {-# UNPACK #-} !Color -- ^ Default Foreground, Caret, Delimiters, Operators
  , base06 :: {-# UNPACK #-} !Color -- ^ Light Foreground
  , base07 :: {-# UNPACK #-} !Color -- ^ Light Background / Highest contrast foreground
  , base08 :: {-# UNPACK #-} !Color -- ^ Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted (Red)
  , base09 :: {-# UNPACK #-} !Color -- ^ Integers, Boolean, Constants, XML Attributes, Markup Link Url (Orange)
  , base0A :: {-# UNPACK #-} !Color -- ^ Classes, Markup Bold, Search Text Background (Yellow)
  , base0B :: {-# UNPACK #-} !Color -- ^ Strings, Inherited Class, Markup Code, Diff Inserted (Green)
  , base0C :: {-# UNPACK #-} !Color -- ^ Support, Regular Expressions, Escape Characters, Markup Quotes (Cyan)
  , base0D :: {-# UNPACK #-} !Color -- ^ Functions, Methods, Attribute IDs, Headings (Blue / Primary Accent)
  , base0E :: {-# UNPACK #-} !Color -- ^ Keywords, Storage, Selector, Markup Italic, Diff Changed (Purple / Magenta)
  , base0F :: {-# UNPACK #-} !Color -- ^ Deprecated, Opening/Closing Embedded Language Tags (Brown)
  }
  deriving (Eq, Show)

-- | Calculate a 'Theme' from a 'Base16' colorscheme, automatically selecting
-- dark or light styling based on background vs foreground luminance.
themeFromBase16 :: Base16 -> Theme
themeFromBase16 b
  | isDark = themeFromBase16Dark b
  | otherwise = themeFromBase16Light b
  where
    isDark = colorLuminance (base00 b) < colorLuminance (base05 b)

-- | Calculate a dark 'Theme' from a 'Base16' colorscheme.
themeFromBase16Dark :: Base16 -> Theme
themeFromBase16Dark b =
  let edgeCol = lerpColor (base02 b) (base03 b) 0.35
      panelBg = lerpColor (base01 b) (base02 b) 0.3
      panelSurface =
        flatStyle
          panelBg
          (base05 b)
          edgeCol
          (lerpColor panelBg (base02 b) 0.5)
          (lerpColor panelBg (base00 b) 0.4)
   in Theme
        { themeWindow = base00 b
        , themePanel = panelSurface
        , themeFloatingWindow = panelSurface
        , themeButton =
            flatStyle
              (base02 b)
              (base07 b)
              edgeCol
              (lerpColor (base02 b) (base03 b) 0.4)
              (base01 b)
        , themeInput =
            flatStyle
              (base00 b)
              (base05 b)
              edgeCol
              (base01 b)
              (base00 b)
        , themeSeparator = edgeCol
        , themeAccent = base0D b
        , themeMuted = base03 b
        , themeRed = base08 b
        , themeOrange = base09 b
        , themeYellow = base0A b
        , themeGreen = base0B b
        , themePurple = base0E b
        , themeOverlayDim = colorRGBA 0 0 0 160
        , themeOnAccent = if colorLuminance (base0D b) > 0.6 then base00 b else colorRGBA 255 255 255 255
        , themeSelection = fadeAlpha (base0D b) 115
        , themeFocusRing = base0D b
        , themeLink = base0D b
        , themeShadow = colorRGBA 0 0 0 72
        , themeDisabledFade = 0.55
        }

-- | Calculate a light 'Theme' from a 'Base16' colorscheme.
themeFromBase16Light :: Base16 -> Theme
themeFromBase16Light b =
  let edgeCol = base02 b
      panelBg = lerpColor (base00 b) (base01 b) 0.5
      panelSurface =
        flatStyle
          panelBg
          (base05 b)
          edgeCol
          (lerpColor panelBg (base00 b) 0.4)
          (lerpColor panelBg (base02 b) 0.4)
   in Theme
        { themeWindow = base00 b
        , themePanel = panelSurface
        , themeFloatingWindow = panelSurface
        , themeButton =
            flatStyle
              (base01 b)
              (base05 b)
              edgeCol
              (base02 b)
              (lerpColor (base02 b) (base03 b) 0.35)
        , themeInput =
            flatStyle
              (base00 b)
              (base05 b)
              edgeCol
              (lerpColor (base00 b) (base01 b) 0.3)
              (lerpColor (base00 b) (base01 b) 0.6)
        , themeSeparator = edgeCol
        , themeAccent = base0D b
        , themeMuted = base03 b
        , themeRed = base08 b
        , themeOrange = base09 b
        , themeYellow = base0A b
        , themeGreen = base0B b
        , themePurple = base0E b
        , themeOverlayDim = colorRGBA 0 0 0 100
        , themeOnAccent = if colorLuminance (base0D b) > 0.6 then base07 b else colorRGBA 255 255 255 255
        , themeSelection = fadeAlpha (base0D b) 80
        , themeFocusRing = base0D b
        , themeLink = base0D b
        , themeShadow = colorRGBA 0 0 0 36
        , themeDisabledFade = 0.55
        }

-- | Tomorrow Night Base16 reference palette.
base16TomorrowNight :: Base16
base16TomorrowNight =
  Base16
    { base00 = colorRGBA 29 31 33 255     -- #1D1F21
    , base01 = colorRGBA 40 42 46 255     -- #282A2E
    , base02 = colorRGBA 55 59 65 255     -- #373B41
    , base03 = colorRGBA 150 152 150 255 -- #969896
    , base04 = colorRGBA 180 183 180 255 -- #B4B7B4
    , base05 = colorRGBA 197 200 198 255 -- #C5C8C6
    , base06 = colorRGBA 224 224 224 255 -- #E0E0E0
    , base07 = colorRGBA 255 255 255 255 -- #FFFFFF
    , base08 = colorRGBA 213 78 83 255   -- #D54E53
    , base09 = colorRGBA 231 140 69 255  -- #E78C45
    , base0A = colorRGBA 231 197 71 255  -- #E7C547
    , base0B = colorRGBA 185 202 74 255  -- #B9CA4A
    , base0C = colorRGBA 112 192 186 255 -- #70C0BA
    , base0D = colorRGBA 103 150 230 255 -- #6796E6
    , base0E = colorRGBA 195 151 216 255 -- #C397D8
    , base0F = colorRGBA 163 104 90 255  -- #A3685A
    }

-- | Tomorrow Light Base16 reference palette.
base16TomorrowLight :: Base16
base16TomorrowLight =
  Base16
    { base00 = colorRGBA 255 255 255 255 -- #FFFFFF
    , base01 = colorRGBA 242 242 242 255 -- #F2F2F2
    , base02 = colorRGBA 222 222 222 255 -- #DEDEDE
    , base03 = colorRGBA 140 140 140 255 -- #8C8C8C
    , base04 = colorRGBA 150 152 150 255 -- #969896
    , base05 = colorRGBA 55 59 65 255    -- #373B41
    , base06 = colorRGBA 40 42 46 255    -- #282A2E
    , base07 = colorRGBA 29 31 33 255    -- #1D1F21
    , base08 = colorRGBA 197 78 82 255   -- #C54E52
    , base09 = colorRGBA 231 140 69 255  -- #E78C45
    , base0A = colorRGBA 231 197 71 255  -- #E7C547
    , base0B = colorRGBA 113 140 0 255   -- #718C00
    , base0C = colorRGBA 62 153 159 255  -- #3E999F
    , base0D = colorRGBA 82 134 188 255  -- #5286BC
    , base0E = colorRGBA 137 91 144 255  -- #895B90
    , base0F = colorRGBA 163 104 90 255  -- #A3685A
    }