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