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

"TaggedLayouts" to make defining interfaces more streamlined #1057

Open
JCQuintas opened this issue Apr 22, 2024 · 1 comment
Open

"TaggedLayouts" to make defining interfaces more streamlined #1057

JCQuintas opened this issue Apr 22, 2024 · 1 comment
Labels
enhancement New feature or request

Comments

@JCQuintas
Copy link

Problem

I'm always struggling to name the variables results of calling area(rect)|split(rect), as you have to do them in order bigger elements to smaller elements and make sure everything aligns.

Example:

let [header, body, footer] = Layout::vertical([
    Constraint::Length(1), // header
    Constraint::Fill(4),   // body
    Constraint::Max(5),    // footer
])
.areas(total_area);

let [books_list, book_test] = Layout::horizontal([
    Constraint::Percentage(10), // books_list
    Constraint::Percentage(90), // book_text
])
.areas(body);

let [scroll_up, scroll_down, previous_page, next_page] = Layout::horizontal([
    Constraint::Ratio(1, 4), // scroll_up
    Constraint::Ratio(1, 4), // scroll_down
    Constraint::Ratio(1, 4), // previous_page
    Constraint::Ratio(1, 4), // next_page
])
.areas(footer);

Solution

I would like a way to define the UI/Constraints in a single structure, and then build/split it only once. With all the constraints being automatically fed to the split/areas behind the scenes, in a way I have a simple way to access the resulting "tagged" areas/layouts.

API Suggestion:

let layout = TaggedLayout::vertical()
    .rows([
        Row::new("header", Constraint::Length(1)),
        Row::new("body", Constraint::Fill(4)).columns([
            Column::new("books_list", Constraint::Percentage(10)),
            Column::new("book_text", Constraint::Percentage(90)),
        ]),
        Row::new("footer", Constraint::Max(5)).columns([
            Column::new("scroll_up", Constraint::Ratio(1, 4)),
            Column::new("scroll_down", Constraint::Ratio(1, 4)),
            Column::new("previous_page", Constraint::Ratio(1, 4)),
            Column::new("next_page", Constraint::Ratio(1, 4)),
        ]),
    ])
    .build(total_area); // HashMap<String, Rect>? Serializer?
                        // possibly other builders like `areas/split` as well?

layout.get("scroll_up").unwrap().intersects(mouse)

this could also allow parts of the UI be defined/built by different parts of the application, or on arbitrary orders, like in the example below, where we first thefine the smaller areas into variables, and only then build the main layout. So smaller elements before bigger elements. Which is the oposite way of the current api.

let header = Row::new("header", Constraint::Length(1));

let body = Row::new("body", Constraint::Fill(4)).columns([
    Column::new("books_list", Constraint::Percentage(10)),
    Column::new("book_text", Constraint::Percentage(90)),
]);

let footer = Row::new("footer", Constraint::Max(5)).columns([
    Column::new("scroll_up", Constraint::Ratio(1, 4)),
    Column::new("scroll_down", Constraint::Ratio(1, 4)),
    Column::new("previous_page", Constraint::Ratio(1, 4)),
    Column::new("next_page", Constraint::Ratio(1, 4)),
]);

let layout = TaggedLayout::vertical()
    .rows([header, body, footer])
    .build(total_area);

Alternatives

Additional context

@JCQuintas JCQuintas added the enhancement New feature or request label Apr 22, 2024
@joshka
Copy link
Member

joshka commented Apr 24, 2024

An alternative solution to this that goes one step further is to create a container type that accepts widgets and constraints together. The recent addition of the WidgetRef trait allows Boxed widgets to be stored in containers alongside the contstraint. A PoC version of this is available in https://docs.rs/ratatui-widgets/latest/ratatui_widgets/stack_container/struct.StackContainer.html

let stack = StackContainer::horizontal().with_widgets(vec![
    (Box::new(Paragraph::new("Left")), Constraint::Fill(1)),
    (Box::new(Paragraph::new("Center")), Constraint::Fill(2)),
    (Box::new(Paragraph::new("Right")), Constraint::Fill(1)),
]);

The one part this doesn't do is handle being able to use the areas for things like mouse click checks, but there might be a nice way to do that if designed right.

Note that ratatui-widgets is in an experimental state, with progress highly dependent on availability of inspiration and motivation. Feel free to borrow and adapt in your own app.

A third alternative is to write a proc macro that adapts some sort of syntax that makes variables and constraints easier to write together. Perhaps something like:

vertical! {
    header => length!(1),
    body => fill!(4, horizontal! {
        books_list => percentage!(90),
        books_test => percentage!(10),
    },
    footer = max!(5, horizontal! {
        scroll_up => fill!(1),
        scroll_down => fill!(1),
        previous_page => fill!(1),
        next_page => fill!(1),
    }
}

There's probably some parts of this that can't easily work, or are better expressed some other way, and perhaps the tagged layout would be the underlying piece that would make such a macro possible to write easily. This could work well with some of the ideas in https://github.com/ratatui-org/ratatui-macros

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

No branches or pull requests

2 participants