19 Commits

Author SHA1 Message Date
a5679cb1fc version 0.17.0 2023-06-02 15:30:44 -04:00
bdbf7daf4e Merge pull request 'switch from ncurses to brick' (#1) from brick into dev
Reviewed-on: #1
2023-06-02 15:28:41 -04:00
e0efe2657f ynHandler should ignore keypresses with modifier keys 2023-06-02 15:26:22 -04:00
886cf0b243 even more stylistic changes
I hope to God I'm done with these now.
2023-06-01 19:51:04 -04:00
251dc90cea more stylistic changes 2023-06-01 19:06:46 -04:00
17b3f9a03e minor stylistic edits 2023-06-01 18:39:46 -04:00
01457dbe6f removed signature line 2023-06-01 17:18:09 -04:00
134787e1be removed Travis CI configuration file 2023-06-01 17:15:29 -04:00
284a8c6725 various layout fixes 2023-05-31 22:19:18 -04:00
d92722be9c use Editor istead of String 2023-05-31 20:08:49 -04:00
820aab5e96 fix layout of selection prompt 2023-05-31 13:21:49 -04:00
2d5c4e6471 fixed spacing on title screen 2023-05-30 19:01:30 -04:00
097d51f34b properly centre menu headings 2023-05-30 18:56:44 -04:00
166483dc50 fixed missing blank line between menu header and options 2023-05-30 18:45:03 -04:00
08e0f96a81 cursor position fix
cursor X and Y coordinates were transposed for the simple string prompts
2023-05-30 18:30:49 -04:00
afae5ea14a updated ChangeLog 2023-05-30 18:21:56 -04:00
ea9a9c6a85 bugfix: backspace
backspace functionality was mistakenly mapped to the escape key for some reason
2023-05-30 18:11:54 -04:00
d40b56da37 bail on CTRL-C 2023-05-30 18:06:32 -04:00
5ea2d77921 bugfix: make the whole background blue 2023-05-30 17:58:45 -04:00
20 changed files with 184 additions and 171 deletions

View File

@@ -1,40 +0,0 @@
# This is the simple Travis configuration, which is intended for use
# on applications which do not require cross-platform and
# multiple-GHC-version support. For more information and other
# options, see:
#
# https://docs.haskellstack.org/en/stable/travis_ci/
#
# Copy these contents into the root directory of your Github project in a file
# named .travis.yml
# Choose a build environment
dist: xenial
# Do not choose a language; we provide our own build tools.
language: generic
# Caching so the next build will be fast too.
cache:
directories:
- $HOME/.stack
# Ensure necessary system libraries are present
addons:
apt:
packages:
- libgmp-dev
before_install:
# Download and unpack the stack executable
- mkdir -p ~/.local/bin
- export PATH=$HOME/.local/bin:$PATH
- travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
install:
# Build dependencies
- stack --no-terminal --install-ghc test --only-dependencies
script:
# Build the package, its tests, and its docs and run the tests
- stack --no-terminal test --haddock --no-haddock-deps

View File

@@ -1,6 +1,9 @@
# Changelog for mtlstats # Changelog for mtlstats
## 0.16.1 ## current
- updated code to use brick instead of ncurses
## 0.17.0
- Don't automatically start a new game on new season - Don't automatically start a new game on new season
## 0.16.0 ## 0.16.0

View File

@@ -1,5 +1,5 @@
name: mtlstats name: mtlstats
version: 0.16.1 version: 0.17.0
license: GPL-3.0-or-later license: GPL-3.0-or-later
author: "Jonathan Lamothe" author: "Jonathan Lamothe"
maintainer: "jlamothe1980@gmail.com" maintainer: "jlamothe1980@gmail.com"
@@ -31,6 +31,7 @@ dependencies:
- microlens-th >= 0.4.2.3 && < 0.5 - microlens-th >= 0.4.2.3 && < 0.5
- mtl >= 2.2.2 && < 2.3 - mtl >= 2.2.2 && < 2.3
- random >= 1.2.1.1 && < 1.3 - random >= 1.2.1.1 && < 1.3
- text-zipper >= 0.12 && < 0.13
- time >= 1.11.1.1 && < 1.12 - time >= 1.11.1.1 && < 1.12
- vty >= 5.37 && < 5.38 - vty >= 5.37 && < 5.38

View File

@@ -22,11 +22,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats (app) where module Mtlstats (app) where
import Brick.AttrMap (AttrMap, forceAttrMap) import Brick.AttrMap (AttrMap, forceAttrMap)
import Brick.Main (App (..), showFirstCursor) import Brick.Main (App (..), halt, showFirstCursor)
import Brick.Types (BrickEvent (VtyEvent), Widget)
import Brick.Util (on) import Brick.Util (on)
import Brick.Widgets.Core (fill)
import Control.Monad.State.Class (gets)
import Graphics.Vty.Attributes.Color (blue, white) import Graphics.Vty.Attributes.Color (blue, white)
import Lens.Micro (to) import Graphics.Vty.Input.Events
import Lens.Micro.Mtl (use) ( Event (EvKey)
, Modifier (MCtrl)
, Key (KChar)
)
import Mtlstats.Control import Mtlstats.Control
import Mtlstats.Types import Mtlstats.Types
@@ -34,19 +40,24 @@ import Mtlstats.Types
-- | The main application -- | The main application
app :: App ProgState () () app :: App ProgState () ()
app = App app = App
{ appDraw = \s -> [drawController (dispatch s) s] { appDraw = draw
, appChooseCursor = showFirstCursor , appChooseCursor = showFirstCursor
, appHandleEvent = handler , appHandleEvent = handler
, appStartEvent = return () , appStartEvent = return ()
, appAttrMap = const myAttrMap , appAttrMap = const myAttrMap
} }
draw :: ProgState -> [Widget ()]
draw s =
[ drawController (dispatch s) s
, fill ' '
]
handler :: Handler () handler :: Handler ()
handler (VtyEvent (EvKey (KChar 'c') [MCtrl])) = halt
handler e = do handler e = do
c <- use (to dispatch) c <- gets dispatch
handleController c e handleController c e
myAttrMap :: AttrMap myAttrMap :: AttrMap
myAttrMap = forceAttrMap (white `on` blue) myAttrMap = forceAttrMap (white `on` blue)
--jl

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE LambdaCase, ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
module Mtlstats.Actions module Mtlstats.Actions
( startNewSeason ( startNewSeason
@@ -27,8 +27,6 @@ module Mtlstats.Actions
, clearRookies , clearRookies
, resetStandings , resetStandings
, startNewGame , startNewGame
, addChar
, removeChar
, createPlayer , createPlayer
, createGoalie , createGoalie
, edit , edit
@@ -41,16 +39,19 @@ module Mtlstats.Actions
, resetCreatePlayerState , resetCreatePlayerState
, resetCreateGoalieState , resetCreateGoalieState
, backHome , backHome
, clearEditor
, loadDatabase , loadDatabase
, saveDatabase , saveDatabase
) where ) where
import Brick.Main (viewportScroll) import Brick.Main (viewportScroll)
import Brick.Widgets.Edit (Editor, applyEdit)
import Control.Exception (IOException, catch) import Control.Exception (IOException, catch)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.State.Class (modify) import Control.Monad.State.Class (modify)
import Data.Aeson (decodeFileStrict, encodeFile) import Data.Aeson (decodeFileStrict, encodeFile)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text.Zipper (gotoBOF, killToEOF)
import Lens.Micro ((^.), (&), (.~), (%~)) import Lens.Micro ((^.), (&), (.~), (%~))
import Lens.Micro.Mtl ((.=), use) import Lens.Micro.Mtl ((.=), use)
import System.EasyFile import System.EasyFile
@@ -93,16 +94,6 @@ startNewGame
= (progMode .~ NewGame newGameState) = (progMode .~ NewGame newGameState)
. (database . dbGames %~ succ) . (database . dbGames %~ succ)
-- | Adds a character to the input buffer
addChar :: Char -> ProgState -> ProgState
addChar c = inputBuffer %~ (++[c])
-- | Removes a character from the input buffer (if possible)
removeChar :: ProgState -> ProgState
removeChar = inputBuffer %~ \case
"" -> ""
str -> init str
-- | Starts player creation mode -- | Starts player creation mode
createPlayer :: ProgState -> ProgState createPlayer :: ProgState -> ProgState
createPlayer = let createPlayer = let
@@ -206,9 +197,13 @@ resetCreateGoalieState = progMode.createGoalieStateL
-- | Resets the program state back to the main menu -- | Resets the program state back to the main menu
backHome :: ProgState -> ProgState backHome :: ProgState -> ProgState
backHome backHome
= (progMode .~ MainMenu) = (progMode .~ MainMenu)
. (inputBuffer .~ "") . (editorW %~ clearEditor)
. (scroller .~ viewportScroll ()) . (scroller .~ viewportScroll ())
-- | Clears an editor
clearEditor :: Editor String () -> Editor String ()
clearEditor = applyEdit $ killToEOF . gotoBOF
-- | Loads the database -- | Loads the database
loadDatabase :: Action () loadDatabase :: Action ()

View File

@@ -24,6 +24,7 @@ module Mtlstats.Control.CreateGoalie (createGoalieC) where
import Brick.Widgets.Core (str) import Brick.Widgets.Core (str)
import Control.Monad.State.Class (gets, modify) import Control.Monad.State.Class (gets, modify)
import Lens.Micro ((^.), (.~), (?~), (%~), to) import Lens.Micro ((^.), (.~), (?~), (%~), to)
import Lens.Micro.Mtl ((.=))
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Format import Mtlstats.Format
@@ -63,7 +64,7 @@ getActiveFlagC :: Controller
getActiveFlagC = Controller getActiveFlagC = Controller
{ drawController = const $ str "Is this goalie active? (Y/N)" { drawController = const $ str "Is this goalie active? (Y/N)"
, handleController = \e -> , handleController = \e ->
modify $ progMode.createGoalieStateL.cgsActiveFlag .~ ynHandler e progMode.createGoalieStateL.cgsActiveFlag .= ynHandler e
} }
confirmCreateGoalieC :: Controller confirmCreateGoalieC :: Controller

View File

@@ -24,6 +24,7 @@ module Mtlstats.Control.CreatePlayer (createPlayerC) where
import Brick.Widgets.Core (str) import Brick.Widgets.Core (str)
import Control.Monad.State.Class (gets, modify) import Control.Monad.State.Class (gets, modify)
import Lens.Micro ((^.), (.~), (?~), (%~), to) import Lens.Micro ((^.), (.~), (?~), (%~), to)
import Lens.Micro.Mtl ((.=), use)
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Format import Mtlstats.Format
@@ -66,7 +67,7 @@ getActiveFlagC :: Controller
getActiveFlagC = Controller getActiveFlagC = Controller
{ drawController = const $ str "Is the player active? (Y/N)" { drawController = const $ str "Is the player active? (Y/N)"
, handleController = \e -> , handleController = \e ->
modify $ progMode.createPlayerStateL.cpsActiveFlag .~ ynHandler e progMode.createPlayerStateL.cpsActiveFlag .= ynHandler e
} }
confirmCreatePlayerC :: Controller confirmCreatePlayerC :: Controller
@@ -84,7 +85,7 @@ confirmCreatePlayerC = Controller
, "Create player: are you sure? (Y/N)" , "Create player: are you sure? (Y/N)"
] ]
, handleController = \e -> do , handleController = \e -> do
cps <- gets (^.progMode.createPlayerStateL) cps <- use $ progMode.createPlayerStateL
let let
success = cps^.cpsSuccessCallback success = cps^.cpsSuccessCallback
failure = cps^.cpsFailureCallback failure = cps^.cpsFailureCallback

View File

@@ -25,9 +25,10 @@ module Mtlstats.Control.EditGoalie (editGoalieC) where
import Brick.Types (Widget) import Brick.Types (Widget)
import Brick.Widgets.Core (str, vBox) import Brick.Widgets.Core (str, vBox)
import Control.Monad.State.Class (gets, modify) import Control.Monad.State.Class (modify)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Lens.Micro ((^.), (.~), (%~)) import Lens.Micro ((^.))
import Lens.Micro.Mtl ((.=), (%=), use)
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Handlers import Mtlstats.Handlers
@@ -99,10 +100,10 @@ deleteC _ = Controller
in str $ hdr ++ "Are you sure you want to delete this goalie? (Y/N)" in str $ hdr ++ "Are you sure you want to delete this goalie? (Y/N)"
, handleController = \e -> case ynHandler e of , handleController = \e -> case ynHandler e of
Just True -> do Just True -> do
gets (^.progMode.editGoalieStateL.egsSelectedGoalie) >>= mapM_ use (progMode.editGoalieStateL.egsSelectedGoalie) >>= mapM_
(\gid -> modify $ database.dbGoalies %~ dropNth gid) (\gid -> database.dbGoalies %= dropNth gid)
modify edit modify edit
Just False -> modify $ progMode.editGoalieStateL.egsMode .~ EGMenu Just False -> progMode.editGoalieStateL.egsMode .= EGMenu
Nothing -> return () Nothing -> return ()
} }

View File

@@ -23,9 +23,10 @@ module Mtlstats.Control.EditPlayer (editPlayerC) where
import Brick.Types (Widget) import Brick.Types (Widget)
import Brick.Widgets.Core (emptyWidget, str, vBox) import Brick.Widgets.Core (emptyWidget, str, vBox)
import Control.Monad.State.Class (gets, modify) import Control.Monad.State.Class (modify)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Lens.Micro ((^.), (.~), (%~)) import Lens.Micro ((^.))
import Lens.Micro.Mtl ((.=), (%=), use)
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Handlers import Mtlstats.Handlers
@@ -90,10 +91,10 @@ deleteC _ = Controller
in str $ hdr ++ "Are you sure you want to delete this player? (Y/N)" in str $ hdr ++ "Are you sure you want to delete this player? (Y/N)"
, handleController = \e -> case ynHandler e of , handleController = \e -> case ynHandler e of
Just True -> do Just True -> do
gets (^.progMode.editPlayerStateL.epsSelectedPlayer) >>= mapM_ use (progMode.editPlayerStateL.epsSelectedPlayer) >>= mapM_
(\pid -> modify $ database.dbPlayers %~ dropNth pid) (\pid -> database.dbPlayers %= dropNth pid)
modify edit modify edit
Just False -> modify $ progMode.editPlayerStateL.epsMode .~ EPMenu Just False -> progMode.editPlayerStateL.epsMode .= EPMenu
Nothing -> return () Nothing -> return ()
} }

View File

@@ -24,7 +24,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.EditStandings (editStandingsC) where module Mtlstats.Control.EditStandings (editStandingsC) where
import Brick.Types (Widget) import Brick.Types (Widget)
import Brick.Widgets.Core (str, vBox) import Brick.Widgets.Core (vBox)
import Lens.Micro ((^.)) import Lens.Micro ((^.))
import Mtlstats.Format import Mtlstats.Format
@@ -34,6 +34,7 @@ import Mtlstats.Prompt
import Mtlstats.Prompt.EditStandings import Mtlstats.Prompt.EditStandings
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Types.Menu import Mtlstats.Types.Menu
import Mtlstats.Util
-- | Controller for the edit standings menu -- | Controller for the edit standings menu
editStandingsC :: EditStandingsMode -> Controller editStandingsC :: EditStandingsMode -> Controller
@@ -75,7 +76,10 @@ header s w = let
[ ( "HOME", valsFor home ) [ ( "HOME", valsFor home )
, ( "ROAD", valsFor away ) , ( "ROAD", valsFor away )
] ]
in vBox $ map str (table ++ [""]) ++ [w] in vBox
[ linesToWidget $ table ++ [""]
, w
]
valsFor :: GameStats -> [Int] valsFor :: GameStats -> [Int]
valsFor gs = valsFor gs =

View File

@@ -37,7 +37,7 @@ import Graphics.Vty.Input.Events
, Key (KDown, KHome, KEnter, KUp) , Key (KDown, KHome, KEnter, KUp)
) )
import Lens.Micro ((^.), (.~)) import Lens.Micro ((^.), (.~))
import Lens.Micro.Mtl (use) import Lens.Micro.Mtl ((.=), use)
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Actions.NewGame import Mtlstats.Actions.NewGame
@@ -96,14 +96,14 @@ overtimeFlagC = Controller
{ drawController = \s -> header s $ { drawController = \s -> header s $
str "Did the game go into overtime? (Y/N)" str "Did the game go into overtime? (Y/N)"
, handleController = \e -> , handleController = \e ->
modify $ progMode.gameStateL.overtimeFlag .~ ynHandler e progMode.gameStateL.overtimeFlag .= ynHandler e
} }
verifyDataC :: Controller verifyDataC :: Controller
verifyDataC = Controller verifyDataC = Controller
{ drawController = \s -> let { drawController = \s -> let
gs = s^.progMode.gameStateL gs = s^.progMode.gameStateL
in header s $ vBox $ map str $ in header s $ linesToWidget $
[""] ++ [""] ++
labelTable labelTable
[ ( "Date", gameDate gs ) [ ( "Date", gameDate gs )
@@ -172,7 +172,7 @@ confirmGoalDataC = Controller
[ "" [ ""
, "Is the above information correct? (Y/N)" , "Is the above information correct? (Y/N)"
] ]
in vBox $ map str msg in linesToWidget msg
, handleController = \e -> do , handleController = \e -> do
case ynHandler e of case ynHandler e of
Just True -> modify recordGoalAssists Just True -> modify recordGoalAssists
@@ -201,7 +201,7 @@ getPMinsC = Controller
reportC :: Controller reportC :: Controller
reportC = Controller reportC = Controller
{ drawController = viewport () Vertical . hCenter . vBox . map str . { drawController = viewport () Vertical . hCenter . linesToWidget .
displayReport reportCols displayReport reportCols
, handleController = \e -> do , handleController = \e -> do
scr <- use scroller scr <- use scroller
@@ -239,8 +239,11 @@ monthHeader s w = let
, "NOVEMBER" , "NOVEMBER"
, "DECEMBER" , "DECEMBER"
] ]
in header s $ vBox $ map (hCenter . str) in header s $ vBox
(["MONTH:", ""] ++ table ++ [""]) ++ [w] [ linesToWidgetC $
["MONTH:", ""] ++ table ++ [""]
, w
]
gameGoal :: ProgState -> (Int, Int) gameGoal :: ProgState -> (Int, Int)
gameGoal s = gameGoal s =

View File

@@ -24,18 +24,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.TitleScreen (titleScreenC) where module Mtlstats.Control.TitleScreen (titleScreenC) where
import Brick.Types (BrickEvent (VtyEvent)) import Brick.Types (BrickEvent (VtyEvent))
import Brick.Widgets.Center (hCenter)
import Brick.Widgets.Core (str, vBox)
import Control.Monad.State.Class (modify) import Control.Monad.State.Class (modify)
import Data.Char (chr) import Data.Char (chr)
import Graphics.Vty.Input.Events (Event (EvKey)) import Graphics.Vty.Input.Events (Event (EvKey))
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Util
titleScreenC :: Controller titleScreenC :: Controller
titleScreenC = Controller titleScreenC = Controller
{ drawController = const $ vBox $ map (hCenter . str) { drawController = const $ linesToWidgetC
$ [ "" $ [ ""
, "MONTREAL CANADIENS STATISTICS" , "MONTREAL CANADIENS STATISTICS"
] ]
@@ -48,7 +47,7 @@ titleScreenC = Controller
] ]
, handleController = \case , handleController = \case
VtyEvent (EvKey _ _) -> modify backHome VtyEvent (EvKey _ _) -> modify backHome
_ -> return () _ -> return ()
} }
titleText :: [String] titleText :: [String]

View File

@@ -27,7 +27,7 @@ import Graphics.Vty.Input.Events (Event (EvKey), Key (KChar))
-- | Handler for a yes/no prompt -- | Handler for a yes/no prompt
ynHandler :: BrickEvent () () -> Maybe Bool ynHandler :: BrickEvent () () -> Maybe Bool
ynHandler (VtyEvent (EvKey (KChar c) _)) = case toUpper c of ynHandler (VtyEvent (EvKey (KChar c) [])) = case toUpper c of
'Y' -> Just True 'Y' -> Just True
'N' -> Just False 'N' -> Just False
_ -> Nothing _ -> Nothing

View File

@@ -36,8 +36,6 @@ module Mtlstats.Menu (
import Brick.Main (halt) import Brick.Main (halt)
import Brick.Types (BrickEvent (VtyEvent), Widget) import Brick.Types (BrickEvent (VtyEvent), Widget)
import Brick.Widgets.Center (hCenter)
import Brick.Widgets.Core (str, vBox)
import Control.Monad.State.Class (gets, modify) import Control.Monad.State.Class (gets, modify)
import Data.Char (toUpper) import Data.Char (toUpper)
import qualified Data.Map as M import qualified Data.Map as M
@@ -87,7 +85,7 @@ menuStateController menuFunc = Controller
drawMenu :: Menu a -> Widget () drawMenu :: Menu a -> Widget ()
drawMenu m = let drawMenu m = let
menuLines = lines $ show m menuLines = lines $ show m
in hCenter $ vBox $ map str menuLines in linesToWidgetC menuLines
-- | The event handler for a 'Menu' -- | The event handler for a 'Menu'
menuHandler :: Menu a -> Handler a menuHandler :: Menu a -> Handler a

View File

@@ -48,19 +48,20 @@ module Mtlstats.Prompt (
playerToEditPrompt playerToEditPrompt
) where ) where
import Brick.Types (BrickEvent (VtyEvent), Location (Location), Widget) import Brick.Types (BrickEvent (VtyEvent), Widget)
import Brick.Widgets.Core (hBox, showCursor, str) import Brick.Widgets.Core (hBox, str, vBox)
import Brick.Widgets.Edit (editContentsL, renderEditor)
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.Extra (whenJust) import Control.Monad.Extra (whenJust)
import Control.Monad.State.Class (gets, modify) import Control.Monad.State.Class (gets, modify)
import Data.Char (isAlphaNum, isDigit, toUpper) import Data.Char (isAlphaNum, isDigit, toUpper)
import Graphics.Text.Width (safeWcswidth) import Data.Text.Zipper (deletePrevChar, insertChar)
import Graphics.Vty.Input.Events import Graphics.Vty.Input.Events
( Event (EvKey) ( Event (EvKey)
, Key (KChar, KEnter, KEsc, KFun) , Key (KBS, KChar, KEnter, KFun)
) )
import Lens.Micro ((^.), (&), (.~), (?~), (%~)) import Lens.Micro ((^.), (&), (.~), (?~), (%~), to)
import Lens.Micro.Mtl ((.=), use) import Lens.Micro.Mtl ((%=), use)
import Text.Read (readMaybe) import Text.Read (readMaybe)
import Mtlstats.Actions import Mtlstats.Actions
@@ -72,13 +73,13 @@ import Mtlstats.Util
-- | Event handler for a prompt -- | Event handler for a prompt
promptHandler :: Prompt -> Handler () promptHandler :: Prompt -> Handler ()
promptHandler p (VtyEvent (EvKey KEnter [])) = do promptHandler p (VtyEvent (EvKey KEnter [])) = do
val <- use inputBuffer val <- use $ editorW.to userText
inputBuffer .= "" editorW %= clearEditor
promptAction p val promptAction p val
promptHandler p (VtyEvent (EvKey (KChar c) [])) = promptHandler p (VtyEvent (EvKey (KChar c) [])) =
modify $ inputBuffer %~ promptProcessChar p c editorW %= promptProcessChar p c
promptHandler _ (VtyEvent (EvKey KEsc [])) = promptHandler _ (VtyEvent (EvKey KBS [])) =
modify removeChar editorW.editContentsL %= deletePrevChar
promptHandler p (VtyEvent (EvKey k m)) = promptHandler p (VtyEvent (EvKey k m)) =
promptSpecialKey p k m promptSpecialKey p k m
promptHandler _ _ = return () promptHandler _ _ = return ()
@@ -113,7 +114,7 @@ strPrompt
-> Prompt -> Prompt
strPrompt pStr act = Prompt strPrompt pStr act = Prompt
{ drawPrompt = drawSimplePrompt pStr { drawPrompt = drawSimplePrompt pStr
, promptProcessChar = \ch -> (++ [ch]) , promptProcessChar = \ch -> editContentsL %~ insertChar ch
, promptAction = act , promptAction = act
, promptSpecialKey = \_ _ -> return () , promptSpecialKey = \_ _ -> return ()
} }
@@ -126,7 +127,7 @@ ucStrPrompt
-- ^ The callback function for the result -- ^ The callback function for the result
-> Prompt -> Prompt
ucStrPrompt pStr act = (strPrompt pStr act) ucStrPrompt pStr act = (strPrompt pStr act)
{ promptProcessChar = \ch -> (++ [toUpper ch]) } { promptProcessChar = \ch -> editContentsL %~ insertChar ch }
-- | Creates a prompt which forces capitalization of input to -- | Creates a prompt which forces capitalization of input to
-- accomodate a player or goalie name -- accomodate a player or goalie name
@@ -174,7 +175,7 @@ numPromptWithFallback
numPromptWithFallback pStr fallback act = Prompt numPromptWithFallback pStr fallback act = Prompt
{ drawPrompt = drawSimplePrompt pStr { drawPrompt = drawSimplePrompt pStr
, promptProcessChar = \ch existing -> if isDigit ch , promptProcessChar = \ch existing -> if isDigit ch
then existing ++ [ch] then existing & editContentsL %~ insertChar ch
else existing else existing
, promptAction = maybe fallback act . readMaybe , promptAction = maybe fallback act . readMaybe
, promptSpecialKey = \_ _ -> return () , promptSpecialKey = \_ _ -> return ()
@@ -189,7 +190,7 @@ dbNamePrompt
-> Prompt -> Prompt
dbNamePrompt pStr act = (strPrompt pStr act) dbNamePrompt pStr act = (strPrompt pStr act)
{ promptProcessChar = \ch -> if isAlphaNum ch || ch == '-' { promptProcessChar = \ch -> if isAlphaNum ch || ch == '-'
then (++[toUpper ch]) then editContentsL %~ insertChar (toUpper ch)
else id else id
} }
@@ -209,18 +210,20 @@ newSeasonPrompt = dbNamePrompt "Filename for new season: " $ \fn ->
selectPrompt :: SelectParams a -> Prompt selectPrompt :: SelectParams a -> Prompt
selectPrompt params = Prompt selectPrompt params = Prompt
{ drawPrompt = \s -> let { drawPrompt = \s -> let
sStr = s^.inputBuffer sStr = s^.editorW.to userText
pStr = spPrompt params ++ sStr pStr = spPrompt params
pWidth = safeWcswidth pStr
results = zip [1..maxFunKeys] $ spSearch params sStr (s^.database) results = zip [1..maxFunKeys] $ spSearch params sStr (s^.database)
fmtRes = map fmtRes = map
(\(n, (_, x)) -> let (\(n, (_, x)) -> let
desc = spElemDesc params x desc = spElemDesc params x
in str $ "F" ++ show n ++ ") " ++ desc) in str $ "F" ++ show n ++ ") " ++ desc)
results results
in hBox $ in vBox $
[ showCursor () (Location (0, pWidth)) $ str pStr [ hBox
, str "" [ str pStr
, renderEditor linesToWidget True (s^.editorW)
]
, str " "
, str $ spSearchHeader params , str $ spSearchHeader params
] ++ fmtRes ] ++ fmtRes
, promptProcessChar = spProcessChar params , promptProcessChar = spProcessChar params
@@ -233,14 +236,14 @@ selectPrompt params = Prompt
Just n -> spCallback params $ Just n Just n -> spCallback params $ Just n
, promptSpecialKey = \key _ -> case key of , promptSpecialKey = \key _ -> case key of
KFun rawK -> do KFun rawK -> do
sStr <- use inputBuffer sStr <- use $ editorW . to userText
db <- use database db <- use database
let let
n = pred rawK n = pred rawK
results = spSearch params sStr db results = spSearch params sStr db
when (n < maxFunKeys) $ when (n < maxFunKeys) $
whenJust (nth n results) $ \(sel, _) -> do whenJust (nth n results) $ \(sel, _) -> do
modify $ inputBuffer .~ "" editorW %= clearEditor
spCallback params $ Just sel spCallback params $ Just sel
_ -> return () _ -> return ()
} }
@@ -393,7 +396,7 @@ selectPositionPrompt pStr callback = selectPrompt SelectParams
, spSearch = posSearch , spSearch = posSearch
, spSearchExact = posSearchExact , spSearchExact = posSearchExact
, spElemDesc = id , spElemDesc = id
, spProcessChar = \ch -> (++ [toUpper ch]) , spProcessChar = \c -> editContentsL %~ insertChar (toUpper c)
, spCallback = posCallback callback , spCallback = posCallback callback
, spNotFound = callback , spNotFound = callback
} }
@@ -403,7 +406,7 @@ playerToEditPrompt = selectPlayerPrompt "Player to edit: " $
modify . (progMode.editPlayerStateL.epsSelectedPlayer .~) modify . (progMode.editPlayerStateL.epsSelectedPlayer .~)
drawSimplePrompt :: String -> Renderer drawSimplePrompt :: String -> Renderer
drawSimplePrompt pStr s = let drawSimplePrompt pStr s = hBox
fullStr = pStr ++ s^.inputBuffer [ str pStr
strWidth = safeWcswidth fullStr , renderEditor linesToWidget True (s^.editorW)
in showCursor () (Location (0, strWidth)) $ str fullStr ]

View File

@@ -53,7 +53,7 @@ module Mtlstats.Types (
database, database,
progMode, progMode,
dbName, dbName,
inputBuffer, editorW,
scroller, scroller,
-- ** ProgMode Lenses -- ** ProgMode Lenses
gameStateL, gameStateL,
@@ -199,6 +199,7 @@ module Mtlstats.Types (
import Brick.Main (ViewportScroll, viewportScroll) import Brick.Main (ViewportScroll, viewportScroll)
import Brick.Types (BrickEvent, EventM, Widget) import Brick.Types (BrickEvent, EventM, Widget)
import Brick.Widgets.Edit (Editor, editor)
import Data.Aeson import Data.Aeson
( FromJSON ( FromJSON
, ToJSON , ToJSON
@@ -241,15 +242,15 @@ type Handler a = BrickEvent () () -> Action a
-- | Represents the program state -- | Represents the program state
data ProgState = ProgState data ProgState = ProgState
{ _database :: Database { _database :: Database
-- ^ The data to be saved -- ^ The data to be saved
, _progMode :: ProgMode , _progMode :: ProgMode
-- ^ The program's mode -- ^ The program's mode
, _dbName :: String , _dbName :: String
-- ^ The name of the database file -- ^ The name of the database file
, _inputBuffer :: String , _editorW :: Editor String ()
-- ^ Buffer for user input -- ^ Editor widget
, _scroller :: ViewportScroll () , _scroller :: ViewportScroll ()
-- ^ Scroller for the reports -- ^ Scroller for the reports
} }
@@ -543,8 +544,8 @@ data GameStats = GameStats
data Prompt = Prompt data Prompt = Prompt
{ drawPrompt :: ProgState -> Widget () { drawPrompt :: ProgState -> Widget ()
-- ^ Draws the prompt to the screen -- ^ Draws the prompt to the screen
, promptProcessChar :: Char -> String -> String , promptProcessChar :: Char -> Editor String () -> Editor String ()
-- ^ Modifies the string based on the character entered -- ^ Modifies an editor based on the character entered
, promptAction :: String -> Action () , promptAction :: String -> Action ()
-- ^ Action to perform when the value is entered -- ^ Action to perform when the value is entered
, promptSpecialKey :: Key -> [Modifier] -> Action () , promptSpecialKey :: Key -> [Modifier] -> Action ()
@@ -563,7 +564,7 @@ data SelectParams a = SelectParams
-- ^ Search function looking for an exact match -- ^ Search function looking for an exact match
, spElemDesc :: a -> String , spElemDesc :: a -> String
-- ^ Provides a string description of an element -- ^ Provides a string description of an element
, spProcessChar :: Char -> String -> String , spProcessChar :: Char -> Editor String () -> Editor String ()
-- ^ Processes a character entered by the user -- ^ Processes a character entered by the user
, spCallback :: Maybe Int -> Action () , spCallback :: Maybe Int -> Action ()
-- ^ The function when the selection is made -- ^ The function when the selection is made
@@ -795,11 +796,11 @@ esmSubModeL = lens
-- | Constructor for a 'ProgState' -- | Constructor for a 'ProgState'
newProgState :: ProgState newProgState :: ProgState
newProgState = ProgState newProgState = ProgState
{ _database = newDatabase { _database = newDatabase
, _progMode = TitleScreen , _progMode = TitleScreen
, _dbName = "" , _dbName = ""
, _inputBuffer = "" , _editorW = editor () (Just 1) ""
, _scroller = viewportScroll () , _scroller = viewportScroll ()
} }
-- | Constructor for a 'GameState' -- | Constructor for a 'GameState'

View File

@@ -27,12 +27,18 @@ module Mtlstats.Util
, slice , slice
, capitalizeName , capitalizeName
, linesToWidget , linesToWidget
, linesToWidgetC
, userText
) where ) where
import Brick.Types (Widget) import Brick.Types (Widget)
import Brick.Widgets.Center (hCenter)
import Brick.Widgets.Core (str, vBox) import Brick.Widgets.Core (str, vBox)
import Brick.Widgets.Edit (Editor, editContentsL, getEditContents)
import Data.Char (isSpace, toUpper) import Data.Char (isSpace, toUpper)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Text.Zipper (insertChar)
import Lens.Micro ((^.), (&), (%~), to)
-- | Attempt to select the element from a list at a given index -- | Attempt to select the element from a list at a given index
nth nth
@@ -104,12 +110,13 @@ slice offset len = take len . drop offset
capitalizeName capitalizeName
:: Char :: Char
-- ^ The character being input -- ^ The character being input
-> String -> Editor String ()
-- ^ The current string -- ^ The current string
-> String -> Editor String ()
-- ^ The resulting string -- ^ The resulting string
capitalizeName ch s = s ++ [ch'] capitalizeName ch e = e & editContentsL %~ insertChar ch'
where where
s = e^.to userText
ch' = if lockFlag s ch' = if lockFlag s
then toUpper ch then toUpper ch
else ch else ch
@@ -122,5 +129,21 @@ capitalizeName ch s = s ++ [ch']
| isSpace c = lockFlag' cs | isSpace c = lockFlag' cs
| otherwise = False | otherwise = False
-- | Converts a list of lines to a widget
linesToWidget :: [String] -> Widget () linesToWidget :: [String] -> Widget ()
linesToWidget = vBox . map str linesToWidget = vBox . map (str . keepBlank)
-- | Converts a list of lines to a widget with each line horizontally
-- centered
linesToWidgetC :: [String] -> Widget ()
linesToWidgetC = vBox . map (hCenter . str . keepBlank)
-- | Fetches the text from an editor widget
userText :: Editor String () -> String
userText w = case getEditContents w of
(x:_) -> x
[] -> ""
keepBlank :: String -> String
keepBlank "" = " "
keepBlank s = s

View File

@@ -24,7 +24,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module ActionsSpec (spec) where module ActionsSpec (spec) where
import Control.Monad (replicateM) import Control.Monad (replicateM)
import Lens.Micro ((^.), (&), (.~), (?~), (%~)) import Lens.Micro ((^.), (&), (.~), (?~), (%~), to)
import Test.Hspec import Test.Hspec
( Spec ( Spec
, context , context
@@ -37,9 +37,11 @@ import Test.Hspec
import Mtlstats.Actions import Mtlstats.Actions
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Util
import qualified Actions.NewGameSpec as NewGame import qualified Actions.NewGameSpec as NewGame
import qualified Actions.EditStandingsSpec as EditStandings import qualified Actions.EditStandingsSpec as EditStandings
import SpecHelpers
import qualified TypesSpec as TS import qualified TypesSpec as TS
spec :: Spec spec :: Spec
@@ -49,8 +51,6 @@ spec = describe "Mtlstats.Actions" $ do
resetYtdSpec resetYtdSpec
clearRookiesSpec clearRookiesSpec
resetStandingsSpec resetStandingsSpec
addCharSpec
removeCharSpec
createPlayerSpec createPlayerSpec
createGoalieSpec createGoalieSpec
editSpec editSpec
@@ -204,29 +204,6 @@ resetStandingsSpec = describe "resetStandings" $ do
it "should be reset" $ it "should be reset" $
ps^.database.dbAwayGameStats `shouldBe` newGameStats ps^.database.dbAwayGameStats `shouldBe` newGameStats
addCharSpec :: Spec
addCharSpec = describe "addChar" $
it "should add the character to the input buffer" $ let
s = newProgState
& inputBuffer .~ "foo"
& addChar 'd'
in s ^. inputBuffer `shouldBe` "food"
removeCharSpec :: Spec
removeCharSpec = describe "removeChar" $ do
context "empty" $
it "should remove the character from the input buffer" $ let
s = removeChar newProgState
in s ^. inputBuffer `shouldBe` ""
context "not empty" $
it "should remove the character from the input buffer" $ let
s = newProgState
& inputBuffer .~ "foo"
& removeChar
in s ^. inputBuffer `shouldBe` "fo"
createPlayerSpec :: Spec createPlayerSpec :: Spec
createPlayerSpec = describe "createPlayer" $ createPlayerSpec = describe "createPlayer" $
it "should change the mode appropriately" $ let it "should change the mode appropriately" $ let
@@ -422,7 +399,7 @@ backHomeSpec = describe "backHome" $ do
let let
input = newProgState input = newProgState
& progMode.gameStateL .~ newGameState & progMode.gameStateL .~ newGameState
& inputBuffer .~ "foo" & editorW .~ mkEditor "foo"
result = backHome input result = backHome input
it "should set the program mode back to MainMenu" $ it "should set the program mode back to MainMenu" $
@@ -431,4 +408,4 @@ backHomeSpec = describe "backHome" $ do
_ -> False _ -> False
it "should clear the input buffer" $ it "should clear the input buffer" $
result^.inputBuffer `shouldBe` "" result^.editorW.to userText `shouldBe` ""

29
test/SpecHelpers.hs Normal file
View File

@@ -0,0 +1,29 @@
{-
mtlstats
Copyright (C) Rhéal Lamothe
<rheal.lamothe@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
module SpecHelpers where
import Brick.Widgets.Edit (Editor, editContentsL, editor)
import Data.Text.Zipper (gotoEOL)
import Lens.Micro ((&), (%~))
mkEditor :: String -> Editor String ()
mkEditor str = editor () (Just 1) str & editContentsL %~ gotoEOL

View File

@@ -26,6 +26,8 @@ import Test.Hspec (Spec, context, describe, it, shouldBe)
import Mtlstats.Util import Mtlstats.Util
import SpecHelpers
spec :: Spec spec :: Spec
spec = describe "Mtlstats.Util" $ do spec = describe "Mtlstats.Util" $ do
nthSpec nthSpec
@@ -114,7 +116,7 @@ capitalizeNameSpec :: Spec
capitalizeNameSpec = describe "capitalizeName" $ mapM_ capitalizeNameSpec = describe "capitalizeName" $ mapM_
(\(label, ch, str, expected) -> context label $ (\(label, ch, str, expected) -> context label $
it ("should be " ++ expected) $ it ("should be " ++ expected) $
capitalizeName ch str `shouldBe` expected) userText (capitalizeName ch $ mkEditor str) `shouldBe` expected)
-- label, character, string, expected -- label, character, string, expected
[ ( "initial lower", 'a', "", "A" ) [ ( "initial lower", 'a', "", "A" )
, ( "initial upper", 'A', "", "A" ) , ( "initial upper", 'A', "", "A" )