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}