packages feed

herms-1.9.0.4: src/AddCLI.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RankNTypes #-}
module AddCLI where

import Lens.Micro
import Lens.Micro.TH
import qualified Graphics.Vty as V

import qualified Brick.Main as M
import qualified Brick.Types as T
import Brick.Widgets.Core
  ( (<+>)
  , (<=>)
  , hLimit
  , vLimit
  , str
  )
import qualified Brick.Widgets.Center as C
import qualified Brick.Widgets.Edit as E
import qualified Brick.AttrMap as A
import qualified Brick.Focus as F
import Brick.Util (on)
import ReadConfig (Translator)
import qualified Lang.Strings as Str

data Name = RecipeName
          | Description
          | ServingSize
          | IngrAmount
          | IngrUnit
          | IngrName
          | IngrAttr
          | Directions
          | Tags
          deriving (Ord, Show, Eq)

data FocusChange = FocusDown
                 | FocusUp
                 | FocusLeft
                 | FocusRight
                 deriving Eq

data St =
    St { _focusRing :: F.FocusRing Name
       , _edit1 :: E.Editor String Name
       , _edit2 :: E.Editor String Name
       , _edit3 :: E.Editor String Name
       , _edit4 :: E.Editor String Name
       , _edit5 :: E.Editor String Name
       , _edit6 :: E.Editor String Name
       , _edit7 :: E.Editor String Name
       , _edit8 :: E.Editor String Name
       , _edit9 :: E.Editor String Name
       }

makeLenses ''St

drawUI :: Translator -> St -> [T.Widget Name]
drawUI t st = [ui]
    where
        e1 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit1)
        e2 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit2)
        e3 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit3)
        e4 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit4)
        e5 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit5)
        e6 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit6)
        e7 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit7)
        e8 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit8)
        e9 = F.withFocusRing (st^.focusRing) (E.renderEditor (str . unlines)) (st^.edit9)

        ui = C.center $
            str (t Str.tuiTitle) <=>
            str " " <=>
            (str (t Str.tuiName) <+> hLimit 62 e1) <=>
            str " " <=>
            (str (t Str.tuiDesc) <+> hLimit 62 (vLimit 4 e2)) <=>
            str " " <=>
            (str (t Str.tuiServingSize) <+> hLimit 3 e3) <=>
            str " " <=>
            str (t Str.tuiHeaders) <=>
            (str (t Str.tuiIngrs)
              <+> hLimit 7 (vLimit 7 e4)
              <+> hLimit 9 (vLimit 7 e5)
              <+> hLimit 28 (vLimit 7 e6)
              <+> hLimit 18 (vLimit 7 e7))
              <=>
            str " " <=>
            (str (t Str.tuiDirs) <+> hLimit 62 (vLimit 8 e8)) <=>
            str " " <=>
            (str (t Str.tuiTags) <+> hLimit 62 (vLimit 1 e9)) <=>
            str " " <=>
            str (t Str.tuiHelp1) <=>
            str (t Str.tuiHelp2) <=>
            str (t Str.tuiHelp3) <=>
            str (t Str.tuiHelp4)

appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)
appEvent st (T.VtyEvent ev) =
    case ev of
        V.EvKey V.KEsc [] -> M.halt st
        V.EvKey (V.KChar '\t') [] -> M.continue $ st & focusRing %~ F.focusNext
        V.EvKey V.KBackTab [] -> M.continue $ st & focusRing %~ F.focusPrev

        -- Ctrl + <Arrow Keys>
        V.EvKey V.KDown [V.MCtrl] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusDown st
        V.EvKey V.KUp [V.MCtrl] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusUp st
        V.EvKey V.KRight [V.MCtrl] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusRight st
        V.EvKey V.KLeft [V.MCtrl] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusLeft st

        -- Meta + <h-j-k-l>
        V.EvKey (V.KChar 'h') [V.MMeta] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusLeft st
        V.EvKey (V.KChar 'j') [V.MMeta] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusDown st
        V.EvKey (V.KChar 'k') [V.MMeta] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusUp st
        V.EvKey (V.KChar 'l') [V.MMeta] ->
          M.continue $ st & focusRing %~ determineNextFocus FocusRight st

        _ -> M.continue =<< case F.focusGetCurrent (st^.focusRing) of
               Just RecipeName -> T.handleEventLensed st edit1 E.handleEditorEvent ev
               Just Description -> T.handleEventLensed st edit2 E.handleEditorEvent ev
               Just ServingSize -> T.handleEventLensed st edit3 E.handleEditorEvent ev
               Just IngrAmount -> T.handleEventLensed st edit4 E.handleEditorEvent ev
               Just IngrUnit -> T.handleEventLensed st edit5 E.handleEditorEvent ev
               Just IngrName -> T.handleEventLensed st edit6 E.handleEditorEvent ev
               Just IngrAttr -> T.handleEventLensed st edit7 E.handleEditorEvent ev
               Just Directions -> T.handleEventLensed st edit8 E.handleEditorEvent ev
               Just Tags -> T.handleEventLensed st edit9 E.handleEditorEvent ev
               Nothing -> return st
appEvent st _ = M.continue st

determineNextFocus :: FocusChange -> St -> F.FocusRing n -> F.FocusRing n
determineNextFocus action st =
  case action of
    FocusDown -> case currentFocus of
        Just RecipeName -> F.focusNext
        Just Description -> F.focusNext
        Just ServingSize -> F.focusNext
        Just IngrAmount -> F.focusNext . F.focusNext . F.focusNext . F.focusNext
        Just IngrUnit -> F.focusNext . F.focusNext . F.focusNext
        Just IngrName -> F.focusNext . F.focusNext
        Just IngrAttr -> F.focusNext
        Just Directions -> F.focusNext
        Just Tags -> F.focusNext
        Nothing -> F.focusNext
    FocusUp -> case currentFocus of
        Just RecipeName -> F.focusPrev
        Just Description -> F.focusPrev
        Just ServingSize -> F.focusPrev
        Just IngrAttr -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev
        Just IngrName -> F.focusPrev . F.focusPrev . F.focusPrev
        Just IngrUnit -> F.focusPrev . F.focusPrev
        Just IngrAmount -> F.focusPrev
        Just Directions -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev
        Just Tags -> F.focusPrev
        Nothing -> F.focusPrev
    FocusLeft -> case currentFocus of
        Just RecipeName -> F.focusNext . F.focusNext
        Just Description -> F.focusNext
        Just ServingSize -> F.focusNext
        Just IngrAmount -> F.focusNext . F.focusNext . F.focusNext
        Just IngrUnit -> F.focusPrev
        Just IngrName -> F.focusPrev
        Just IngrAttr -> F.focusPrev
        Just Directions -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev
        Just Tags -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev
        Nothing -> F.focusNext
    FocusRight -> case currentFocus of
        Just RecipeName -> F.focusNext . F.focusNext
        Just Description -> F.focusNext
        Just ServingSize -> F.focusNext
        Just IngrAmount -> F.focusNext
        Just IngrUnit -> F.focusNext
        Just IngrName -> F.focusNext
        Just IngrAttr -> F.focusPrev . F.focusPrev . F.focusPrev
        Just Directions -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev
        Just Tags -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev
        Nothing -> F.focusNext
  where currentFocus = F.focusGetCurrent $ st^.focusRing


initialState :: String -> String -> String -> String -> String -> String -> String -> String -> String -> St
initialState name desc serving amounts units ingrs attrs dirs tags =
    St (F.focusRing [RecipeName, Description, ServingSize, IngrAmount, IngrUnit, IngrName, IngrAttr, Directions, Tags])
       (E.editor RecipeName (Just 1) name)
       (E.editor Description Nothing desc)
       (E.editor ServingSize (Just 1) serving)
       (E.editor IngrAmount Nothing amounts)
       (E.editor IngrUnit Nothing units)
       (E.editor IngrName Nothing ingrs)
       (E.editor IngrAttr Nothing attrs)
       (E.editor Directions Nothing dirs)
       (E.editor Tags (Just 1) tags)

theMap :: A.AttrMap
theMap = A.attrMap V.defAttr
    [ (E.editAttr,                   V.white `on` V.black)
    , (E.editFocusedAttr,            V.black `on` V.white)
    ]

appCursor :: St -> [T.CursorLocation Name] -> Maybe (T.CursorLocation Name)
appCursor = F.focusRingCursor (^.focusRing)

theApp :: Translator -> M.App St e Name
theApp t =
    M.App { M.appDraw = drawUI t
          , M.appChooseCursor = appCursor
          , M.appHandleEvent = appEvent
          , M.appStartEvent = return
          , M.appAttrMap = const theMap
          }

getEdit :: Translator -> String -> String -> String -> String -> String -> String -> String -> String -> String -> IO [[String]]
getEdit t name desc serving amounts units ingrs attrs dirs tags = do
  st <- M.defaultMain (theApp t) (initialState name desc serving amounts units ingrs attrs dirs tags)
  return [ E.getEditContents $ st^.edit1
             , E.getEditContents $ st^.edit2
             , E.getEditContents $ st^.edit3
             , E.getEditContents $ st^.edit4
             , E.getEditContents $ st^.edit5
             , E.getEditContents $ st^.edit6
             , E.getEditContents $ st^.edit7
             , E.getEditContents $ st^.edit8
             , E.getEditContents $ st^.edit9
             ]

getAddInput :: Translator -> IO [[String]]
getAddInput t = do
  st <- M.defaultMain (theApp t) (initialState "" "" "" "" "" "" "" "" "")
  return [ E.getEditContents $ st^.edit1
             , E.getEditContents $ st^.edit2
             , E.getEditContents $ st^.edit3
             , E.getEditContents $ st^.edit4
             , E.getEditContents $ st^.edit5
             , E.getEditContents $ st^.edit6
             , E.getEditContents $ st^.edit7
             , E.getEditContents $ st^.edit8
             , E.getEditContents $ st^.edit9
             ]