Skip to content

Commit

Permalink
Inventory search/filter mode (#1250)
Browse files Browse the repository at this point in the history
When the inventory panel is focused, hit `/` to enter search mode (maybe "search" is a bit of a misnomer, it's really more of a "filter" mode).  Then type stuff to modify the current search term, shown at the bottom of the inventory panel.  You can still navigate the filtered inventory list with arrow keys etc. Hit Esc to exit search mode or Enter to pop out the focused item (and also exit search mode).

Closes #126.
  • Loading branch information
byorgey committed May 12, 2023
1 parent 305867a commit 07fc47e
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 27 deletions.
81 changes: 61 additions & 20 deletions src/Swarm/TUI/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1312,29 +1312,70 @@ adjustTPS (+/-) = uiState . lgTicksPerSecond %~ (+/- 1)

-- | Handle user input events in the robot panel.
handleRobotPanelEvent :: BrickEvent Name AppEvent -> EventM Name AppState ()
handleRobotPanelEvent = \case
(Key V.KEnter) ->
gets focusedEntity >>= maybe continueWithoutRedraw descriptionModal
(CharKey 'm') ->
gets focusedEntity >>= maybe continueWithoutRedraw makeEntity
(CharKey '0') -> do
handleRobotPanelEvent bev = do
search <- use (uiState . uiInventorySearch)
case search of
Just _ -> handleInventorySearchEvent bev
Nothing -> case bev of
Key V.KEnter ->
gets focusedEntity >>= maybe continueWithoutRedraw descriptionModal
CharKey 'm' ->
gets focusedEntity >>= maybe continueWithoutRedraw makeEntity
CharKey '0' -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiShowZero %= not
CharKey ';' -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiInventorySort %= cycleSortOrder
CharKey ':' -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiInventorySort %= cycleSortDirection
CharKey '/' -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiInventorySearch .= Just ""
VtyEvent ev -> handleInventoryListEvent ev
_ -> continueWithoutRedraw

-- | Handle an event to navigate through the inventory list.
handleInventoryListEvent :: V.Event -> EventM Name AppState ()
handleInventoryListEvent ev = do
-- Note, refactoring like this is tempting:
--
-- Brick.zoom (uiState . uiInventory . _Just . _2) (handleListEventWithSeparators ev (is _Separator))
--
-- However, this does not work since we want to skip redrawing in the no-list case!

mList <- preuse $ uiState . uiInventory . _Just . _2
case mList of
Nothing -> continueWithoutRedraw
Just l -> do
l' <- nestEventM' l (handleListEventWithSeparators ev (is _Separator))
uiState . uiInventory . _Just . _2 .= l'

-- | Handle a user input event in the robot/inventory panel, while in
-- inventory search mode.
handleInventorySearchEvent :: BrickEvent Name AppEvent -> EventM Name AppState ()
handleInventorySearchEvent = \case
-- Escape: stop filtering and go back to regular inventory mode
EscapeKey -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiShowZero %= not
(CharKey ';') -> do
uiState . uiInventorySearch .= Nothing
-- Enter: return to regular inventory mode, and pop out the selected item
Key V.KEnter -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiInventorySearch .= Nothing
gets focusedEntity >>= maybe continueWithoutRedraw descriptionModal
-- Any old character: append to the current search string
CharKey c -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiInventorySort %= cycleSortOrder
(CharKey ':') -> do
uiState . uiInventorySearch %= fmap (`snoc` c)
-- Backspace: chop the last character off the end of the current search string
BackspaceKey -> do
uiState . uiInventoryShouldUpdate .= True
uiState . uiInventorySort %= cycleSortDirection
(VtyEvent ev) -> do
-- This does not work we want to skip redrawing in the no-list case
-- Brick.zoom (uiState . uiInventory . _Just . _2) (handleListEventWithSeparators ev (is _Separator))
mList <- preuse $ uiState . uiInventory . _Just . _2
case mList of
Nothing -> continueWithoutRedraw
Just l -> do
l' <- nestEventM' l (handleListEventWithSeparators ev (is _Separator))
uiState . uiInventory . _Just . _2 .= l'
uiState . uiInventorySearch %= fmap (T.dropEnd 1)
-- Handle any other event as list navigation, so we can look through
-- the filtered inventory using e.g. arrow keys
VtyEvent ev -> handleInventoryListEvent ev
_ -> continueWithoutRedraw

-- | Attempt to make an entity selected from the inventory, if the
Expand Down
3 changes: 3 additions & 0 deletions src/Swarm/TUI/Controller/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pattern ShiftKey k = VtyEvent (V.EvKey k [V.MShift])
pattern EscapeKey :: BrickEvent n e
pattern EscapeKey = VtyEvent (V.EvKey V.KEsc [])

pattern BackspaceKey :: BrickEvent n e
pattern BackspaceKey = VtyEvent (V.EvKey V.KBS [])

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

Expand Down
7 changes: 6 additions & 1 deletion src/Swarm/TUI/Model.hs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ import Swarm.TUI.Model.Name
import Swarm.TUI.Model.Repl
import Swarm.TUI.Model.UI
import Swarm.Version (NewReleaseFailure (NoMainUpstreamRelease))
import Text.Fuzzy qualified as Fuzzy

------------------------------------------------------------
-- Custom UI label types
Expand Down Expand Up @@ -264,13 +265,14 @@ populateInventoryList (Just r) = do
mList <- preuse (uiInventory . _Just . _2)
showZero <- use uiShowZero
sortOptions <- use uiInventorySort
search <- use uiInventorySearch
let mkInvEntry (n, e) = InventoryEntry n e
mkInstEntry (_, e) = EquippedEntry e
itemList isInventoryDisplay mk label =
(\case [] -> []; xs -> Separator label : xs)
. map mk
. sortInventory sortOptions
. filter shouldDisplay
. filter ((&&) <$> matchesSearch <*> shouldDisplay)
. elems
where
-- Display items if we have a positive number of them, or they
Expand All @@ -283,6 +285,9 @@ populateInventoryList (Just r) = do
&& showZero
&& not ((r ^. equippedDevices) `E.contains` e)

matchesSearch :: (Count, Entity) -> Bool
matchesSearch (_, e) = maybe (const True) Fuzzy.test search (e ^. E.entityName)

items =
(r ^. robotInventory . to (itemList True mkInvEntry "Inventory"))
++ (r ^. equippedDevices . to (itemList False mkInstEntry "Equipped devices"))
Expand Down
6 changes: 6 additions & 0 deletions src/Swarm/TUI/Model/UI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module Swarm.TUI.Model.UI (
uiREPL,
uiInventory,
uiInventorySort,
uiInventorySearch,
uiMoreInfoTop,
uiMoreInfoBot,
uiScrollToEnd,
Expand Down Expand Up @@ -94,6 +95,7 @@ data UIState = UIState
, _uiREPL :: REPLState
, _uiInventory :: Maybe (Int, BL.List Name InventoryListEntry)
, _uiInventorySort :: InventorySortOptions
, _uiInventorySearch :: Maybe Text
, _uiMoreInfoTop :: Bool
, _uiMoreInfoBot :: Bool
, _uiScrollToEnd :: Bool
Expand Down Expand Up @@ -156,6 +158,9 @@ uiREPL :: Lens' UIState REPLState
-- | The order and direction of sorting inventory list.
uiInventorySort :: Lens' UIState InventorySortOptions

-- | The current search string used to narrow the inventory view.
uiInventorySearch :: Lens' UIState (Maybe Text)

-- | The hash value of the focused robot entity (so we can tell if its
-- inventory changed) along with a list of the items in the
-- focused robot's inventory.
Expand Down Expand Up @@ -296,6 +301,7 @@ initUIState showMainMenu cheatMode = do
, _uiREPL = initREPLState $ newREPLHistory history
, _uiInventory = Nothing
, _uiInventorySort = defaultSortOptions
, _uiInventorySearch = Nothing
, _uiMoreInfoTop = False
, _uiMoreInfoBot = False
, _uiScrollToEnd = False
Expand Down
28 changes: 22 additions & 6 deletions src/Swarm/TUI/View.hs
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,18 @@ drawGameUI s =
hBox
[ hLimitPercent 25 $
vBox
[ vLimitPercent 50 $ panel highlightAttr fr (FocusablePanel RobotPanel) plainBorder $ drawRobotPanel s
[ vLimitPercent 50
$ panel
highlightAttr
fr
(FocusablePanel RobotPanel)
( plainBorder
& bottomLabels . centerLabel
.~ fmap
(txt . (" Search: " <>) . (<> " "))
(s ^. uiState . uiInventorySearch)
)
$ drawRobotPanel s
, panel
highlightAttr
fr
Expand Down Expand Up @@ -846,6 +857,7 @@ drawKeyMenu s =
goal = hasAnythingToShow $ s ^. uiState . uiGoal . goalsContent
showZero = s ^. uiState . uiShowZero
inventorySort = s ^. uiState . uiInventorySort
inventorySearch = s ^. uiState . uiInventorySearch
ctrlMode = s ^. uiState . uiREPL . replControlMode
canScroll = creative || (s ^. gameState . worldScrollable)
handlerInstalled = isJust (s ^. gameState . inputHandler)
Expand Down Expand Up @@ -897,11 +909,15 @@ drawKeyMenu s =
++ [("c", "recenter") | not viewingBase]
++ [("f", "FPS")]
keyCmdsFor (Just (FocusablePanel RobotPanel)) =
[ ("Enter", "pop out")
, ("m", "make")
, ("0", (if showZero then "hide" else "show") <> " 0")
, (":/;", T.unwords ["Sort:", renderSortMethod inventorySort])
]
("Enter", "pop out")
: if isJust inventorySearch
then [("Esc", "exit search")]
else
[ ("m", "make")
, ("0", (if showZero then "hide" else "show") <> " 0")
, (":/;", T.unwords ["Sort:", renderSortMethod inventorySort])
, ("/", "search")
]
keyCmdsFor (Just (FocusablePanel InfoPanel)) = []
keyCmdsFor _ = []

Expand Down
1 change: 1 addition & 0 deletions swarm.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ library
filepath >= 1.4 && < 1.5,
fused-effects >= 1.1.1.1 && < 1.2,
fused-effects-lens >= 1.2.0.1 && < 1.3,
fuzzy >= 0.1 && < 0.2,
githash >= 0.1.6 && < 0.2,
hashable >= 1.3.4 && < 1.5,
hsnoise >= 0.0.3 && < 0.1,
Expand Down

0 comments on commit 07fc47e

Please sign in to comment.