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