packages feed

spade-0.1.0.9: src/UI/Widgets/Spade/Selector.hs

module UI.Widgets.Spade.Selector where

import qualified Data.Text as T
import Data.Typeable
import qualified System.Console.ANSI as A
import Control.Monad.State.Strict
import Control.Monad.Reader
import Control.Concurrent.STM.TVar

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

setOptions :: WidgetC m => WRef SelectorWidget -> [(Value, Text)] ->  m ()
setOptions ref options = modifyWRef ref (\s -> s { selHiglighted = Nothing, selSelected = Nothing, selContent = options })

getOptions :: WidgetC m => WRef SelectorWidget -> m [(Value, Text)]
getOptions ref = selContent <$> readWRef ref

getSelection :: WidgetC m => WRef SelectorWidget -> m (Maybe Value)
getSelection ref = do
  w <- readWRef ref
  let a = selSelected w
  let options = selContent w
  case a of
    Just x -> do
      case safeIndex options x of
        Just (y, _) -> do
          pure $ Just y
        Nothing -> error "Invalid selection index"
    Nothing -> pure Nothing

data SelectorWidget = SelectorWidget
  { selContent             :: [(Value, Text)]
  , selLabel               :: Text
  , selSelected            :: Maybe Int
  , selHiglighted          :: Maybe Int
  , selOpen                :: Bool
  , selOptionsBoxLength    :: Int
  , selDim                 :: Dimensions
  , selPos                 :: ScreenPos
  , selVisibility          :: Bool
  , selFocused             :: Bool
  , selOptionsScrollOffset :: Int
  , selAction              :: Maybe Callback
  }


instance Widget SelectorWidget where
  hasCapability (DrawableCap _) = Just Dict
  hasCapability (MoveableCap _) = Just Dict
  hasCapability (FocusableCap _) = Just Dict
  hasCapability (KeyInputCap _) = Just Dict
  hasCapability _ = Nothing

instance Moveable SelectorWidget where
  getPos ref = selPos <$> readWRef ref
  move ref pos =
    modifyWRef ref (\tcw -> tcw { selPos = pos })
  getDim ref = selDim <$> readWRef ref
  resize ref cb =
    modifyWRef ref (\tcw -> tcw { selDim = cb $ selDim tcw })

instance Drawable SelectorWidget where
  setVisibility ref v = modifyWRef ref (\b -> b { selVisibility = v })
  getVisibility ref = selVisibility <$> readWRef ref
  draw ref = do
    w <- readWRef ref
    let
      maxOptionWidth = case selContent w of
        [] -> 0
        x -> Prelude.maximum (T.length . snd <$> x)
    let
      styleFn = if selFocused w
        then (\x -> StyledText (Fg A.Red) [Plain x])
        else Plain
    let
      displayLabel = case selSelected w of
        Just idx -> case safeIndex (selContent w) idx of
          Just t  -> snd t
          Nothing -> selLabel w
        Nothing -> selLabel w
    wSetCursor (selPos w)
    csPutText $ styleFn $ T.singleton verticalLine
    let
      dropBoxWidth = max (diW $ selDim w) maxOptionWidth
      cropWidth t = T.take (dropBoxWidth - 3) t
    wSetCursor (moveRight 1 $ selPos w)
    csPutText (Plain $ (T.singleton downarrow) <> " " <> cropWidth displayLabel)
    if selOpen w
      then do
        let
          printOption (idx, (_, txt)) = do
            wSetCursor (moveRight 1 $ moveDown (idx+1) $ selPos w)
            csPutText $ StyledText (FgBg A.White A.Black) [Plain $ T.replicate dropBoxWidth " "]
            case selHiglighted w of
              Just x -> if x == idx + (selOptionsScrollOffset w)
                then csPutText (StyledText (FgBg A.Red A.Black) [Plain $ "  " <> cropWidth txt])
                else csPutText (StyledText (FgBg A.White A.Black) [Plain $ "  " <> cropWidth txt])
              Nothing -> csPutText (StyledText (FgBg A.White A.Black) [Plain $ "  " <> cropWidth txt])
        let itemsLength = Prelude.length (selContent w)
        let itemsBoxLength = min itemsLength (selOptionsBoxLength w)
        let maxScrollOffset = max 0 (itemsLength - itemsBoxLength)
        let maxScrollbarPos = itemsBoxLength - 1
        let mScrollbarPos = if maxScrollOffset > 0 then Just $ max 0 $ min maxScrollbarPos (div (maxScrollbarPos * (div (selOptionsScrollOffset w * 100) maxScrollOffset)) 100) else Nothing
        mapM_ printOption $ Prelude.zip [0..] (Prelude.take itemsBoxLength $ Prelude.drop (selOptionsScrollOffset w) $ selContent w)
        case mScrollbarPos of
          Just scrollbarPos -> do
            wSetCursor $ moveDown (scrollbarPos + 1) (moveRight (diW $ selDim w) $ selPos w)
            csPutText $ Plain (T.singleton block)
          Nothing -> pass
      else pass

instance KeyInput SelectorWidget where
  handleInput :: forall m. WidgetC m => WRef SelectorWidget -> KeyEvent -> m ()
  handleInput ref ev = do
    case ev of
      KeyCtrl _ _ _ ArrowUp -> do
        w <- readWRef ref
        case selOpen w of
          True  -> modifyWRef ref (moveSelection -1)
          False -> modifyWRef ref (\u -> u { selOpen = True })
      KeyCtrl _ _ _ ArrowDown -> do
        w <- readWRef ref
        case selOpen w of
          True  -> modifyWRef ref (moveSelection 1)
          False -> modifyWRef ref (\u -> u { selOpen = True })
      KeyCtrl _ _ _ Return -> do
        w <- readWRef ref
        case selOpen w of
          True  -> do
            modifyWRef ref (\u -> u { selSelected = selHiglighted w, selOpen = False })
            case selHiglighted w of
              Just _ -> do
                case (selAction w) of
                  Just cb -> case eqT @m @(ReaderT (TVar InterpreterState) IO) of
                    Just Refl -> void $ evaluateCallback cb [WidgetValue (SomeWidgetRef ref)]
                    Nothing -> error ""
                  Nothing -> pass
              Nothing -> pass
          False -> pass
      _ -> pass
  getCursorInfo _ = pure Nothing

moveSelection :: Int -> SelectorWidget -> SelectorWidget
moveSelection d w = let
  newHiglighted = case selHiglighted w of
    Just idx -> idx + d
    Nothing  -> 0
  newHiglighted' = max 0 (min newHiglighted ((Prelude.length (selContent w)) - 1))
  newScrollOffset = max 0 (newHiglighted' - 4)
  in w { selOptionsScrollOffset = newScrollOffset, selHiglighted = Just newHiglighted' }

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

selector
  :: WidgetC m
  => Text
  -> ScreenPos
  -> Dimensions
  -> [(Value, Text)]
  -> Maybe Callback
  -> m (WRef SelectorWidget)
selector label sp dim options action = newWRef $ SelectorWidget options label Nothing Nothing False 5 dim sp True False 0 action