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}