packages feed

hascard-0.6.0.2: src/Parameters.hs

{-# LANGUAGE RankNTypes, FlexibleContexts #-}
module Parameters where
import UI.Attributes
import Brick
import Brick.Widgets.Center
import Brick.Forms
import DeckHandling
import Data.Maybe
import Data.Char (isDigit)
import Data.Text (pack)
import Lens.Micro.Platform
import States
import Text.Read (readMaybe)
import UI.BrickHelpers
import qualified Data.Text as T
import qualified Graphics.Vty as V

mkParameterForm :: Int -> Parameters -> Form Parameters e Name
mkParameterForm n ps =
  let label s w = padBottom (Pad 1) $ padRight (Pad 2) (strWrap s) <+> w
      form = newForm
        [ chunkSubsetField n (chunkSubsetLens n)
        , label "Shuffle the deck?" @@= yesnoField True pShuffle ShuffleField ""
        , label "Review mode?" @@= yesnoField True pReviewMode ReviewModeField ""
        , hCenter @@= okField pOk ParametersOkField "Ok"
        ] ps
  in setFormFocus ParametersOkField form

chunkSubsetLens :: Int -> Lens' Parameters (Chunk, Int)
chunkSubsetLens n = lens getter setter
  where getter ps = (ps^.pChunk, fromMaybe n (ps^.pSubset))
        setter ps (c, int) = ps & pChunk.~c & pSubset ?~ int

chunkSubsetField :: Int -> Lens' s (Chunk, Int) -> s -> FormFieldState s e Name
chunkSubsetField capacity stLens initialState = 
  let (initChunk, initInt) = initialState ^. stLens

      handleChunkEvent1 :: BrickEvent n e -> EventM n (Chunk, Int) ()
      handleChunkEvent1 (VtyEvent ev) = do
        s@(c@(Chunk i n), int) <- get
        case ev of
          V.EvKey (V.KChar c) [] | isDigit c -> do 
            let i' = read (show i ++ [c])
            put $ if i' <= n || n == 0 then (Chunk i' n, getSizeOfChunk (Chunk i' n)) else (Chunk n n, getSizeOfChunk (Chunk n n))
          V.EvKey V.KBS [] -> do
            let calcNew x = if null (show x) then 0 else fromMaybe 0 (readMaybe (init (show x)))
            put (Chunk (calcNew i) n, int)
          _ -> return ()
      handleChunkEvent1 _ = return ()

      handleChunkEvent2 :: BrickEvent n e -> EventM n (Chunk, Int) ()
      handleChunkEvent2 (VtyEvent ev) = do 
        s@(c@(Chunk i n), int) <- get
        case ev of
          V.EvKey (V.KChar c) [] | isDigit c -> do
            let n' = read (show n ++ [c])
                i' = if i <= n' || n' == 0 then i else n'
            put $ if n' <= capacity then (Chunk i' n', getSizeOfChunk (Chunk i' n')) else (Chunk i capacity, getSizeOfChunk (Chunk i capacity))
          V.EvKey V.KBS [] -> do
            let calcNew x = if null (show x) then 0 else fromMaybe 0 (readMaybe (init (show x)))
                newN = calcNew n
                newI = if i <= newN || newN == 0 then i else newN
            put (Chunk newI newN, int)
          _ -> return ()
      handleChunkEvent2 _ = return ()

      handleSubsetEvent :: BrickEvent n e -> EventM n (Chunk, Int) ()
      handleSubsetEvent (VtyEvent ev) = do
        s@(ch@(Chunk i n), int) <- get
        let bound = getSizeOfChunk ch in
          case ev of
          V.EvKey (V.KChar c) [] | isDigit c -> do
            let newValue = read (show int ++ [c])
                int' = min newValue bound
            put (ch, int')
          V.EvKey V.KBS [] -> do
            let int' = case show int of
                            "" -> 0
                            xs -> fromMaybe 0 (readMaybe (init xs))
            put (ch, int')
          _ -> return ()
      handleSubsetEvent _ = return ()

      renderChunk1 :: Bool -> (Chunk, Int) -> Widget Name
      renderChunk1 foc (Chunk i n, _) = 
        let addAttr = if foc then withDefAttr focusedFormInputAttr else id
            csr x = if foc then showCursor ChunkField1 (Location (length x,0)) else id
            val' 0 = ""
            val' x = show x
          in addAttr (csr (val' i) (str (val' i))) <+> str "/"

      renderChunk2 :: Bool -> (Chunk, Int) -> Widget Name
      renderChunk2 foc (Chunk i n, _) = 
        let addAttr = if foc then withDefAttr focusedFormInputAttr else id
            csr x = if foc then showCursor ChunkField2 (Location (length x,0)) else id
            val' 0 = ""
            val' x = show x
          in addAttr (csr (val' n) (str (val' n)))

      customConcat :: [Widget Name] -> Widget Name
      customConcat [chunk1, chunk2, subset] = 
        (str "Select chunk:" <+> hFill ' ' <+> chunk1 <+> chunk2) 
        <=>
        str " "
        <=>
        (str "Number of cards:" <+> hFill ' ' <+> subset)
        <=>
        str " "
      customConcat _ = error "chunkSubsetField form field concatenation has gone wrong"

      getSizeOfChunk :: Chunk -> Int
      getSizeOfChunk (Chunk i n) = 
        if i >= 1 && n >= 1 && i <= n
          then length (splitIntoNChunks n [1..capacity] !! (i-1))
          else capacity

      renderSubset :: Bool -> (Chunk, Int) -> Widget Name
      renderSubset foc (c, value) = 
        let cardsInChunk = getSizeOfChunk c
        in renderNaturalNumber cardsInChunk ("/" <> show cardsInChunk) SubsetField foc value
      
      validateChunk (c@(Chunk i n), int) = if i >= 1 && n >= 1 && i <= n then Just (c, int) else Nothing
      validateSubset = Just

  in FormFieldState { formFieldState = (initChunk, initInt)
                    , formFields = [ 
                                     FormField ChunkField1 validateChunk True
                                       renderChunk1
                                       handleChunkEvent1,
                                     FormField ChunkField2 validateChunk True
                                       renderChunk2
                                       handleChunkEvent2,
                                     FormField SubsetField validateSubset True
                                       renderSubset
                                       handleSubsetEvent
                                   ]
                    , formFieldLens = stLens
                    , formFieldUpdate = const
                    , formFieldRenderHelper = id
                    , formFieldConcat = customConcat
                    , formFieldVisibilityMode = ShowAugmentedField }

okField :: (Ord n, Show n) => Lens' s Bool -> n -> String -> s -> FormFieldState s e n
okField stLens name label initialState =
  let initVal = initialState ^. stLens

      handleEvent (VtyEvent (V.EvKey V.KEnter [])) = put True
      handleEvent _ = return ()
  
  in FormFieldState { formFieldState = initVal
                    , formFields = [ FormField name Just True 
                                       (renderOk label name)
                                       handleEvent ]
                    , formFieldLens = stLens
                    , formFieldUpdate = const
                    , formFieldRenderHelper = id
                    , formFieldConcat = vBox
                    , formFieldVisibilityMode = ShowAugmentedField }

renderOk :: String -> n -> Bool -> Bool -> Widget n
renderOk label _ focus _ =
  (if focus then withAttr selectedAttr else id) $ str "Ok"