use crate::sys;
use super::Context;
#[cfg(feature = "multi-viewport")]
use super::attachment::{ContextPlatformWindowTeardown, ContextPlatformWindowTeardownError};
use super::binding::{CTX_MUTEX, with_bound_context};
#[cfg(feature = "multi-viewport")]
struct PlatformDrawDataTextureMask {
entries: Vec<(
std::ptr::NonNull<sys::ImDrawData>,
*mut sys::ImVector_ImTextureDataPtr,
)>,
}
#[cfg(feature = "multi-viewport")]
impl PlatformDrawDataTextureMask {
unsafe fn install(platform_io: *mut sys::ImGuiPlatformIO) -> Self {
let mut entries = Vec::new();
let viewports = unsafe { &(*platform_io).Viewports };
if viewports.Size <= 0 {
return Self { entries };
}
assert!(
viewports.Capacity >= viewports.Size && !viewports.Data.is_null(),
"ImGuiPlatformIO.Viewports has invalid native storage"
);
for index in 0..viewports.Size as usize {
let viewport = unsafe { *viewports.Data.add(index) };
let Some(viewport) = std::ptr::NonNull::new(viewport) else {
continue;
};
let Some(draw_data) = std::ptr::NonNull::new(unsafe { viewport.as_ref().DrawData })
else {
continue;
};
let textures = unsafe { draw_data.as_ref().Textures };
if textures.is_null() {
continue;
}
unsafe { (*draw_data.as_ptr()).Textures = std::ptr::null_mut() };
entries.push((draw_data, textures));
}
Self { entries }
}
}
#[cfg(feature = "multi-viewport")]
impl Drop for PlatformDrawDataTextureMask {
fn drop(&mut self) {
for (draw_data, textures) in self.entries.drain(..) {
unsafe { (*draw_data.as_ptr()).Textures = textures };
}
}
}
impl Context {
#[doc(alias = "GetPlatformIO")]
pub fn platform_io(&self) -> &crate::platform_io::PlatformIo {
let _guard = CTX_MUTEX.lock();
unsafe {
let pio = self.platform_io_ptr("Context::platform_io()");
crate::platform_io::PlatformIo::from_raw(pio)
}
}
pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
let _guard = CTX_MUTEX.lock();
unsafe {
let pio = self.platform_io_ptr("Context::platform_io_mut()");
crate::platform_io::PlatformIo::from_raw_mut(pio)
}
}
#[doc(alias = "GetMainViewport")]
pub fn main_viewport(&mut self) -> &mut crate::platform_io::Viewport {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
let ptr = sys::igGetMainViewport();
if ptr.is_null() {
panic!("Context::main_viewport() requires a valid ImGui context");
}
crate::platform_io::Viewport::from_raw_mut(ptr)
})
}
}
#[cfg(feature = "multi-viewport")]
pub fn enable_multi_viewport(&mut self) {
let io = self.io_mut();
let mut flags = io.config_flags();
flags.insert(crate::ConfigFlags::VIEWPORTS_ENABLE);
io.set_config_flags(flags);
}
#[cfg(feature = "multi-viewport")]
#[doc(alias = "UpdatePlatformWindows")]
pub fn update_platform_windows(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
self.assert_can_update_platform_windows_unlocked(
"Context::update_platform_windows()",
);
sys::igUpdatePlatformWindows();
});
}
}
#[cfg(feature = "multi-viewport")]
#[doc(alias = "RenderPlatformWindowsDefault")]
pub fn render_platform_windows_default(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
let raw = &*self.raw;
assert!(
raw.FrameCount > 0
&& raw.FrameCountRendered == raw.FrameCount
&& raw.FrameCountPlatformEnded == raw.FrameCount,
"Context::render_platform_windows_default() requires a rendered frame followed by Context::update_platform_windows()"
);
let platform_io = sys::igGetPlatformIO_Nil();
assert!(
!platform_io.is_null()
&& ((*platform_io).Platform_RenderWindow.is_some()
|| (*platform_io).Renderer_RenderWindow.is_some()),
"Context::render_platform_windows_default() requires Platform_RenderWindow or Renderer_RenderWindow; render snapshots directly when the renderer does not use default callbacks"
);
let renderer_has_textures =
(*self.io_ptr("Context::render_platform_windows_default()")).BackendFlags
& sys::ImGuiBackendFlags_RendererHasTextures as i32
!= 0;
if renderer_has_textures {
assert!(
self.snapshot_hub
.is_synchronous_frame_reconciled(raw.FrameCount),
"Context::render_platform_windows_default() requires managed-texture reconciliation for the current rendered frame"
);
}
let _texture_mask = renderer_has_textures
.then(|| PlatformDrawDataTextureMask::install(platform_io));
sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
});
}
}
#[cfg(feature = "multi-viewport")]
#[doc(alias = "DestroyPlatformWindows")]
pub fn destroy_platform_windows(&mut self) -> Result<(), ContextPlatformWindowTeardownError> {
let _guard = CTX_MUTEX.lock();
self.end_frame_for_teardown_unlocked();
let teardown = ContextPlatformWindowTeardown::new(&self.state);
let invocation = self.attachments.begin_platform_window_teardown(&teardown)?;
unsafe {
with_bound_context(self.raw, || {
sys::igDestroyPlatformWindows();
});
}
invocation.finish(&teardown)
}
#[cfg(feature = "multi-viewport")]
pub(super) fn prepare_multi_viewport_new_frame_contract_unlocked(&self, caller: &str) {
unsafe {
let config_flags = (*self.io_ptr(caller)).ConfigFlags;
let viewports_enabled = config_flags & sys::ImGuiConfigFlags_ViewportsEnable != 0;
if !viewports_enabled {
return;
}
let frame_count = (*self.raw).FrameCount;
let frame_count_ended = (*self.raw).FrameCountEnded;
let frame_count_platform_ended = (*self.raw).FrameCountPlatformEnded;
let config_flags_current_frame = (*self.raw).ConfigFlagsCurrFrame;
if frame_count == 1
&& config_flags_current_frame & sys::ImGuiConfigFlags_ViewportsEnable == 0
{
panic!(
"{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"
);
}
if !self.multi_viewport_backends_advertised_unlocked() {
return;
}
if frame_count > 0 && frame_count_platform_ended != frame_count {
if config_flags_current_frame & sys::ImGuiConfigFlags_ViewportsEnable == 0 {
assert_eq!(
frame_count_ended, frame_count,
"{caller} cannot enable multi-viewport while the previous frame is still open"
);
sys::igUpdatePlatformWindows();
} else {
panic!(
"{caller} cannot begin a new multi-viewport frame before Context::update_platform_windows() completes the previous frame"
);
}
}
self.assert_multi_viewport_backend_contract_unlocked(
caller,
config_flags | config_flags_current_frame,
);
}
}
#[cfg(feature = "multi-viewport")]
fn assert_can_update_platform_windows_unlocked(&self, caller: &str) {
unsafe {
let frame_count = (*self.raw).FrameCount;
let frame_count_ended = (*self.raw).FrameCountEnded;
let frame_count_platform_ended = (*self.raw).FrameCountPlatformEnded;
let config_flags_current_frame = (*self.raw).ConfigFlagsCurrFrame;
assert!(
frame_count_ended == frame_count,
"{caller} requires Context::render() or an ended frame first"
);
assert!(
frame_count_platform_ended < frame_count,
"{caller} was already called for frame {}",
frame_count
);
self.assert_multi_viewport_backend_contract_unlocked(
caller,
config_flags_current_frame,
);
}
}
#[cfg(feature = "multi-viewport")]
fn assert_multi_viewport_backend_contract_unlocked(&self, caller: &str, config_flags: i32) {
if config_flags & sys::ImGuiConfigFlags_ViewportsEnable == 0 {
return;
}
unsafe {
let io = &*self.io_ptr(caller);
assert!(
self.multi_viewport_backends_advertised_unlocked(),
"{caller} requires platform and renderer backends that advertise multi-viewport support"
);
let platform_io = &*self.platform_io_ptr(caller);
for (name, installed) in [
(
"Platform_CreateWindow",
platform_io.Platform_CreateWindow.is_some(),
),
(
"Platform_DestroyWindow",
platform_io.Platform_DestroyWindow.is_some(),
),
(
"Platform_ShowWindow",
platform_io.Platform_ShowWindow.is_some(),
),
(
"Platform_GetWindowPos",
platform_io.Platform_GetWindowPos.is_some(),
),
(
"Platform_SetWindowPos",
platform_io.Platform_SetWindowPos.is_some(),
),
(
"Platform_GetWindowSize",
platform_io.Platform_GetWindowSize.is_some(),
),
(
"Platform_SetWindowSize",
platform_io.Platform_SetWindowSize.is_some(),
),
(
"Platform_SetWindowTitle",
platform_io.Platform_SetWindowTitle.is_some(),
),
] {
assert!(
installed,
"{caller} requires the {name} callback before multi-viewport can run"
);
}
let monitor_count = platform_io.Monitors.Size;
assert!(
monitor_count > 0 && !platform_io.Monitors.Data.is_null(),
"{caller} requires at least one valid PlatformIO monitor"
);
assert!(
platform_io.Monitors.Capacity >= monitor_count,
"{caller} rejected a corrupt PlatformIO monitor vector"
);
let monitors = std::slice::from_raw_parts(
platform_io.Monitors.Data,
usize::try_from(monitor_count)
.expect("positive PlatformIO monitor count must fit usize"),
);
crate::platform_io::assert_monitor_contract(monitors, caller);
let main_viewport = sys::igGetMainViewport();
assert!(
!main_viewport.is_null(),
"{caller} requires a valid main viewport"
);
assert!(
!(*main_viewport).PlatformUserData.is_null()
|| !(*main_viewport).PlatformHandle.is_null(),
"{caller} requires the platform backend to initialize the main viewport"
);
let transparent_docking = io.ConfigDockingTransparentPayload
&& config_flags & sys::ImGuiConfigFlags_DockingEnable != 0;
assert!(
!transparent_docking || platform_io.Platform_SetWindowAlpha.is_some(),
"{caller} requires Platform_SetWindowAlpha when transparent docking payloads are enabled"
);
}
}
#[cfg(feature = "multi-viewport")]
fn multi_viewport_backends_advertised_unlocked(&self) -> bool {
unsafe {
let backend_flags = (*self.io_ptr("multi-viewport backend validation")).BackendFlags;
backend_flags & sys::ImGuiBackendFlags_PlatformHasViewports != 0
&& backend_flags & sys::ImGuiBackendFlags_RendererHasViewports != 0
}
}
}
#[cfg(all(test, feature = "multi-viewport"))]
mod tests {
use super::*;
#[test]
fn managed_platform_draw_texture_mask_restores_every_pointer() {
let mut textures = sys::ImVector_ImTextureDataPtr::default();
let textures_ptr = &mut textures as *mut sys::ImVector_ImTextureDataPtr;
let mut draw_data = sys::ImDrawData {
Textures: textures_ptr,
..Default::default()
};
let mut viewport = sys::ImGuiViewport {
DrawData: &mut draw_data,
..Default::default()
};
let viewport = &mut viewport as *mut sys::ImGuiViewport;
let mut viewports = [viewport, viewport];
let mut platform_io = sys::ImGuiPlatformIO {
Viewports: sys::ImVector_ImGuiViewportPtr {
Size: viewports.len() as i32,
Capacity: viewports.len() as i32,
Data: viewports.as_mut_ptr(),
},
..Default::default()
};
let mask = unsafe { PlatformDrawDataTextureMask::install(&mut platform_io) };
assert!(draw_data.Textures.is_null());
drop(mask);
assert_eq!(draw_data.Textures, textures_ptr);
}
}