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

[Draft] Introductory mode #850

Merged
merged 8 commits into from
Dec 2, 2023
Merged

[Draft] Introductory mode #850

merged 8 commits into from
Dec 2, 2023

Conversation

lsmor
Copy link
Collaborator

@lsmor lsmor commented Jul 11, 2023

This is a proof of concept for #847 . It creates a PopUp on instalation, asking if you want to set the installed tool as the default.

This isn't an introductory mode, but it reflectes how challenging would it be. This PR contains very very ugly code, which would become unmaintanable very quickly. More over creating a complicated introductory mode would require a huge overhaul of the actual code.

  • Dependencies on microlens are unavoidable is we want decent code
  • Modal data type should be include. Something like data Mode = ShowingDialog | Normal | Introduction | ...
  • Change appDraw to depend on the Mode. Adding different drawing functions for each Mode
  • Modify the evenHandler the same way (one handler for each mode, and a function to dispatch).

I am willing to do it but I don't know If It worth a huge rewrite.

(Notice, this poc is because you commented "That requires some design and probably a lot more TUI code" in the discord thread... And that stament is very true, I just wanted to give some perspective)

ghcup_dialog_example

@hasufell
Copy link
Member

This is pretty cool!

I am willing to do it but I don't know If It worth a huge rewrite.

The TUI code is not very large, so I'm open to a complete rewrite. But the visual design should mainly stay the same, with the finicky separators... those required me to copy-paste internals and adjust them, see:

The other thing I want to get rid of is this: #65
It's not very nice to suspend the entire TUI. It's a shortcut, a hack. We should open a new dialog window, gather the logs from the functions, print them as we go etc.... but that's a lot more work and may also require some ghcup internals to be re-designed, e.g.

execLogged :: ( MonadReader env m


Wrt the actually proposed change... I think we want to avoid popups by default. I could imagine that after ghcup installation, we print a message to start in introductory mode via ghcup tui --assistant or so.

@lsmor
Copy link
Collaborator Author

lsmor commented Jul 11, 2023

But the visual design should mainly stay the same

Absolutely. I am not planning to change it.

It's not very nice to suspend the entire TUI. It's a shortcut

Well. I can give it a try, but I suspect this is going to be indeed difficult. Any case since the refactoring should include Mode type (following recommendations by brick's author); such a mode could include an Action constructor which isn't used, but with a big TODO: "Should be used to stream logs in a dialog box"

I think we want to avoid popups by default. I could imagine that after ghcup installation, we print a message to start in introductory mode via ghcup tui --assistant or so.

I think many people would like to have the install andThen set behaviour as the default. Would you consider a gchup-tui.conf/json/yaml file with a serialize version of BrickSettings (the refactor would include new fields such as setDefaultOnInstall), When installing ghcup the default value would be Nothing, In such a case, a popup shows

               would you like to set the installed tool as the default
 -----------------------------------------------------------------------------------------
 | Yes |    | No |    |Yes, and remember this option |    | No, and remember this option |

@hasufell
Copy link
Member

I think many people would like to have the install andThen set behaviour as the default.

I don't have any evidence for this. It's the first time someone complains about it.

Would you consider a gchup-tui.conf/json/yaml file with a serialize version of BrickSettings (the refactor would include new fields such as setDefaultOnInstall), When installing ghcup the default value would be Nothing, In such a case, a popup shows

I'm fine with new specific settings, because the average user doesn't need to know about it.

However, I'm not fond of popup questions by default. These should only exist in "introductory mode" or something else.

Another way is to just introduce another key that denotes "install and set".

@hasufell
Copy link
Member

Any news?

@lsmor
Copy link
Collaborator Author

lsmor commented Jul 15, 2023

@hasufell . I've been getting used to the code base a little bit more.

  • Is there any reason to not use the Brick.Widgets.List.GenericList?
  • Shouldn't KeyBindings belong to BrickSettings instead of Settings?
  • The whole settings' :: IORef AppState seems a little bit risky. Are you willing to change that?
  • The introductory mode (I'd prefer tutorial which is more explicit) can be done adding constructors to the previously mentioned Mode type. It'd be some popups (one or two) explaining the simbols (cross, check mark and double check mark) and some generic information about the tools.

This is doable (but will take time). If we want a decent/maintanable code a full rewrite of the TUI part should be done. The parts we can save from the current code are those which handle the AppState: install', set', etc...

As for the ETA. With very few hours a week, I could have it in october or so. Do you want to continue in this draft or to open a new issue?

@hasufell
Copy link
Member

Is there any reason to not use the Brick.Widgets.List.GenericList?

I'm not very familiar with brick. I don't do TUI often. The code was reviewed once in its early stages by the brick author, but that was it.

Shouldn't KeyBindings belong to BrickSettings instead of Settings?

Yeah maybe.

The whole settings' :: IORef AppState seems a little bit risky. Are you willing to change that?

Yeah, sure. But it's not a multi-threaded environment or anything.

As for the ETA. With very few hours a week, I could have it in october or so. Do you want to continue in this draft or to open a new issue?

Sure, just keep it here.

@lsmor
Copy link
Collaborator Author

lsmor commented Jul 25, 2023

A quick update before vacation. I did some deeper research on the code and brick's API. These are the things I'll try to implement in september:

  • I see there are many functions using BrickState -> EventM n BrickState () type signature. Given that EventM implements MonadState BrickState. All such functions can be simplified to just EventM n BrickState ().

  • By refactoring BrickInternalState as a Data.Map-like structure, a widget with finicky separators can be drawn easily, and we can re-use Brick.Widgets.List; simplifiying code a lot.

  • Using a Brick.Keybindings.KeyDispatcher is a good decision.

  • I've been experimenting with creating a "logging widget" so there is no need to suspend the TUI. The way to achive this is by creating custom event type type LogLine = Text and a Brick.BChan holding those events. I haven't try yet but maybe this could be surprisingly easy If the LoggerConfig.consoleOutter can be redirected to write into the BChan.

  • Last but not least, the introductory mode is just two or three panels showing important information about the tool.

ghcup.cabal Outdated
@@ -352,6 +352,9 @@ executable ghcup
, transformers ^>=0.5
, unix ^>=2.7
, vty ^>=5.37
, microlens ^>=0.4.13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optics does not work? Why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this particular commit it isn't necessary but notice that brick builds on top of microlens; hence if we want to reuse brick widgets (like Brick.Widget.List or the new widget for logs) we need microlens. It is a transitive dependency already, so I think it doesn't incur in bigger build times.

If this is a stopper for you I could try to not use microlens but keep in mind brick is kind of opinionated about this

@lsmor
Copy link
Collaborator Author

lsmor commented Sep 11, 2023

Hi, I am comming back to this as you notice.

I wasn't willing to push to this branch, I am working in a separate branch on my fork. Nevertheless, In order to review this, Do you prefer a rebase from master and then squash all changes? or do you prefer one commit for task?

Btw: I've read #848 . Thanks for your work, I'd like to help but my knowledge in distribution channels is very limited 😅 . That being said I am happy to document/answer question about the TUI part once I implement it

@hasufell
Copy link
Member

I don't believe in squashing. Organize the commits however you want.

I'll have capacity to review it. If you have links to the brick documentation about changes done, that might help.

@lsmor
Copy link
Collaborator Author

lsmor commented Sep 15, 2023

I'll be able to drop dependencies on microlens. The main problem was function zoom (Re-exported by brick) which comes from microlens and work with its lenses. It turns out that the type is LensLike ... -> and optics package allows for converting from its own optic representation to LensLike optics representation. Conclusion: there is interoperability with only a few changes in code.

ghcup.cabal Outdated
@@ -352,6 +352,9 @@ executable ghcup
, transformers ^>=0.5
, unix ^>=2.7
, vty ^>=5.37
, microlens ^>=0.4.13
, microlens-th ^>=0.4.3
, microlens-mtl ^>=0.2.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These deps also need to be added to the ghcup-optparse library component.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've dropped dependency in mircrolens in favor of optics. So It is consistent with the rest of the code base. I am still working on this but in somewhat slow path. But I'll finish it

@lsmor
Copy link
Collaborator Author

lsmor commented Oct 17, 2023

I have rebase the branch and finalice the important part of the work (that is the tutorial). I also, change the main data structure for a map-like one which integrates better with brick and reuses its code.

tutorial_example

Some changes with respect the scrolling have changed. Also, the tutorial display is a draft. Tomorrow, I'll make comments in the changes, so it is easier to review

Copy link
Collaborator Author

@lsmor lsmor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These commits have some behaviour changes:

  • The viewport isn't as smooth as before. It keeps the whole tool section on screen. Not the end of the world
  • Tab and Shift + Tab can be use to move from one tool to another. This can be changed with two lines of code, but I find it useful
  • pressing x open the tutorial screen.
  • pressing enter when the tutorial screen is open, closes it
  • when in tutorial, no other key works other than Enter.

ghcup.cabal Outdated
@@ -352,6 +352,9 @@ executable ghcup
, transformers ^>=0.5
, unix ^>=2.7
, vty ^>=5.37
, microlens ^>=0.4.13
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this particular commit it isn't necessary but notice that brick builds on top of microlens; hence if we want to reuse brick widgets (like Brick.Widget.List or the new widget for logs) we need microlens. It is a transitive dependency already, so I think it doesn't incur in bigger build times.

If this is a stopper for you I could try to not use microlens but keep in mind brick is kind of opinionated about this

app/ghcup/BrickMain.hs Outdated Show resolved Hide resolved
app/ghcup/BrickMain.hs Outdated Show resolved Hide resolved
app/ghcup/BrickMain.hs Show resolved Hide resolved
ghcup.cabal Outdated
@@ -352,6 +352,9 @@ executable ghcup
, transformers ^>=0.5
, unix ^>=2.7
, vty ^>=5.37
, microlens ^>=0.4.13
, microlens-th ^>=0.4.3
, microlens-mtl ^>=0.2.0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've dropped dependency in mircrolens in favor of optics. So It is consistent with the rest of the code base. I am still working on this but in somewhat slow path. But I'll finish it

$ viewport "GHCup" Vertical
$ vBox
$ V.toList drawnElements

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this function completely as renderSectionList do its job

, Brick.txt "Press Enter to exit the tutorial"
]
in [tutorial, ui dimAttrs st]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drawUI now splits, depending on which mode we are. This is the reason Mode type is so usefull. We can define handlers, drawings, etc... for each mode with barely extra complexity

]
where
withBackColor | no_color = \attr _ -> attr `Vty.withStyle` Vty.reverseVideo
| otherwise = Vty.withBackColor

eventHandler :: BrickState -> BrickEvent String e -> EventM String BrickState ()
eventHandler st@BrickState{..} ev = do
tutorialHandler :: BrickEvent Name e -> EventM Name BrickState ()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We define a handler for each mode

m <- use mode
case m of
Navigation -> navigationHandler ev
Tutorial -> tutorialHandler ev

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventHandler dispaches the event depending on the Mode

putStrLn "Press enter to continue"
_ <- getLine
pure (updateList data' as)
Left err -> throwIO $ userError err
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to make this part of the TUI, instead of SuspendAndResume. You can find a decent attempt here. It isn't difficult, as it is only to extent the Mode type and to add a custom event channel, but there are some difficulties

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the logging and receiving the process output is the main issue. It's tricky with the current code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well the logging isn't a problem. It is relativaly easy to deviate the logging part to a Brick channel so It it can be used as a widget. The main problem is the output of external processes, for example the call to curl prints directly on top of the TUI... I don't think this feature will be implemented in this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem is the output of external processes, for example the call to curl prints directly on top of the TUI... I don't think this feature will be implemented in this PR

That is what I meant with "logging". On the linux side we use low-level handling of stdout/stderr file descriptors... they could be exposed and used as callback style thing:

-- fork the subprocess
pid <- SPP.forkProcess $ do
void $ dupTo stdoutWrite stdOutput
void $ dupTo stdoutWrite stdError
closeFd stdoutRead
closeFd stdoutWrite

On windows side we use the process package fake pipes:

execLogged exe args chdir lfile env = do
Dirs {..} <- getDirs
logDebug $ T.pack $ "Running " <> exe <> " with arguments " <> show args
let stdoutLogfile = fromGHCupPath logsDir </> lfile <> ".stdout.log"
stderrLogfile = fromGHCupPath logsDir </> lfile <> ".stderr.log"
cp <- createProcessWithMingwPath ((proc exe args)
{ cwd = chdir
, env = env
, std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
})
fmap (toProcessError exe args)
$ liftIO
$ withCreateProcess cp
$ \_ mout merr ph ->
case (mout, merr) of
(Just cStdout, Just cStderr) -> do
withForkWait (tee stdoutLogfile cStdout) $ \waitOut ->
withForkWait (tee stderrLogfile cStderr) $ \waitErr -> do
waitOut
waitErr
waitForProcess ph
_ -> fail "Could not acquire out/err handle"

So a solution would probably have to write a wrapper around both that exposes the necessary parts and a callback that listens to messages on the pipe/Fd and reacts to it.

@hasufell
Copy link
Member

I tried to build and run this and moving through the list of elements in the list has some odd behavior. I'm not sure how to explain.

E.g.:

  • when I select the first GHC version, the screen starts scrolling down, although that element is in the very middle of the visible list
  • when I toggle "show all versions", the currently selected element completely goes out of the visible area
  • when I'm at the bottom of the list... I can still press "down" and after 9 key presses, it will jump to the top most element

I also noticed the horizontal separator is missing under the "Tool Version ..." header

@lsmor
Copy link
Collaborator Author

lsmor commented Oct 23, 2023

I can reproduce with a big enough font (or an small enough terminal). This is happening because the scroll follows the whole section instead of the element in the section. I should change that.

  • when I'm at the bottom of the list... I can still press "down" and after 9 key presses, it will jump to the top most element

Well, this is happening for the same thing. Essentially you have 9 items out of view...

I also noticed the horizontal separator is missing under the "Tool Version ..." header

upps.. already fixed in my local machine.

Do you have any preference about "tutorial" part? the current text is rather poor (in essence a Mock), but any change is easy to implement.

@hasufell
Copy link
Member

hasufell commented Nov 6, 2023

What's the status of this PR?

@lsmor
Copy link
Collaborator Author

lsmor commented Nov 6, 2023

What's the status of this PR?

This PR implements the tutorial / introductory mode. A major effort has gone into refactoring the code so It follows )a more brickish approach (with Mode type and using built-in Brick data structures). Currently, Everything should work as before but when pressing 'x' a Tutorial widget pops ups. The branch has been rebased with current master.

I think the main purpose of the PR is done (maybe the Tutorial widget needs better text). I am willing to merge it soon, but given the current windows (#912) work, maybe you prefer to prioritize that PR over this.

@lsmor lsmor changed the title [Draft] PopUp after installing a tool. [Draft] Introductory mode Nov 6, 2023
@hasufell
Copy link
Member

hasufell commented Nov 9, 2023

The scrolling behavior is still quite different, as can be seen here:

This PR

Peek 2023-11-09 16-54

Old behavior

Peek 2023-11-09 16-55


I personally prefer the old behavior. It's very simple and also matches how most GUIs behave (e.g. file manager where you press up button on the list of files). I asked one other user and they also prefer the old behavior.

@hasufell
Copy link
Member

Another possible topic for introductory mode: haskell/ghcup-metadata#143

@lsmor
Copy link
Collaborator Author

lsmor commented Nov 15, 2023

Excellent. I'll be in hospital tomorrow and then on sick leave, so I'm not sure when I get around for a proper review

No rush!, I'll continue with the three unchecked items. Take as much time as you need

@hasufell
Copy link
Member

Add configurable keybinding for tutorial (currently, hardcoded as 'x')

How about we use ? or the h (for help) keys... then we show the keybindings more verbosely and have a dialog allowing you to choose between Close and Show tutorial?

That way we can also design a proper help page. And I think it's natural to find the tutorial under a help page?

@lsmor
Copy link
Collaborator Author

lsmor commented Nov 18, 2023

How about we use ? or the h (for help) keys... then we show the keybindings more verbosely and have a dialog allowing you to choose between Close and Show tutorial?

I have added a new info layer with a verbose description of the keybindings. I don't know what t is actually for. Go back to navigation pressing q and go to next layer (tutorial) pressing Enter. Both are hardcoded at the moment. I also have improved the tutorial section, I have some doubts about when a tool is recommended. (You exit with harcoded q)

With respect to Keybindings.

  • Should we add configurable keys for the new widgets (open and close them)?
  • should we use bQuit instead of harcoded-q to quit both KeyInfo and Tutorial?
  • using ? might be problematic. In spanish keyboard (for example) it is Shift + apostrophe, I have tested and seems to work well, but maybe other internationalization settings wouldn't work... don't know

Remmember this is a draft and I am not a native English speaker, so I might use too much the passive voice 😆

ScreenShots for each new widgets:

Key Info

image

Tutorial

image

@hasufell
Copy link
Member

I guess this is mostly ready to merge then. If you mark the PR as ready, I'll try to go over the code some time.

@lsmor lsmor marked this pull request as ready for review November 18, 2023 11:53
@lsmor
Copy link
Collaborator Author

lsmor commented Nov 22, 2023

Notice that there are two items with missing documentation and I don't now exactly what to put there

  • recommended tag. Up to ghcup web page: is at the discretion of the GHCup maintainers and based on community adoption (hackage libraries, tools like HLS, stackage support, etc.) and known bugs. Should I use that directly or there is something more to be consider? I think this tag could be a source of confusion, so I'd rather prefer comfirm the companion text
  • usage of t key: it does nothing in my computer, and as far as I know, the only tools ghcup is able to install are the one in display.

Best

@hasufell
Copy link
Member

Notice that there are two items with missing documentation and I don't now exactly what to put there

  • recommended tag. Up to ghcup web page: is at the discretion of the GHCup maintainers and based on community adoption (hackage libraries, tools like HLS, stackage support, etc.) and known bugs. Should I use that directly or there is something more to be consider? I think this tag could be a source of confusion, so I'd rather prefer comfirm the companion text

We shouldn't go into too much detail here but just say this is the most reasonable choice for people starting out with haskell or companies that are not keen to experiment.

  • usage of t key: it does nothing in my computer, and as far as I know, the only tools ghcup is able to install are the one in display.

That key is removed in master already (unfortunately, you'll have to rebase due to that... if it's too much trouble, I can revert it temporarily).

@lsmor
Copy link
Collaborator Author

lsmor commented Nov 23, 2023

That key is removed in master already (unfortunately, you'll have to rebase due to that... if it's too much trouble, I can revert it temporarily).

Don't worry, I'll rebase it and include the missing recommended doc

@hasufell
Copy link
Member

hasufell commented Dec 2, 2023

I don't really understand all of the code yet, but I'm just going to merge and maybe comment later or provide follow-up PRs.

@hasufell hasufell merged commit e214695 into haskell:master Dec 2, 2023
24 of 26 checks passed
@hasufell
Copy link
Member

I noticed the TUI still doesn't behave like it used to wrt the viewport.

Old:

Peek 2024-01-22 17-08

New:

Peek 2024-01-22 17-10


As you can see. Once you're at an offset higher than the render limit of the terminal... your "cursor" will stay at the bottom of the screen. That's not the behavior of e.g. the old GHCup or other terminal TUIs like tig.

I tried to fix it in the code, but don't see an evident way how to do it.

@hasufell
Copy link
Member

We basically need a way to store the current applied viewport translation and then calculate whether the new offset would already fall into that visible viewport when applying the previous translation.

I forgot how the previous implementation did it.

@hasufell
Copy link
Member

hasufell commented Jan 22, 2024

I also don't see a way to query Brick "which list elements are currently visible?". If that was possible, we can infer the current translation that is applied.

Edit: well, that won't work, because we have multiple lists.

@hasufell
Copy link
Member

hasufell commented Jan 22, 2024

The old implementation simply relied on visible.

I tried the same with the current code (we have to apply visible to both the section list and the selected list element):

@@ -212,31 +215,27 @@ renderSectionList render_elem section_focus (GenericSectionList focus elms sl_na
         c <- Brick.getContext
         let -- A section is focused if the whole thing is focused, and the inner list has focus
             section_is_focused l = section_focus && (Just (L.listName l) == F.focusGetCurrent focus)
-            -- We need to limit the widget size when the length of the list is higher than the size of the terminal
-            limit = min (Brick.windowHeight c) (Brick.availHeight c)
-            s_idx = fromMaybe 0 $ V.findIndex section_is_focused elms
-            render_inner_list has_focus l = Brick.vLimit (length l) $ L.renderList (\b -> render_elem (b && has_focus)) has_focus l
-            (widget, off) =
-                V.ifoldl' (\wacc i list ->
+            render_inner_list has_focus l = Brick.vLimit (length l) $ L.renderList (\b -> let v = b && has_focus in makeVisible' v . render_elem $
+            makeVisible' b = if b then Brick.visible else id
+            widget =
+                V.ifoldl' (\(!acc_widget) i list ->
                                 let has_focus_list = section_is_focused list
-                                    (!acc_widget, !acc_off) = wacc
-                                    new_widget = if i == 0 then render_inner_list has_focus_list list else hBorder <=> render_inner_list has_focu$
-                                    new_off
-                                        | i < s_idx  = 1 + L.listItemHeight list * length list
-                                        | i == s_idx = 1 + L.listItemHeight list * fromMaybe 0 (L.listSelected list)
-                                        | otherwise  = 0
-                                in  (acc_widget <=> new_widget, acc_off + new_off)
+                                    new_widget = if i == 0
+                                                 then makeVisible' has_focus_list $ render_inner_list has_focus_list list
+                                                 else hBorder <=> (makeVisible' has_focus_list $ render_inner_list has_focus_list list)
+                                in acc_widget <=> new_widget
                                 )
-                (Brick.emptyWidget, 0)
+                Brick.emptyWidget
                 elms
-        Brick.render $  Brick.viewport sl_name Brick.Vertical $ Brick.translateBy (Brick.Location (0, min 0 (limit-off))) widget
+        Brick.render $ Brick.viewport sl_name Brick.Vertical widget

This somewhat restores the old behavior, but that has two issues:

  • in order to make a whole section visible, you'll get a small sudden "bump" when switching elements between sections.
  • if the section is bigger than the whole viewport, it doesn't work at all (e.g. the last elements in the section may not actually become visible)

@lsmor
Copy link
Collaborator Author

lsmor commented Jan 22, 2024

Let me check this. I remmember, reliying on visible did not work, you have no manually calculate the viewport tranlation

@hasufell
Copy link
Member

We probably need https://hackage.haskell.org/package/brick-2.3.1/docs/Brick-Widgets-Core.html#v:visibleRegion

Just visible will try to make the upper left corner of the widget visible, which doesn't work if it's a section and we are past the half of the list items.

I think computing the relative location of the selected list item in the section should be possible. That may fix both issues.

limit = min (Brick.windowHeight c) (Brick.availHeight c)
s_idx = fromMaybe 0 $ V.findIndex section_is_focused elms
render_inner_list has_focus l = Brick.vLimit (length l) $ L.renderList (\b -> render_elem (b && has_focus)) has_focus l
(widget, off) =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is where the viewport translation is calculated. s_idx stands for section index, hence the amount of offset is offset = (each previous section * its length) + (the current section * the current index in such a list) + (0 * next sections)

This is calculated with and indexed left fold. The variable new_off implements the logic above (notice we add 1 in each iteration to count for the hborder.)

Current master is not building in my computer due to missing dependencies (bzlib + file-uri). So I am having troubles testing this. Nevetheless, the idea should be in line 232 substitute

-- AS IS
Brick.translateBy (Brick.Location (0, min 0 (limit-off)))

-- TO BE
--                                                  |- Probably something slightly different
Brick.visibleRegion (Brick.Location (0, min 0 (limit-off))) (region_of_the_current_item)

@hasufell
Copy link
Member

-- This re-uses Brick.Widget.List.renderList
renderSectionList :: (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
                  -> Bool                                -- ^ Whether the section list has focus
                  -> GenericSectionList n t e            -- ^ The section list to render
                  -> Widget n
renderSectionList render_elem section_focus ge@(GenericSectionList focus elms sl_name) =
    Brick.Widget Brick.Greedy Brick.Greedy $ do
        c <- Brick.getContext
        let -- A section is focused if the whole thing is focused, and the inner list has focus
            section_is_focused l = section_focus && (Just (L.listName l) == F.focusGetCurrent focus)
            render_inner_list has_focus l = Brick.vLimit (length l) $ L.renderList (\b -> let v = b && has_focus in render_elem v) has_focus l
            widget =
                V.ifoldl' (\(!acc_widget) i list ->
                                let has_focus_list = section_is_focused list
                                    (c', r') = case sectionListSelectedElement ge of
                                                 Nothing -> (2, 0)
                                                 Just (selElIx, _) -> (2, selElIx)
                                    new_widget = if i == 0
                                                 then makeVisible' has_focus_list (c', r') $ render_inner_list has_focus_list list
                                                 else hBorder <=> (makeVisible' has_focus_list (c', r') $ render_inner_list has_focus_list list)
                                in acc_widget <=> new_widget
                                )
                Brick.emptyWidget
                elms
        Brick.render $ Brick.viewport sl_name Brick.Vertical widget
 where
  makeVisible' b (c, r) p
    | b = Brick.Widget (hSize p) (vSize p) $ do
            result <- render p
            let imageSize = ( result ^. lensVL Brick.imageL % to Vty.imageWidth
                            , result ^. lensVL Brick.imageL % to Vty.imageHeight - r
                            )
            return $ if imageSize^._1 > 0 && imageSize^._2 > 0
                     then result & (lensVL Brick.visibilityRequestsL) %~ (Brick.VR (Brick.Location (c, r)) imageSize :)
                     else result
    | otherwise = p

This comes very close. But there's still some rough edges.

@hasufell
Copy link
Member

hasufell commented Jan 22, 2024

Ah... got it.... the above is 90% there, just had to change

     then result & (lensVL Brick.visibilityRequestsL) %~ (Brick.VR (Brick.Location (c, r)) imageSize :)

to

     then result & (lensVL Brick.visibilityRequestsL) %~ (Brick.VR (Brick.Location (c, r)) (1, 1) :)

So it always focuses on a tiny dimension. That restores previous behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants