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}