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