progress/
progress.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MIT or Apache-2.0
3
4//! A component which allows the caller to define what options ae in its list.
5//!
6//! On init of the view, an output is sent to the caller to request to load widgets.
7//!
8//! Clicking a button will open the webpage to that option.
9//!
10//! Clicking the clear button will clear the list box and send a command to the
11//! background that waits 2 seconds before issuing a reload command back to the
12//! component, which forwards the reload command back to the caller of the
13//! component, which then issues to reload the widgets again.
14
15use futures::FutureExt;
16use gtk::prelude::*;
17use relm4::*;
18
19fn main() {
20    RelmApp::new("org.relm4.ProgressExample").run::<App>("Settings List Demo".into());
21}
22
23#[derive(Default)]
24pub struct App {
25    /// Tracks progress status
26    computing: bool,
27
28    /// Contains output of a completed task.
29    task: Option<CmdOut>,
30}
31
32pub struct Widgets {
33    button: gtk::Button,
34    label: gtk::Label,
35    progress: gtk::ProgressBar,
36}
37
38#[derive(Debug)]
39pub enum Input {
40    Compute,
41}
42
43#[derive(Debug)]
44pub enum Output {
45    Clicked(u32),
46}
47
48#[derive(Debug)]
49pub enum CmdOut {
50    /// Progress update from a command.
51    Progress(f32),
52    /// The final output of the command.
53    Finished(Result<String, ()>),
54}
55
56impl Component for App {
57    type Init = String;
58    type Input = Input;
59    type Output = Output;
60    type CommandOutput = CmdOut;
61    type Widgets = Widgets;
62    type Root = gtk::Window;
63
64    fn init_root() -> Self::Root {
65        gtk::Window::default()
66    }
67
68    fn init(
69        _args: Self::Init,
70        root: Self::Root,
71        sender: ComponentSender<Self>,
72    ) -> ComponentParts<Self> {
73        relm4::view! {
74            container = gtk::Box {
75                set_halign: gtk::Align::Center,
76                set_valign: gtk::Align::Center,
77                set_width_request: 300,
78                set_spacing: 12,
79                set_margin_top: 4,
80                set_margin_bottom: 4,
81                set_margin_start: 12,
82                set_margin_end: 12,
83                set_orientation: gtk::Orientation::Horizontal,
84
85                gtk::Box {
86                    set_spacing: 4,
87                    set_hexpand: true,
88                    set_valign: gtk::Align::Center,
89                    set_orientation: gtk::Orientation::Vertical,
90
91                    append: label = &gtk::Label {
92                        set_xalign: 0.0,
93                        set_label: "Find the answer to life:",
94                    },
95
96                    append: progress = &gtk::ProgressBar {
97                        set_visible: false,
98                    },
99                },
100
101                append: button = &gtk::Button {
102                    set_label: "Compute",
103                    connect_clicked => Input::Compute,
104                }
105            }
106        }
107
108        root.set_child(Some(&container));
109
110        ComponentParts {
111            model: App::default(),
112            widgets: Widgets {
113                label,
114                button,
115                progress,
116            },
117        }
118    }
119
120    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
121        match message {
122            Input::Compute => {
123                self.computing = true;
124                sender.command(|out, shutdown| {
125                    shutdown
126                        // Performs this operation until a shutdown is triggered
127                        .register(async move {
128                            let mut progress = 0.0;
129
130                            while progress < 1.0 {
131                                out.send(CmdOut::Progress(progress)).unwrap();
132                                progress += 0.1;
133                                tokio::time::sleep(std::time::Duration::from_millis(333)).await;
134                            }
135
136                            out.send(CmdOut::Finished(Ok("42".into()))).unwrap();
137                        })
138                        // Perform task until a shutdown interrupts it
139                        .drop_on_shutdown()
140                        // Wrap into a `Pin<Box<Future>>` for return
141                        .boxed()
142                });
143            }
144        }
145    }
146
147    fn update_cmd(
148        &mut self,
149        message: Self::CommandOutput,
150        _sender: ComponentSender<Self>,
151        _root: &Self::Root,
152    ) {
153        if let CmdOut::Finished(_) = message {
154            self.computing = false;
155        }
156
157        self.task = Some(message);
158    }
159
160    fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender<Self>) {
161        widgets.button.set_sensitive(!self.computing);
162
163        if let Some(ref progress) = self.task {
164            match progress {
165                CmdOut::Progress(p) => {
166                    widgets.label.set_label("Searching for the answer...");
167                    widgets.progress.set_visible(true);
168                    widgets.progress.set_fraction(*p as f64);
169                }
170                CmdOut::Finished(result) => {
171                    widgets.progress.set_visible(false);
172                    widgets
173                        .label
174                        .set_label(&format!("The answer to life is: {result:?}"));
175                }
176            }
177        }
178    }
179}