druid_shell/
application.rs

1// Copyright 2019 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The top-level application type.
16
17use std::cell::RefCell;
18use std::rc::Rc;
19use std::sync::atomic::{AtomicBool, Ordering};
20
21use crate::backend::application as backend;
22use crate::clipboard::Clipboard;
23use crate::error::Error;
24use crate::util;
25
26/// A top-level handler that is not associated with any window.
27///
28/// This is most important on macOS, where it is entirely normal for
29/// an application to exist with no open windows.
30///
31/// # Note
32///
33/// This is currently very limited in its functionality, and is currently
34/// designed to address a single case, which is handling menu commands when
35/// no window is open.
36///
37/// It is possible that this will expand to cover additional functionality
38/// in the future.
39pub trait AppHandler {
40    /// Called when a menu item is selected.
41    #[allow(unused_variables)]
42    fn command(&mut self, id: u32) {}
43}
44
45/// The top level application object.
46///
47/// This can be thought of as a reference and it can be safely cloned.
48#[derive(Clone)]
49pub struct Application {
50    pub(crate) backend_app: backend::Application,
51    state: Rc<RefCell<State>>,
52}
53
54/// Platform-independent `Application` state.
55struct State {
56    running: bool,
57}
58
59/// Used to ensure only one Application instance is ever created.
60static APPLICATION_CREATED: AtomicBool = AtomicBool::new(false);
61
62thread_local! {
63    /// A reference object to the current `Application`, if any.
64    static GLOBAL_APP: RefCell<Option<Application>> = RefCell::new(None);
65}
66
67impl Application {
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/linebender/druid/issues/771
77    pub fn new() -> Result<Application, Error> {
78        APPLICATION_CREATED
79            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
80            .map_err(|_| Error::ApplicationAlreadyExists)?;
81        util::claim_main_thread();
82        let backend_app = backend::Application::new()?;
83        let state = Rc::new(RefCell::new(State { running: false }));
84        let app = Application { backend_app, state };
85        GLOBAL_APP.with(|global_app| {
86            *global_app.borrow_mut() = Some(app.clone());
87        });
88        Ok(app)
89    }
90
91    /// Get the current globally active `Application`.
92    ///
93    /// A globally active `Application` exists
94    /// after [`new`] is called and until [`run`] returns.
95    ///
96    /// # Panics
97    ///
98    /// Panics if there is no globally active `Application`.
99    /// For a non-panicking function use [`try_global`].
100    ///
101    /// This function will also panic if called from a non-main thread.
102    ///
103    /// [`new`]: #method.new
104    /// [`run`]: #method.run
105    /// [`try_global`]: #method.try_global
106    #[inline]
107    pub fn global() -> Application {
108        // Main thread assertion takes place in try_global()
109        Application::try_global().expect("There is no globally active Application")
110    }
111
112    /// Get the current globally active `Application`.
113    ///
114    /// A globally active `Application` exists
115    /// after [`new`] is called and until [`run`] returns.
116    ///
117    /// # Panics
118    ///
119    /// Panics if called from a non-main thread.
120    ///
121    /// [`new`]: #method.new
122    /// [`run`]: #method.run
123    pub fn try_global() -> Option<Application> {
124        util::assert_main_thread_or_main_unclaimed();
125        GLOBAL_APP.with(|global_app| global_app.borrow().clone())
126    }
127
128    /// Start the `Application` runloop.
129    ///
130    /// The provided `handler` will be used to inform of events.
131    ///
132    /// This will consume the `Application` and block the current thread
133    /// until the `Application` has finished executing.
134    ///
135    /// # Panics
136    ///
137    /// Panics if the `Application` is already running.
138    pub fn run(self, handler: Option<Box<dyn AppHandler>>) {
139        // Make sure this application hasn't run() yet.
140        if let Ok(mut state) = self.state.try_borrow_mut() {
141            if state.running {
142                panic!("Application is already running");
143            }
144            state.running = true;
145        } else {
146            panic!("Application state already borrowed");
147        }
148
149        // Run the platform application
150        self.backend_app.run(handler);
151
152        // This application is no longer active, so clear the global reference
153        GLOBAL_APP.with(|global_app| {
154            *global_app.borrow_mut() = None;
155        });
156        // .. and release the main thread
157        util::release_main_thread();
158        // .. and mark as done so a new sequence can start
159        APPLICATION_CREATED
160            .compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire)
161            .expect("Application marked as not created while still running.");
162    }
163
164    /// Quit the `Application`.
165    ///
166    /// This will cause [`run`] to return control back to the calling function.
167    ///
168    /// [`run`]: #method.run
169    pub fn quit(&self) {
170        self.backend_app.quit()
171    }
172
173    /// Returns a handle to the system clipboard.
174    pub fn clipboard(&self) -> Clipboard {
175        self.backend_app.clipboard().into()
176    }
177
178    /// Returns the current locale string.
179    ///
180    /// This should a [Unicode language identifier].
181    ///
182    /// [Unicode language identifier]: https://unicode.org/reports/tr35/#Unicode_language_identifier
183    pub fn get_locale() -> String {
184        backend::Application::get_locale()
185    }
186}