Skip to main content

dear_imgui_rs/context/
platform.rs

1use 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
8impl Context {
9    /// Get shared access to the platform IO.
10    ///
11    /// Note: `ImGuiPlatformIO` exists even when multi-viewport is disabled. We expose it
12    /// unconditionally so callers can use ImGui 1.92+ texture management via `PlatformIO.Textures[]`.
13    #[doc(alias = "GetPlatformIO")]
14    pub fn platform_io(&self) -> &crate::platform_io::PlatformIo {
15        let _guard = CTX_MUTEX.lock();
16        unsafe {
17            let pio = self.platform_io_ptr("Context::platform_io()");
18            crate::platform_io::PlatformIo::from_raw(pio)
19        }
20    }
21
22    /// Get mutable access to the platform IO.
23    ///
24    /// Note: `ImGuiPlatformIO` exists even when multi-viewport is disabled. We expose it
25    /// unconditionally so callers can use ImGui 1.92+ texture management via `PlatformIO.Textures[]`.
26    pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
27        let _guard = CTX_MUTEX.lock();
28        unsafe {
29            let pio = self.platform_io_ptr("Context::platform_io_mut()");
30            crate::platform_io::PlatformIo::from_raw_mut(pio)
31        }
32    }
33
34    /// Returns a reference to the main Dear ImGui viewport.
35    ///
36    /// The returned reference is owned by this ImGui context and
37    /// must not be used after the context is destroyed.
38    #[doc(alias = "GetMainViewport")]
39    pub fn main_viewport(&mut self) -> &mut crate::platform_io::Viewport {
40        let _guard = CTX_MUTEX.lock();
41        unsafe {
42            with_bound_context(self.raw, || {
43                let ptr = sys::igGetMainViewport();
44                if ptr.is_null() {
45                    panic!("Context::main_viewport() requires a valid ImGui context");
46                }
47                crate::platform_io::Viewport::from_raw_mut(ptr)
48            })
49        }
50    }
51
52    /// Enable Dear ImGui's multi-viewport capability.
53    ///
54    /// Docking is an independent capability and must be enabled explicitly by the caller.
55    /// A platform and renderer backend that advertise multi-viewport support must be installed
56    /// before the next frame begins.
57    ///
58    /// Prefer enabling this before the first frame so Dear ImGui can load settings in the correct
59    /// coordinate space. Enabling it between the first and second frames is rejected by Dear
60    /// ImGui. When it is enabled after a later completed frame, [`Context::frame`] automatically
61    /// advances the platform-window lifecycle for the preceding disabled frame.
62    #[cfg(feature = "multi-viewport")]
63    pub fn enable_multi_viewport(&mut self) {
64        let io = self.io_mut();
65        let mut flags = io.config_flags();
66        flags.insert(crate::ConfigFlags::VIEWPORTS_ENABLE);
67        io.set_config_flags(flags);
68    }
69
70    /// Update platform windows
71    ///
72    /// This function should be called every frame when multi-viewport is enabled.
73    /// It updates all platform windows and handles viewport management.
74    ///
75    /// # Panics
76    ///
77    /// Panics if the current frame has not ended, this frame was already updated, or the active
78    /// multi-viewport backend contract is incomplete.
79    #[cfg(feature = "multi-viewport")]
80    #[doc(alias = "UpdatePlatformWindows")]
81    pub fn update_platform_windows(&mut self) {
82        let _guard = CTX_MUTEX.lock();
83        unsafe {
84            with_bound_context(self.raw, || {
85                self.assert_can_update_platform_windows_unlocked(
86                    "Context::update_platform_windows()",
87                );
88                sys::igUpdatePlatformWindows();
89            });
90        }
91    }
92
93    /// Render platform windows with default implementation
94    ///
95    /// This function renders all platform windows using the default implementation.
96    /// It calls the platform and renderer backends to render each viewport.
97    ///
98    /// # Panics
99    ///
100    /// Panics unless [`Self::update_platform_windows`] completed for the current frame after
101    /// [`Self::render`], or unless the installed backends provide `Platform_RenderWindow` or
102    /// `Renderer_RenderWindow`. Snapshot-driven renderers should render their detached viewport
103    /// data directly instead of calling this default callback pump.
104    #[cfg(feature = "multi-viewport")]
105    #[doc(alias = "RenderPlatformWindowsDefault")]
106    pub fn render_platform_windows_default(&mut self) {
107        let _guard = CTX_MUTEX.lock();
108        unsafe {
109            with_bound_context(self.raw, || {
110                let raw = &*self.raw;
111                assert!(
112                    raw.FrameCount > 0
113                        && raw.FrameCountRendered == raw.FrameCount
114                        && raw.FrameCountPlatformEnded == raw.FrameCount,
115                    "Context::render_platform_windows_default() requires a rendered frame followed by Context::update_platform_windows()"
116                );
117                let platform_io = sys::igGetPlatformIO_Nil();
118                assert!(
119                    !platform_io.is_null()
120                        && ((*platform_io).Platform_RenderWindow.is_some()
121                            || (*platform_io).Renderer_RenderWindow.is_some()),
122                    "Context::render_platform_windows_default() requires Platform_RenderWindow or Renderer_RenderWindow; render snapshots directly when the renderer does not use default callbacks"
123                );
124                sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
125            });
126        }
127    }
128
129    /// Destroy all platform windows
130    ///
131    /// This function should be called during shutdown to properly clean up
132    /// all platform windows and their associated resources.
133    ///
134    /// Any open frame is ended first through the same idempotent lifecycle path used by Context
135    /// destruction. This lets backends revoke UI access before destroying secondary windows. A
136    /// rejected observer preflight still leaves that frame ended.
137    ///
138    /// Platform backends receive a bounded observer scope around the native call so they can
139    /// validate callback ownership and keep their teardown state coherent. If that preflight
140    /// fails, native teardown does not begin.
141    ///
142    /// This is a platform-backend shutdown primitive. A registered backend may transition its
143    /// own multi-viewport runtime to a released state; prefer its explicit shutdown API when one
144    /// is available.
145    ///
146    /// # Errors
147    ///
148    /// Returns an error when the active platform attachment rejects the transaction, cannot
149    /// complete post-teardown cleanup, or when the operation is re-entered. Post-teardown errors
150    /// report that native platform windows have already been destroyed.
151    #[cfg(feature = "multi-viewport")]
152    #[doc(alias = "DestroyPlatformWindows")]
153    pub fn destroy_platform_windows(&mut self) -> Result<(), ContextPlatformWindowTeardownError> {
154        let _guard = CTX_MUTEX.lock();
155        self.end_frame_for_teardown_unlocked();
156        let teardown = ContextPlatformWindowTeardown::new(&self.state);
157        let invocation = self.attachments.begin_platform_window_teardown(&teardown)?;
158        unsafe {
159            with_bound_context(self.raw, || {
160                sys::igDestroyPlatformWindows();
161            });
162        }
163        invocation.finish(&teardown)
164    }
165
166    #[cfg(feature = "multi-viewport")]
167    pub(super) fn prepare_multi_viewport_new_frame_contract_unlocked(&self, caller: &str) {
168        unsafe {
169            let config_flags = (*self.io_ptr(caller)).ConfigFlags;
170            let viewports_enabled = config_flags & sys::ImGuiConfigFlags_ViewportsEnable != 0;
171            if !viewports_enabled {
172                return;
173            }
174
175            let frame_count = (*self.raw).FrameCount;
176            let frame_count_ended = (*self.raw).FrameCountEnded;
177            let frame_count_platform_ended = (*self.raw).FrameCountPlatformEnded;
178            let config_flags_current_frame = (*self.raw).ConfigFlagsCurrFrame;
179
180            if frame_count == 1
181                && config_flags_current_frame & sys::ImGuiConfigFlags_ViewportsEnable == 0
182            {
183                panic!(
184                    "{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"
185                );
186            }
187            if !self.multi_viewport_backends_advertised_unlocked() {
188                // Dear ImGui intentionally clears ViewportsEnable when either backend declines
189                // support. Preserve that graceful fallback instead of turning it into an error.
190                return;
191            }
192            if frame_count > 0 && frame_count_platform_ended != frame_count {
193                if config_flags_current_frame & sys::ImGuiConfigFlags_ViewportsEnable == 0 {
194                    assert_eq!(
195                        frame_count_ended, frame_count,
196                        "{caller} cannot enable multi-viewport while the previous frame is still open"
197                    );
198                    // The preceding frame had viewports disabled, so native UpdatePlatformWindows
199                    // only advances its frame watermark and cannot invoke backend callbacks.
200                    sys::igUpdatePlatformWindows();
201                } else {
202                    panic!(
203                        "{caller} cannot begin a new multi-viewport frame before Context::update_platform_windows() completes the previous frame"
204                    );
205                }
206            }
207            self.assert_multi_viewport_backend_contract_unlocked(
208                caller,
209                config_flags | config_flags_current_frame,
210            );
211        }
212    }
213
214    #[cfg(feature = "multi-viewport")]
215    fn assert_can_update_platform_windows_unlocked(&self, caller: &str) {
216        unsafe {
217            let frame_count = (*self.raw).FrameCount;
218            let frame_count_ended = (*self.raw).FrameCountEnded;
219            let frame_count_platform_ended = (*self.raw).FrameCountPlatformEnded;
220            let config_flags_current_frame = (*self.raw).ConfigFlagsCurrFrame;
221            assert!(
222                frame_count_ended == frame_count,
223                "{caller} requires Context::render() or an ended frame first"
224            );
225            assert!(
226                frame_count_platform_ended < frame_count,
227                "{caller} was already called for frame {}",
228                frame_count
229            );
230            self.assert_multi_viewport_backend_contract_unlocked(
231                caller,
232                config_flags_current_frame,
233            );
234        }
235    }
236
237    #[cfg(feature = "multi-viewport")]
238    fn assert_multi_viewport_backend_contract_unlocked(&self, caller: &str, config_flags: i32) {
239        if config_flags & sys::ImGuiConfigFlags_ViewportsEnable == 0 {
240            return;
241        }
242
243        unsafe {
244            let io = &*self.io_ptr(caller);
245            assert!(
246                self.multi_viewport_backends_advertised_unlocked(),
247                "{caller} requires platform and renderer backends that advertise multi-viewport support"
248            );
249
250            let platform_io = &*self.platform_io_ptr(caller);
251            for (name, installed) in [
252                (
253                    "Platform_CreateWindow",
254                    platform_io.Platform_CreateWindow.is_some(),
255                ),
256                (
257                    "Platform_DestroyWindow",
258                    platform_io.Platform_DestroyWindow.is_some(),
259                ),
260                (
261                    "Platform_ShowWindow",
262                    platform_io.Platform_ShowWindow.is_some(),
263                ),
264                (
265                    "Platform_GetWindowPos",
266                    platform_io.Platform_GetWindowPos.is_some(),
267                ),
268                (
269                    "Platform_SetWindowPos",
270                    platform_io.Platform_SetWindowPos.is_some(),
271                ),
272                (
273                    "Platform_GetWindowSize",
274                    platform_io.Platform_GetWindowSize.is_some(),
275                ),
276                (
277                    "Platform_SetWindowSize",
278                    platform_io.Platform_SetWindowSize.is_some(),
279                ),
280                (
281                    "Platform_SetWindowTitle",
282                    platform_io.Platform_SetWindowTitle.is_some(),
283                ),
284            ] {
285                assert!(
286                    installed,
287                    "{caller} requires the {name} callback before multi-viewport can run"
288                );
289            }
290
291            let monitor_count = platform_io.Monitors.Size;
292            assert!(
293                monitor_count > 0 && !platform_io.Monitors.Data.is_null(),
294                "{caller} requires at least one valid PlatformIO monitor"
295            );
296            assert!(
297                platform_io.Monitors.Capacity >= monitor_count,
298                "{caller} rejected a corrupt PlatformIO monitor vector"
299            );
300            let monitors = std::slice::from_raw_parts(
301                platform_io.Monitors.Data,
302                usize::try_from(monitor_count)
303                    .expect("positive PlatformIO monitor count must fit usize"),
304            );
305            crate::platform_io::assert_monitor_contract(monitors, caller);
306
307            let main_viewport = sys::igGetMainViewport();
308            assert!(
309                !main_viewport.is_null(),
310                "{caller} requires a valid main viewport"
311            );
312            assert!(
313                !(*main_viewport).PlatformUserData.is_null()
314                    || !(*main_viewport).PlatformHandle.is_null(),
315                "{caller} requires the platform backend to initialize the main viewport"
316            );
317
318            let transparent_docking = io.ConfigDockingTransparentPayload
319                && config_flags & sys::ImGuiConfigFlags_DockingEnable != 0;
320            assert!(
321                !transparent_docking || platform_io.Platform_SetWindowAlpha.is_some(),
322                "{caller} requires Platform_SetWindowAlpha when transparent docking payloads are enabled"
323            );
324        }
325    }
326
327    #[cfg(feature = "multi-viewport")]
328    fn multi_viewport_backends_advertised_unlocked(&self) -> bool {
329        unsafe {
330            let backend_flags = (*self.io_ptr("multi-viewport backend validation")).BackendFlags;
331            backend_flags & sys::ImGuiBackendFlags_PlatformHasViewports != 0
332                && backend_flags & sys::ImGuiBackendFlags_RendererHasViewports != 0
333        }
334    }
335}