Skip to content

Commit c0b344e

Browse files
committed
Move app quit code to the application module.
1 parent a7a5f44 commit c0b344e

File tree

23 files changed

+372
-453
lines changed

23 files changed

+372
-453
lines changed

druid-shell/examples/invalidate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ impl WinHandler for InvalidateTest {
8484
}
8585

8686
fn main() {
87-
let mut app = Application::new(None);
88-
let mut builder = WindowBuilder::new();
87+
let app = Application::new();
88+
let mut builder = WindowBuilder::new(app.clone());
8989
let inv_test = InvalidateTest {
9090
size: Default::default(),
9191
handle: Default::default(),
@@ -98,5 +98,5 @@ fn main() {
9898

9999
let window = builder.build().unwrap();
100100
window.show();
101-
app.run();
101+
app.run(None);
102102
}

druid-shell/examples/perftest.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use time::Instant;
1919
use piet_common::kurbo::{Line, Rect};
2020
use piet_common::{Color, FontBuilder, Piet, RenderContext, Text, TextLayoutBuilder};
2121

22-
use druid_shell::{AppState, Application, KeyEvent, WinHandler, WindowBuilder, WindowHandle};
22+
use druid_shell::{Application, KeyEvent, WinHandler, WindowBuilder, WindowHandle};
2323

2424
const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22);
2525
const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea);
@@ -107,7 +107,7 @@ impl WinHandler for PerfTest {
107107
}
108108

109109
fn destroy(&mut self) {
110-
Application::quit()
110+
Application::global().quit()
111111
}
112112

113113
fn as_any(&mut self) -> &mut dyn Any {
@@ -116,9 +116,8 @@ impl WinHandler for PerfTest {
116116
}
117117

118118
fn main() {
119-
let state = AppState::new();
120-
let mut app = Application::new(state.clone(), None);
121-
let mut builder = WindowBuilder::new(state);
119+
let app = Application::new();
120+
let mut builder = WindowBuilder::new(app.clone());
122121
let perf_test = PerfTest {
123122
size: Default::default(),
124123
handle: Default::default(),
@@ -131,5 +130,5 @@ fn main() {
131130
let window = builder.build().unwrap();
132131
window.show();
133132

134-
app.run();
133+
app.run(None);
135134
}

druid-shell/examples/shello.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use druid_shell::kurbo::{Line, Rect, Vec2};
1818
use druid_shell::piet::{Color, RenderContext};
1919

2020
use druid_shell::{
21-
AppState, Application, Cursor, FileDialogOptions, FileSpec, HotKey, KeyEvent, KeyModifiers,
22-
Menu, MouseEvent, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle,
21+
Application, Cursor, FileDialogOptions, FileSpec, HotKey, KeyEvent, KeyModifiers, Menu,
22+
MouseEvent, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle,
2323
};
2424

2525
const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22);
@@ -48,7 +48,7 @@ impl WinHandler for HelloState {
4848
match id {
4949
0x100 => {
5050
self.handle.close();
51-
Application::quit();
51+
Application::global().quit()
5252
}
5353
0x101 => {
5454
let options = FileDialogOptions::new().show_hidden().allowed_types(vec![
@@ -101,7 +101,7 @@ impl WinHandler for HelloState {
101101
}
102102

103103
fn destroy(&mut self) {
104-
Application::quit()
104+
Application::global().quit()
105105
}
106106

107107
fn as_any(&mut self) -> &mut dyn Any {
@@ -129,15 +129,14 @@ fn main() {
129129
menubar.add_dropdown(Menu::new(), "Application", true);
130130
menubar.add_dropdown(file_menu, "&File", true);
131131

132-
let state = AppState::new();
133-
let mut app = Application::new(state.clone(), None);
134-
let mut builder = WindowBuilder::new(state);
132+
let app = Application::new();
133+
let mut builder = WindowBuilder::new(app.clone());
135134
builder.set_handler(Box::new(HelloState::default()));
136135
builder.set_title("Hello example");
137136
builder.set_menu(menubar);
138137

139138
let window = builder.build().unwrap();
140139
window.show();
141140

142-
app.run();
141+
app.run(None);
143142
}

druid-shell/src/application.rs

Lines changed: 112 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414

1515
//! The top-level application type.
1616
17+
use std::cell::RefCell;
18+
use std::rc::Rc;
1719
use std::sync::atomic::{AtomicBool, Ordering};
1820

1921
use crate::clipboard::Clipboard;
2022
use crate::platform::application as platform;
23+
use crate::util;
2124

2225
/// A top-level handler that is not associated with any window.
2326
///
@@ -38,73 +41,148 @@ pub trait AppHandler {
3841
fn command(&mut self, id: u32) {}
3942
}
4043

41-
/// The top level application state.
44+
/// The top level application object.
4245
///
43-
/// This helps the application track all the state that it has created,
44-
/// which it later needs to clean up.
46+
/// This can be thought of as a reference and it can be safely cloned.
4547
#[derive(Clone)]
46-
pub struct AppState(pub(crate) platform::AppState);
47-
48-
impl AppState {
49-
/// Create a new `AppState` instance.
50-
pub fn new() -> AppState {
51-
AppState(platform::AppState::new())
52-
}
48+
pub struct Application {
49+
state: Rc<RefCell<State>>,
50+
pub(crate) platform_app: platform::Application,
5351
}
5452

55-
//TODO: we may want to make the user create an instance of this (Application::global()?)
56-
//but for now I'd like to keep changes minimal.
57-
/// The top level application object.
58-
pub struct Application(platform::Application);
53+
/// Platform-independent `Application` state.
54+
struct State {
55+
running: bool,
56+
}
5957

60-
// Used to ensure only one Application instance is ever created.
61-
// This may change in the future.
62-
// For more information see https://github.com/xi-editor/druid/issues/771
58+
/// Used to ensure only one Application instance is ever created.
6359
static APPLICATION_CREATED: AtomicBool = AtomicBool::new(false);
6460

61+
thread_local! {
62+
/// A reference object to the current `Application`, if any.
63+
static GLOBAL_APP: RefCell<Option<Application>> = RefCell::new(None);
64+
}
65+
6566
impl Application {
6667
/// Create a new `Application`.
6768
///
68-
/// It takes the application `state` and a `handler` which will be used to inform of events.
69+
/// # Panics
70+
///
71+
/// Panics if an `Application` has already been created.
6972
///
70-
/// Right now only one application can be created. See [druid#771] for discussion.
73+
/// This may change in the future. See [druid#771] for discussion.
7174
///
7275
/// [druid#771]: https://github.com/xi-editor/druid/issues/771
73-
pub fn new(state: AppState, handler: Option<Box<dyn AppHandler>>) -> Application {
76+
pub fn new() -> Application {
7477
if APPLICATION_CREATED.compare_and_swap(false, true, Ordering::AcqRel) {
7578
panic!("The Application instance has already been created.");
7679
}
77-
Application(platform::Application::new(state.0, handler))
80+
util::claim_main_thread();
81+
let state = Rc::new(RefCell::new(State { running: false }));
82+
let app = Application {
83+
state,
84+
platform_app: platform::Application::new(),
85+
};
86+
GLOBAL_APP.with(|global_app| {
87+
*global_app.borrow_mut() = Some(app.clone());
88+
});
89+
app
7890
}
7991

80-
/// Start the runloop.
92+
/// Get the current globally active `Application`.
8193
///
82-
/// This will block the current thread until the program has finished executing.
83-
pub fn run(&mut self) {
84-
self.0.run()
94+
/// A globally active `Application` exists
95+
/// after [`new`] is called and until [`run`] returns.
96+
///
97+
/// # Panics
98+
///
99+
/// Panics if there is no globally active `Application`.
100+
/// For a non-panicking function use [`try_global`].
101+
///
102+
/// This function will also panic if called from a non-main thread.
103+
///
104+
/// [`new`]: #method.new
105+
/// [`run`]: #method.run
106+
/// [`try_global`]: #method.try_global
107+
#[inline]
108+
pub fn global() -> Application {
109+
// Main thread assertion takes place in try_global()
110+
Application::try_global().expect("There is no globally active Application")
85111
}
86112

87-
/// Terminate the application.
88-
pub fn quit() {
89-
platform::Application::quit()
113+
/// Get the current globally active `Application`.
114+
///
115+
/// A globally active `Application` exists
116+
/// after [`new`] is called and until [`run`] returns.
117+
///
118+
/// # Panics
119+
///
120+
/// Panics if called from a non-main thread.
121+
///
122+
/// [`new`]: #method.new
123+
/// [`run`]: #method.run
124+
pub fn try_global() -> Option<Application> {
125+
util::assert_main_thread();
126+
GLOBAL_APP.with(|global_app| global_app.borrow().clone())
127+
}
128+
129+
/// Start the `Application` runloop.
130+
///
131+
/// The provided `handler` will be used to inform of events.
132+
///
133+
/// This will consume the `Application` and block the current thread
134+
/// until the `Application` has finished executing.
135+
///
136+
/// # Panics
137+
///
138+
/// Panics if the `Application` is already running.
139+
pub fn run(self, handler: Option<Box<dyn AppHandler>>) {
140+
// Make sure this application hasn't run() yet.
141+
if let Ok(mut state) = self.state.try_borrow_mut() {
142+
if state.running {
143+
panic!("Application is already running");
144+
}
145+
state.running = true;
146+
} else {
147+
panic!("Application state already borrowed");
148+
}
149+
150+
// Run the platform application
151+
self.platform_app.run(handler);
152+
153+
// This application is no longer active, so clear the global reference
154+
GLOBAL_APP.with(|global_app| {
155+
*global_app.borrow_mut() = None;
156+
});
157+
// .. and release the main thread
158+
util::release_main_thread();
159+
}
160+
161+
/// Terminate the `Application`.
162+
///
163+
/// This will cause [`run`] to return control back to the calling function.
164+
///
165+
/// [`run`]: #method.run
166+
pub fn quit(&self) {
167+
self.platform_app.quit()
90168
}
91169

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

99177
/// Hide all other applications. (cmd+opt+H)
100-
pub fn hide_others() {
178+
pub fn hide_others(&self) {
101179
#[cfg(target_os = "macos")]
102-
platform::Application::hide_others()
180+
self.platform_app.hide_others()
103181
}
104182

105183
/// Returns a handle to the system clipboard.
106-
pub fn clipboard() -> Clipboard {
107-
platform::Application::clipboard().into()
184+
pub fn clipboard(&self) -> Clipboard {
185+
self.platform_app.clipboard().into()
108186
}
109187

110188
/// Returns the current locale string.

druid-shell/src/clipboard.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub use crate::platform::clipboard as platform;
7070
/// ```no_run
7171
/// use druid_shell::{Application, Clipboard};
7272
///
73-
/// let mut clipboard = Application::clipboard();
73+
/// let mut clipboard = Application::global().clipboard();
7474
/// clipboard.put_string("watch it there pal");
7575
/// if let Some(contents) = clipboard.get_string() {
7676
/// assert_eq!("what it there pal", contents.as_str());
@@ -83,7 +83,7 @@ pub use crate::platform::clipboard as platform;
8383
/// ```no_run
8484
/// use druid_shell::{Application, Clipboard, ClipboardFormat};
8585
///
86-
/// let mut clipboard = Application::clipboard();
86+
/// let mut clipboard = Application::global().clipboard();
8787
///
8888
/// let custom_type_id = "io.xieditor.path-clipboard-type";
8989
///
@@ -104,7 +104,7 @@ pub use crate::platform::clipboard as platform;
104104
/// ```no_run
105105
/// use druid_shell::{Application, Clipboard, ClipboardFormat};
106106
///
107-
/// let clipboard = Application::clipboard();
107+
/// let clipboard = Application::global().clipboard();
108108
///
109109
/// let custom_type_id = "io.xieditor.path-clipboard-type";
110110
/// let supported_types = &[custom_type_id, ClipboardFormat::SVG, ClipboardFormat::PDF];

druid-shell/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ mod keycodes;
3535
mod menu;
3636
mod mouse;
3737
mod platform;
38+
mod util;
3839
mod window;
3940

40-
pub use application::{AppHandler, AppState, Application};
41+
pub use application::{AppHandler, Application};
4142
pub use clipboard::{Clipboard, ClipboardFormat, FormatId};
4243
pub use common_util::Counter;
4344
pub use dialog::{FileDialogOptions, FileInfo, FileSpec};

druid-shell/src/platform/gtk/application.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,10 @@ thread_local!(
3232
);
3333

3434
#[derive(Clone)]
35-
pub struct AppState;
36-
37-
pub struct Application;
38-
39-
impl AppState {
40-
pub(crate) fn new() -> AppState {
41-
AppState
42-
}
43-
}
35+
pub(crate) struct Application;
4436

4537
impl Application {
46-
pub fn new(_state: AppState, _handler: Option<Box<dyn AppHandler>>) -> Application {
38+
pub fn new() -> Application {
4739
// TODO: we should give control over the application ID to the user
4840
let application = GtkApplication::new(
4941
Some("com.github.xi-editor.druid"),
@@ -68,7 +60,7 @@ impl Application {
6860
Application
6961
}
7062

71-
pub fn run(&mut self) {
63+
pub fn run(self, _handler: Option<Box<dyn AppHandler>>) {
7264
util::assert_main_thread();
7365

7466
// TODO: should we pass the command line arguments?
@@ -80,7 +72,7 @@ impl Application {
8072
});
8173
}
8274

83-
pub fn quit() {
75+
pub fn quit(&self) {
8476
util::assert_main_thread();
8577
with_application(|app| {
8678
match app.get_active_window() {
@@ -95,7 +87,7 @@ impl Application {
9587
});
9688
}
9789

98-
pub fn clipboard() -> Clipboard {
90+
pub fn clipboard(&self) -> Clipboard {
9991
Clipboard
10092
}
10193

0 commit comments

Comments
 (0)