1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Code a desktop app runs on the shell's main thread. It sees each window's
//! raw handles as the window opens and runs before the event loop waits, so
//! a tray icon, a native menu or another platform integration the shell does
//! not provide attaches without forking the shell.
use cranpose_app_shell::AppShell;
use cranpose_render_wgpu::WgpuRenderer;
use raw_window_handle::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
};
use winit::event_loop::EventLoopProxy;
/// Names a desktop window while it is open.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DesktopWindowId {
/// The app's primary window.
Primary,
/// A window that [`WindowModifierExt::window`](crate::WindowModifierExt::window)
/// opens, by the raw value of its key.
Native(u64),
}
/// A window of a desktop app, as a [`DesktopIntegration`] call sees it. Its
/// raw handles are valid for that call.
pub struct DesktopWindow<'w> {
id: DesktopWindowId,
window: &'w dyn winit::window::Window,
}
impl DesktopWindow<'_> {
/// Which window this is.
pub fn id(&self) -> DesktopWindowId {
self.id
}
}
impl HasWindowHandle for DesktopWindow<'_> {
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
self.window.window_handle()
}
}
impl HasDisplayHandle for DesktopWindow<'_> {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.window.display_handle()
}
}
/// Wakes the desktop event loop from any thread, so that it calls
/// [`DesktopIntegration::about_to_wait`] again: for event sources that do
/// not wake it themselves.
#[derive(Clone)]
pub struct DesktopWaker {
proxy: EventLoopProxy,
}
impl DesktopWaker {
/// Wakes the event loop.
pub fn wake(&self) {
self.proxy.wake_up();
}
}
/// What a [`DesktopIntegration`] can do during a call.
pub struct DesktopIntegrationContext<'a> {
app: &'a mut AppShell<WgpuRenderer>,
proxy: &'a EventLoopProxy,
}
impl DesktopIntegrationContext<'_> {
/// Runs `update` as an event handler runs: in the app's context and a
/// mutable snapshot, so it may write state, and the app draws the frame
/// the change asks for. Fails when the snapshot does not apply.
pub fn update<T>(&mut self, update: impl FnOnce() -> T) -> Result<T, &'static str> {
let _event_handler = cranpose_core::enter_event_handler_scope();
let result = self
.app
.app_context()
.enter(|| cranpose_core::run_in_mutable_snapshot(update));
self.app.mark_dirty();
self.proxy.wake_up();
result
}
/// A waker for the event loop, which may move to another thread.
pub fn waker(&self) -> DesktopWaker {
DesktopWaker {
proxy: self.proxy.clone(),
}
}
}
/// Code a desktop app runs on the shell's main thread, given to
/// [`AppLauncher::with_desktop_integration`](crate::AppLauncher::with_desktop_integration).
/// Every call does nothing by default.
pub trait DesktopIntegration: 'static {
/// `window` opened: the primary window first, then each window that
/// [`WindowModifierExt::window`](crate::WindowModifierExt::window) opens.
fn window_opened(
&mut self,
window: &DesktopWindow<'_>,
context: &mut DesktopIntegrationContext<'_>,
) {
let _ = (window, context);
}
/// The native window `id` closed.
fn window_closed(&mut self, id: DesktopWindowId, context: &mut DesktopIntegrationContext<'_>) {
let _ = (id, context);
}
/// The event loop is about to wait for events. Take the events of the
/// integration's own sources here, such as a tray icon's clicks.
fn about_to_wait(&mut self, context: &mut DesktopIntegrationContext<'_>) {
let _ = context;
}
}
/// The integration of a running desktop app, if it has one.
#[derive(Default)]
pub(crate) struct Integration(Option<Box<dyn DesktopIntegration>>);
impl Integration {
pub(crate) fn new(integration: Option<Box<dyn DesktopIntegration>>) -> Self {
Self(integration)
}
pub(crate) fn window_opened(
&mut self,
id: DesktopWindowId,
window: &dyn winit::window::Window,
app: &mut AppShell<WgpuRenderer>,
proxy: &EventLoopProxy,
) {
if let Some(integration) = &mut self.0 {
let window = DesktopWindow { id, window };
integration.window_opened(&window, &mut DesktopIntegrationContext { app, proxy });
}
}
pub(crate) fn window_closed(
&mut self,
id: DesktopWindowId,
app: &mut AppShell<WgpuRenderer>,
proxy: &EventLoopProxy,
) {
if let Some(integration) = &mut self.0 {
integration.window_closed(id, &mut DesktopIntegrationContext { app, proxy });
}
}
pub(crate) fn about_to_wait(
&mut self,
app: &mut AppShell<WgpuRenderer>,
proxy: &EventLoopProxy,
) {
if let Some(integration) = &mut self.0 {
integration.about_to_wait(&mut DesktopIntegrationContext { app, proxy });
}
}
}