Relm4 icon indicating copy to clipboard operation
Relm4 copied to clipboard

Idea for book: responsive design GTK example?

Open richardanaya opened this issue 4 years ago • 9 comments

I think the first thing that pops into my brain looking at trackers is, "How do I make a responsive layout that looks good when on a small screen like PinePhone device and desktop?". Example could be like a calculator that looks good when window size is small and different when big.

richardanaya avatar Oct 24 '21 03:10 richardanaya

That sounds like a good idea in general. Of course nowadays libadwaita handles most of the responsiveness in GTK apps but I wouldn't mind having a chapter in the Relm4 book explaining some of its concepts and widgets.

However, I currently don't have time for this as other things have a higher priority.

AaronErhardt avatar Oct 24 '21 13:10 AaronErhardt

Given that libadwaita comes with strings attached that not everyone wants, pure GTK examples are still useful. It's really just watching the size-allocate signal for your container and then doing whatever re-layout you want based on thresholds you've defined.

mmstick avatar Jan 23 '22 17:01 mmstick

You're right. Libadwaita didn't turn out as uncontroversial as I thought. Also, a pure GTK example would be good to better understand the underlying mechanism and of course for learning to write custom responsive widgets/components.

Yet, I don't think I will be able to add this very soon as updating the book to 0.4 has priority.

AaronErhardt avatar Jan 23 '22 17:01 AaronErhardt

I'd be willing to pitch in for some examples. For COSMIC we'll be creating a lot of custom widgets, likely as relm4 components. Just having some difficulty with making components that don't require parent models.

mmstick avatar Jan 23 '22 18:01 mmstick

There are MicroComponents which don't have a parent model and are more flexible in general.

And if you're looking for reusable components you can have a look at the relm4-components crate in this repository. And I'm always open for new ideas if that doesn't fit you.

AaronErhardt avatar Jan 23 '22 19:01 AaronErhardt

After a lot of experimentation, MicroComponents seems to work as long as every component is implemented manually. That way sub-components can be initialized at the same time and stored alongside the widgets of a component.

fn init_view(_model: &AppModel, _parent: &(), _sender: Sender<()>) -> Self {
    let panel = relm4::MicroComponent::new(SupportModel::new(), ());

    relm4_macros::view! {
        window = gtk::ApplicationWindow {
            set_title: Some("Support"),
            set_child = Some(&gtk::Box) {
                append: panel.root_widget()
            },
        }
    }

    Self { window, _panel: panel }
}

Having micro components in the model causes a segfault unless you have gtk::init() before creating your models. May also not be ideal to have some sub-components initialized before their parents.

I noticed that when you do automatically derive a micro-widgets, the fn post_init() { } claims post_init an unidentified identifier, and fn post_view() {} is never called.


So far I'm not sure I understand the need for Component when MicroComponent is both simpler to implement and more flexible. If you initialize a component from relm4::Widgets::init_view, relm4::Model::update, MicroWidgets::init_view or MicroModel::update, you already have access to a sender of the parent that you could attach to the model of the sub-component if you desired to have ownership of a parent sender in your child component.

Although the alternative would be for components to have an output channel in addition to an input channel. The parent component that initializes the child component can forward events from the output to its own event handler. Either way, in both scenarios you've eliminated the need for cyclic type definitions.

mmstick avatar Jan 23 '22 21:01 mmstick

Having micro components in the model causes a segfault unless you have gtk::init() before creating your models. May also not be ideal to have some sub-components initialized before their parents.

I will have a closer look at the initialization process. Btw. the models of components are initialized from the root to the outermost component, then the widgets are initialized in opposite direction. If you have a micro component in the model of your component, the micro component will be initialized first because the model is always initialized first.

I noticed that when you do automatically derive a micro-widgets, the fn post_init() { } claims post_init an unidentified identifier, and fn post_view() {} is never called.

post_init should work now with the latest commit, but I can't tell why post_view isn't working. It seems correct in the macro code...


Components are there for historic reasons but I still think they have their place in Relm4. They allow you to structure your application and allow Relm4 to handle the whole initialization. Also, they are a better abstraction than micro components because they can only be modified through messages which eliminates the need for an internal RefCell and related ownership issues. Also, they fit in well with workers and message handlers.

In general I would say that regular components are good when you want to initialize the component at the start and to destroy it when the app is closed. For other things I'd use micro components.

I might be a bit biased though because I'm used to regular components and micro components are relatively new.

AaronErhardt avatar Jan 23 '22 22:01 AaronErhardt

So far I can't find a single use case for Components that isn't already solved better by MicroComponents. Especially if you want to create a library with reusable components that cannot by nature have any knowledge of the parent that may be using it. If I want to update a MicroComponent, all I need is access to the sender that sends event to the MicroComponent. So I think that's what we'll use instead for everything, or go back to tokio channels across async glib local contexts.

I'm sure there's a way to do everything without a single RefCell anywhere if each component spawns its own glib::MainContext::default().spawn_local(event_handler) that has every widget from that component owned by that same scope. As I mention the model that you attach to this component could have the sender to the parent widget so there's zero need for any reference counters or complex cyclic type dependencies outside the cloned senders.

mmstick avatar Jan 23 '22 22:01 mmstick