relm4 0.11.0

An idiomatic GUI library inspired by Elm and based on gtk4-rs
use gtk::prelude::*;
use relm4::factory::FactoryVecDeque;
use relm4::prelude::*;

#[derive(Debug)]
struct Task {
    name: String,
    completed: bool,
}

#[derive(Debug)]
enum TaskInput {
    Toggle(bool),
}

#[derive(Debug)]
enum TaskOutput {
    Delete(DynamicIndex),
}

#[relm4::factory]
impl FactoryComponent for Task {
    type Init = String;
    type Input = TaskInput;
    type Output = TaskOutput;
    type CommandOutput = ();
    type ParentWidget = gtk::ListBox;

    view! {
        gtk::Box {
            set_orientation: gtk::Orientation::Horizontal,

            gtk::CheckButton {
                set_active: false,
                set_margin_all: 12,
                connect_toggled[sender] => move |checkbox| {
                    sender.input(TaskInput::Toggle(checkbox.is_active()));
                }
            },

            #[name(label)]
            gtk::Label {
                set_label: &self.name,
                set_hexpand: true,
                set_halign: gtk::Align::Start,
                set_margin_all: 12,
            },

            gtk::Button {
                set_icon_name: "edit-delete",
                set_margin_all: 12,

                connect_clicked[sender, index] => move |_| {
                    sender.output(TaskOutput::Delete(index.clone())).unwrap();
                }
            }
        }
    }

    fn pre_view() {
        let attrs = widgets.label.attributes().unwrap_or_default();
        attrs.change(gtk::pango::AttrInt::new_strikethrough(self.completed));
        widgets.label.set_attributes(Some(&attrs));
    }

    fn init_model(name: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
        Self {
            name,
            completed: false,
        }
    }

    fn update(&mut self, message: Self::Input, _sender: FactorySender<Self>) {
        match message {
            TaskInput::Toggle(completed) => {
                self.completed = completed;
            }
        }
    }
}

#[derive(Debug)]
enum AppMsg {
    DeleteEntry(DynamicIndex),
    AddEntry(String),
}

struct App {
    tasks: FactoryVecDeque<Task>,
}

#[relm4::component]
impl SimpleComponent for App {
    type Init = ();
    type Input = AppMsg;
    type Output = ();

    view! {
        main_window = gtk::ApplicationWindow {
            set_width_request: 360,
            set_title: Some("To-Do"),

            gtk::Box {
                set_orientation: gtk::Orientation::Vertical,
                set_margin_all: 12,
                set_spacing: 6,

                gtk::Entry {
                    connect_activate[sender] => move |entry| {
                        let buffer = entry.buffer();
                        sender.input(AppMsg::AddEntry(buffer.text().into()));
                        buffer.delete_text(0, None);
                    }
                },

                gtk::ScrolledWindow {
                    set_hscrollbar_policy: gtk::PolicyType::Never,
                    set_min_content_height: 360,
                    set_vexpand: true,

                    #[local_ref]
                    task_list_box -> gtk::ListBox {}
                }
            }

        }
    }

    fn update(&mut self, msg: AppMsg, _sender: ComponentSender<Self>) {
        match msg {
            AppMsg::DeleteEntry(index) => {
                self.tasks.guard().remove(index.current_index());
            }
            AppMsg::AddEntry(name) => {
                self.tasks.guard().push_back(name);
            }
        }
    }

    fn init(
        _: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let tasks =
            FactoryVecDeque::builder()
                .launch_default()
                .forward(sender.input_sender(), |output| match output {
                    TaskOutput::Delete(index) => AppMsg::DeleteEntry(index),
                });

        let model = App { tasks };

        let task_list_box = model.tasks.widget();
        let widgets = view_output!();

        ComponentParts { model, widgets }
    }
}

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