settings_list/
settings_list.rs1use gtk::prelude::*;
16use relm4::*;
17
18fn main() {
19 gtk::Application::builder()
20 .application_id("org.relm4.SettingsListExample")
21 .launch(|_app, window| {
22 let mut component = App::builder()
24 .attach_to(&window)
26 .launch("Settings List Demo".into())
28 .connect_receiver(move |sender, message| match message {
30 Output::Clicked(id) => {
31 eprintln!("ID {id} Clicked");
32
33 match id {
34 0 => xdg_open("https://github.com/Relm4/Relm4".into()),
35 1 => xdg_open("https://docs.rs/relm4/".into()),
36 2 => {
37 sender.send(Input::Clear).unwrap();
38 }
39 _ => (),
40 }
41 }
42
43 Output::Reload => {
44 sender
45 .send(Input::AddSetting {
46 description: "Browse GitHub Repository".into(),
47 button: "GitHub".into(),
48 id: 0,
49 })
50 .unwrap();
51
52 sender
53 .send(Input::AddSetting {
54 description: "Browse Documentation".into(),
55 button: "Docs".into(),
56 id: 1,
57 })
58 .unwrap();
59
60 sender
61 .send(Input::AddSetting {
62 description: "Clear List".into(),
63 button: "Clear".into(),
64 id: 2,
65 })
66 .unwrap();
67 }
68 });
69
70 component.detach_runtime();
72
73 println!("parent is {:?}", component.widget().toplevel_window());
74 });
75}
76
77#[derive(Default)]
78pub struct App {
79 pub options: Vec<(String, String, u32)>,
80}
81
82pub struct Widgets {
83 pub list: gtk::ListBox,
84 pub options: Vec<gtk::Box>,
85 pub button_sg: gtk::SizeGroup,
86}
87
88#[derive(Debug)]
89pub enum Input {
90 AddSetting {
91 description: String,
92 button: String,
93 id: u32,
94 },
95 Clear,
96 Reload,
97}
98
99#[derive(Debug)]
100pub enum Output {
101 Clicked(u32),
102 Reload,
103}
104
105#[derive(Debug)]
106pub enum CmdOut {
107 Reload,
108}
109
110impl Component for App {
111 type Init = String;
112 type Input = Input;
113 type Output = Output;
114 type CommandOutput = CmdOut;
115 type Widgets = Widgets;
116 type Root = gtk::Box;
117
118 fn init_root() -> Self::Root {
119 gtk::Box::builder()
120 .halign(gtk::Align::Center)
121 .hexpand(true)
122 .orientation(gtk::Orientation::Vertical)
123 .build()
124 }
125
126 fn init(
127 title: Self::Init,
128 root: Self::Root,
129 sender: ComponentSender<Self>,
130 ) -> ComponentParts<Self> {
131 sender.output(Output::Reload).unwrap();
133
134 let label = gtk::Label::builder().label(title).margin_top(24).build();
135
136 let list = gtk::ListBox::builder()
137 .halign(gtk::Align::Center)
138 .margin_bottom(24)
139 .margin_top(24)
140 .selection_mode(gtk::SelectionMode::None)
141 .build();
142
143 root.append(&label);
144 root.append(&list);
145
146 ComponentParts {
147 model: App::default(),
148 widgets: Widgets {
149 list,
150 button_sg: gtk::SizeGroup::new(gtk::SizeGroupMode::Both),
151 options: Default::default(),
152 },
153 }
154 }
155
156 fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
157 match message {
158 Input::AddSetting {
159 description,
160 button,
161 id,
162 } => {
163 self.options.push((description, button, id));
164 }
165
166 Input::Clear => {
167 self.options.clear();
168
169 sender.oneshot_command(async move {
171 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
172 CmdOut::Reload
173 });
174 }
175
176 Input::Reload => {
177 sender.output(Output::Reload).unwrap();
178 }
179 }
180 }
181
182 fn update_cmd(
183 &mut self,
184 message: Self::CommandOutput,
185 sender: ComponentSender<Self>,
186 _root: &Self::Root,
187 ) {
188 match message {
189 CmdOut::Reload => {
190 sender.output(Output::Reload).unwrap();
191 }
192 }
193 }
194
195 fn update_view(&self, widgets: &mut Self::Widgets, sender: ComponentSender<Self>) {
196 if self.options.is_empty() && !widgets.options.is_empty() {
197 widgets.list.remove_all();
198 } else if self.options.len() != widgets.options.len() {
199 if let Some((description, button_label, id)) = self.options.last() {
200 let id = *id;
201 relm4::view! {
202 widget = gtk::Box {
203 set_orientation: gtk::Orientation::Horizontal,
204 set_margin_start: 20,
205 set_margin_end: 20,
206 set_margin_top: 8,
207 set_margin_bottom: 8,
208 set_spacing: 24,
209
210 append = >k::Label {
211 set_label: description,
212 set_halign: gtk::Align::Start,
213 set_hexpand: true,
214 set_valign: gtk::Align::Center,
215 set_ellipsize: gtk::pango::EllipsizeMode::End,
216 },
217
218 append: button = >k::Button {
219 set_label: button_label,
220 set_size_group: &widgets.button_sg,
221
222 connect_clicked[sender] => move |_| {
223 sender.output(Output::Clicked(id)).unwrap();
224 }
225 }
226 }
227 }
228
229 widgets.list.append(&widget);
230 widgets.options.push(widget);
231 }
232 }
233 }
234}
235
236fn xdg_open(item: String) {
237 std::thread::spawn(move || {
238 let _ = std::process::Command::new("xdg-open").arg(item).status();
239 });
240}