Skip to content

Commit

Permalink
Merge pull request #149 from sophie-h/dialog-run-future
Browse files Browse the repository at this point in the history
Add Dialog::run_future()
  • Loading branch information
sdroege authored Jan 11, 2021
2 parents 602092e + a0bedc6 commit e010ced
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 4 deletions.
3 changes: 3 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ name = "css"
[[bin]]
name = "custom_paintable"

[[bin]]
name = "dialog"

[[bin]]
name = "entry_completion"

Expand Down
77 changes: 77 additions & 0 deletions examples/src/bin/dialog.rs
Original file line number Diff line number Diff line change
@@ -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<W: IsA<gtk::Window>>(window: Rc<W>) {
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: &gtk::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<dyn std::error::Error>> {
let application = gtk::ApplicationBuilder::new()
.application_id("com.github.gtk-rs.examples.dialog")
.build()?;

application.connect_activate(build_ui);

application.run(&args().collect::<Vec<_>>());

Ok(())
}
1 change: 1 addition & 0 deletions gtk4/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
50 changes: 46 additions & 4 deletions gtk4/src/dialog.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -36,9 +39,25 @@ pub trait DialogExtManual: 'static {

#[doc(alias = "gtk_dialog_get_response_for_widget")]
fn get_response_for_widget<P: IsA<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<Box<dyn Future<Output = ResponseType> + 'a>>;
}

impl<O: IsA<Dialog>> DialogExtManual for O {
impl<O: IsA<Dialog> + IsA<Widget>> 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
Expand All @@ -49,9 +68,32 @@ impl<O: IsA<Dialog>> DialogExtManual for O {
fn get_response_for_widget<P: IsA<Widget>>(&self, widget: &P) -> ResponseType {
unsafe {
from_glib(ffi::gtk_dialog_get_response_for_widget(
self.as_ref().to_glib_none().0,
AsRef::<Dialog>::as_ref(self).to_glib_none().0,
widget.as_ref().to_glib_none().0,
))
}
}

fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + '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
}
})
}
}

0 comments on commit e010ced

Please sign in to comment.