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

Support adw::TabView properly #110

Closed
YaLTeR opened this issue Feb 16, 2022 · 1 comment
Closed

Support adw::TabView properly #110

YaLTeR opened this issue Feb 16, 2022 · 1 comment
Milestone

Comments

@YaLTeR
Copy link

YaLTeR commented Feb 16, 2022

I tried to get started with relm4 by porting one of my applications, but unfortunately I hit a major roadblock in form of adw::TabView, which proved to be challenging to fit into relm4's scaffold. The natural relm4 way to operate a TabView seems to be through factories:

struct Page {
    foo: String,
}

#[relm4::factory_prototype]
impl FactoryPrototype for Page {
    type Factory = FactoryVec<Self>;
    type Widgets = FactoryWidgets;
    type View = adw::TabView;
    type Msg = AppMsg;

    view! {
        gtk::Label {
            set_label: &self.foo,
        }
    }

    fn position(&self, _index: &usize) {}
}

struct AppModel {
    pages: FactoryVec<Page>,
}

#[relm4::widget]
impl Widgets<AppModel, ()> for AppWidgets {
    view! {
        /* ... */
        &gtk::Box {
            set_orientation: gtk::Orientation::Vertical,

            append = &adw::TabBar {
                set_autohide: false,
                set_view: Some(&tab_view),
            },

            append: tab_view = &adw::TabView {
                factory!(model.pages),
            },
        },
        /* ... */
    }
}

However, there are two problems. The first one is that you want your Page prototype to control not just the page's child widget, but also the properties of the adw::TabPage itself, such as title or loading or icon, ideally though a mechanism similar to view! {} with change tracking and all. This could be worked around by giving the prototype some way to access to adw::TabPage from view(); something similar is needed for dialog components that get access to their parent's window.

The second problem is unfortunately much more difficult. adw::TabView is far from just a view; it has its own internal model of the tabs, which it actively manages. An adw::TabBar lets the user close, rearrange, detach, attach, move to and from a different tab view, any tab, which needs to somehow be reflected back onto our AppModel's pages field. adw::TabView provides signals such as page-attached, page-detached, page-reordered that notify us of the changes that we need to propagate to our own pages, however:

  • Changing our own pages will result in the factory trying to "apply" these changes to the TabView "again". There are a few widgets that already have this issue, e.g. gtk::DropDown, which in this relm4 example is handled by listening to its notify callback and reflecting the changes on the model. Here double-state-application is not a problem because gtk::DropDown will simply check that we're setting it the same value and avoid emitting the notify callback, and thus an infinite loop. adw::TabView has much more complex interactions that need careful handling.
  • adw::TabView operates on adw::TabPages and their child widgets: you can look up an adw::TabPage by the child widget, and you get an adw::TabPage from the callbacks. This corresponds to the GTK architecture where widgets contain all their state, but not to the relm4 architecture where the state (the model) is separated from the view (the widget). Therefore there needs to be some way to get the model back from an adw::TabPage (and it needs to support receiving a TabPage from some other TabView elsewhere in the app). One idea I came up with is a wrapper bin widget that stores a pointer to the model (i.e. as a OnceCell<Box<dyn Any>>) and allows relm4 to retrieve it back; I'm not sure if that's the best approach.

I tried to get some sort of hackish solution to adw::TabView working within the bounds of relm4, but I couldn't really get past the roadblocks I kept hitting. I believe it needs some rearchitecturing of relm4 to support these kinds of widgets.

@AaronErhardt
Copy link
Member

I think most concerns have been addressed, but feel free to open an issue if there's something left to improve on the "next" branch.

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