to_do/
to_do.rs

1use gtk::prelude::*;
2use relm4::factory::FactoryVecDeque;
3use relm4::prelude::*;
4
5#[derive(Debug)]
6struct Task {
7    name: String,
8    completed: bool,
9}
10
11#[derive(Debug)]
12enum TaskInput {
13    Toggle(bool),
14}
15
16#[derive(Debug)]
17enum TaskOutput {
18    Delete(DynamicIndex),
19}
20
21#[relm4::factory]
22impl FactoryComponent for Task {
23    type Init = String;
24    type Input = TaskInput;
25    type Output = TaskOutput;
26    type CommandOutput = ();
27    type ParentWidget = gtk::ListBox;
28
29    view! {
30        gtk::Box {
31            set_orientation: gtk::Orientation::Horizontal,
32
33            gtk::CheckButton {
34                set_active: false,
35                set_margin_all: 12,
36                connect_toggled[sender] => move |checkbox| {
37                    sender.input(TaskInput::Toggle(checkbox.is_active()));
38                }
39            },
40
41            #[name(label)]
42            gtk::Label {
43                set_label: &self.name,
44                set_hexpand: true,
45                set_halign: gtk::Align::Start,
46                set_margin_all: 12,
47            },
48
49            gtk::Button {
50                set_icon_name: "edit-delete",
51                set_margin_all: 12,
52
53                connect_clicked[sender, index] => move |_| {
54                    sender.output(TaskOutput::Delete(index.clone())).unwrap();
55                }
56            }
57        }
58    }
59
60    fn pre_view() {
61        let attrs = widgets.label.attributes().unwrap_or_default();
62        attrs.change(gtk::pango::AttrInt::new_strikethrough(self.completed));
63        widgets.label.set_attributes(Some(&attrs));
64    }
65
66    fn init_model(name: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
67        Self {
68            name,
69            completed: false,
70        }
71    }
72
73    fn update(&mut self, message: Self::Input, _sender: FactorySender<Self>) {
74        match message {
75            TaskInput::Toggle(completed) => {
76                self.completed = completed;
77            }
78        }
79    }
80}
81
82#[derive(Debug)]
83enum AppMsg {
84    DeleteEntry(DynamicIndex),
85    AddEntry(String),
86}
87
88struct App {
89    tasks: FactoryVecDeque<Task>,
90}
91
92#[relm4::component]
93impl SimpleComponent for App {
94    type Init = ();
95    type Input = AppMsg;
96    type Output = ();
97
98    view! {
99        main_window = gtk::ApplicationWindow {
100            set_width_request: 360,
101            set_title: Some("To-Do"),
102
103            gtk::Box {
104                set_orientation: gtk::Orientation::Vertical,
105                set_margin_all: 12,
106                set_spacing: 6,
107
108                gtk::Entry {
109                    connect_activate[sender] => move |entry| {
110                        let buffer = entry.buffer();
111                        sender.input(AppMsg::AddEntry(buffer.text().into()));
112                        buffer.delete_text(0, None);
113                    }
114                },
115
116                gtk::ScrolledWindow {
117                    set_hscrollbar_policy: gtk::PolicyType::Never,
118                    set_min_content_height: 360,
119                    set_vexpand: true,
120
121                    #[local_ref]
122                    task_list_box -> gtk::ListBox {}
123                }
124            }
125
126        }
127    }
128
129    fn update(&mut self, msg: AppMsg, _sender: ComponentSender<Self>) {
130        match msg {
131            AppMsg::DeleteEntry(index) => {
132                self.tasks.guard().remove(index.current_index());
133            }
134            AppMsg::AddEntry(name) => {
135                self.tasks.guard().push_back(name);
136            }
137        }
138    }
139
140    fn init(
141        _: Self::Init,
142        root: Self::Root,
143        sender: ComponentSender<Self>,
144    ) -> ComponentParts<Self> {
145        let tasks =
146            FactoryVecDeque::builder()
147                .launch_default()
148                .forward(sender.input_sender(), |output| match output {
149                    TaskOutput::Delete(index) => AppMsg::DeleteEntry(index),
150                });
151
152        let model = App { tasks };
153
154        let task_list_box = model.tasks.widget();
155        let widgets = view_output!();
156
157        ComponentParts { model, widgets }
158    }
159}
160
161fn main() {
162    let app = RelmApp::new("relm4.example.to_do");
163    app.run::<App>(());
164}