cranpose_services/
host_surface.rs1use std::sync::{Arc, Mutex, OnceLock, atomic::Ordering};
11
12use cranpose_core::{State, rememberEventStream};
13use portable_atomic::AtomicU64;
14
15use crate::registry::ServiceRegistry;
16
17#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct HostSurfaceSize {
21 pub width: f32,
23 pub height: f32,
25 pub scale: f32,
27}
28
29impl Default for HostSurfaceSize {
30 fn default() -> Self {
31 Self {
32 width: 0.0,
33 height: 0.0,
34 scale: 1.0,
35 }
36 }
37}
38
39impl HostSurfaceSize {
40 pub fn physical(&self) -> (u32, u32) {
42 (
43 (self.width * self.scale).round().max(0.0) as u32,
44 (self.height * self.scale).round().max(0.0) as u32,
45 )
46 }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
51pub enum ResizeRefused {
52 #[error("this host does not accept surface resize requests")]
55 Unsupported,
56 #[error("the host refused the requested surface size")]
58 Rejected,
59}
60
61pub trait HostSurface: Send + Sync {
68 fn can_resize(&self) -> bool {
70 false
71 }
72
73 fn request_size(&self, width: f32, height: f32) -> Result<(), ResizeRefused> {
79 let _ = (width, height);
80 Err(ResizeRefused::Unsupported)
81 }
82}
83
84pub type HostSurfaceRef = Arc<dyn HostSurface>;
86
87struct NoHostSurface;
88
89impl HostSurface for NoHostSurface {}
90
91static PLATFORM_HOST_SURFACE: ServiceRegistry<dyn HostSurface> = ServiceRegistry::new();
92
93pub fn set_platform_host_surface(surface: HostSurfaceRef) {
95 PLATFORM_HOST_SURFACE.set(surface);
96}
97
98pub fn clear_platform_host_surface() {
100 PLATFORM_HOST_SURFACE.clear();
101 if let Ok(mut observers) = observers().lock() {
102 observers.clear();
103 }
104 if let Ok(mut last) = last_published().lock() {
105 *last = HostSurfaceSize::default();
106 }
107}
108
109pub fn host_surface_can_resize() -> bool {
111 host_surface().can_resize()
112}
113
114pub fn host_surface() -> HostSurfaceRef {
116 PLATFORM_HOST_SURFACE
117 .get()
118 .unwrap_or_else(|| Arc::new(NoHostSurface))
119}
120
121pub fn host_surface_size() -> HostSurfaceSize {
130 last_published()
131 .lock()
132 .map(|size| *size)
133 .unwrap_or_default()
134}
135
136fn last_published() -> &'static Mutex<HostSurfaceSize> {
137 static SLOT: OnceLock<Mutex<HostSurfaceSize>> = OnceLock::new();
138 SLOT.get_or_init(|| Mutex::new(HostSurfaceSize::default()))
139}
140
141pub fn request_host_surface_size(width: f32, height: f32) -> Result<(), ResizeRefused> {
143 host_surface().request_size(width, height)
144}
145
146type Observer = Arc<dyn Fn(HostSurfaceSize) + Send + Sync>;
147
148fn observers() -> &'static Mutex<Vec<(u64, Observer)>> {
149 static SLOT: OnceLock<Mutex<Vec<(u64, Observer)>>> = OnceLock::new();
150 SLOT.get_or_init(|| Mutex::new(Vec::new()))
151}
152
153static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
154
155pub struct HostSurfaceObserver {
157 id: u64,
158}
159
160impl Drop for HostSurfaceObserver {
161 fn drop(&mut self) {
162 if let Ok(mut observers) = observers().lock() {
163 observers.retain(|(id, _)| *id != self.id);
164 }
165 }
166}
167
168pub fn observe_host_surface_size(
171 observer: impl Fn(HostSurfaceSize) + Send + Sync + 'static,
172) -> HostSurfaceObserver {
173 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
174 if let Ok(mut observers) = observers().lock() {
175 observers.push((id, Arc::new(observer)));
176 }
177 HostSurfaceObserver { id }
178}
179
180pub fn publish_host_surface_size(size: HostSurfaceSize) {
183 if let Ok(mut last) = last_published().lock() {
184 if *last == size {
185 return;
186 }
187 *last = size;
188 }
189 let observers = observers()
190 .lock()
191 .map(|observers| {
192 observers
193 .iter()
194 .map(|(_, observer)| Arc::clone(observer))
195 .collect::<Vec<_>>()
196 })
197 .unwrap_or_default();
198 for observer in observers {
199 observer(size);
200 }
201}
202
203#[expect(non_snake_case)]
206#[track_caller]
207pub fn rememberHostSurfaceSize() -> State<HostSurfaceSize> {
208 let updates = rememberEventStream((), |sender| {
209 observe_host_surface_size(move |size| sender.send(size))
210 });
211 cranpose_core::collectAsState(updates, (), host_surface_size())
212}
213
214#[cfg(test)]
215#[path = "tests/host_surface_tests.rs"]
216mod tests;