diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 124c35a660dd..f19bf4f19037 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -33,6 +33,9 @@ name = "css" [[bin]] name = "custom_paintable" +[[bin]] +name = "dialog" + [[bin]] name = "entry_completion" diff --git a/examples/src/bin/dialog.rs b/examples/src/bin/dialog.rs new file mode 100644 index 000000000000..6e94de4d04dd --- /dev/null +++ b/examples/src/bin/dialog.rs @@ -0,0 +1,77 @@ +//! # Dialog Example +//! +//! Example of how to obtain the response to a dialog as a future + +use gtk::glib::clone; +use gtk::glib::signal::Inhibit; +use gtk::prelude::*; + +use std::env::args; +use std::rc::Rc; + +async fn dialog>(window: Rc) { + let question_dialog = gtk::MessageDialogBuilder::new() + .transient_for(&*window) + .modal(true) + .buttons(gtk::ButtonsType::OkCancel) + .text("What is your answer?") + .build(); + + let answer = question_dialog.run_future().await; + question_dialog.close(); + + let info_dialog = gtk::MessageDialogBuilder::new() + .transient_for(&*window) + .modal(true) + .buttons(gtk::ButtonsType::Close) + .text("You answered") + .secondary_text(&format!("Your answer: {:?}", answer)) + .build(); + + info_dialog.run_future().await; + info_dialog.close(); +} + +fn build_ui(application: >k::Application) { + let button = gtk::ButtonBuilder::new() + .label("Open Dialog") + .halign(gtk::Align::Center) + .valign(gtk::Align::Center) + .build(); + + let window = Rc::new( + gtk::ApplicationWindowBuilder::new() + .application(application) + .title("Dialog Example") + .default_width(350) + .default_height(70) + .child(&button) + .visible(true) + .build(), + ); + + button.connect_clicked(clone!(@strong window => + move |_| { + gtk::glib::MainContext::default().spawn_local(dialog(Rc::clone(&window))); + } + )); + + window.connect_close_request(move |window| { + if let Some(application) = window.get_application() { + application.remove_window(window); + } + Inhibit(false) + }); +} + +fn main() -> Result<(), Box> { + let application = gtk::ApplicationBuilder::new() + .application_id("com.github.gtk-rs.examples.dialog") + .build()?; + + application.connect_activate(build_ui); + + application.run(&args().collect::>()); + + Ok(()) +} diff --git a/gtk4/Cargo.toml b/gtk4/Cargo.toml index 915ff0f41f72..2857223b1719 100644 --- a/gtk4/Cargo.toml +++ b/gtk4/Cargo.toml @@ -36,6 +36,7 @@ git = "https://github.com/gtk-rs/lgpl-docs" libc = "0.2" bitflags = "1.0" field-offset = "0.3" +futures-channel = "0.3" once_cell = "1.0" ffi = { package = "gtk4-sys", path = "./sys" } gtk4-macros = { path = "../gtk4-macros" } diff --git a/gtk4/src/dialog.rs b/gtk4/src/dialog.rs index f4444cb3ff04..c2b5cd949d43 100644 --- a/gtk4/src/dialog.rs +++ b/gtk4/src/dialog.rs @@ -1,9 +1,12 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::{Dialog, DialogExt, DialogFlags, ResponseType, Widget, Window}; +use crate::{Dialog, DialogExt, DialogFlags, ResponseType, Widget, WidgetExt, Window}; use glib::object::Cast; use glib::translate::*; -use glib::IsA; +use glib::{IsA, ObjectExt}; +use std::cell::Cell; +use std::future::Future; +use std::pin::Pin; use std::ptr; impl Dialog { @@ -36,9 +39,25 @@ pub trait DialogExtManual: 'static { #[doc(alias = "gtk_dialog_get_response_for_widget")] fn get_response_for_widget>(&self, widget: &P) -> ResponseType; + + // rustdoc-stripper-ignore-next + /// Shows the dialog and returns a `Future` that resolves to the + /// `ResponseType` on response. + /// + /// ```ignore + /// let dialog = gtk::MessageDialogBuilder::new() + /// .buttons(gtk::ButtonsType::OkCancel) + /// .text("What is your answer?") + /// .build(); + /// + /// let answer = dialog.run_future().await; + /// dialog.close(); + /// println!("Answer: {:?}", answer); + /// ``` + fn run_future<'a>(&'a self) -> Pin + 'a>>; } -impl> DialogExtManual for O { +impl + IsA> DialogExtManual for O { fn add_buttons(&self, buttons: &[(&str, ResponseType)]) { for &(text, id) in buttons { //FIXME: self.add_button don't work on 1.8 @@ -49,9 +68,32 @@ impl> DialogExtManual for O { fn get_response_for_widget>(&self, widget: &P) -> ResponseType { unsafe { from_glib(ffi::gtk_dialog_get_response_for_widget( - self.as_ref().to_glib_none().0, + AsRef::::as_ref(self).to_glib_none().0, widget.as_ref().to_glib_none().0, )) } } + + fn run_future<'a>(&'a self) -> Pin + 'a>> { + Box::pin(async move { + let (sender, receiver) = futures_channel::oneshot::channel(); + + let sender = Cell::new(Some(sender)); + + let response_handler = self.connect_response(move |_, response_type| { + if let Some(m) = sender.replace(None) { + let _result = m.send(response_type); + } + }); + + self.show(); + + if let Ok(response) = receiver.await { + self.disconnect(response_handler); + response + } else { + ResponseType::None + } + }) + } }