Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support tui list item selection via mouse click #1158

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib-tui/GHCup/Brick/App.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,19 @@ app :: AttrMap -> AttrMap -> App BrickState () Name
app attrs dimAttrs =
App { appDraw = drawUI dimAttrs
, appHandleEvent = eventHandler
, appStartEvent = return ()
, appStartEvent = setupVtyMode
, appAttrMap = const attrs
, appChooseCursor = Brick.showFirstCursor
}

-- | Enable mouse mode if supported by the terminal
setupVtyMode :: EventM Name BrickState ()
setupVtyMode = do
vty <- Brick.getVtyHandle
let output = Vty.outputIface vty
when (Vty.supportsMode output Vty.Mouse) $
liftIO $ Vty.setMode output Vty.Mouse True

drawUI :: AttrMap -> BrickState -> [Widget Name]
drawUI dimAttrs st =
let
Expand Down
1 change: 1 addition & 0 deletions lib-tui/GHCup/Brick/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ pattern HadrianGhcSelectBox = ResourceId 22
-- to have all of them defined, just in case
data Name = AllTools -- ^ The main list widget
| Singular Tool -- ^ The particular list for each tool
| ListItem Tool Int -- ^ An item in list
| KeyInfoBox -- ^ The text box widget with action informacion
| TutorialBox -- ^ The tutorial widget
| ContextBox -- ^ The resource for Context Menu
Expand Down
13 changes: 9 additions & 4 deletions lib-tui/GHCup/Brick/Widgets/Navigation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ draw dimAttrs section_list
minTagSize = V.maximum $ V.map (length . intercalate "," . fmap tagToString . lTag) allElements
minVerSize = V.maximum $ V.map (\ListResult{..} -> T.length $ tVerToText (GHCTargetVersion lCross lVer)) allElements
in Brick.withDefAttr L.listAttr $ SectionList.renderSectionList (renderItem minTagSize minVerSize) True bis
renderItem minTagSize minVerSize b listResult@ListResult{lTag = lTag', ..} =
renderItem minTagSize minVerSize listIx b listResult@ListResult{lTag = lTag', ..} =
let marks = if
| lSet -> (Brick.withAttr Attributes.setAttr $ Brick.str Common.setSign)
| lInstalled -> (Brick.withAttr Attributes.installedAttr $ Brick.str Common.installedSign)
Expand All @@ -100,8 +100,8 @@ draw dimAttrs section_list
| elem Latest lTag' && not lInstalled =
Brick.withAttr Attributes.hoorayAttr
| otherwise = id
active = if b then Common.enableScreenReader Common.AllTools else id
in hooray $ active $ dim
active = if b then Common.enableScreenReader (Common.ListItem lTool listIx) else id
in Brick.clickable (Common.ListItem lTool listIx) $ hooray $ active $ dim
( marks
<+> Brick.padLeft (Pad 2)
( minHSize 6
Expand Down Expand Up @@ -146,4 +146,9 @@ draw dimAttrs section_list
Nothing -> mempty
Just d -> [Brick.withAttr Attributes.dayAttr $ Brick.str (show d)])

minHSize s' = Brick.hLimit s' . Brick.vLimit 1 . (<+> Brick.fill ' ')
minHSize s' = Brick.hLimit s' . Brick.vLimit 1 . (<+> Brick.fill ' ')

instance SectionList.ListItemSectionNameIndex Common.Name where
getListItemSectionNameIndex = \case
Common.ListItem tool ix -> Just (Common.Singular tool, ix)
_ -> Nothing
19 changes: 16 additions & 3 deletions lib-tui/GHCup/Brick/Widgets/SectionList.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ makeLensesFor [("sectionListFocusRing", "sectionListFocusRingL"), ("sectionListE

type SectionList n e = GenericSectionList n V.Vector e

-- | To support selection by mouse click we need to obtain section name and item
-- index from the name of the item that got clicked. This helper class is to get that
class ListItemSectionNameIndex n where
getListItemSectionNameIndex :: n -> Maybe (n, Int)

-- | Build a SectionList from nonempty list. If empty we could not defined sectionL lenses.
sectionList :: Foldable t
Expand Down Expand Up @@ -129,6 +133,13 @@ moveUp = do
Just new_l -> Common.zoom (sectionL new_l) (Brick.modify L.listMoveToEnd)
else Common.zoom (sectionL l) $ Brick.modify L.listMoveUp

sectionListSelectItem :: (L.Splittable t, Eq n, ListItemSectionNameIndex n, Foldable t) => n -> EventM n (GenericSectionList n t e) ()
sectionListSelectItem selectedItem = case getListItemSectionNameIndex selectedItem of
Nothing -> pure ()
Just (secName, ix) -> do
sectionListFocusRingL %= F.focusSetCurrent secName
Common.zoom (sectionL secName) (Brick.modify $ L.listMoveTo ix)

-- | Handle events for list cursor movement. Events handled are:
--
-- * Up (up arrow key). If first element of section, then jump prev section
Expand All @@ -137,12 +148,14 @@ moveUp = do
-- * Page Down (PgDown)
-- * Go to next section (Tab)
-- * Go to prev section (BackTab)
handleGenericListEvent :: (Foldable t, L.Splittable t, Ord n)
-- * Select an element via Mouse left click
handleGenericListEvent :: (Foldable t, L.Splittable t, Ord n, ListItemSectionNameIndex n)
=> BrickEvent n a
-> EventM n (GenericSectionList n t e) ()
handleGenericListEvent (VtyEvent (Vty.EvResize _ _)) = pure ()
handleGenericListEvent (VtyEvent (Vty.EvKey (Vty.KChar '\t') [])) = sectionListFocusRingL %= F.focusNext
handleGenericListEvent (VtyEvent (Vty.EvKey Vty.KBackTab [])) = sectionListFocusRingL %= F.focusPrev
handleGenericListEvent (MouseDown n Vty.BLeft _ _) = sectionListSelectItem n
handleGenericListEvent (MouseDown _ Vty.BScrollDown _ _) = moveDown
handleGenericListEvent (MouseDown _ Vty.BScrollUp _ _) = moveUp
handleGenericListEvent (VtyEvent (Vty.EvKey Vty.KDown [])) = moveDown
Expand All @@ -156,7 +169,7 @@ handleGenericListEvent _ = pure ()

-- This re-uses Brick.Widget.List.renderList
renderSectionList :: forall n t e . (Traversable t, Ord n, Show n, Eq n, L.Splittable t, Semigroup (t e))
=> (Bool -> e -> Widget n) -- ^ Rendering function of the list element, True for the selected element
=> (Int -> Bool -> e -> Widget n) -- ^ Rendering function of the list element, True for the selected element
-> Bool -- ^ Whether the section list has focus
-> GenericSectionList n t e -- ^ The section list to render
-> Widget n
Expand All @@ -177,7 +190,7 @@ renderSectionList renderElem sectionFocus ge@(GenericSectionList focus elms slNa
sectionIsFocused l = sectionFocus && (Just (L.listName l) == F.focusGetCurrent focus)

renderInnerList :: Bool -> L.GenericList n t e -> Widget n
renderInnerList hasFocus l = Brick.vLimit (length l) $ L.renderList (\b -> renderElem (b && hasFocus)) hasFocus l
renderInnerList hasFocus l = Brick.vLimit (length l) $ L.renderListWithIndex (\i b -> renderElem i (b && hasFocus)) hasFocus l

-- compute the location to focus on within the active section
(c, r) :: (Int, Int) = case sectionListSelectedElement ge of
Expand Down
Loading