14
14
15
15
//! The top-level application type.
16
16
17
+ use std:: cell:: RefCell ;
18
+ use std:: rc:: Rc ;
19
+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
20
+
17
21
use crate :: clipboard:: Clipboard ;
22
+ use crate :: error:: Error ;
18
23
use crate :: platform:: application as platform;
24
+ use crate :: util;
19
25
20
26
/// A top-level handler that is not associated with any window.
21
27
///
@@ -36,44 +42,152 @@ pub trait AppHandler {
36
42
fn command ( & mut self , id : u32 ) { }
37
43
}
38
44
39
- //TODO: we may want to make the user create an instance of this (Application::global()?)
40
- //but for now I'd like to keep changes minimal.
41
45
/// The top level application object.
42
- pub struct Application ( platform:: Application ) ;
46
+ ///
47
+ /// This can be thought of as a reference and it can be safely cloned.
48
+ #[ derive( Clone ) ]
49
+ pub struct Application {
50
+ pub ( crate ) platform_app : platform:: Application ,
51
+ state : Rc < RefCell < State > > ,
52
+ }
53
+
54
+ /// Platform-independent `Application` state.
55
+ struct State {
56
+ running : bool ,
57
+ }
58
+
59
+ /// Used to ensure only one Application instance is ever created.
60
+ static APPLICATION_CREATED : AtomicBool = AtomicBool :: new ( false ) ;
61
+
62
+ thread_local ! {
63
+ /// A reference object to the current `Application`, if any.
64
+ static GLOBAL_APP : RefCell <Option <Application >> = RefCell :: new( None ) ;
65
+ }
43
66
44
67
impl Application {
45
- pub fn new ( handler : Option < Box < dyn AppHandler > > ) -> Application {
46
- Application ( platform:: Application :: new ( handler) )
68
+ /// Create a new `Application`.
69
+ ///
70
+ /// # Errors
71
+ ///
72
+ /// Errors if an `Application` has already been created.
73
+ ///
74
+ /// This may change in the future. See [druid#771] for discussion.
75
+ ///
76
+ /// [druid#771]: https://github.com/xi-editor/druid/issues/771
77
+ pub fn new ( ) -> Result < Application , Error > {
78
+ if APPLICATION_CREATED . compare_and_swap ( false , true , Ordering :: AcqRel ) {
79
+ return Err ( Error :: ApplicationAlreadyExists ) ;
80
+ }
81
+ util:: claim_main_thread ( ) ;
82
+ let platform_app = match platform:: Application :: new ( ) {
83
+ Ok ( app) => app,
84
+ Err ( err) => return Err ( Error :: Platform ( err) ) ,
85
+ } ;
86
+ let state = Rc :: new ( RefCell :: new ( State { running : false } ) ) ;
87
+ let app = Application {
88
+ platform_app,
89
+ state,
90
+ } ;
91
+ GLOBAL_APP . with ( |global_app| {
92
+ * global_app. borrow_mut ( ) = Some ( app. clone ( ) ) ;
93
+ } ) ;
94
+ Ok ( app)
95
+ }
96
+
97
+ /// Get the current globally active `Application`.
98
+ ///
99
+ /// A globally active `Application` exists
100
+ /// after [`new`] is called and until [`run`] returns.
101
+ ///
102
+ /// # Panics
103
+ ///
104
+ /// Panics if there is no globally active `Application`.
105
+ /// For a non-panicking function use [`try_global`].
106
+ ///
107
+ /// This function will also panic if called from a non-main thread.
108
+ ///
109
+ /// [`new`]: #method.new
110
+ /// [`run`]: #method.run
111
+ /// [`try_global`]: #method.try_global
112
+ #[ inline]
113
+ pub fn global ( ) -> Application {
114
+ // Main thread assertion takes place in try_global()
115
+ Application :: try_global ( ) . expect ( "There is no globally active Application" )
116
+ }
117
+
118
+ /// Get the current globally active `Application`.
119
+ ///
120
+ /// A globally active `Application` exists
121
+ /// after [`new`] is called and until [`run`] returns.
122
+ ///
123
+ /// # Panics
124
+ ///
125
+ /// Panics if called from a non-main thread.
126
+ ///
127
+ /// [`new`]: #method.new
128
+ /// [`run`]: #method.run
129
+ pub fn try_global ( ) -> Option < Application > {
130
+ util:: assert_main_thread ( ) ;
131
+ GLOBAL_APP . with ( |global_app| global_app. borrow ( ) . clone ( ) )
47
132
}
48
133
49
- /// Start the runloop.
134
+ /// Start the `Application` runloop.
135
+ ///
136
+ /// The provided `handler` will be used to inform of events.
137
+ ///
138
+ /// This will consume the `Application` and block the current thread
139
+ /// until the `Application` has finished executing.
140
+ ///
141
+ /// # Panics
50
142
///
51
- /// This will block the current thread until the program has finished executing.
52
- pub fn run ( & mut self ) {
53
- self . 0 . run ( )
143
+ /// Panics if the `Application` is already running.
144
+ pub fn run ( self , handler : Option < Box < dyn AppHandler > > ) {
145
+ // Make sure this application hasn't run() yet.
146
+ if let Ok ( mut state) = self . state . try_borrow_mut ( ) {
147
+ if state. running {
148
+ panic ! ( "Application is already running" ) ;
149
+ }
150
+ state. running = true ;
151
+ } else {
152
+ panic ! ( "Application state already borrowed" ) ;
153
+ }
154
+
155
+ // Run the platform application
156
+ self . platform_app . run ( handler) ;
157
+
158
+ // This application is no longer active, so clear the global reference
159
+ GLOBAL_APP . with ( |global_app| {
160
+ * global_app. borrow_mut ( ) = None ;
161
+ } ) ;
162
+ // .. and release the main thread
163
+ util:: release_main_thread ( ) ;
54
164
}
55
165
56
- /// Terminate the application.
57
- pub fn quit ( ) {
58
- platform:: Application :: quit ( )
166
+ /// Quit the `Application`.
167
+ ///
168
+ /// This will cause [`run`] to return control back to the calling function.
169
+ ///
170
+ /// [`run`]: #method.run
171
+ pub fn quit ( & self ) {
172
+ self . platform_app . quit ( )
59
173
}
60
174
61
175
// TODO: do these two go in some kind of PlatformExt trait?
62
176
/// Hide the application this window belongs to. (cmd+H)
63
- pub fn hide ( ) {
177
+ pub fn hide ( & self ) {
64
178
#[ cfg( target_os = "macos" ) ]
65
- platform :: Application :: hide ( )
179
+ self . platform_app . hide ( )
66
180
}
67
181
68
182
/// Hide all other applications. (cmd+opt+H)
69
- pub fn hide_others ( ) {
183
+ pub fn hide_others ( & self ) {
70
184
#[ cfg( target_os = "macos" ) ]
71
- platform :: Application :: hide_others ( )
185
+ self . platform_app . hide_others ( )
72
186
}
73
187
74
188
/// Returns a handle to the system clipboard.
75
- pub fn clipboard ( ) -> Clipboard {
76
- platform :: Application :: clipboard ( ) . into ( )
189
+ pub fn clipboard ( & self ) -> Clipboard {
190
+ self . platform_app . clipboard ( ) . into ( )
77
191
}
78
192
79
193
/// Returns the current locale string.
0 commit comments