packages feed

herms-1.8.1.2: src/AddCLI.hs

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

import Control.Monad
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)

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 :: St -> [T.Widget Name]
drawUI 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 "                                    Herm's - Add a recipe" <=>
            str " " <=>
            (str "          Name: " <+> (hLimit 62 e1)) <=>
            str " " <=>
            (str "   Description: " <+> (hLimit 62 $ vLimit 4 e2)) <=>
            str " " <=>
            (str "  Serving size: " <+> (hLimit 3 e3)) <=>
            str " " <=>
            str "                  qty.   unit               name                attribute" <=>
            (str "  Ingredients: \n(one per line)  " 
              <+> (hLimit 7 $ vLimit 7 e4)
              <+> (hLimit 9 $ vLimit 7 e5)
              <+> (hLimit 28 $ vLimit 7 e6)
              <+> (hLimit 18 $ vLimit 7 e7))
              <=> 
            str " " <=>
            (str "   Directions: \n(one per line)  " <+> (hLimit 62 $ vLimit 8 e8)) <=>
            str " " <=>
            (str "          Tags: " <+> (hLimit 62 $ vLimit 1 e9)) <=>
            str " " <=>
            str "                      Tab / Shift+Tab           - Next / Previous field" <=>
            str "                      Ctrl + <Arrow keys>       - Navigate fields" <=>
            str "                      [Meta or Alt] + <h-j-k-l> - Navigate fields" <=>
            str "                      Esc                       - Save or Cancel"

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 :: M.App St e Name
theApp =
    M.App { M.appDraw = drawUI
          , M.appChooseCursor = appCursor
          , M.appHandleEvent = appEvent
          , M.appStartEvent = return
          , M.appAttrMap = const theMap
          }

getEdit :: String -> String -> String -> String -> String -> String -> String -> String -> String -> IO ([[String]])
getEdit name desc serving amounts units ingrs attrs dirs tags = do
  st <- M.defaultMain theApp (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 :: IO ([[String]])
getAddInput = do 
  st <- M.defaultMain theApp (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
             ]