relm4 0.11.0

An idiomatic GUI library inspired by Elm and based on gtk4-rs
// Catch potential problems with clippy and the macro
#![warn(
    missing_debug_implementations,
    rust_2018_idioms,
    unreachable_pub,
    unused_qualifications,
    clippy::cargo,
    clippy::must_use_candidate,
    clippy::used_underscore_binding
)]

use gtk::prelude::{
    BoxExt, ButtonExt, GridExt, GtkWindowExt, ObjectExt, OrientableExt, ToggleButtonExt, WidgetExt,
};
use relm4::{ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};

#[tracker::track]
struct App {
    value: u8,
}

#[derive(Debug)]
enum Msg {
    Increment,
    Decrement,
}

struct AppInit {
    counter: u8,
}

#[relm4::component(pub)]
impl SimpleComponent for App {
    type Init = AppInit;
    type Input = Msg;
    type Output = ();
    // AppWidgets is generated by the macro,
    // Can be omitted in most cases.
    type Widgets = AppWidgets;

    view! {
        #[root]
        #[name(main_window)]
        gtk::Window {
            set_title: Some("Macro reference example"),
            set_default_size: (300, 100),

            gtk::Box {
                set_orientation: gtk::Orientation::Vertical,
                set_spacing: 5,
                set_margin_all: 5,

                append: inc_button = &gtk::Button {
                    set_label: "Increment",
                    // Only set this if `icon_name` is Some
                    set_icon_name?: icon_name,
                    connect_clicked[sender] => move |_| {
                        sender.input(Msg::Increment);
                    }
                },

                gtk::Button {
                    set_label: "Decrement",
                    connect_clicked => Msg::Decrement,
                },

                gtk::Grid {
                    attach[1, 1, 1, 1] = &gtk::Label {
                        set_label: "Count to 10 to see if the tracker works!",
                        // Alternative: #[track = "counter.value % 10 == 0"]
                        #[track(skip_init, counter.value.is_multiple_of(10))]
                        set_label: &format!("Grid works! ({})", counter.value),
                    }
                },

                // A conditional widget
                // Alternative: #[transition = "SlideLeft"]
                #[transition(SlideLeft)]
                append = if counter.value.is_multiple_of(2) {
                    gtk::Label {
                        set_label: "Value is even",
                    }
                } else if counter.value.is_multiple_of(3) {
                    gtk::Label {
                        set_label: "Value is dividable by 3",
                    }
                } else {
                    gtk::Label {
                        set_label: "Value is odd",
                    }
                },

                #[transition = "SlideRight"]
                append: match_stack = match counter.value {
                    (0..=2) => {
                        gtk::Label {
                            set_label: "Value is smaller than 3",
                        }
                    },
                    _ => {
                        gtk::Label {
                            set_label: "Value is higher than 2",
                        }
                    },
                },

                append = &gtk::Label,

                gtk::Label::builder()
                    .label("Builder pattern works!")
                    .selectable(true)
                    .build(),

                gtk::Label::new(Some("Constructors work!")),

                /// Counter label
                #[name = "counter_label"]
                gtk::Label {
                    // Mirror property "label" for widget "bind_label"
                    #[chain(build())]
                    bind_property: ("label", &bind_label, "label"),
                    set_label: "Click the counter so see the value!",

                    #[watch(skip_init)]
                    set_label: &format!("Counter: {}", counter.value),
                    #[track]
                    set_margin_all: counter.value.into(),
                },

                #[name = "bind_label"]
                gtk::Label {},

                // You can also use returned widgets
                gtk::Stack {
                    add_child = &gtk::Label {
                        set_label: "placeholder",
                    } -> {
                        // Set the title of the stack page
                        set_title: "page title",
                    }
                },

                gtk::ToggleButton {
                    set_label: "Counter is even",
                    #[watch]
                    #[block_signal(toggle_handler)]
                    set_active: counter.value.is_multiple_of(2),

                    connect_toggled[sender] => move |_| {
                        sender.input(Msg::Increment);
                    } @toggle_handler,
                },

                #[local]
                local_label -> gtk::Label {
                    set_opacity: 0.7,
                },

                #[local_ref]
                local_ref_label -> gtk::Label {
                    set_opacity: 0.7,
                    set_size_request: (40, 40),
                },
            }
        },
        gtk::Window {
            set_title: Some("Another window"),
            set_default_size: (300, 100),
            set_transient_for: Some(&main_window),
            set_visible: false,
            // Empty args
            grab_focus: (),

            #[watch]
            set_visible: counter.value == 42,

            #[name = "my_label_name"]
            gtk::Label {
                set_label: "You made it to 42!",
            }
        }
    }

    // Initialize the component.
    fn init(
        init: Self::Init,
        renamed_root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let counter = App {
            value: init.counter,
            tracker: 0,
        };

        // Set icon name randomly to Some("go-up-symbolic") or None
        let icon_name = rand::random::<bool>().then_some("go-up-symbolic");

        let local_label = gtk::Label::new(Some("local_label"));
        let local_ref_label_value = gtk::Label::new(Some("local_ref_label"));
        let local_ref_label = &local_ref_label_value;
        // Insert the macro code generation here
        let widgets = view_output!();

        ComponentParts {
            model: counter,
            widgets,
        }
    }

    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
        self.reset();
        match msg {
            Msg::Increment => {
                self.set_value(self.value.wrapping_add(1));
            }
            Msg::Decrement => {
                self.set_value(self.value.wrapping_sub(1));
            }
        }
    }
}

fn main() {
    let app = RelmApp::new("relm4.example.macro_reference");
    app.run::<App>(AppInit { counter: 0 });
}