packages feed

cabal-matrix-1.0.0.0: src/Cabal/Matrix/Tui/Headers.hs

module Cabal.Matrix.Tui.Headers
  ( HeaderState(..)
  , mkHeaderState
  , HeaderEditorState
  , initHeaderEditorState
  , headerEditorWidget
  , headerEditorHandleEvent
  , headerEditorKeybinds
  ) where

import Cabal.Matrix.Rectangle (Rectangle)
import Cabal.Matrix.Rectangle qualified as Rectangle
import Cabal.Matrix.Tui.Common
import Data.Foldable
import Data.Primitive
import Data.Text (Text)
import Data.Text qualified as Text
import Graphics.Vty


data HeaderState = HeaderState
  { vertical :: Array Bool
    -- ^ INVARIANT: must have the same length as the number of columns in the
    -- build matrix in use
  , horizontalHeader :: Rectangle () Text (Maybe Text)
  , verticalHeader :: Rectangle () Text (Maybe Text)
  , gridToFlavor :: Rectangle () () (Maybe Int)
  }

mkHeaderState :: Rectangle flavor Text (Maybe Text) -> Array Bool -> HeaderState
mkHeaderState matrix vertical = HeaderState{..}
  where
    (horizontalHeader, verticalHeader, gridToFlavor)
      = Rectangle.unCartesianProduct vertical matrix

data HeaderEditorState = HeaderEditorState
  { index :: Int
  , scroll :: Int
  }

initHeaderEditorState :: HeaderEditorState
initHeaderEditorState = HeaderEditorState
  { index = 0
  , scroll = 0
  }

headerEditorWidget
  :: DisplayRegion
  -> Rectangle flavor Text (Maybe Text)
  -> HeaderState
  -> HeaderEditorState
  -> (HeaderEditorState, Image)
headerEditorWidget (_, height) matrix hs hes =
  ( hes { scroll = scroll' }
  , vertCat $ padToCommonWidth
    [ ( attr
      , ' '
      , text' (attr `withForeColor` yellow)
        (if indexArray hs.vertical i then "[V] " else "[H] ")
        <|> text' attr column
      )
      | (i, column) <- take height $ drop scroll'
        $ zip [0..] $ toList $ Rectangle.columns matrix
      , let
          attr = if i == hes.index
            then defAttr `withBackColor` blue
            else defAttr
    ]
  )
  where
    -- make it so that 1 <= hes.index - hes.scroll < height - 1
    scroll' = clamp (hes.index - height + 2) (hes.index - 1) hes.scroll

headerEditorHandleEvent
  :: Rectangle flavor Text (Maybe Text)
  -> Event
  -> (HeaderEditorState, HeaderState)
  -> (HeaderEditorState, HeaderState)
headerEditorHandleEvent matrix ev (hes, hs) = case ev of
  EvKey KUp _ ->
    ( hes { index = clamp 0 (sizeofArray hs.vertical - 1) $ pred hes.index }
    , hs
    )
  EvKey KDown _ ->
    ( hes { index = clamp 0 (sizeofArray hs.vertical - 1) $ succ hes.index }
    , hs
    )
  EvKey (isToggleKey -> True) _ | hes.index < sizeofArray hs.vertical
    -> (hes, mkHeaderState matrix $ toggleArray hes.index hs.vertical)
  _ -> (hes, hs)
  where
    isToggleKey = \case
      KChar ' ' -> True
      KEnter -> True
      _ -> False
    toggleArray i arr = runArray do
      m <- thawArray arr 0 (sizeofArray arr)
      writeArray m i . not =<< readArray m i
      pure m

headerEditorKeybinds :: [(Text, Text)]
headerEditorKeybinds =
  [ (Text.pack [triangleN, triangleS], "select field")
  , ("<Enter>/<Space>", "toggle vertical/horizontal axis")
  ]