Skip to content

Commit

Permalink
Extract utils from Controller and View
Browse files Browse the repository at this point in the history
  • Loading branch information
kostmo committed Dec 8, 2022
1 parent 73b5c9f commit 9628667
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 102 deletions.
20 changes: 1 addition & 19 deletions src/Swarm/TUI/Controller.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}

-- |
-- Module : Swarm.TUI.Controller
Expand Down Expand Up @@ -81,6 +80,7 @@ import Swarm.Language.Requirement qualified as R
import Swarm.Language.Syntax
import Swarm.Language.Typed (Typed (..))
import Swarm.Language.Types
import Swarm.TUI.Controller.ControllerUtils
import Swarm.TUI.Inventory.Sorting (cycleSortDirection, cycleSortOrder)
import Swarm.TUI.List
import Swarm.TUI.Model
Expand All @@ -90,24 +90,6 @@ import Swarm.Version (NewReleaseFailure (..))
import System.Clock
import Witch (into)

-- | Pattern synonyms to simplify brick event handler
pattern Key :: V.Key -> BrickEvent n e
pattern Key k = VtyEvent (V.EvKey k [])

pattern CharKey, ControlChar, MetaChar :: Char -> BrickEvent n e
pattern CharKey c = VtyEvent (V.EvKey (V.KChar c) [])
pattern ControlChar c = VtyEvent (V.EvKey (V.KChar c) [V.MCtrl])
pattern MetaChar c = VtyEvent (V.EvKey (V.KChar c) [V.MMeta])

pattern ShiftKey :: V.Key -> BrickEvent n e
pattern ShiftKey k = VtyEvent (V.EvKey k [V.MShift])

pattern EscapeKey :: BrickEvent n e
pattern EscapeKey = VtyEvent (V.EvKey V.KEsc [])

pattern FKey :: Int -> BrickEvent n e
pattern FKey c = VtyEvent (V.EvKey (V.KFun c) [])

-- | The top-level event handler for the TUI.
handleEvent :: BrickEvent Name AppEvent -> EventM Name AppState ()
handleEvent = \case
Expand Down
24 changes: 24 additions & 0 deletions src/Swarm/TUI/Controller/ControllerUtils.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{-# LANGUAGE PatternSynonyms #-}

module Swarm.TUI.Controller.ControllerUtils where

import Brick hiding (Direction)
import Graphics.Vty qualified as V

-- | Pattern synonyms to simplify brick event handler
pattern Key :: V.Key -> BrickEvent n e
pattern Key k = VtyEvent (V.EvKey k [])

pattern CharKey, ControlChar, MetaChar :: Char -> BrickEvent n e
pattern CharKey c = VtyEvent (V.EvKey (V.KChar c) [])
pattern ControlChar c = VtyEvent (V.EvKey (V.KChar c) [V.MCtrl])
pattern MetaChar c = VtyEvent (V.EvKey (V.KChar c) [V.MMeta])

pattern ShiftKey :: V.Key -> BrickEvent n e
pattern ShiftKey k = VtyEvent (V.EvKey k [V.MShift])

pattern EscapeKey :: BrickEvent n e
pattern EscapeKey = VtyEvent (V.EvKey V.KEsc [])

pattern FKey :: Int -> BrickEvent n e
pattern FKey c = VtyEvent (V.EvKey (V.KFun c) [])
84 changes: 1 addition & 83 deletions src/Swarm/TUI/View.hs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ import Swarm.Game.World qualified as W
import Swarm.Language.Pretty (prettyText)
import Swarm.Language.Syntax
import Swarm.Language.Typecheck (inferConst)
import Swarm.Language.Types (Polytype)
import Swarm.TUI.Attr
import Swarm.TUI.Border
import Swarm.TUI.Inventory.Sorting (renderSortMethod)
import Swarm.TUI.Model
import Swarm.TUI.Panel
import Swarm.TUI.View.ViewUtils
import Swarm.Util
import Swarm.Version (NewReleaseFailure (..))
import System.Clock (TimeSpec (..))
Expand Down Expand Up @@ -390,10 +390,6 @@ drawTime t showTicks =
maybeDrawTime :: Integer -> Bool -> GameState -> Maybe (Widget n)
maybeDrawTime t showTicks gs = guard (clockInstalled gs) $> drawTime t showTicks

-- | Render the type of the current REPL input to be shown to the user.
drawType :: Polytype -> Widget Name
drawType = withAttr infoAttr . padLeftRight 1 . txt . prettyText

-- | Draw info about the current number of ticks per second.
drawTPS :: AppState -> Widget Name
drawTPS s = hBox (tpsInfo : rateInfo)
Expand Down Expand Up @@ -426,10 +422,6 @@ chooseCursor s locs = case s ^. uiState . uiModal of
Nothing -> showFirstCursor s locs
Just _ -> Nothing

-- | Width cap for modal and error message windows
maxModalWindowWidth :: Int
maxModalWindowWidth = 500

-- | Render the error dialog window with a given error message
renderErrorDialog :: Text -> Widget Name
renderErrorDialog err = renderDialog (dialog (Just "Error") Nothing (maxModalWindowWidth `min` requiredWidth)) errContent
Expand Down Expand Up @@ -473,77 +465,6 @@ drawModal s = \case
GoalModal g -> padLeftRight 1 (displayParagraphs g)
KeepPlayingModal -> padLeftRight 1 (displayParagraphs ["Have fun! Hit Ctrl-Q whenever you're ready to proceed to the next challenge or return to the menu."])

quitMsg :: Menu -> Text
quitMsg m = "Are you sure you want to " <> quitAction <> "? All progress on this scenario will be lost!"
where
quitAction = case m of
NoMenu -> "quit"
_ -> "return to the menu"

-- | Generate a fresh modal window of the requested type.
generateModal :: AppState -> ModalType -> Modal
generateModal s mt = Modal mt (dialog (Just title) buttons (maxModalWindowWidth `min` requiredWidth))
where
currentScenario = s ^. uiState . scenarioRef
currentSeed = s ^. gameState . seed
haltingMessage = case s ^. uiState . uiMenu of
NoMenu -> Just "Quit"
_ -> Nothing
descriptionWidth = 100
helpWidth = 80
(title, buttons, requiredWidth) =
case mt of
HelpModal -> (" Help ", Nothing, helpWidth)
RobotsModal -> ("Robots", Nothing, descriptionWidth)
RecipesModal -> ("Available Recipes", Nothing, descriptionWidth)
CommandsModal -> ("Available Commands", Nothing, descriptionWidth)
MessagesModal -> ("Messages", Nothing, descriptionWidth)
WinModal ->
let nextMsg = "Next challenge!"
stopMsg = fromMaybe "Return to the menu" haltingMessage
continueMsg = "Keep playing"
in ( ""
, Just
( 0
, [ (nextMsg, NextButton scene)
| Just scene <- [nextScenario (s ^. uiState . uiMenu)]
]
++ [ (stopMsg, QuitButton)
, (continueMsg, KeepPlayingButton)
]
)
, sum (map length [nextMsg, stopMsg, continueMsg]) + 32
)
DescriptionModal e -> (descriptionTitle e, Nothing, descriptionWidth)
QuitModal ->
let stopMsg = fromMaybe ("Quit to" ++ maybe "" (" " ++) (into @String <$> curMenuName s) ++ " menu") haltingMessage
maybeStartOver = sequenceA ("Start over", StartOverButton currentSeed <$> currentScenario)
in ( ""
, Just
( 0
, catMaybes
[ Just ("Keep playing", CancelButton)
, maybeStartOver
, Just (stopMsg, QuitButton)
]
)
, T.length (quitMsg (s ^. uiState . uiMenu)) + 4
)
GoalModal _ ->
let goalModalTitle = case currentScenario of
Nothing -> "Goal"
Just (scenario, _) -> scenario ^. scenarioName
in (" " <> T.unpack goalModalTitle <> " ", Nothing, 80)
KeepPlayingModal -> ("", Just (0, [("OK", CancelButton)]), 80)

-- | Get the name of the current New Game menu.
curMenuName :: AppState -> Maybe Text
curMenuName s = case s ^. uiState . uiMenu of
NewGameMenu (_ :| (parentMenu : _)) ->
Just (parentMenu ^. BL.listSelectedElementL . to scenarioItemName)
NewGameMenu _ -> Just "Scenarios"
_ -> Nothing

robotsListWidget :: AppState -> Widget Name
robotsListWidget s = hCenter table
where
Expand Down Expand Up @@ -713,9 +634,6 @@ drawConst c = hBox [padLeft (Pad $ 13 - T.length constName) (txt constName), txt
constName = syntax . constInfo $ c
constSig = " : " <> prettyText (inferConst c)

descriptionTitle :: Entity -> String
descriptionTitle e = " " ++ from @Text (e ^. entityName) ++ " "

-- | Generate a pop-up widget to display the description of an entity.
descriptionWidget :: AppState -> Entity -> Widget Name
descriptionWidget s e = padLeftRight 1 (explainEntry s e)
Expand Down
103 changes: 103 additions & 0 deletions src/Swarm/TUI/View/ViewUtils.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{-# LANGUAGE OverloadedStrings #-}

module Swarm.TUI.View.ViewUtils where

import Brick hiding (Direction)
import Brick.Widgets.Dialog
import Brick.Widgets.List qualified as BL
import Control.Lens hiding (Const, from)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (catMaybes, fromMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import Swarm.Game.Entity as E
import Swarm.Game.Scenario (scenarioName)
import Swarm.Game.ScenarioInfo (scenarioItemName)
import Swarm.Game.State
import Swarm.Language.Pretty (prettyText)
import Swarm.Language.Types (Polytype)
import Swarm.TUI.Attr
import Swarm.TUI.Model
import Witch (from, into)

-- | Generate a fresh modal window of the requested type.
generateModal :: AppState -> ModalType -> Modal
generateModal s mt = Modal mt (dialog (Just title) buttons (maxModalWindowWidth `min` requiredWidth))
where
currentScenario = s ^. uiState . scenarioRef
currentSeed = s ^. gameState . seed
haltingMessage = case s ^. uiState . uiMenu of
NoMenu -> Just "Quit"
_ -> Nothing
descriptionWidth = 100
helpWidth = 80
(title, buttons, requiredWidth) =
case mt of
HelpModal -> (" Help ", Nothing, helpWidth)
RobotsModal -> ("Robots", Nothing, descriptionWidth)
RecipesModal -> ("Available Recipes", Nothing, descriptionWidth)
CommandsModal -> ("Available Commands", Nothing, descriptionWidth)
MessagesModal -> ("Messages", Nothing, descriptionWidth)
WinModal ->
let nextMsg = "Next challenge!"
stopMsg = fromMaybe "Return to the menu" haltingMessage
continueMsg = "Keep playing"
in ( ""
, Just
( 0
, [ (nextMsg, NextButton scene)
| Just scene <- [nextScenario (s ^. uiState . uiMenu)]
]
++ [ (stopMsg, QuitButton)
, (continueMsg, KeepPlayingButton)
]
)
, sum (map length [nextMsg, stopMsg, continueMsg]) + 32
)
DescriptionModal e -> (descriptionTitle e, Nothing, descriptionWidth)
QuitModal ->
let stopMsg = fromMaybe ("Quit to" ++ maybe "" (" " ++) (into @String <$> curMenuName s) ++ " menu") haltingMessage
maybeStartOver = sequenceA ("Start over", StartOverButton currentSeed <$> currentScenario)
in ( ""
, Just
( 0
, catMaybes
[ Just ("Keep playing", CancelButton)
, maybeStartOver
, Just (stopMsg, QuitButton)
]
)
, T.length (quitMsg (s ^. uiState . uiMenu)) + 4
)
GoalModal _ ->
let goalModalTitle = case currentScenario of
Nothing -> "Goal"
Just (scenario, _) -> scenario ^. scenarioName
in (" " <> T.unpack goalModalTitle <> " ", Nothing, 80)
KeepPlayingModal -> ("", Just (0, [("OK", CancelButton)]), 80)

-- | Render the type of the current REPL input to be shown to the user.
drawType :: Polytype -> Widget Name
drawType = withAttr infoAttr . padLeftRight 1 . txt . prettyText

descriptionTitle :: Entity -> String
descriptionTitle e = " " ++ from @Text (e ^. entityName) ++ " "

-- | Width cap for modal and error message windows
maxModalWindowWidth :: Int
maxModalWindowWidth = 500

-- | Get the name of the current New Game menu.
curMenuName :: AppState -> Maybe Text
curMenuName s = case s ^. uiState . uiMenu of
NewGameMenu (_ :| (parentMenu : _)) ->
Just (parentMenu ^. BL.listSelectedElementL . to scenarioItemName)
NewGameMenu _ -> Just "Scenarios"
_ -> Nothing

quitMsg :: Menu -> Text
quitMsg m = "Are you sure you want to " <> quitAction <> "? All progress on this scenario will be lost!"
where
quitAction = case m of
NoMenu -> "quit"
_ -> "return to the menu"
2 changes: 2 additions & 0 deletions swarm.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ library
Swarm.TUI.Panel
Swarm.TUI.Model
Swarm.TUI.View
Swarm.TUI.View.ViewUtils
Swarm.TUI.Controller
Swarm.TUI.Controller.ControllerUtils
Swarm.TUI.Inventory.Sorting
Swarm.App
Swarm.Version
Expand Down

0 comments on commit 9628667

Please sign in to comment.