libcosmic/crates/component-system/README.md
2022-01-26 11:42:55 +01:00

3.4 KiB

COSMIC Component System

This library is a GTK4 GUI framework inspired by Relm, which is inspired by Elm. The philosophy for this framework is to isolate custom widgets into reusable components. You start with a custom Model type that implements Component, which is used to register a component with an optional argument. On registration, the model is used to construct the view and its widgets in the init_view() function. An event-handler is also spawned to handle events from both the component and any component emitting events to it. Those events are received and handled in the update() function. Both the init_view() and update() methods also have access to an outbound sender, which the caller can forward and consume however desired. See the examples directory for a demonstration of how to create a component.

Using a Macro to Define a Component

The simplest way to define a component is to use the component!() macro.

pub enum MyCustomInputMessage {
    Variant1,
    Variant2,
}

component! {
    // The model stores the state of this component.
    pub struct MyCustomModel {
        pub state: String,
    }

    // Widgets managed by the view are stored here.
    pub struct MyCustomWidgets {
        description: gtk::Label,
    }

    // The type of the input sender
    type Input = MyCustomInputMessage;

    // The type of the output sender
    type Output = ();

    // Declares the root widget and how it should be constructed.
    type Root = gtk::Box {
        ccs::view! {
            root = gtk::Box {
                set_orientation: gtk::Orientation::Vertical,
            }
        }

        root
    };

    // Constructs the inner component's model and widgets, using the
    // initial parameter given by `args`.
    fn init(args: (), root, input, output) {
        let description = gtk::Label::new();

        root.append(&description);

        ComponentInner {
            model: MyCustomModel { state: String::new() },
            widgets: MyCustomWidgets { description },
            input,
            output
        }
    }

    // Where events are received, with `component` is the `ComponentInner`,
    // and `event` is a `MyCustomInputMessage` which was just received.
    fn update(component, event) {
        match event {
            MyCustomInputMessage::Variant1 => {

            }

            MyCustomInputMessage::Variant2 => {

            }
        }
    }
}

Components can be created and have their output events forwarded:

let counter = InfoButton::init("Clicked 0 times".into(), "Click".into())
    .forward(input.clone(), |event| match event {
        InfoButtonOutput::Clicked => AppEvent::Increment
    });

The handle returned can be used to emit inputs to it, and to get the root widget.

counter.emit(InfoButtonInput::SetDescription(format!("Clicked {} times", count)));

box.append(counter.widget());

See Also

Relm4 uses a similar approach, but closely follows the Elm model. This library was created as an alternative approach that makes developing reusable components with forwardable events simpler.

License

Licensed under the Mozilla Public License 2.0.

Contribution

Any contribution intentionally submitted for inclusion in the work by you shall be licensed under the Mozilla Public License 2.0 (MPL-2.0).