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

Improve Layout #36

Closed

Conversation

minimapletinytools
Copy link
Member

@minimapletinytools minimapletinytools commented Jan 24, 2021

This PR is paired with this issue explaining the changes.

Test cases for changes are here as it depends on reflex-vty-test-host which has not been merged yet.

Unfortunately the reflex frp network is a little convoluted. I think it pretty much has to be this way if we want to use the existing Layout monad design.

This PR is feature complete and has been tested.

Things left todo before PR is ready to be merged

  • improve documentation, possible add diagram showing how events are processed.
  • finish implementing and test "no focus" cases
  • finish implementing LayoutDebugTree
  • write test cases using https://github.com/pdlla/reflex-vty-test-host
  • update example code to use new Layout stuff


-- TODO should prob be (Branch [LayoutDebugTree] | Leaf PosDim
-- but it's weird cuz a leaf node won't know it's PosDim until combined with a Region...
data LayoutDebugTree t = LayoutDebugTree_Branch [LayoutDebugTree t] | LayoutDebugTree_Leaf
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little confused about what's going on with this type... t is phantom here? Is the idea to provide live debugging info about the layout tree? Maybe it would be useful to use Data.Tree for its drawTree function? I'm not sure.

Copy link
Member Author

@minimapletinytools minimapletinytools Jan 25, 2021

Choose a reason for hiding this comment

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

I outlined it's intent in the attached issue but yeah basically what you said except for automated testing not debugging (pardon the name). I think it might be quite useful for non-testing cases too.

This part of the code is entirely incomplete. Once folks are OK with the overall implementation I'll move forward with finishing this part of the code.

emptyLayoutDebugTree :: LayoutDebugTree t
emptyLayoutDebugTree = LayoutDebugTree_Leaf

class IsLayoutReturn t b a where
Copy link
Contributor

@cgibbard cgibbard Jan 25, 2021

Choose a reason for hiding this comment

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

This polymorphism seems unnecessary. Maybe just make a record data type something like:

data LayoutInfo t = LayoutInfo
  { layoutInfo_numChildren :: Int
  , layoutInfo_focusedDyn :: Dynamic t (Maybe Int)
  , layoutInfo_tree :: LayoutDebugTree t
  }

and then pair it with the result of the layout thing, and a simplified version which discards this info can be provided alongside?

That seems like it should do away with the awkward ambiguous types.

Copy link
Member Author

@minimapletinytools minimapletinytools Jan 26, 2021

Choose a reason for hiding this comment

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

Yes, I think you're right. I had really hoped that there wouldn't need to be multiple variations of fixed/stretch and the interface could be seamless between sublayout and non-sublayout cases. I don't think that's possible anymore in which case this type class is unnecessary 😭

Copy link
Member Author

Choose a reason for hiding this comment

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

I decided to keep the typeclass because it makes the implementation of tile simpler. The typeclass is hidden in the package. I could also just remove it and modify tile_ implementation

@cgibbard
Copy link
Contributor

Thanks for working on this problem! It's something that Ali and I were thinking about how to do early on, but one of our approaches to it got mired in some details surrounding Adjustable, and we ended up leaving something oversimplified in its place.

@minimapletinytools
Copy link
Member Author

minimapletinytools commented Jan 26, 2021

You're welcome! Glad to be contributing to such an awesome project!

I built off the existing implementation and felt what I ended up with got a little convoluted :(. It does however work and it seems to me that the complexity is inevitable if the network needs to pass through the Layout/VtyWidget boundary.

If the overall approach and modified interface looks OK I'll start finalizing this PR (which may take a while).

@cgibbard
Copy link
Contributor

I think it's a good start, and that we should definitely do something along these lines, even if it ends up looking different by the time it's 100% ready to go. Starting from something that works is always great. :)

If you can get a simple version of this building, then it'll be easier for us to have a deeper look at it and maybe figure out how to make it more satisfying if possible.

Our original idea was to have the Layout monad be the thing that kept track of tab order, so that it would usually be unnecessary to have tab order leave one runLayout and enter another and vice-versa, but if there's a reasonable scheme to make that possible, all the better. (The original way it was going to do that was with a forwards-and-backwards State monad that let us send events forward and backward to adjacent tiles in the Layout, but state monads of any kind are tricky to write satisfactory Adjustable instances for. Avoiding that shenanigans and just doing the thing in a way that involves a bit of global oversight seems the way to go.)

@minimapletinytools
Copy link
Member Author

minimapletinytools commented Feb 1, 2021

The newest revision is mainly cleanup and documentation. It should compile and run now as well. For a straight-forward example, you can run easyExample in example.hs.

Still need to implement LayoutDebugTree and write tests. Note that I'll be writing the tests in my own fork for now as the vty testing framework I wrote/want to use depends on this PR

@minimapletinytools minimapletinytools changed the title [PROPOSAL] Improve Layout Improve Layout Feb 1, 2021
Description: Monad transformer and tools for arranging widgets and building screen layouts
-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
Copy link
Member Author

@minimapletinytools minimapletinytools Feb 1, 2021

Choose a reason for hiding this comment

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

BTW, what linter do y'all prefer?

Copy link
Contributor

Choose a reason for hiding this comment

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

We haven't been using one, but I will say that generally we don't align things beyond the margin (apart from in cases where it will make code correctness much more obvious), since it leads to people who aren't using the same tools doing a bunch of extra work to keep stuff aligned, and the subsequent whitespace changes make the diffs harder to examine.

@minimapletinytools
Copy link
Member Author

minimapletinytools commented Feb 4, 2021

The latest revision implements LayoutTree and fixes a few minor issues. I also wrote unit tests for Layout using my own testing framework. Please take a look here

PR is feature complete. Looking forward to y'alls feedback.

Related plug, I'd like to merge my testing framework reflex-vty-test-host someday since I think it's super useful for testing. It's dependent on this PR which has yet to be reviewed 😭

@cgibbard
Copy link
Contributor

Having another look now, and I'll take a look at the testing one right after.

@ali-abrar
Copy link
Member

@pdlla Your PR got me thinking and I'm going to improve this a bit further this weekend prior to merging. I think some of the awkwardness you're working around can be avoided entirely (though it'll involve breaking changes).

-- | The Layout monad transformer keeps track of the configuration (e.g., 'Orientation') and
-- 'Constraint's of its child widgets, apportions vty real estate to each, and acts as a
-- switchboard for focus requests. See 'tile' and 'runLayout'.
-- switchboard for focus requests. See 'tile_' and 'runLayout'.
newtype Layout t m a = Layout
Copy link
Member

Choose a reason for hiding this comment

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

I've come to think it was a mistake to combine the layout and focus selection functionality into one monad. All of this might be substantially easier if the two concepts were separate. One of the problems with the old implementation was that the NodeId order was significant - i.e., things were focused in the order they were created. We shouldn't depend on that kind of ordering at all - which I think is what the Int you've got here is all about.

We can use ordered-containers so that we get efficient lookups while preserving order.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree with your points. I definitely hope for a simpler implementation. Having tab navigable focus that just works is great of course but this code is rather unreasonable to understand/modify limiting its usefulness for developers.

Unclear what your proposed alternative is apart from simply removing this feature. It seems to me passing focus events into a nested layout monad tree would involve re-implementing what we have here one way or another (ordered-containers vs nodeid<->index bimap will only make things a little less complex).

@ali-abrar
Copy link
Member

ali-abrar commented Mar 23, 2021

This ended up growing into a larger refactor than originally intended and introduces many breaking changes (though I think they're good ones). Let me know what you think about this interface.

To make that diff more consumable, I added a changelog entry, but that's still a lot of information. The executive summary is:

  • Layout and Focus have been broken up into separate monads (rather than piggy-backing on layout for focus information)
  • Most widgets have been made "classier" so that they can be dropped in anywhere in a layout
  • the layout combinators have been refactored to (I hope) make layout construction more comprehensible

@minimapletinytools
Copy link
Member Author

This ended up growing into a larger refactor than originally intended and introduces many breaking changes (though I think they're good ones). Let me know what you think about this interface.

To make that diff more consumable, I added a changelog entry, but that's still a lot of information. The executive summary is:

* Layout and Focus have been broken up into separate monads (rather than piggy-backing on layout for focus information)

* Most widgets have been made "classier" so that they can be dropped in anywhere in a layout

* the layout combinators have been refactored to (I hope) make layout construction more comprehensible

Cool, turning into a PR to make it easier for me to comment. Probably won't get through it today.

@ali-abrar
Copy link
Member

Closing in favor of #43

@ali-abrar ali-abrar closed this Apr 9, 2021
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.

3 participants