jtdaugherty / brick

A declarative Unix terminal UI library written in Haskell
Other
1.6k stars 164 forks source link

How to use dialogSelection with appHandleEvent function? #509

Closed tobz619 closed 4 months ago

tobz619 commented 4 months ago

I'm building Snake in order to learn brick and I'm stuck as to how I can press Enter to return the value selected in the main menu.

The reason for this being that appEvent is a function of type.

appEvent :: BrickEvent Choice e -> EventM Choice (D.Dialog Choice Choice) ()

and so I cannot return anything useful.

I notice that calling halt prints the selection result to the terminal in ghci so I'm thinking in a larger stack of my different pages, I'd be calling execState to return the state?

I also am aware that technically I could be rendering my widgets as a list on top of each other but for now I want to load them as separate pages.

Any help would be appreciated.

jtdaugherty commented 4 months ago

Could you say a bit more about exactly what you mean by "return the value"? For example, are you wanting to obtain that value in main after Brick's event loop exits? Or store the value in the application state for later use while the application is running?

tobz619 commented 4 months ago

Could you say a bit more about exactly what you mean by "return the value"? For example, are you wanting to obtain that value in main after Brick's event loop exits? Or store the value in the application state for later use while the application is running?

The second: store the value while app is running

jtdaugherty commented 4 months ago

Okay, great.

Out of curiosity, have you read the introductory sections of the Brick User Guide? That provides a lot of important material on how to design applications in Brick.

The type EventM Choice (D.Dialog Choice Choice) () indicates that in this case EventM is a state monad over D.Dialog Choice Choice. So as written, the only thing that can change in the event handler is some state in that Dialog value. But if you want to do more than that, you'll need to enrich your state type, something like

data MyState =
    MyState { dialog :: D.Dialog Choice Choice
            , dialogResult :: Maybe Choice -- Initially Nothing, then Just once a
                                           -- choice has been made and stored here.
            }

Once that's been declared, your App's state type would need to be changed from D.Dialog Choice Choice to MyState, and then in your event handler you can update the dialog result when you're ready with something like

modify $ \s -> s { dialogResult = ... }

or use lenses on the record fields.

tobz619 commented 4 months ago

Ahh I get it now! I did read the guide.rst but I thought that was if you wanted to mix and match different Editor/Dialog/Brick types.

This explains this perfectly. Thank you so much! :D

jtdaugherty commented 4 months ago

Okay, great!

tobz619 commented 4 months ago

For reference, here's the completed implementation as far as I can tell for now. Is this pattern as intended?

https://github.com/tobz619/haskell-snake-learn/blob/3-create-main-menu-page-in-app/src/UI/MainMenu.hs

jtdaugherty commented 4 months ago

I'm not sure if you are wanting me to look at a specific part of that, but overall it looks about like I would expect.

I will mention that lenses would make this

        ev -> do d <- T.gets dialog
                 res <- T.nestEventM' d (D.handleDialogEvent ev)
                 T.modify $ \st -> st { dialog = res }

more easily written as follows, avoiding the gets/nestEventM'/modify sequence:

        ev -> zoom dialog $ D.handleDialogEvent ev

Doing that would require making the fields of your state into lenses. Brick's API gets even nicer to use when state fields are lenses for this reason since it helps avoid lots of boilerplate.