show_image/backend/proxy.rs
1use crate::ContextHandle;
2use crate::Image;
3use crate::WindowHandle;
4use crate::WindowId;
5use crate::error::{InvalidWindowId, SetImageError};
6use crate::event::Event;
7use crate::event::EventHandlerControlFlow;
8use crate::event::WindowEvent;
9use crate::oneshot;
10
11use std::sync::mpsc;
12
13/// Proxy object to interact with a window from a user thread.
14///
15/// The proxy object only exposes a small subset of the functionality of a window.
16/// However, you can use [`run_function()`][Self::run_function]
17/// to get access to the underlying [`WindowHandle`] from the context thread.
18/// With [`run_function_wait()`][Self::run_function_wait`] you can also get the return value of the function back:
19///
20/// ```no_run
21/// # fn foo(window_proxy: show_image::WindowProxy) -> Result<(), show_image::error::InvalidWindowId> {
22/// let inner_size = window_proxy.run_function_wait(|window| window.inner_size())?;
23/// # Ok(())
24/// # }
25/// ```
26///
27/// You should not use proxy objects from withing the global context thread.
28/// The proxy objects often wait for the global context to perform some action.
29/// Doing so from within the global context thread would cause a deadlock.
30#[derive(Clone)]
31pub struct WindowProxy {
32 window_id: WindowId,
33 context_proxy: ContextProxy,
34}
35
36/// Proxy object to interact with the global context from a user thread.
37///
38/// You should not use proxy objects from withing the global context thread.
39/// The proxy objects often wait for the global context to perform some action.
40/// Doing so from within the global context thread would cause a deadlock.
41#[derive(Clone)]
42pub struct ContextProxy {
43 event_loop: EventLoopProxy,
44 context_thread: std::thread::ThreadId,
45}
46
47/// Dynamic function that can be run by the global context.
48pub type ContextFunction = Box<dyn FnOnce(&mut ContextHandle) + Send>;
49
50/// Internal shorthand for the correct `winit::event::EventLoopProxy`.
51///
52/// Not for use in public APIs.
53type EventLoopProxy = winit::event_loop::EventLoopProxy<ContextFunction>;
54
55impl ContextProxy {
56 /// Wrap an [`EventLoopProxy`] in a [`ContextProxy`].
57 pub(crate) fn new(event_loop: EventLoopProxy, context_thread: std::thread::ThreadId) -> Self {
58 Self {
59 event_loop,
60 context_thread,
61 }
62 }
63
64 /// Add a global event handler to the context.
65 ///
66 /// Events that are already queued with the event loop will not be passed to the handler.
67 ///
68 /// This function uses [`Self::run_function_wait`] internally, so it blocks until the event handler is added.
69 /// To avoid blocking, you can use [`Self::run_function`] to post a lambda that adds an error handler instead.
70 ///
71 /// # Panics
72 /// This function will panic if called from within the context thread.
73 pub fn add_event_handler<F>(&self, handler: F)
74 where
75 F: FnMut(&mut ContextHandle, &mut Event, &mut EventHandlerControlFlow) + Send + 'static,
76 {
77 self.run_function_wait(move |context| context.add_event_handler(handler))
78 }
79
80 /// Add an event handler for a specific window.
81 ///
82 /// Events that are already queued with the event loop will not be passed to the handler.
83 ///
84 /// This function uses [`Self::run_function_wait`] internally, so it blocks until the event handler is added.
85 /// To avoid blocking, you can use [`Self::run_function`] to post a lambda that adds an error handler instead.
86 ///
87 /// # Panics
88 /// This function will panic if called from within the context thread.
89 pub fn add_window_event_handler<F>(&self, window_id: WindowId, handler: F) -> Result<(), InvalidWindowId>
90 where
91 F: FnMut(WindowHandle, &mut WindowEvent, &mut EventHandlerControlFlow) + Send + 'static,
92 {
93 self.run_function_wait(move |context| {
94 let mut window = context.window(window_id)?;
95 window.add_event_handler(handler);
96 Ok(())
97 })
98 }
99
100 /// Post a function for execution in the context thread without waiting for it to execute.
101 ///
102 /// This function returns immediately, without waiting for the posted function to start or complete.
103 /// If you want to get a return value back from the function, use [`Self::run_function_wait`] instead.
104 ///
105 /// *Note:*
106 /// You should not post functions to the context thread that block for a long time.
107 /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue.
108 /// Consider using [`Self::run_background_task`] for long blocking tasks instead.
109 ///
110 /// # Panics
111 /// This function will panic if called from within the context thread.
112 pub fn run_function<F>(&self, function: F)
113 where
114 F: 'static + FnOnce(&mut ContextHandle) + Send,
115 {
116 let function = Box::new(function);
117 if self.event_loop.send_event(function).is_err() {
118 panic!("global context stopped running but somehow the process is still alive");
119 }
120 }
121
122 /// Post a function for execution in the context thread and wait for the return value.
123 ///
124 /// If you do not need a return value from the posted function,
125 /// you can use [`Self::run_function`] to avoid blocking the calling thread until it completes.
126 ///
127 /// *Note:*
128 /// You should not post functions to the context thread that block for a long time.
129 /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue.
130 /// Consider using [`Self::run_background_task`] for long blocking tasks instead.
131 ///
132 /// # Panics
133 /// This function will panic if called from within the context thread.
134 pub fn run_function_wait<F, T>(&self, function: F) -> T
135 where
136 F: FnOnce(&mut ContextHandle) -> T + Send + 'static,
137 T: Send + 'static,
138 {
139 self.assert_thread();
140
141 let (result_tx, result_rx) = oneshot::channel();
142 self.run_function(move |context| result_tx.send((function)(context)));
143 result_rx.recv()
144 .expect("global context failed to send function return value back, which can only happen if the event loop stopped, but that should also kill the process")
145 }
146
147 /// Run a task in a background thread and register it with the context.
148 ///
149 /// The task will be executed in a different thread than the context.
150 /// Currently, each task is spawned in a separate thread.
151 /// In the future, tasks may be run in a dedicated thread pool.
152 ///
153 /// The background task will be joined before the process is terminated when you use [`Self::exit()`] or one of the other exit functions of this crate.
154 pub fn run_background_task<F>(&self, task: F)
155 where
156 F: FnOnce() + Send + 'static,
157 {
158 self.run_function(move |context| {
159 context.run_background_task(task);
160 });
161 }
162
163 /// Create a channel that receives events from the context.
164 ///
165 /// To close the channel, simply drop de receiver.
166 ///
167 /// *Warning:*
168 /// The created channel blocks when you request an event until one is available.
169 /// You should never use the receiver from within an event handler or a function posted to the global context thread.
170 /// Doing so would cause a deadlock.
171 ///
172 /// # Panics
173 /// This function will panic if called from within the context thread.
174 pub fn event_channel(&self) -> mpsc::Receiver<Event> {
175 let (tx, rx) = mpsc::channel();
176 self.add_event_handler(move |_context, event, control| {
177 // If the receiver is dropped, remove the handler.
178 if tx.send(event.clone()).is_err() {
179 control.remove_handler = true;
180 }
181 });
182
183 rx
184 }
185
186 /// Create a channel that receives events from a window.
187 ///
188 /// To close the channel, simply drop de receiver.
189 /// The channel is closed automatically when the window is destroyed.
190 ///
191 /// *Warning:*
192 /// The created channel blocks when you request an event until one is available.
193 /// You should never use the receiver from within an event handler or a function posted to the global context thread.
194 /// Doing so would cause a deadlock.
195 ///
196 /// # Panics
197 /// This function will panic if called from within the context thread.
198 pub fn window_event_channel(&self, window_id: WindowId) -> Result<mpsc::Receiver<WindowEvent>, InvalidWindowId> {
199 let (tx, rx) = mpsc::channel();
200 self.add_window_event_handler(window_id, move |_window, event, control| {
201 // If the receiver is dropped, remove the handler.
202 if tx.send(event.clone()).is_err() {
203 control.remove_handler = true;
204 }
205 })?;
206 Ok(rx)
207 }
208
209 /// Join all background tasks and then exit the process.
210 ///
211 /// If you use [`std::process::exit`], running background tasks may be killed.
212 /// To ensure no data loss occurs, you should use this function instead.
213 ///
214 /// Background tasks are spawned when an image is saved through the built-in Ctrl+S or Ctrl+Shift+S shortcut, or by user code.
215 ///
216 /// # Panics
217 /// This function will panic if called from within the context thread.
218 pub fn exit(&self, code: i32) -> ! {
219 self.assert_thread();
220 self.run_function(move |context| context.exit(code));
221 loop {
222 std::thread::park();
223 }
224 }
225
226 /// Check that the current thread is not running the context event loop.
227 ///
228 /// # Panics
229 /// This function will panic if called from within the context thread.
230 #[track_caller]
231 fn assert_thread(&self) {
232 if std::thread::current().id() == self.context_thread {
233 panic!("ContextProxy used from within the context thread, which would cause a deadlock. Use ContextHandle instead.");
234 }
235 }
236}
237
238impl WindowProxy {
239 /// Create a new window proxy from a context proxy and a window ID.
240 pub fn new(window_id: WindowId, context_proxy: ContextProxy) -> Self {
241 Self { window_id, context_proxy }
242 }
243
244 /// Get the window ID.
245 pub fn id(&self) -> WindowId {
246 self.window_id
247 }
248
249 /// Get the context proxy of the window proxy.
250 pub fn context_proxy(&self) -> &ContextProxy {
251 &self.context_proxy
252 }
253
254 /// Set the displayed image of the window.
255 ///
256 /// The real work is done in the context thread.
257 /// This function blocks until the context thread has performed the action.
258 ///
259 /// Note that you can not change the overlays with this function.
260 /// To modify those, you can use [`Self::run_function`] or [`Self::run_function_wait`]
261 /// to get access to the [`WindowHandle`].
262 ///
263 /// # Panics
264 /// This function will panic if called from within the context thread.
265 pub fn set_image(&self, name: impl Into<String>, image: impl Into<Image>) -> Result<(), SetImageError> {
266 let name = name.into();
267 let image = image.into();
268 self.run_function_wait(move |mut window| -> Result<(), SetImageError> {
269 window.set_image(name, &image.as_image_view()?);
270 Ok(())
271 })?
272 }
273
274 /// Add an event handler for the window.
275 ///
276 /// Events that are already queued with the event loop will not be passed to the handler.
277 ///
278 /// This function uses [`ContextProxy::run_function_wait`] internally, so it blocks until the event handler is added.
279 /// To avoid blocking, you can use [`ContextProxy::run_function`] to post a lambda that adds an event handler instead.
280 ///
281 /// # Panics
282 /// This function will panic if called from within the context thread.
283 pub fn add_event_handler<F>(&self, handler: F) -> Result<(), InvalidWindowId>
284 where
285 F: FnMut(WindowHandle, &mut WindowEvent, &mut EventHandlerControlFlow) + Send + 'static,
286 {
287 self.context_proxy.add_window_event_handler(self.window_id, handler)
288 }
289
290 /// Create a channel that receives events from the window.
291 ///
292 /// To close the channel, simply drop de receiver.
293 /// The channel is closed automatically when the window is destroyed.
294 ///
295 /// *Warning:*
296 /// The created channel blocks when you request an event until one is available.
297 /// You should never use the receiver from within an event handler or a function posted to the global context thread.
298 /// Doing so would cause a deadlock.
299 ///
300 /// # Panics
301 /// This function will panic if called from within the context thread.
302 pub fn event_channel(&self) -> Result<mpsc::Receiver<WindowEvent>, InvalidWindowId> {
303 self.context_proxy.window_event_channel(self.window_id)
304 }
305
306 /// Wait for the window to be destroyed.
307 ///
308 /// This can happen if the application code destroys the window or if the user closes the window.
309 ///
310 /// *Warning:*
311 /// This function blocks until the window is closed.
312 /// You should never use this function from within an event handler or a function posted to the global context thread.
313 /// Doing so would cause a deadlock.
314 ///
315 /// # Panics
316 /// This function will panic if called from within the context thread.
317 pub fn wait_until_destroyed(&self) -> Result<(), InvalidWindowId> {
318 let (tx, rx) = oneshot::channel::<()>();
319 self.add_event_handler(move |_window, _event, _control| {
320 // Need to mention the tx half so it gets moved into the closure.
321 let _tx = &tx;
322 })?;
323
324 // We actually want to wait for the transmit handle to be dropped, so ignore receive errors.
325 let _ = rx.recv();
326 Ok(())
327 }
328
329 /// Post a function for execution in the context thread without waiting for it to execute.
330 ///
331 /// This function returns immediately, without waiting for the posted function to start or complete.
332 /// If you want to get a return value back from the function, use [`Self::run_function_wait`] instead.
333 ///
334 /// *Note:*
335 /// You should not use this to post functions that block for a long time.
336 /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue.
337 /// Consider using [`self.context_proxy().run_background_task(...)`][ContextProxy::run_background_task] for long blocking tasks instead.
338 ///
339 /// # Panics
340 /// This function will panic if called from within the context thread.
341 pub fn run_function<F>(&self, function: F)
342 where
343 F: 'static + FnOnce(WindowHandle) + Send,
344 {
345 let window_id = self.window_id;
346 self.context_proxy.run_function(move |context| {
347 if let Ok(window) = context.window(window_id) {
348 function(window);
349 }
350 })
351 }
352
353 /// Post a function for execution in the context thread and wait for the return value.
354 ///
355 /// If you do not need a return value from the posted function,
356 /// you can use [`Self::run_function`] to avoid blocking the calling thread until it completes.
357 ///
358 /// *Note:*
359 /// You should not use this to post functions that block for a long time.
360 /// Doing so will block the event loop and will make the windows unresponsive until the event loop can continue.
361 /// Consider using [`self.context_proxy().run_background_task(...)`][ContextProxy::run_background_task] for long blocking tasks instead.
362 ///
363 /// # Panics
364 /// This function will panic if called from within the context thread.
365 pub fn run_function_wait<F, T>(&self, function: F) -> Result<T, InvalidWindowId>
366 where
367 F: FnOnce(WindowHandle) -> T + Send + 'static,
368 T: Send + 'static,
369 {
370 let window_id = self.window_id;
371 self.context_proxy.run_function_wait(move |context| {
372 let window = context.window(window_id)?;
373 Ok(function(window))
374 })
375 }
376}