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

Add better support for dynamic view groups #20

Closed
StefanBossbaly opened this issue Jun 26, 2023 · 2 comments · Fixed by #21
Closed

Add better support for dynamic view groups #20

StefanBossbaly opened this issue Jun 26, 2023 · 2 comments · Fixed by #21

Comments

@StefanBossbaly
Copy link
Contributor

Hello,

I am writing a layout that will be populated with data from quering a backend service. Depending on the response we will populate the layout in different manners. However I am having a bit of trouble doing this with the current implementation of embedded-layout. Currently the ways of holding views are Views , Chain and the derive macro. Views allows for holding of the same type while Chain allows holding of different Drawables and also takes over ownership of the Views/Drawables. However since Chain/Views are using trait bounds, the type will vary depending on the current contents.

Below are two examples of an upcoming arrivals view for a train, one attemping to use Chain and the other attempting to use Views. Both fail since the type information will differ depending on if the list of arrivals is empty or not.

Attempt using Chain

Problem is that northbound_layouts does not have a single type since the Chain type will differ if the list is empty or the list have items.

let mut northbound_layouts = Vec::new();

// Views must always have at least one item
// state_unlocked.northbound is populated on another thread
if state_unlocked.northbound.is_empty() {
    let chain = Chain::new(Text::new(
        "No Status",
        Point::zero(),
        MonoTextStyle::new(&mono_font::ascii::FONT_6X9, Rgb888::WHITE),
    ));

    northbound_layouts.push(
        LinearLayout::horizontal(chain)
            .with_alignment(vertical::Center)
            .with_spacing(spacing::FixedMargin(6))
            .arrange(),
    );
} else {
    for train in state_unlocked.northbound.iter() {
        let text_color = match train.status.as_str() {
            "On Time" => Rgb888::GREEN,
            _ => Rgb888::RED,
        };

        let chain = Chain::new(Text::new(
            &train.train_id,
            Point::zero(),
            MonoTextStyle::new(&mono_font::ascii::FONT_6X9, Rgb888::WHITE),
        ))
        .append(Text::new(
            train.status.as_str(),
            Point::zero(),
            MonoTextStyle::new(&mono_font::ascii::FONT_5X7, text_color),
        ));

        northbound_layouts.push(
            LinearLayout::horizontal(chain)
                .with_alignment(vertical::Center)
                .with_spacing(spacing::FixedMargin(6))
                .arrange(),
        );
    }
}

Attempt using Views

Problem is that layout_holder does not have a single type since the Array type will differ if the list is empty or the list have items.

let mut northbound_layouts = Vec::new();
let mut layout_holder = Vec::new();

// Views must always have at least one item
// state_unlocked.northbound is populated on another thread
if state_unlocked.northbound.is_empty() {
    layout_holder.push([Text::new(
        "No Status",
        Point::zero(),
        MonoTextStyle::new(&mono_font::ascii::FONT_6X9, Rgb888::WHITE),
    )]);

    northbound_layouts.push(
        LinearLayout::horizontal(Views::new(layout_holder.last_mut().unwrap()))
            .with_alignment(vertical::Center)
            .with_spacing(spacing::FixedMargin(6))
            .arrange(),
    );
} else {
    for train in state_unlocked.northbound.iter() {
        let text_color = match train.status.as_str() {
            "On Time" => Rgb888::GREEN,
            _ => Rgb888::RED,
        };

        layout_holder.push([
            Text::new(
                &train.train_id,
                Point::zero(),
                MonoTextStyle::new(&mono_font::ascii::FONT_6X9, Rgb888::WHITE),
            ),
            Text::new(
                train.status.as_str(),
                Point::zero(),
                MonoTextStyle::new(&mono_font::ascii::FONT_5X7, text_color),
            ),
        ]);

        northbound_layouts.push(
            LinearLayout::horizontal(Views::new(layout_holder.last_mut().unwrap()))
                .with_alignment(vertical::Center)
                .with_spacing(spacing::FixedMargin(6))
                .arrange(),
        );
    }
}

Ideally there would a datastructure that would be able to hold a list of dyn Drawable<Color = ..., Output = ()> but since the draw function has a generic it violates object safety and can't be done. I was wondering if you had any ideas on how to dynamically create layouts with the given Views/Chain structures or if you would be open to creating a new structure that woudl be more suited this use case. Thanks and awesome crate.

@bugadani
Copy link
Owner

Hi and welcome!

I think, because Drawable isn't object safe, the best we can do is enums. In your case, an enum of the two layouts you're trying to draw. Working with those is not supported out of box right now (i.e. the enum won't be a ViewGroup or anything) but maybe we can add/modify a derive macro to help with that. Alternatively, you can dispatch based on that enum manually if that isn't too inconvenient.

I don't think an alloc/Vec<Box<dyn ...>> solution is possible right now, at least not easily. ű

I haven't touched this code seriously for the last 2 years so I'd need some time to actually re-learn it, and my focus is elsewhere, so apologies if a solution will take some time.

@StefanBossbaly
Copy link
Contributor Author

Awesome, let me give it a try and see if I can't come up with something generic so that it can be included in the repo for other users. I just wanted to make sure I wasn't missing anything since I am still a bit new to Rust. Let me do some experimenting and I will submit a PR once I have a solution that I am happy with. Thanks again!

@bugadani bugadani linked a pull request Jul 1, 2023 that will close this issue
StefanBossbaly added a commit to StefanBossbaly/embedded-layout that referenced this issue Jul 1, 2023
Currently works for both Named and Unamed structures. Right now all
fields have to implement View + Drawable. In addition they have to
implement Clone + Copy otherwise Transform impl will fail since
translate will attempt to move out a value behind a shared reference.
This makes it currently incompatible with LinerarLayout.

Fixes bugadani#20
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 a pull request may close this issue.

2 participants