1414
1515//! The top-level application type.
1616
17+ use std:: cell:: RefCell ;
18+ use std:: rc:: Rc ;
1719use std:: sync:: atomic:: { AtomicBool , Ordering } ;
1820
1921use crate :: clipboard:: Clipboard ;
2022use 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.
6359static 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+
6566impl 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.
0 commit comments