packages feed

netease-fm-1.2.2: exe/UI/Menu.hs

{-# LANGUAGE OverloadedStrings, RecordWildCards #-}

module UI.Menu (
  menuSelection
, menuSelection_
) where

import qualified Brick.Main as UI
import qualified Brick.Types as UI
import qualified Brick.Widgets.Center as UI
import qualified Brick.Widgets.Core as UI
import qualified Graphics.Vty as UI
import qualified UI.Extra as UI

import           Control.Monad (void)
import           Control.Monad.Cont (ContT (..))
import           Data.List (elemIndex)
import           Data.Maybe (fromMaybe)
import qualified Data.Sequence as S

import           Types

data State a = State {
  menuSequence :: S.Seq a
, uiTitle      :: String
, currentIndex :: Int
, continuation :: Maybe (a -> IO ())
, isSelected   :: Bool
}

menuSelectionDraw :: (Show1 a) => State a -> [UI.Widget]
menuSelectionDraw State {..} = [ui]
  where
    ui = UI.vCenter $ UI.vLimit 15 $ UI.vBox [ title, UI.str " ", menu ]
    title = UI.mkYellow $ UI.hCenter $ UI.str uiTitle
    menu = UI.viewport "vp" UI.Vertical $ UI.hCenter $ UI.vBox $ do
      index <- [0 .. S.length menuSequence - 1]
      let mkItem | currentIndex == index = UI.mkCyan . UI.str . UI.mkFocused
                 | otherwise = UI.mkWhite . UI.str . UI.mkUnfocused
      return $ mkItem $ show1 $ menuSequence `S.index` index

menuSelectionEvent :: State a -> UI.Event -> UI.EventM (UI.Next (State a))
menuSelectionEvent state@State {..} event = case event of
  UI.EvKey UI.KEsc [] -> UI.halt state

  UI.EvKey (UI.KChar ' ') [] -> menuSelectionEvent state (UI.EvKey UI.KEnter [])
  UI.EvKey UI.KEnter [] -> emptyGuard $ case continuation of
                             Just continuation -> UI.suspendAndResume $ do
                               continuation (menuSequence `S.index` currentIndex)
                               return state { isSelected = True }
                             Nothing -> UI.halt state { isSelected = True }

  UI.EvKey UI.KDown [] -> emptyGuard $ UI.continue $ state { currentIndex = (currentIndex + 1) `mod` S.length menuSequence }

  UI.EvKey UI.KUp [] -> emptyGuard $ UI.continue $ state { currentIndex = (currentIndex - 1) `mod` S.length menuSequence }

  _ -> UI.continue state

  where emptyGuard m = if S.null menuSequence then UI.continue state else m

menuSelectionApp :: (Show1 a) => UI.App (State a) UI.Event
menuSelectionApp = UI.App { UI.appDraw = menuSelectionDraw
                          , UI.appStartEvent = return
                          , UI.appHandleEvent = menuSelectionEvent
                          , UI.appAttrMap = const UI.defaultAttributeMap
                          , UI.appLiftVtyEvent = id
                          , UI.appChooseCursor = UI.neverShowCursor
                          }

menuSelectionCont :: (Show1 a, Eq a) => [a] -> Maybe a -> String -> Maybe (a -> IO ()) -> IO (Maybe Int)
menuSelectionCont menu def title continuation = do
  let startIndex = maybe 0 (\def -> fromMaybe 0 (elemIndex def menu)) def
  let state = State { menuSequence = S.fromList menu
                    , uiTitle = title
                    , currentIndex = startIndex
                    , continuation = continuation
                    , isSelected = False
                    }
  State {..} <- UI.defaultMain menuSelectionApp state
  return $ if isSelected then Just currentIndex else Nothing

menuSelection :: (Show1 a, Eq a) => [a] -> Maybe a -> String -> ContT () IO a
menuSelection menu def title = ContT (void . menuSelectionCont menu def title . Just)

menuSelection_ :: (Show1 a, Eq a) => [a] -> Maybe a -> String -> IO (Maybe a)
menuSelection_ menu def title = do
  result <- menuSelectionCont menu def title Nothing
  return $ case result of
    Just id -> Just (menu !! id)
    Nothing -> Nothing