packages feed

spade-0.1.0.7: src/UI/Widgets/Spade/TextContainer.hs

module UI.Widgets.Spade.TextContainer where

import qualified Data.Text as T
import Data.Typeable
import qualified System.Console.ANSI as A

import Common
import UI.Chars
import DiffRender.DiffRender
import UI.Widgets.Common

data TextContainerWidget = TextContainerWidget
  { tcwContent      :: Text
  , tcwDim          :: Dimensions
  , tcwPos          :: ScreenPos
  , tcwVisibility   :: Bool
  , tcwFocused      :: Bool
  , tcwScrollOffset :: Int
  }

instance Container TextContainerWidget Text where
  setContent ref t = do
    modifyWRef ref (\tcw -> tcw { tcwContent = t })
  getContent ref =
    tcwContent <$> readWRef ref

instance Widget TextContainerWidget where
  hasCapability (ContainerCap _ (_ :: Proxy cnt)) = case eqT @cnt @Text of
    Just Refl -> Just Dict
    Nothing   -> Nothing
  hasCapability (DrawableCap _) = Just Dict
  hasCapability (MoveableCap _) = Just Dict
  hasCapability (FocusableCap _)  = Just Dict
  hasCapability (KeyInputCap _)  = Just Dict
  hasCapability _ = Nothing

instance KeyInput TextContainerWidget where
  getCursorInfo _ = pure Nothing
  handleInput ref ev = case ev of
    KeyCtrl _ _ _ ArrowUp -> modifyWRef ref (scroll -1)
    KeyCtrl _ _ _ ArrowDown -> modifyWRef ref (scroll 1)
    _ -> pass

scroll :: Int -> TextContainerWidget -> TextContainerWidget
scroll d w =
  let
    contentWidth = (diW $ tcwDim w) - 2
    contentHeight = (diH $ tcwDim w)
    contentLines' =
      Prelude.concat $ (chunksOf contentWidth <$> (splitOn "\n" (tcwContent w)))
    contentLinesSize = Prelude.length contentLines'
    maxScrollOffset = max 0 (contentLinesSize - contentHeight)
    newScrollOffset = tcwScrollOffset w + d
  in w { tcwScrollOffset = min maxScrollOffset (max 0 newScrollOffset) }

instance Moveable TextContainerWidget where
  getPos ref = tcwPos <$> readWRef ref
  move ref pos =
    modifyWRef ref (\tcw -> tcw { tcwPos = pos })
  getDim ref = tcwDim <$> readWRef ref
  resize ref cb =
    modifyWRef ref (\tcw -> tcw { tcwDim = cb $ tcwDim tcw })

instance Focusable TextContainerWidget where
  setFocus ref b =
    modifyWRef ref (\w -> w { tcwFocused = b })
  getFocus ref =
    tcwFocused <$> (readWRef ref)

instance Drawable TextContainerWidget where
  setVisibility ref v = modifyWRef ref (\b -> b { tcwVisibility = v })
  getVisibility ref = tcwVisibility <$> readWRef ref
  draw :: forall m. WidgetC m => WRef TextContainerWidget -> m ()
  draw ref = do
    w <- readWRef ref
    let
      styleFn = if tcwFocused w
        then (\x -> StyledText (Fg A.Red) [Plain x])
        else Plain

    -- drawBorderBox' (tcwPos w) (tcwDim w) styleFn
    forM_ [0..(diH $ tcwDim w)] (\r -> do
      wSetCursor $ moveDown r (tcwPos w)
      csPutText $ styleFn $ T.singleton verticalLine
      )
    let
      contentWidth = (diW $ tcwDim w) - 2
      contentHeight = (diH $ tcwDim w)
      contentLines =
        Prelude.concat (chunksOf contentWidth <$> (splitOn "\n" (tcwContent w)))
      contentLinesSize = Prelude.length contentLines
      visibleContentLines = Prelude.take contentHeight $
        Prelude.drop (tcwScrollOffset w) contentLines
      maxScrollOffset = max 0 (contentLinesSize - contentHeight)
      maxScrollbarPos = contentHeight - 1 -- Top most position of scroll bar is zero. This is converted to relative location later.
      mScrollbarPos = if maxScrollOffset > 0 then Just $ max 0 $ min maxScrollbarPos (div (maxScrollbarPos * (div (tcwScrollOffset w * 100) maxScrollOffset)) 100) else Nothing
    let
      printLine :: (Int, Text) -> m ()
      printLine (ln, c) = do
        wSetCursor $ moveDown ln (moveRight 1 $ tcwPos w)
        csPutText $ Plain c
    let emp = T.replicate ((diW $ tcwDim w) - 2) " "
    mapM_ printLine (Prelude.zip [0..] (Prelude.take ((diH $ tcwDim w) - 2) $ Prelude.repeat emp))
    mapM_ printLine (Prelude.zip [0..] visibleContentLines)
    if (tcwFocused w) then
      case mScrollbarPos of
        Just scrollbarPos -> do
          wSetCursor $ moveDown scrollbarPos (moveRight ((diW $ tcwDim w) - 1) $ tcwPos w)
          csPutText $ Plain (T.singleton block)
        Nothing -> pass
      else pass
    where

textContainer
  :: WidgetC m
  => ScreenPos
  -> Dimensions
  -> m (WRef TextContainerWidget)
textContainer sp dim = newWRef $ TextContainerWidget "" dim sp True False 0