1414
1515//! The top-level application type.
1616
17+ use std:: cell:: RefCell ;
18+ use std:: rc:: Rc ;
19+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
20+
1721use crate :: clipboard:: Clipboard ;
22+ use crate :: error:: Error ;
1823use crate :: platform:: application as platform;
24+ use crate :: util;
1925
2026/// A top-level handler that is not associated with any window.
2127///
@@ -36,44 +42,152 @@ pub trait AppHandler {
3642 fn command ( & mut self , id : u32 ) { }
3743}
3844
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.
4145/// 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+ }
4366
4467impl 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 ( ) )
47132 }
48133
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
50142 ///
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 ( ) ;
54164 }
55165
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 ( )
59173 }
60174
61175 // TODO: do these two go in some kind of PlatformExt trait?
62176 /// Hide the application this window belongs to. (cmd+H)
63- pub fn hide ( ) {
177+ pub fn hide ( & self ) {
64178 #[ cfg( target_os = "macos" ) ]
65- platform :: Application :: hide ( )
179+ self . platform_app . hide ( )
66180 }
67181
68182 /// Hide all other applications. (cmd+opt+H)
69- pub fn hide_others ( ) {
183+ pub fn hide_others ( & self ) {
70184 #[ cfg( target_os = "macos" ) ]
71- platform :: Application :: hide_others ( )
185+ self . platform_app . hide_others ( )
72186 }
73187
74188 /// 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 ( )
77191 }
78192
79193 /// Returns the current locale string.
0 commit comments