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

Absolute Position Layout #459

Closed
bgirard opened this issue Jun 14, 2020 · 18 comments
Closed

Absolute Position Layout #459

bgirard opened this issue Jun 14, 2020 · 18 comments

Comments

@bgirard
Copy link

bgirard commented Jun 14, 2020

First: Suggestion / Help

Problem description

Context: I've got a toy re-implementation of React in Rust generating a Virtual DOM. It's already handling layout via Stretch. I'm trying to use Cursive as one 'DOM' backend to show a new novel use case of implementation.

I'm trying to find the best method to absolute position the views according to my position/size information. Looks like it's not supported? Is my best bet to fork LinearLayout into an absolute view?

Environment

  • Operating system: Windows
  • Backend used: crossterm
  • Current locale (run locale in a terminal). en_US.UTF-8
  • Cursive version 15
@gyscos
Copy link
Owner

gyscos commented Jun 14, 2020

Hi, and thanks for the report!

Forking/taking inspiration from LinearLayout is indeed the current way to add view groups that aren't available as built-ins yet. If the layout itself is already taken care of, then a lot of code from there will be unnecessary.

Do you have more details regarding this absolute layout you'd like? What kind of API would you expect?

@bgirard
Copy link
Author

bgirard commented Jun 14, 2020

Thanks for responding and maintaining this great project. I just need something to set the position relative to its parent view. Modified from the hello world example:

siv.add_layer(
        Dialog::around(
            AbsoluteLayout::new()
                .child(TextView::new("Title").h_align(HAlign::Center), x, y)
                // no longer needed: Use a DummyView as spacer
                // .child(DummyView.fixed_height(1))
                .child(TextView::new(text), x2,y2)
                .child(TextView::new(text).scrollable(), x3, y3)
                .fixed_width(30),
        )
        .button("Quit", |s| s.quit())
        .h_align(HAlign::Center),
    );

I think everything else I can achieve by composing the existing views.

@gyscos
Copy link
Owner

gyscos commented Jun 14, 2020

I see. What size would each child receive? The maximum size possible without causing overlap?

Say I add 3 children, all asking to be as big as possible:

  • Child A at (0, 0)
  • Child B at (1, 0)
  • Child C at (0, 1)

And assuming the view is given a size of (2, 2) to work with. Something like:

    0   1
  +---+---+
0 | A | B |
  +---+---+
1 | C | ? |
  +---+---+

Here. A cannot be bigger than (1, 1), since anything larger would overlap either B or C.
The main question is who, between B and C, should take the (1, 1) cell. Both B and C want it.

Or would you handle this internally with Stretch?

@bgirard
Copy link
Author

bgirard commented Jun 14, 2020

I'm not very familiar with layout algorithms. I imagined that was some kind of default behavior and that I could add a wrapping fixed_width/fixed_height wrapper view around each element to specify what I need. Perhaps that should just be a width/height argument?

For me your question is all handled with Stretch. B and C would have their own (x,y,w,h) describing who receives the space for ?. Say they're using flex layout it would depend on the flex-direction and flex-wrap values.

Here's a neat playground: https://yogalayout.com/playground/ . Once Stretch has ran I traverse the layout position and update all the components to their new positions (x, y, w, h) and add/destroy components that mount/unmount.

@gyscos
Copy link
Owner

gyscos commented Jun 17, 2020

Thanks for the details!

I started working on such a layout; the main complexity is now just in handling focus change (for example find out which view is "to the left" of the current view).

The other question is how the view dimensions/positions will be updated, either if views are added or if the terminal is resized.

  • We need a way to tell the external process that a re-layout is required.
  • We need a way for the external process to refresh the view position and sizes
    • Either synchronously, in the call to layout() itself
    • Or asynchronously, from a separate thread.

@bgirard
Copy link
Author

bgirard commented Jun 17, 2020

Thanks so much for looking into this!

For my use case what I'd prefer is actually an API like requestAnimationFrame(). On resize the layout is marked as dirty somehow, then after processing N events a paint occurs. I would catch requestAnimationFrame() and consolidate my state to Cursive and update layouts.

I think for my use case I'll be wrapping the event loop and single step Cursive and interleave my event:
https://docs.rs/cursive/0.15.0/cursive/struct.Cursive.html#method.run

But I haven't gotten that far yet. I think I already have everything I need as far as that's concerned.

I can work with anything you provide.

@bgirard
Copy link
Author

bgirard commented Jun 25, 2020

Do you have a branch or a draft I can take a look? I’m going to look into this some more.

@gyscos
Copy link
Owner

gyscos commented Jun 26, 2020

A bit busy these days. Will come back to this soon.

@gyscos
Copy link
Owner

gyscos commented Jun 29, 2020

The fixed_layout branch brings views::FixedLayout - does it do what you're looking for?

EDIT: The branch also brings OnLayoutView which lets you listen for the call to layout, and possibly re-compute children sizes there.

@bgirard
Copy link
Author

bgirard commented Jun 30, 2020

Thank you for doing this! Looking great so far. I think FixedLayout::required_size should use bottom_right instead. Right now the child position's height extends the bounds but the width does not.

I'm still implementing I'll have more concrete feedback shortly.

@gyscos
Copy link
Owner

gyscos commented Jun 30, 2020

Whoops you're very right, should be fixed now

@bgirard
Copy link
Author

bgirard commented Jul 2, 2020

It appears that view names are not updated for children added during call_on_name. I'm struggling to update the views at runtime.

    let mut s = cursive::default();
    let mut root_layer = FixedLayout::new().with_name("root");
    let test_view = TextView::new("test").with_name("test");
    /*
    // I can find the view if it's added before add_layer
    root_layer
        .get_mut()
        .add_child(Rect::from_size((0, 0), (1, 1)), test_view);
    */

    s.add_layer(root_layer);

    s.call_on_name("root", |view: &mut FixedLayout| {
        // but not if added after
        view.add_child(Rect::from_size((0, 0), (10, 1)), test_view);
    });
    s.call_on_name("test", |view: &mut TextView| {
        panic!("Should be called");
    });

@gyscos
Copy link
Owner

gyscos commented Jul 2, 2020

Arf yesh I forgot some method implementations from the View trait - should be better now.

@bgirard
Copy link
Author

bgirard commented Jul 2, 2020

Initial render, and wrapping the event loop is all working fine. I'm facing an issue with updating the position of views and removing views.

FixedLayout is accessed via position. This requires my code to track position in-sync out of band. I could either iterate over all the children myself and check if they're a named view, but AFAIK I can't query the name of a NamedView. I also can't use call_on_view to locate the view because that doesn't give me the view id.

Suggestion: Have a variant of set_child_position / remove_child that accepts an Name / Selector.

@gyscos
Copy link
Owner

gyscos commented Jul 3, 2020

Ah right - added FixedLayout::find_child_from_name(), which returns the position of the view (or the parent of the view) with the given name.

Also added NamedView::name(), which was sounds obvious but was missing.

@bgirard
Copy link
Author

bgirard commented Jul 3, 2020

Perfect. Thank you!

@bgirard
Copy link
Author

bgirard commented Jul 3, 2020

Works great now, thank you!
image

I haven't touched OnLayoutView yet. I might need that or something similar once I handled certain interactions like resizing while stretching to the full window.

@gyscos
Copy link
Owner

gyscos commented Jul 3, 2020

Glad it solves your problem (for now)!
Should now be part of master.

@bgirard bgirard closed this as completed Jul 3, 2020
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

No branches or pull requests

2 participants