From 3650bbf2d58f7605f9818c5711a881d9dc130b5f Mon Sep 17 00:00:00 2001 From: Divam Date: Tue, 19 Nov 2024 20:21:46 +0900 Subject: [PATCH] Support tui list item selection via mouse click --- lib-tui/GHCup/Brick/App.hs | 10 +++++++++- lib-tui/GHCup/Brick/Common.hs | 1 + lib-tui/GHCup/Brick/Widgets/Navigation.hs | 13 +++++++++---- lib-tui/GHCup/Brick/Widgets/SectionList.hs | 19 ++++++++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index fdbf4508..1f5ee1f2 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -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 diff --git a/lib-tui/GHCup/Brick/Common.hs b/lib-tui/GHCup/Brick/Common.hs index db3b54ff..fe04f3b3 100644 --- a/lib-tui/GHCup/Brick/Common.hs +++ b/lib-tui/GHCup/Brick/Common.hs @@ -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 diff --git a/lib-tui/GHCup/Brick/Widgets/Navigation.hs b/lib-tui/GHCup/Brick/Widgets/Navigation.hs index 89cb0884..6829d17b 100644 --- a/lib-tui/GHCup/Brick/Widgets/Navigation.hs +++ b/lib-tui/GHCup/Brick/Widgets/Navigation.hs @@ -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) @@ -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 @@ -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 ' ') \ No newline at end of file + 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 diff --git a/lib-tui/GHCup/Brick/Widgets/SectionList.hs b/lib-tui/GHCup/Brick/Widgets/SectionList.hs index ade14f28..3c62f940 100644 --- a/lib-tui/GHCup/Brick/Widgets/SectionList.hs +++ b/lib-tui/GHCup/Brick/Widgets/SectionList.hs @@ -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 @@ -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 @@ -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 @@ -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 @@ -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