dear_imgui_rs/context/
platform.rs1use crate::sys;
2
3use super::Context;
4#[cfg(feature = "multi-viewport")]
5use super::attachment::{ContextPlatformWindowTeardown, ContextPlatformWindowTeardownError};
6use super::binding::{CTX_MUTEX, with_bound_context};
7
8#[cfg(feature = "multi-viewport")]
9struct PlatformDrawDataTextureMask {
10 entries: Vec<(
11 std::ptr::NonNull<sys::ImDrawData>,
12 *mut sys::ImVector_ImTextureDataPtr,
13 )>,
14}
15
16#[cfg(feature = "multi-viewport")]
17impl PlatformDrawDataTextureMask {
18 unsafe fn install(platform_io: *mut sys::ImGuiPlatformIO) -> Self {
19 let mut entries = Vec::new();
20 let viewports = unsafe { &(*platform_io).Viewports };
21 if viewports.Size <= 0 {
22 return Self { entries };
23 }
24 assert!(
25 viewports.Capacity >= viewports.Size && !viewports.Data.is_null(),
26 "ImGuiPlatformIO.Viewports has invalid native storage"
27 );
28 for index in 0..viewports.Size as usize {
29 let viewport = unsafe { *viewports.Data.add(index) };
30 let Some(viewport) = std::ptr::NonNull::new(viewport) else {
31 continue;
32 };
33 let Some(draw_data) = std::ptr::NonNull::new(unsafe { viewport.as_ref().DrawData })
34 else {
35 continue;
36 };
37 let textures = unsafe { draw_data.as_ref().Textures };
38 if textures.is_null() {
39 continue;
40 }
41 unsafe { (*draw_data.as_ptr()).Textures = std::ptr::null_mut() };
42 entries.push((draw_data, textures));
43 }
44 Self { entries }
45 }
46}
47
48#[cfg(feature = "multi-viewport")]
49impl Drop for PlatformDrawDataTextureMask {
50 fn drop(&mut self) {
51 for (draw_data, textures) in self.entries.drain(..) {
52 unsafe { (*draw_data.as_ptr()).Textures = textures };
53 }
54 }
55}
56
57impl Context {
58 #[doc(alias = "GetPlatformIO")]
63 pub fn platform_io(&self) -> &crate::platform_io::PlatformIo {
64 let _guard = CTX_MUTEX.lock();
65 unsafe {
66 let pio = self.platform_io_ptr("Context::platform_io()");
67 crate::platform_io::PlatformIo::from_raw(pio)
68 }
69 }
70
71 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
76 let _guard = CTX_MUTEX.lock();
77 unsafe {
78 let pio = self.platform_io_ptr("Context::platform_io_mut()");
79 crate::platform_io::PlatformIo::from_raw_mut(pio)
80 }
81 }
82
83 #[doc(alias = "GetMainViewport")]
88 pub fn main_viewport(&mut self) -> &mut crate::platform_io::Viewport {
89 let _guard = CTX_MUTEX.lock();
90 unsafe {
91 with_bound_context(self.raw, || {
92 let ptr = sys::igGetMainViewport();
93 if ptr.is_null() {
94 panic!("Context::main_viewport() requires a valid ImGui context");
95 }
96 crate::platform_io::Viewport::from_raw_mut(ptr)
97 })
98 }
99 }
100
101 #[cfg(feature = "multi-viewport")]
112 pub fn enable_multi_viewport(&mut self) {
113 let io = self.io_mut();
114 let mut flags = io.config_flags();
115 flags.insert(crate::ConfigFlags::VIEWPORTS_ENABLE);
116 io.set_config_flags(flags);
117 }
118
119 #[cfg(feature = "multi-viewport")]
129 #[doc(alias = "UpdatePlatformWindows")]
130 pub fn update_platform_windows(&mut self) {
131 let _guard = CTX_MUTEX.lock();
132 unsafe {
133 with_bound_context(self.raw, || {
134 self.assert_can_update_platform_windows_unlocked(
135 "Context::update_platform_windows()",
136 );
137 sys::igUpdatePlatformWindows();
138 });
139 }
140 }
141
142 #[cfg(feature = "multi-viewport")]
154 #[doc(alias = "RenderPlatformWindowsDefault")]
155 pub fn render_platform_windows_default(&mut self) {
156 let _guard = CTX_MUTEX.lock();
157 unsafe {
158 with_bound_context(self.raw, || {
159 let raw = &*self.raw;
160 assert!(
161 raw.FrameCount > 0
162 && raw.FrameCountRendered == raw.FrameCount
163 && raw.FrameCountPlatformEnded == raw.FrameCount,
164 "Context::render_platform_windows_default() requires a rendered frame followed by Context::update_platform_windows()"
165 );
166 let platform_io = sys::igGetPlatformIO_Nil();
167 assert!(
168 !platform_io.is_null()
169 && ((*platform_io).Platform_RenderWindow.is_some()
170 || (*platform_io).Renderer_RenderWindow.is_some()),
171 "Context::render_platform_windows_default() requires Platform_RenderWindow or Renderer_RenderWindow; render snapshots directly when the renderer does not use default callbacks"
172 );
173 let renderer_has_textures =
174 (*self.io_ptr("Context::render_platform_windows_default()")).BackendFlags
175 & sys::ImGuiBackendFlags_RendererHasTextures as i32
176 != 0;
177 if renderer_has_textures {
178 assert!(
179 self.snapshot_hub
180 .is_synchronous_frame_reconciled(raw.FrameCount),
181 "Context::render_platform_windows_default() requires managed-texture reconciliation for the current rendered frame"
182 );
183 }
184 let _texture_mask = renderer_has_textures
185 .then(|| PlatformDrawDataTextureMask::install(platform_io));
186 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
187 });
188 }
189 }
190
191 #[cfg(feature = "multi-viewport")]
214 #[doc(alias = "DestroyPlatformWindows")]
215 pub fn destroy_platform_windows(&mut self) -> Result<(), ContextPlatformWindowTeardownError> {
216 let _guard = CTX_MUTEX.lock();
217 self.end_frame_for_teardown_unlocked();
218 let teardown = ContextPlatformWindowTeardown::new(&self.state);
219 let invocation = self.attachments.begin_platform_window_teardown(&teardown)?;
220 unsafe {
221 with_bound_context(self.raw, || {
222 sys::igDestroyPlatformWindows();
223 });
224 }
225 invocation.finish(&teardown)
226 }
227
228 #[cfg(feature = "multi-viewport")]
229 pub(super) fn prepare_multi_viewport_new_frame_contract_unlocked(&self, caller: &str) {
230 unsafe {
231 let config_flags = (*self.io_ptr(caller)).ConfigFlags;
232 let viewports_enabled = config_flags & sys::ImGuiConfigFlags_ViewportsEnable != 0;
233 if !viewports_enabled {
234 return;
235 }
236
237 let frame_count = (*self.raw).FrameCount;
238 let frame_count_ended = (*self.raw).FrameCountEnded;
239 let frame_count_platform_ended = (*self.raw).FrameCountPlatformEnded;
240 let config_flags_current_frame = (*self.raw).ConfigFlagsCurrFrame;
241
242 if frame_count == 1
243 && config_flags_current_frame & sys::ImGuiConfigFlags_ViewportsEnable == 0
244 {
245 panic!(
246 "{caller} cannot enable multi-viewport on the second frame; enable it before the first frame or after the second frame so Dear ImGui preserves its settings contract"
247 );
248 }
249 if !self.multi_viewport_backends_advertised_unlocked() {
250 return;
253 }
254 if frame_count > 0 && frame_count_platform_ended != frame_count {
255 if config_flags_current_frame & sys::ImGuiConfigFlags_ViewportsEnable == 0 {
256 assert_eq!(
257 frame_count_ended, frame_count,
258 "{caller} cannot enable multi-viewport while the previous frame is still open"
259 );
260 sys::igUpdatePlatformWindows();
263 } else {
264 panic!(
265 "{caller} cannot begin a new multi-viewport frame before Context::update_platform_windows() completes the previous frame"
266 );
267 }
268 }
269 self.assert_multi_viewport_backend_contract_unlocked(
270 caller,
271 config_flags | config_flags_current_frame,
272 );
273 }
274 }
275
276 #[cfg(feature = "multi-viewport")]
277 fn assert_can_update_platform_windows_unlocked(&self, caller: &str) {
278 unsafe {
279 let frame_count = (*self.raw).FrameCount;
280 let frame_count_ended = (*self.raw).FrameCountEnded;
281 let frame_count_platform_ended = (*self.raw).FrameCountPlatformEnded;
282 let config_flags_current_frame = (*self.raw).ConfigFlagsCurrFrame;
283 assert!(
284 frame_count_ended == frame_count,
285 "{caller} requires Context::render() or an ended frame first"
286 );
287 assert!(
288 frame_count_platform_ended < frame_count,
289 "{caller} was already called for frame {}",
290 frame_count
291 );
292 self.assert_multi_viewport_backend_contract_unlocked(
293 caller,
294 config_flags_current_frame,
295 );
296 }
297 }
298
299 #[cfg(feature = "multi-viewport")]
300 fn assert_multi_viewport_backend_contract_unlocked(&self, caller: &str, config_flags: i32) {
301 if config_flags & sys::ImGuiConfigFlags_ViewportsEnable == 0 {
302 return;
303 }
304
305 unsafe {
306 let io = &*self.io_ptr(caller);
307 assert!(
308 self.multi_viewport_backends_advertised_unlocked(),
309 "{caller} requires platform and renderer backends that advertise multi-viewport support"
310 );
311
312 let platform_io = &*self.platform_io_ptr(caller);
313 for (name, installed) in [
314 (
315 "Platform_CreateWindow",
316 platform_io.Platform_CreateWindow.is_some(),
317 ),
318 (
319 "Platform_DestroyWindow",
320 platform_io.Platform_DestroyWindow.is_some(),
321 ),
322 (
323 "Platform_ShowWindow",
324 platform_io.Platform_ShowWindow.is_some(),
325 ),
326 (
327 "Platform_GetWindowPos",
328 platform_io.Platform_GetWindowPos.is_some(),
329 ),
330 (
331 "Platform_SetWindowPos",
332 platform_io.Platform_SetWindowPos.is_some(),
333 ),
334 (
335 "Platform_GetWindowSize",
336 platform_io.Platform_GetWindowSize.is_some(),
337 ),
338 (
339 "Platform_SetWindowSize",
340 platform_io.Platform_SetWindowSize.is_some(),
341 ),
342 (
343 "Platform_SetWindowTitle",
344 platform_io.Platform_SetWindowTitle.is_some(),
345 ),
346 ] {
347 assert!(
348 installed,
349 "{caller} requires the {name} callback before multi-viewport can run"
350 );
351 }
352
353 let monitor_count = platform_io.Monitors.Size;
354 assert!(
355 monitor_count > 0 && !platform_io.Monitors.Data.is_null(),
356 "{caller} requires at least one valid PlatformIO monitor"
357 );
358 assert!(
359 platform_io.Monitors.Capacity >= monitor_count,
360 "{caller} rejected a corrupt PlatformIO monitor vector"
361 );
362 let monitors = std::slice::from_raw_parts(
363 platform_io.Monitors.Data,
364 usize::try_from(monitor_count)
365 .expect("positive PlatformIO monitor count must fit usize"),
366 );
367 crate::platform_io::assert_monitor_contract(monitors, caller);
368
369 let main_viewport = sys::igGetMainViewport();
370 assert!(
371 !main_viewport.is_null(),
372 "{caller} requires a valid main viewport"
373 );
374 assert!(
375 !(*main_viewport).PlatformUserData.is_null()
376 || !(*main_viewport).PlatformHandle.is_null(),
377 "{caller} requires the platform backend to initialize the main viewport"
378 );
379
380 let transparent_docking = io.ConfigDockingTransparentPayload
381 && config_flags & sys::ImGuiConfigFlags_DockingEnable != 0;
382 assert!(
383 !transparent_docking || platform_io.Platform_SetWindowAlpha.is_some(),
384 "{caller} requires Platform_SetWindowAlpha when transparent docking payloads are enabled"
385 );
386 }
387 }
388
389 #[cfg(feature = "multi-viewport")]
390 fn multi_viewport_backends_advertised_unlocked(&self) -> bool {
391 unsafe {
392 let backend_flags = (*self.io_ptr("multi-viewport backend validation")).BackendFlags;
393 backend_flags & sys::ImGuiBackendFlags_PlatformHasViewports != 0
394 && backend_flags & sys::ImGuiBackendFlags_RendererHasViewports != 0
395 }
396 }
397}
398
399#[cfg(all(test, feature = "multi-viewport"))]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn managed_platform_draw_texture_mask_restores_every_pointer() {
405 let mut textures = sys::ImVector_ImTextureDataPtr::default();
406 let textures_ptr = &mut textures as *mut sys::ImVector_ImTextureDataPtr;
407 let mut draw_data = sys::ImDrawData {
408 Textures: textures_ptr,
409 ..Default::default()
410 };
411 let mut viewport = sys::ImGuiViewport {
412 DrawData: &mut draw_data,
413 ..Default::default()
414 };
415 let viewport = &mut viewport as *mut sys::ImGuiViewport;
416 let mut viewports = [viewport, viewport];
417 let mut platform_io = sys::ImGuiPlatformIO {
418 Viewports: sys::ImVector_ImGuiViewportPtr {
419 Size: viewports.len() as i32,
420 Capacity: viewports.len() as i32,
421 Data: viewports.as_mut_ptr(),
422 },
423 ..Default::default()
424 };
425
426 let mask = unsafe { PlatformDrawDataTextureMask::install(&mut platform_io) };
427 assert!(draw_data.Textures.is_null());
428 drop(mask);
429 assert_eq!(draw_data.Textures, textures_ptr);
430 }
431}