typed_list_view_async/
typed_list_view_async.rs

1use std::time::Duration;
2
3use gtk::{glib, prelude::*};
4use relm4::{
5    RelmObjectExt,
6    binding::{Binding, U8Binding},
7    prelude::*,
8    typed_view::list::{RelmListItem, TypedListView},
9};
10
11struct MyListItem {
12    value: u8,
13    binding: U8Binding,
14    handle: Option<glib::JoinHandle<()>>,
15}
16
17impl PartialEq for MyListItem {
18    fn eq(&self, other: &Self) -> bool {
19        self.value == other.value
20    }
21}
22
23impl Eq for MyListItem {}
24
25impl PartialOrd for MyListItem {
26    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
27        Some(self.cmp(other))
28    }
29}
30
31impl Ord for MyListItem {
32    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
33        self.value.cmp(&other.value)
34    }
35}
36
37impl MyListItem {
38    fn new(value: u8) -> Self {
39        Self {
40            value,
41            binding: U8Binding::new(0),
42            handle: None,
43        }
44    }
45}
46
47struct Widgets {
48    label: gtk::Label,
49    label2: gtk::Label,
50    button: gtk::CheckButton,
51}
52
53impl RelmListItem for MyListItem {
54    type Root = gtk::Box;
55    type Widgets = Widgets;
56
57    fn setup(_item: &gtk::ListItem) -> (gtk::Box, Widgets) {
58        relm4::view! {
59            my_box = gtk::Box {
60                #[name = "label"]
61                gtk::Label {
62                    set_margin_end: 10,
63                },
64
65                #[name = "label2"]
66                gtk::Label {
67                    set_margin_end: 10,
68                },
69
70                #[name = "button"]
71                gtk::CheckButton,
72            }
73        }
74
75        let widgets = Widgets {
76            label,
77            label2,
78            button,
79        };
80
81        (my_box, widgets)
82    }
83
84    fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) {
85        println!("Unbind {}", self.value);
86        let Widgets {
87            label,
88            label2,
89            button,
90        } = widgets;
91
92        let future_binding = self.binding.clone();
93        self.handle = Some(relm4::spawn_local(async move {
94            loop {
95                tokio::time::sleep(Duration::from_secs(1)).await;
96                let mut guard = future_binding.guard();
97                *guard = guard.wrapping_add(1);
98            }
99        }));
100
101        label.set_label(&format!("Value: {} ", self.value));
102        label2.add_write_only_binding(&self.binding, "label");
103        button.set_active(self.value % 2 == 0);
104    }
105
106    fn unbind(&mut self, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {
107        self.handle
108            .take()
109            .unwrap()
110            .into_source_id()
111            .unwrap()
112            .remove();
113        *self.binding.guard() = 0;
114    }
115}
116
117struct App {
118    counter: u8,
119    list_view_wrapper: TypedListView<MyListItem, gtk::SingleSelection>,
120}
121
122#[derive(Debug)]
123enum Msg {
124    Append,
125    Remove,
126    OnlyShowEven(bool),
127}
128
129#[relm4::component]
130impl SimpleComponent for App {
131    type Init = u8;
132    type Input = Msg;
133    type Output = ();
134
135    view! {
136        gtk::Window {
137            set_title: Some("Async + idiomatic list view"),
138            set_default_size: (300, 100),
139
140            gtk::Box {
141                set_orientation: gtk::Orientation::Vertical,
142                set_spacing: 5,
143                set_margin_all: 5,
144
145                gtk::Button {
146                    set_label: "Append 10 items",
147                    connect_clicked => Msg::Append,
148                },
149
150                gtk::Button {
151                    set_label: "Remove second item",
152                    connect_clicked => Msg::Remove,
153                },
154
155                gtk::ToggleButton {
156                    set_label: "Only show even numbers",
157                    connect_clicked[sender] => move |btn| {
158                        sender.input(Msg::OnlyShowEven(btn.is_active()));
159                    }
160                },
161
162                gtk::ScrolledWindow {
163                    set_vexpand: true,
164
165                    #[local_ref]
166                    my_view -> gtk::ListView {}
167                }
168            }
169        }
170    }
171
172    fn init(
173        counter: Self::Init,
174        root: Self::Root,
175        sender: ComponentSender<Self>,
176    ) -> ComponentParts<Self> {
177        // Initialize the ListView wrapper
178        let mut list_view_wrapper: TypedListView<MyListItem, gtk::SingleSelection> =
179            TypedListView::with_sorting();
180
181        // Add a filter and disable it
182        list_view_wrapper.add_filter(|item| item.value % 2 == 0);
183        list_view_wrapper.set_filter_status(0, false);
184
185        let model = App {
186            counter,
187            list_view_wrapper,
188        };
189
190        let my_view = &model.list_view_wrapper.view;
191
192        let widgets = view_output!();
193
194        ComponentParts { model, widgets }
195    }
196
197    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
198        match msg {
199            Msg::Append => {
200                // Add 10 items
201                for _ in 0..10 {
202                    self.counter = self.counter.wrapping_add(1);
203                    self.list_view_wrapper.append(MyListItem::new(self.counter));
204                }
205            }
206            Msg::Remove => {
207                // Remove the second item
208                self.list_view_wrapper.remove(1);
209            }
210            Msg::OnlyShowEven(show_only_even) => {
211                // Disable or enable the first filter
212                self.list_view_wrapper.set_filter_status(0, show_only_even);
213            }
214        }
215    }
216}
217
218fn main() {
219    let app = RelmApp::new("relm4.example.typed-list-view-async");
220    app.run::<App>(0);
221}