Skip to content

Commit 776fa54

Browse files
Hofer-Julianbilelmoussaouifelinira
authored
book: Extend main loop chapter with async section (#1511)
* book: Extend main loop chapter with async section * Update book/listings/main_event_loop/7/main.rs Co-authored-by: Bilal Elmoussaoui <[email protected]> * book: Fix typo * book: Add tokio and reqwest listing * book: Fix app ids * book: Add filenames * Explain spawn_blocking in async context and ashpd * book: Add example that reqwest fails without tokio * book: Start with conclusion * book: Move to async-channel for reqwest demo * book: Add section about tokio * book: Add main_event_loop_1 video * book: Extend chapter * book: Address Sabrina's comments * Apply suggestions from @felinira's code review Co-authored-by: Fina <[email protected]> * Update book/src/main_event_loop.md Co-authored-by: Fina <[email protected]> --------- Co-authored-by: Bilal Elmoussaoui <[email protected]> Co-authored-by: Fina <[email protected]>
1 parent fb3ef4d commit 776fa54

File tree

9 files changed

+2170
-207
lines changed

9 files changed

+2170
-207
lines changed

book/listings/Cargo.lock

Lines changed: 1737 additions & 196 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

book/listings/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ xshell = "0.2"
1515
dirs = "5.0"
1616
walkdir = "2.3"
1717
async-channel = "1.9.0"
18+
ashpd = { version = "0.6.2", features = ["gtk4"] }
19+
tokio = { version = "1.33.0", features = ["rt-multi-thread"] }
20+
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] }
1821

1922
[build-dependencies]
2023
glib-build-tools = "0.18"
@@ -206,6 +209,22 @@ path = "main_event_loop/4/main.rs"
206209
name = "main_event_loop_5"
207210
path = "main_event_loop/5/main.rs"
208211

212+
[[bin]]
213+
name = "main_event_loop_6"
214+
path = "main_event_loop/6/main.rs"
215+
216+
[[bin]]
217+
name = "main_event_loop_7"
218+
path = "main_event_loop/7/main.rs"
219+
220+
[[bin]]
221+
name = "main_event_loop_8"
222+
path = "main_event_loop/8/main.rs"
223+
224+
[[bin]]
225+
name = "main_event_loop_9"
226+
path = "main_event_loop/9/main.rs"
227+
209228
# saving_window_state
210229
[[bin]]
211230
name = "saving_window_state_1"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use glib::{clone, MainContext};
2+
use gtk::prelude::*;
3+
use gtk::{gio, glib};
4+
use gtk::{Application, ApplicationWindow, Button};
5+
use std::thread;
6+
use std::time::Duration;
7+
8+
const APP_ID: &str = "org.gtk_rs.MainEventLoop6";
9+
10+
fn main() -> glib::ExitCode {
11+
// Create a new application
12+
let app = Application::builder().application_id(APP_ID).build();
13+
14+
// Connect to "activate" signal of `app`
15+
app.connect_activate(build_ui);
16+
17+
// Run the application
18+
app.run()
19+
}
20+
21+
fn build_ui(app: &Application) {
22+
// Create a button
23+
let button = Button::builder()
24+
.label("Press me!")
25+
.margin_top(12)
26+
.margin_bottom(12)
27+
.margin_start(12)
28+
.margin_end(12)
29+
.build();
30+
31+
// ANCHOR: callback
32+
// Connect to "clicked" signal of `button`
33+
button.connect_clicked(move |button| {
34+
let main_context = MainContext::default();
35+
// The main loop executes the asynchronous block
36+
main_context.spawn_local(clone!(@weak button => async move {
37+
// Deactivate the button until the operation is done
38+
button.set_sensitive(false);
39+
let enable_button = gio::spawn_blocking(move || {
40+
let ten_seconds = Duration::from_secs(10);
41+
thread::sleep(ten_seconds);
42+
true
43+
})
44+
.await
45+
.expect("Task needs to finish successfully.");
46+
// Set sensitivity of button to `enable_button`
47+
button.set_sensitive(enable_button);
48+
}));
49+
});
50+
// ANCHOR_END: callback
51+
52+
// Create a window
53+
let window = ApplicationWindow::builder()
54+
.application(app)
55+
.title("My GTK App")
56+
.child(&button)
57+
.build();
58+
59+
// Present window
60+
window.present();
61+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use ashpd::desktop::account::UserInformation;
2+
use ashpd::WindowIdentifier;
3+
use glib::{clone, MainContext};
4+
use gtk::prelude::*;
5+
use gtk::{glib, Application, ApplicationWindow, Button};
6+
7+
const APP_ID: &str = "org.gtk_rs.MainEventLoop7";
8+
9+
fn main() -> glib::ExitCode {
10+
// Create a new application
11+
let app = Application::builder().application_id(APP_ID).build();
12+
13+
// Connect to "activate" signal of `app`
14+
app.connect_activate(build_ui);
15+
16+
// Run the application
17+
app.run()
18+
}
19+
20+
fn build_ui(app: &Application) {
21+
// Create a button
22+
let button = Button::builder()
23+
.label("Press me!")
24+
.margin_top(12)
25+
.margin_bottom(12)
26+
.margin_start(12)
27+
.margin_end(12)
28+
.build();
29+
30+
// ANCHOR: callback
31+
// Connect to "clicked" signal of `button`
32+
button.connect_clicked(move |button| {
33+
let main_context = MainContext::default();
34+
// The main loop executes the asynchronous block
35+
main_context.spawn_local(clone!(@weak button => async move {
36+
// Get native of button for window identifier
37+
let native = button.native().expect("Need to be able to get native.");
38+
// Get window identifier so that the dialog will be modal to the main window
39+
let identifier = WindowIdentifier::from_native(&native).await;
40+
let request = UserInformation::request()
41+
.reason("App would like to access user information.")
42+
.identifier(identifier)
43+
.send()
44+
.await;
45+
46+
if let Ok(response) = request.and_then(|r| r.response()) {
47+
println!("User name: {}", response.name());
48+
} else {
49+
println!("Could not access user information.")
50+
}
51+
}));
52+
});
53+
// ANCHOR_END: callback
54+
55+
// Create a window
56+
let window = ApplicationWindow::builder()
57+
.application(app)
58+
.title("My GTK App")
59+
.child(&button)
60+
.build();
61+
62+
// Present window
63+
window.present();
64+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use glib::{clone, MainContext};
2+
use gtk::glib;
3+
use gtk::prelude::*;
4+
use gtk::{Application, ApplicationWindow, Button};
5+
6+
const APP_ID: &str = "org.gtk_rs.MainEventLoop8";
7+
8+
fn main() -> glib::ExitCode {
9+
// Create a new application
10+
let app = Application::builder().application_id(APP_ID).build();
11+
12+
// Connect to "activate" signal of `app`
13+
app.connect_activate(build_ui);
14+
15+
// Run the application
16+
app.run()
17+
}
18+
19+
fn build_ui(app: &Application) {
20+
// Create a button
21+
let button = Button::builder()
22+
.label("Press me!")
23+
.margin_top(12)
24+
.margin_bottom(12)
25+
.margin_start(12)
26+
.margin_end(12)
27+
.build();
28+
29+
// ANCHOR: callback
30+
let (sender, receiver) = async_channel::bounded(1);
31+
// Connect to "clicked" signal of `button`
32+
button.connect_clicked(move |_| {
33+
let main_context = MainContext::default();
34+
// The main loop executes the asynchronous block
35+
main_context.spawn_local(clone!(@strong sender => async move {
36+
let response = reqwest::get("https://www.gtk-rs.org").await;
37+
sender.send(response).await.expect("The channel needs to be open.");
38+
}));
39+
});
40+
41+
let main_context = MainContext::default();
42+
// The main loop executes the asynchronous block
43+
main_context.spawn_local(clone!(@weak button => async move {
44+
while let Ok(response) = receiver.recv().await {
45+
if let Ok(response) = response {
46+
println!("Status: {}", response.status());
47+
} else {
48+
println!("Could not make a `GET` request.");
49+
}
50+
}
51+
}));
52+
// ANCHOR_END: callback
53+
54+
// Create a window
55+
let window = ApplicationWindow::builder()
56+
.application(app)
57+
.title("My GTK App")
58+
.child(&button)
59+
.build();
60+
61+
// Present window
62+
window.present();
63+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use glib::{clone, MainContext};
2+
use gtk::glib;
3+
use gtk::prelude::*;
4+
use gtk::{Application, ApplicationWindow, Button};
5+
use once_cell::sync::Lazy;
6+
use tokio::runtime::Runtime;
7+
8+
// ANCHOR: tokio_runtime
9+
const APP_ID: &str = "org.gtk_rs.MainEventLoop9";
10+
static RUNTIME: Lazy<Runtime> =
11+
Lazy::new(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."));
12+
13+
fn main() -> glib::ExitCode {
14+
// Create a new application
15+
let app = Application::builder().application_id(APP_ID).build();
16+
17+
// Connect to "activate" signal of `app`
18+
app.connect_activate(build_ui);
19+
20+
// Run the application
21+
app.run()
22+
}
23+
// ANCHOR_END: tokio_runtime
24+
25+
fn build_ui(app: &Application) {
26+
// Create a button
27+
let button = Button::builder()
28+
.label("Press me!")
29+
.margin_top(12)
30+
.margin_bottom(12)
31+
.margin_start(12)
32+
.margin_end(12)
33+
.build();
34+
35+
// ANCHOR: callback
36+
let (sender, receiver) = async_channel::bounded(1);
37+
// Connect to "clicked" signal of `button`
38+
button.connect_clicked(move |_| {
39+
RUNTIME.spawn(clone!(@strong sender => async move {
40+
let response = reqwest::get("https://www.gtk-rs.org").await;
41+
sender.send(response).await.expect("The channel needs to be open.");
42+
}));
43+
});
44+
45+
let main_context = MainContext::default();
46+
// The main loop executes the asynchronous block
47+
main_context.spawn_local(clone!(@weak button => async move {
48+
while let Ok(response) = receiver.recv().await {
49+
if let Ok(response) = response {
50+
println!("Status: {}", response.status());
51+
} else {
52+
println!("Could not make a `GET` request.");
53+
}
54+
}
55+
}));
56+
// ANCHOR_END: callback
57+
58+
// Create a window
59+
let window = ApplicationWindow::builder()
60+
.application(app)
61+
.title("My GTK App")
62+
.child(&button)
63+
.build();
64+
65+
// Present window
66+
window.present();
67+
}
36.7 KB
Loading

0 commit comments

Comments
 (0)