Skip to content

Commit

Permalink
Merge pull request #763 from xStrom/win-quit
Browse files Browse the repository at this point in the history
Terminate app run loop on Windows when all windows have closed.
  • Loading branch information
xStrom authored Apr 27, 2020
2 parents cc2929b + 27c373a commit c50ecfe
Show file tree
Hide file tree
Showing 25 changed files with 517 additions and 162 deletions.
6 changes: 3 additions & 3 deletions druid-shell/examples/invalidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ impl WinHandler for InvalidateTest {
}

fn main() {
let mut app = Application::new(None);
let mut builder = WindowBuilder::new();
let app = Application::new().unwrap();
let mut builder = WindowBuilder::new(app.clone());
let inv_test = InvalidateTest {
size: Default::default(),
handle: Default::default(),
Expand All @@ -98,5 +98,5 @@ fn main() {

let window = builder.build().unwrap();
window.show();
app.run();
app.run(None);
}
8 changes: 4 additions & 4 deletions druid-shell/examples/perftest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ impl WinHandler for PerfTest {
}

fn destroy(&mut self) {
Application::quit()
Application::global().quit()
}

fn as_any(&mut self) -> &mut dyn Any {
Expand All @@ -116,8 +116,8 @@ impl WinHandler for PerfTest {
}

fn main() {
let mut app = Application::new(None);
let mut builder = WindowBuilder::new();
let app = Application::new().unwrap();
let mut builder = WindowBuilder::new(app.clone());
let perf_test = PerfTest {
size: Default::default(),
handle: Default::default(),
Expand All @@ -130,5 +130,5 @@ fn main() {
let window = builder.build().unwrap();
window.show();

app.run();
app.run(None);
}
10 changes: 5 additions & 5 deletions druid-shell/examples/shello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl WinHandler for HelloState {
match id {
0x100 => {
self.handle.close();
Application::quit();
Application::global().quit()
}
0x101 => {
let options = FileDialogOptions::new().show_hidden().allowed_types(vec![
Expand Down Expand Up @@ -101,7 +101,7 @@ impl WinHandler for HelloState {
}

fn destroy(&mut self) {
Application::quit()
Application::global().quit()
}

fn as_any(&mut self) -> &mut dyn Any {
Expand Down Expand Up @@ -129,14 +129,14 @@ fn main() {
menubar.add_dropdown(Menu::new(), "Application", true);
menubar.add_dropdown(file_menu, "&File", true);

let mut app = Application::new(None);
let mut builder = WindowBuilder::new();
let app = Application::new().unwrap();
let mut builder = WindowBuilder::new(app.clone());
builder.set_handler(Box::new(HelloState::default()));
builder.set_title("Hello example");
builder.set_menu(menubar);

let window = builder.build().unwrap();
window.show();

app.run();
app.run(None);
}
150 changes: 132 additions & 18 deletions druid-shell/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@

//! The top-level application type.

use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};

use crate::clipboard::Clipboard;
use crate::error::Error;
use crate::platform::application as platform;
use crate::util;

/// A top-level handler that is not associated with any window.
///
Expand All @@ -36,44 +42,152 @@ pub trait AppHandler {
fn command(&mut self, id: u32) {}
}

//TODO: we may want to make the user create an instance of this (Application::global()?)
//but for now I'd like to keep changes minimal.
/// The top level application object.
pub struct Application(platform::Application);
///
/// This can be thought of as a reference and it can be safely cloned.
#[derive(Clone)]
pub struct Application {
pub(crate) platform_app: platform::Application,
state: Rc<RefCell<State>>,
}

/// Platform-independent `Application` state.
struct State {
running: bool,
}

/// Used to ensure only one Application instance is ever created.
static APPLICATION_CREATED: AtomicBool = AtomicBool::new(false);

thread_local! {
/// A reference object to the current `Application`, if any.
static GLOBAL_APP: RefCell<Option<Application>> = RefCell::new(None);
}

impl Application {
pub fn new(handler: Option<Box<dyn AppHandler>>) -> Application {
Application(platform::Application::new(handler))
/// Create a new `Application`.
///
/// # Errors
///
/// Errors if an `Application` has already been created.
///
/// This may change in the future. See [druid#771] for discussion.
///
/// [druid#771]: https://github.com/xi-editor/druid/issues/771
pub fn new() -> Result<Application, Error> {
if APPLICATION_CREATED.compare_and_swap(false, true, Ordering::AcqRel) {
return Err(Error::ApplicationAlreadyExists);
}
util::claim_main_thread();
let platform_app = match platform::Application::new() {
Ok(app) => app,
Err(err) => return Err(Error::Platform(err)),
};
let state = Rc::new(RefCell::new(State { running: false }));
let app = Application {
platform_app,
state,
};
GLOBAL_APP.with(|global_app| {
*global_app.borrow_mut() = Some(app.clone());
});
Ok(app)
}

/// Get the current globally active `Application`.
///
/// A globally active `Application` exists
/// after [`new`] is called and until [`run`] returns.
///
/// # Panics
///
/// Panics if there is no globally active `Application`.
/// For a non-panicking function use [`try_global`].
///
/// This function will also panic if called from a non-main thread.
///
/// [`new`]: #method.new
/// [`run`]: #method.run
/// [`try_global`]: #method.try_global
#[inline]
pub fn global() -> Application {
// Main thread assertion takes place in try_global()
Application::try_global().expect("There is no globally active Application")
}

/// Get the current globally active `Application`.
///
/// A globally active `Application` exists
/// after [`new`] is called and until [`run`] returns.
///
/// # Panics
///
/// Panics if called from a non-main thread.
///
/// [`new`]: #method.new
/// [`run`]: #method.run
pub fn try_global() -> Option<Application> {
util::assert_main_thread();
GLOBAL_APP.with(|global_app| global_app.borrow().clone())
}

/// Start the runloop.
/// Start the `Application` runloop.
///
/// The provided `handler` will be used to inform of events.
///
/// This will consume the `Application` and block the current thread
/// until the `Application` has finished executing.
///
/// # Panics
///
/// This will block the current thread until the program has finished executing.
pub fn run(&mut self) {
self.0.run()
/// Panics if the `Application` is already running.
pub fn run(self, handler: Option<Box<dyn AppHandler>>) {
// Make sure this application hasn't run() yet.
if let Ok(mut state) = self.state.try_borrow_mut() {
if state.running {
panic!("Application is already running");
}
state.running = true;
} else {
panic!("Application state already borrowed");
}

// Run the platform application
self.platform_app.run(handler);

// This application is no longer active, so clear the global reference
GLOBAL_APP.with(|global_app| {
*global_app.borrow_mut() = None;
});
// .. and release the main thread
util::release_main_thread();
}

/// Terminate the application.
pub fn quit() {
platform::Application::quit()
/// Quit the `Application`.
///
/// This will cause [`run`] to return control back to the calling function.
///
/// [`run`]: #method.run
pub fn quit(&self) {
self.platform_app.quit()
}

// TODO: do these two go in some kind of PlatformExt trait?
/// Hide the application this window belongs to. (cmd+H)
pub fn hide() {
pub fn hide(&self) {
#[cfg(target_os = "macos")]
platform::Application::hide()
self.platform_app.hide()
}

/// Hide all other applications. (cmd+opt+H)
pub fn hide_others() {
pub fn hide_others(&self) {
#[cfg(target_os = "macos")]
platform::Application::hide_others()
self.platform_app.hide_others()
}

/// Returns a handle to the system clipboard.
pub fn clipboard() -> Clipboard {
platform::Application::clipboard().into()
pub fn clipboard(&self) -> Clipboard {
self.platform_app.clipboard().into()
}

/// Returns the current locale string.
Expand Down
6 changes: 3 additions & 3 deletions druid-shell/src/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub use crate::platform::clipboard as platform;
/// ```no_run
/// use druid_shell::{Application, Clipboard};
///
/// let mut clipboard = Application::clipboard();
/// let mut clipboard = Application::global().clipboard();
/// clipboard.put_string("watch it there pal");
/// if let Some(contents) = clipboard.get_string() {
/// assert_eq!("what it there pal", contents.as_str());
Expand All @@ -83,7 +83,7 @@ pub use crate::platform::clipboard as platform;
/// ```no_run
/// use druid_shell::{Application, Clipboard, ClipboardFormat};
///
/// let mut clipboard = Application::clipboard();
/// let mut clipboard = Application::global().clipboard();
///
/// let custom_type_id = "io.xieditor.path-clipboard-type";
///
Expand All @@ -104,7 +104,7 @@ pub use crate::platform::clipboard as platform;
/// ```no_run
/// use druid_shell::{Application, Clipboard, ClipboardFormat};
///
/// let clipboard = Application::clipboard();
/// let clipboard = Application::global().clipboard();
///
/// let custom_type_id = "io.xieditor.path-clipboard-type";
/// let supported_types = &[custom_type_id, ClipboardFormat::SVG, ClipboardFormat::PDF];
Expand Down
6 changes: 5 additions & 1 deletion druid-shell/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ use crate::platform::error as platform;

/// Error codes. At the moment, this is little more than HRESULT, but that
/// might change.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Error {
ApplicationAlreadyExists,
Other(&'static str),
Platform(platform::Error),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
Error::ApplicationAlreadyExists => {
write!(f, "An application instance has already been created.")
}
Error::Other(s) => write!(f, "{}", s),
Error::Platform(p) => fmt::Display::fmt(&p, f),
}
Expand Down
1 change: 1 addition & 0 deletions druid-shell/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ mod keycodes;
mod menu;
mod mouse;
mod platform;
mod util;
mod window;

pub use application::{AppHandler, Application};
Expand Down
17 changes: 10 additions & 7 deletions druid-shell/src/platform/gtk/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ use gio::prelude::ApplicationExtManual;
use gio::{ApplicationExt, ApplicationFlags, Cancellable};
use gtk::{Application as GtkApplication, GtkApplicationExt};

use crate::application::AppHandler;

use super::clipboard::Clipboard;
use super::error::Error;
use super::util;
use crate::application::AppHandler;

// XXX: The application needs to be global because WindowBuilder::build wants
// to construct an ApplicationWindow, which needs the application, but
Expand All @@ -31,10 +33,11 @@ thread_local!(
static GTK_APPLICATION: RefCell<Option<GtkApplication>> = RefCell::new(None);
);

pub struct Application;
#[derive(Clone)]
pub(crate) struct Application;

impl Application {
pub fn new(_handler: Option<Box<dyn AppHandler>>) -> Application {
pub fn new() -> Result<Application, Error> {
// TODO: we should give control over the application ID to the user
let application = GtkApplication::new(
Some("com.github.xi-editor.druid"),
Expand All @@ -56,10 +59,10 @@ impl Application {
.expect("Could not register GTK application");

GTK_APPLICATION.with(move |x| *x.borrow_mut() = Some(application));
Application
Ok(Application)
}

pub fn run(&mut self) {
pub fn run(self, _handler: Option<Box<dyn AppHandler>>) {
util::assert_main_thread();

// TODO: should we pass the command line arguments?
Expand All @@ -71,7 +74,7 @@ impl Application {
});
}

pub fn quit() {
pub fn quit(&self) {
util::assert_main_thread();
with_application(|app| {
match app.get_active_window() {
Expand All @@ -86,7 +89,7 @@ impl Application {
});
}

pub fn clipboard() -> Clipboard {
pub fn clipboard(&self) -> Clipboard {
Clipboard
}

Expand Down
Loading

0 comments on commit c50ecfe

Please sign in to comment.