Skip to content

Commit

Permalink
book: Extend main loop chapter with async section (#1511)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people authored Oct 27, 2023
1 parent fb3ef4d commit 776fa54
Show file tree
Hide file tree
Showing 9 changed files with 2,170 additions and 207 deletions.
1,933 changes: 1,737 additions & 196 deletions book/listings/Cargo.lock

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions book/listings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ xshell = "0.2"
dirs = "5.0"
walkdir = "2.3"
async-channel = "1.9.0"
ashpd = { version = "0.6.2", features = ["gtk4"] }
tokio = { version = "1.33.0", features = ["rt-multi-thread"] }
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] }

[build-dependencies]
glib-build-tools = "0.18"
Expand Down Expand Up @@ -206,6 +209,22 @@ path = "main_event_loop/4/main.rs"
name = "main_event_loop_5"
path = "main_event_loop/5/main.rs"

[[bin]]
name = "main_event_loop_6"
path = "main_event_loop/6/main.rs"

[[bin]]
name = "main_event_loop_7"
path = "main_event_loop/7/main.rs"

[[bin]]
name = "main_event_loop_8"
path = "main_event_loop/8/main.rs"

[[bin]]
name = "main_event_loop_9"
path = "main_event_loop/9/main.rs"

# saving_window_state
[[bin]]
name = "saving_window_state_1"
Expand Down
61 changes: 61 additions & 0 deletions book/listings/main_event_loop/6/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use glib::{clone, MainContext};
use gtk::prelude::*;
use gtk::{gio, glib};
use gtk::{Application, ApplicationWindow, Button};
use std::thread;
use std::time::Duration;

const APP_ID: &str = "org.gtk_rs.MainEventLoop6";

fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

// Connect to "activate" signal of `app`
app.connect_activate(build_ui);

// Run the application
app.run()
}

fn build_ui(app: &Application) {
// Create a button
let button = Button::builder()
.label("Press me!")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();

// ANCHOR: callback
// Connect to "clicked" signal of `button`
button.connect_clicked(move |button| {
let main_context = MainContext::default();
// The main loop executes the asynchronous block
main_context.spawn_local(clone!(@weak button => async move {
// Deactivate the button until the operation is done
button.set_sensitive(false);
let enable_button = gio::spawn_blocking(move || {
let ten_seconds = Duration::from_secs(10);
thread::sleep(ten_seconds);
true
})
.await
.expect("Task needs to finish successfully.");
// Set sensitivity of button to `enable_button`
button.set_sensitive(enable_button);
}));
});
// ANCHOR_END: callback

// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();

// Present window
window.present();
}
64 changes: 64 additions & 0 deletions book/listings/main_event_loop/7/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use ashpd::desktop::account::UserInformation;
use ashpd::WindowIdentifier;
use glib::{clone, MainContext};
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow, Button};

const APP_ID: &str = "org.gtk_rs.MainEventLoop7";

fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

// Connect to "activate" signal of `app`
app.connect_activate(build_ui);

// Run the application
app.run()
}

fn build_ui(app: &Application) {
// Create a button
let button = Button::builder()
.label("Press me!")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();

// ANCHOR: callback
// Connect to "clicked" signal of `button`
button.connect_clicked(move |button| {
let main_context = MainContext::default();
// The main loop executes the asynchronous block
main_context.spawn_local(clone!(@weak button => async move {
// Get native of button for window identifier
let native = button.native().expect("Need to be able to get native.");
// Get window identifier so that the dialog will be modal to the main window
let identifier = WindowIdentifier::from_native(&native).await;
let request = UserInformation::request()
.reason("App would like to access user information.")
.identifier(identifier)
.send()
.await;

if let Ok(response) = request.and_then(|r| r.response()) {
println!("User name: {}", response.name());
} else {
println!("Could not access user information.")
}
}));
});
// ANCHOR_END: callback

// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();

// Present window
window.present();
}
63 changes: 63 additions & 0 deletions book/listings/main_event_loop/8/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use glib::{clone, MainContext};
use gtk::glib;
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Button};

const APP_ID: &str = "org.gtk_rs.MainEventLoop8";

fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

// Connect to "activate" signal of `app`
app.connect_activate(build_ui);

// Run the application
app.run()
}

fn build_ui(app: &Application) {
// Create a button
let button = Button::builder()
.label("Press me!")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();

// ANCHOR: callback
let (sender, receiver) = async_channel::bounded(1);
// Connect to "clicked" signal of `button`
button.connect_clicked(move |_| {
let main_context = MainContext::default();
// The main loop executes the asynchronous block
main_context.spawn_local(clone!(@strong sender => async move {
let response = reqwest::get("https://www.gtk-rs.org").await;
sender.send(response).await.expect("The channel needs to be open.");
}));
});

let main_context = MainContext::default();
// The main loop executes the asynchronous block
main_context.spawn_local(clone!(@weak button => async move {
while let Ok(response) = receiver.recv().await {
if let Ok(response) = response {
println!("Status: {}", response.status());
} else {
println!("Could not make a `GET` request.");
}
}
}));
// ANCHOR_END: callback

// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();

// Present window
window.present();
}
67 changes: 67 additions & 0 deletions book/listings/main_event_loop/9/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use glib::{clone, MainContext};
use gtk::glib;
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Button};
use once_cell::sync::Lazy;
use tokio::runtime::Runtime;

// ANCHOR: tokio_runtime
const APP_ID: &str = "org.gtk_rs.MainEventLoop9";
static RUNTIME: Lazy<Runtime> =
Lazy::new(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."));

fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

// Connect to "activate" signal of `app`
app.connect_activate(build_ui);

// Run the application
app.run()
}
// ANCHOR_END: tokio_runtime

fn build_ui(app: &Application) {
// Create a button
let button = Button::builder()
.label("Press me!")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();

// ANCHOR: callback
let (sender, receiver) = async_channel::bounded(1);
// Connect to "clicked" signal of `button`
button.connect_clicked(move |_| {
RUNTIME.spawn(clone!(@strong sender => async move {
let response = reqwest::get("https://www.gtk-rs.org").await;
sender.send(response).await.expect("The channel needs to be open.");
}));
});

let main_context = MainContext::default();
// The main loop executes the asynchronous block
main_context.spawn_local(clone!(@weak button => async move {
while let Ok(response) = receiver.recv().await {
if let Ok(response) = response {
println!("Status: {}", response.status());
} else {
println!("Could not make a `GET` request.");
}
}
}));
// ANCHOR_END: callback

// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("My GTK App")
.child(&button)
.build();

// Present window
window.present();
}
Binary file added book/src/img/main_event_loop_ashpd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 776fa54

Please sign in to comment.