use parking_lot::ReentrantMutex;
use std::cell::{RefCell, UnsafeCell};
use std::ffi::CString;
use std::ops::Drop;
use std::path::PathBuf;
use std::ptr;
use std::rc::{Rc, Weak};
use crate::clipboard::{ClipboardBackend, ClipboardContext};
use crate::fonts::{Font, FontAtlas, SharedFontAtlas};
use crate::io::Io;
use crate::sys;
#[derive(Debug)]
pub struct Context {
raw: *mut sys::ImGuiContext,
alive: Rc<()>,
shared_font_atlas: Option<SharedFontAtlas>,
ini_filename: Option<CString>,
log_filename: Option<CString>,
platform_name: Option<CString>,
renderer_name: Option<CString>,
clipboard_ctx: Box<UnsafeCell<ClipboardContext>>,
ui: crate::ui::Ui,
}
static CTX_MUTEX: ReentrantMutex<()> = parking_lot::const_reentrant_mutex(());
#[derive(Clone)]
struct UserTextureRegistration {
ctx: *mut sys::ImGuiContext,
tex: *mut sys::ImTextureData,
alive: Weak<()>,
}
thread_local! {
static USER_TEXTURE_REGISTRATIONS: RefCell<Vec<UserTextureRegistration>> = RefCell::new(Vec::new());
}
fn clear_current_context() {
unsafe {
sys::igSetCurrentContext(ptr::null_mut());
}
}
fn no_current_context() -> bool {
let ctx = unsafe { sys::igGetCurrentContext() };
ctx.is_null()
}
struct BoundContextGuard {
prev: *mut sys::ImGuiContext,
restore: bool,
}
impl BoundContextGuard {
fn bind(ctx: *mut sys::ImGuiContext) -> Self {
unsafe {
let prev = sys::igGetCurrentContext();
let restore = prev != ctx;
if restore {
sys::igSetCurrentContext(ctx);
}
Self { prev, restore }
}
}
}
impl Drop for BoundContextGuard {
fn drop(&mut self) {
if self.restore {
unsafe {
sys::igSetCurrentContext(self.prev);
}
}
}
}
fn with_bound_context<R>(ctx: *mut sys::ImGuiContext, f: impl FnOnce() -> R) -> R {
let _guard = BoundContextGuard::bind(ctx);
f()
}
fn prune_dead_user_texture_registrations(registrations: &mut Vec<UserTextureRegistration>) {
registrations.retain(|registration| registration.alive.upgrade().is_some());
}
fn is_user_texture_registered(ctx: *mut sys::ImGuiContext, tex: *mut sys::ImTextureData) -> bool {
USER_TEXTURE_REGISTRATIONS.with(|registrations| {
let mut registrations = registrations.borrow_mut();
prune_dead_user_texture_registrations(&mut registrations);
registrations
.iter()
.any(|registration| registration.ctx == ctx && registration.tex == tex)
})
}
fn track_user_texture_registration(
ctx: *mut sys::ImGuiContext,
tex: *mut sys::ImTextureData,
alive: Weak<()>,
) {
USER_TEXTURE_REGISTRATIONS.with(|registrations| {
let mut registrations = registrations.borrow_mut();
prune_dead_user_texture_registrations(&mut registrations);
registrations.push(UserTextureRegistration { ctx, tex, alive });
});
}
fn take_user_texture_registration(
ctx: *mut sys::ImGuiContext,
tex: *mut sys::ImTextureData,
) -> Option<UserTextureRegistration> {
USER_TEXTURE_REGISTRATIONS.with(|registrations| {
let mut registrations = registrations.borrow_mut();
prune_dead_user_texture_registrations(&mut registrations);
registrations
.iter()
.position(|registration| registration.ctx == ctx && registration.tex == tex)
.map(|index| registrations.remove(index))
})
}
fn unregister_user_texture_registration(registration: UserTextureRegistration) {
if registration.ctx.is_null()
|| registration.tex.is_null()
|| registration.alive.upgrade().is_none()
{
return;
}
unsafe {
with_bound_context(registration.ctx, || {
sys::igUnregisterUserTexture(registration.tex);
});
}
}
pub(crate) fn unregister_user_texture_from_all_contexts(tex: *mut sys::ImTextureData) {
if tex.is_null() {
return;
}
let registrations = USER_TEXTURE_REGISTRATIONS.with(|registrations| {
let mut registrations = registrations.borrow_mut();
let mut taken = Vec::new();
let mut index = 0;
while index < registrations.len() {
if registrations[index].alive.upgrade().is_none() {
registrations.remove(index);
} else if registrations[index].tex == tex {
taken.push(registrations.remove(index));
} else {
index += 1;
}
}
taken
});
let _guard = CTX_MUTEX.lock();
for registration in registrations {
unregister_user_texture_registration(registration);
}
}
fn unregister_user_textures_for_context(ctx: *mut sys::ImGuiContext) {
if ctx.is_null() {
return;
}
let registrations = USER_TEXTURE_REGISTRATIONS.with(|registrations| {
let mut registrations = registrations.borrow_mut();
let mut taken = Vec::new();
let mut index = 0;
while index < registrations.len() {
if registrations[index].alive.upgrade().is_none() || registrations[index].ctx == ctx {
let registration = registrations.remove(index);
if registration.ctx == ctx {
taken.push(registration);
}
} else {
index += 1;
}
}
taken
});
for registration in registrations {
unregister_user_texture_registration(registration);
}
}
impl Context {
pub fn try_create() -> crate::error::ImGuiResult<Context> {
Self::try_create_internal(None)
}
pub fn try_create_with_shared_font_atlas(
shared_font_atlas: SharedFontAtlas,
) -> crate::error::ImGuiResult<Context> {
Self::try_create_internal(Some(shared_font_atlas))
}
pub fn create() -> Context {
Self::try_create().expect("Failed to create Dear ImGui context")
}
pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
Self::try_create_with_shared_font_atlas(shared_font_atlas)
.expect("Failed to create Dear ImGui context")
}
pub fn as_raw(&self) -> *mut sys::ImGuiContext {
self.raw
}
pub fn alive_token(&self) -> ContextAliveToken {
ContextAliveToken(Rc::downgrade(&self.alive))
}
fn io_ptr(&self, caller: &str) -> *mut sys::ImGuiIO {
let io = unsafe { sys::igGetIO_ContextPtr(self.raw) };
if io.is_null() {
panic!("{caller} requires a valid ImGui context");
}
io
}
fn platform_io_ptr(&self, caller: &str) -> *mut sys::ImGuiPlatformIO {
let pio = unsafe { sys::igGetPlatformIO_ContextPtr(self.raw) };
if pio.is_null() {
panic!("{caller} requires a valid ImGui context");
}
pio
}
fn assert_current_context(&self, caller: &str) {
assert!(
self.is_current_context(),
"{caller} requires this context to be current"
);
}
fn try_create_internal(
mut shared_font_atlas: Option<SharedFontAtlas>,
) -> crate::error::ImGuiResult<Context> {
let _guard = CTX_MUTEX.lock();
if !no_current_context() {
return Err(crate::error::ImGuiError::ContextAlreadyActive);
}
let shared_font_atlas_ptr = match &mut shared_font_atlas {
Some(atlas) => atlas.as_ptr_mut(),
None => ptr::null_mut(),
};
let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
if raw.is_null() {
return Err(crate::error::ImGuiError::ContextCreation {
reason: "ImGui_CreateContext returned null".to_string(),
});
}
unsafe {
sys::igSetCurrentContext(raw);
}
Ok(Context {
raw,
alive: Rc::new(()),
shared_font_atlas,
ini_filename: None,
log_filename: None,
platform_name: None,
renderer_name: None,
clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
ui: crate::ui::Ui::new(),
})
}
pub fn io_mut(&mut self) -> &mut Io {
let _guard = CTX_MUTEX.lock();
unsafe {
let io_ptr = self.io_ptr("Context::io_mut()");
&mut *(io_ptr as *mut Io)
}
}
pub fn io(&self) -> &crate::io::Io {
let _guard = CTX_MUTEX.lock();
unsafe {
let io_ptr = self.io_ptr("Context::io()");
&*(io_ptr as *const crate::io::Io)
}
}
pub fn style(&self) -> &crate::style::Style {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
let style_ptr = sys::igGetStyle();
if style_ptr.is_null() {
panic!("Context::style() requires a valid ImGui context");
}
&*(style_ptr as *const crate::style::Style)
})
}
}
pub fn style_mut(&mut self) -> &mut crate::style::Style {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
let style_ptr = sys::igGetStyle();
if style_ptr.is_null() {
panic!("Context::style_mut() requires a valid ImGui context");
}
&mut *(style_ptr as *mut crate::style::Style)
})
}
}
pub fn frame(&mut self) -> &mut crate::ui::Ui {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::frame()");
unsafe {
let io = sys::igGetIO_Nil();
if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
panic!(
"Context::frame() called with invalid io.DisplaySize ({}, {}). \
Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
If you are using a windowing/event-loop library, prefer a platform backend such as \
dear-imgui-winit::WinitPlatform::prepare_frame().",
(*io).DisplaySize.x,
(*io).DisplaySize.y
);
}
sys::igNewFrame();
}
&mut self.ui
}
pub fn frame_with<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&crate::ui::Ui) -> R,
{
let ui = self.frame();
f(ui)
}
pub fn render(&mut self) -> &crate::render::DrawData {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::render()");
unsafe {
sys::igRender();
let dd = sys::igGetDrawData();
if dd.is_null() {
panic!("Context::render() returned null draw data");
}
&*(dd as *const crate::render::DrawData)
}
}
pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::draw_data()");
unsafe {
let draw_data = sys::igGetDrawData();
if draw_data.is_null() {
None
} else {
let data = &*(draw_data as *const crate::render::DrawData);
if data.valid() { Some(data) } else { None }
}
}
}
pub fn register_user_texture(&mut self, texture: &mut crate::texture::OwnedTextureData) {
self.register_user_texture_ptr(texture.as_mut().as_raw_mut());
}
pub unsafe fn register_user_texture_raw(&mut self, texture: &mut crate::texture::TextureData) {
self.register_user_texture_ptr(texture.as_raw_mut());
}
fn register_user_texture_ptr(&mut self, texture: *mut sys::ImTextureData) {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::register_user_texture()");
assert!(
!texture.is_null(),
"Context::register_user_texture() received a null texture"
);
if is_user_texture_registered(self.raw, texture) {
return;
}
unsafe {
sys::igRegisterUserTexture(texture);
}
track_user_texture_registration(self.raw, texture, Rc::downgrade(&self.alive));
}
pub fn register_user_texture_token(
&mut self,
texture: &mut crate::texture::OwnedTextureData,
) -> RegisteredUserTexture {
self.register_user_texture(texture);
RegisteredUserTexture {
ctx: self.raw,
tex: texture.as_mut().as_raw_mut(),
alive: Rc::downgrade(&self.alive),
}
}
pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::OwnedTextureData) {
self.unregister_user_texture_ptr(texture.as_mut().as_raw_mut());
}
pub unsafe fn unregister_user_texture_raw(
&mut self,
texture: &mut crate::texture::TextureData,
) {
self.unregister_user_texture_ptr(texture.as_raw_mut());
}
fn unregister_user_texture_ptr(&mut self, texture: *mut sys::ImTextureData) {
let _guard = CTX_MUTEX.lock();
self.assert_current_context("Context::unregister_user_texture()");
assert!(
!texture.is_null(),
"Context::unregister_user_texture() received a null texture"
);
if let Some(registration) = take_user_texture_registration(self.raw, texture) {
unregister_user_texture_registration(registration);
}
}
pub fn set_ini_filename<P: Into<PathBuf>>(
&mut self,
filename: Option<P>,
) -> crate::error::ImGuiResult<()> {
use crate::error::SafeStringConversion;
let _guard = CTX_MUTEX.lock();
self.ini_filename = match filename {
Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
None => None,
};
unsafe {
let io = self.io_ptr("Context::set_ini_filename()");
let ptr = self
.ini_filename
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
(*io).IniFilename = ptr;
}
Ok(())
}
pub fn set_log_filename<P: Into<PathBuf>>(
&mut self,
filename: Option<P>,
) -> crate::error::ImGuiResult<()> {
use crate::error::SafeStringConversion;
let _guard = CTX_MUTEX.lock();
self.log_filename = match filename {
Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
None => None,
};
unsafe {
let io = self.io_ptr("Context::set_log_filename()");
let ptr = self
.log_filename
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
(*io).LogFilename = ptr;
}
Ok(())
}
pub fn set_platform_name<S: Into<String>>(
&mut self,
name: Option<S>,
) -> crate::error::ImGuiResult<()> {
use crate::error::SafeStringConversion;
let _guard = CTX_MUTEX.lock();
self.platform_name = match name {
Some(n) => Some(n.into().to_cstring_safe()?),
None => None,
};
unsafe {
let io = self.io_ptr("Context::set_platform_name()");
let ptr = self
.platform_name
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
(*io).BackendPlatformName = ptr;
}
Ok(())
}
pub fn set_renderer_name<S: Into<String>>(
&mut self,
name: Option<S>,
) -> crate::error::ImGuiResult<()> {
use crate::error::SafeStringConversion;
let _guard = CTX_MUTEX.lock();
self.renderer_name = match name {
Some(n) => Some(n.into().to_cstring_safe()?),
None => None,
};
unsafe {
let io = self.io_ptr("Context::set_renderer_name()");
let ptr = self
.renderer_name
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
(*io).BackendRendererName = ptr;
}
Ok(())
}
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) -> &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(ptr as *const sys::ImGuiViewport)
})
}
}
#[cfg(feature = "multi-viewport")]
pub fn enable_multi_viewport(&mut self) {
crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
}
#[cfg(feature = "multi-viewport")]
pub fn update_platform_windows(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
let main_viewport = sys::igGetMainViewport();
if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
eprintln!(
"update_platform_windows: main viewport not set up, setting it up now"
);
}
sys::igUpdatePlatformWindows();
});
}
}
#[cfg(feature = "multi-viewport")]
pub fn render_platform_windows_default(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
});
}
}
#[cfg(feature = "multi-viewport")]
pub fn destroy_platform_windows(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
sys::igDestroyPlatformWindows();
});
}
}
pub fn suspend(self) -> SuspendedContext {
let _guard = CTX_MUTEX.lock();
assert!(
self.is_current_context(),
"context to be suspended is not the active context"
);
clear_current_context();
SuspendedContext(self)
}
fn is_current_context(&self) -> bool {
let ctx = unsafe { sys::igGetCurrentContext() };
self.raw == ctx
}
pub fn push_font(&mut self, font: &Font) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
sys::igPushFont(font.raw(), 0.0);
});
}
}
#[doc(alias = "PopFont")]
pub fn pop_font(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
sys::igPopFont();
});
}
}
#[doc(alias = "GetFont")]
pub fn current_font(&self) -> &Font {
let _guard = CTX_MUTEX.lock();
unsafe { with_bound_context(self.raw, || Font::from_raw(sys::igGetFont() as *const _)) }
}
#[doc(alias = "GetFontSize")]
pub fn current_font_size(&self) -> f32 {
let _guard = CTX_MUTEX.lock();
unsafe { with_bound_context(self.raw, || sys::igGetFontSize()) }
}
pub fn font_atlas(&self) -> FontAtlas {
let _guard = CTX_MUTEX.lock();
#[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
unsafe {
let io = self.io_ptr("Context::font_atlas()");
let atlas_ptr = (*io).Fonts;
assert!(
!atlas_ptr.is_null(),
"ImGui IO Fonts pointer is null on wasm; provider not initialized?"
);
FontAtlas::from_raw(atlas_ptr)
}
#[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
{
panic!(
"font_atlas() is not supported on wasm32 targets without \
`wasm-font-atlas-experimental` feature; \
see docs/WASM.md for current limitations."
);
}
#[cfg(not(target_arch = "wasm32"))]
unsafe {
let io = self.io_ptr("Context::font_atlas()");
let atlas_ptr = (*io).Fonts;
FontAtlas::from_raw(atlas_ptr)
}
}
pub fn font_atlas_mut(&mut self) -> FontAtlas {
let _guard = CTX_MUTEX.lock();
#[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
unsafe {
let io = self.io_ptr("Context::font_atlas_mut()");
let atlas_ptr = (*io).Fonts;
assert!(
!atlas_ptr.is_null(),
"ImGui IO Fonts pointer is null on wasm; provider not initialized?"
);
return FontAtlas::from_raw(atlas_ptr);
}
#[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
{
panic!(
"font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
enable `wasm-font-atlas-experimental` to opt-in for experiments."
);
}
#[cfg(not(target_arch = "wasm32"))]
unsafe {
let io = self.io_ptr("Context::font_atlas_mut()");
let atlas_ptr = (*io).Fonts;
FontAtlas::from_raw(atlas_ptr)
}
}
pub fn fonts(&mut self) -> FontAtlas {
self.font_atlas_mut()
}
pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
self.shared_font_atlas.clone()
}
#[doc(alias = "LoadIniSettingsFromMemory")]
pub fn load_ini_settings(&mut self, data: &str) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
});
}
}
#[doc(alias = "SaveIniSettingsToMemory")]
pub fn save_ini_settings(&mut self, buf: &mut String) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
let mut out_ini_size: usize = 0;
let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
if data_ptr.is_null() || out_ini_size == 0 {
return;
}
let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
if bytes.last() == Some(&0) {
bytes = &bytes[..bytes.len().saturating_sub(1)];
}
buf.push_str(&String::from_utf8_lossy(bytes));
});
}
}
#[cfg(not(target_arch = "wasm32"))]
#[doc(alias = "LoadIniSettingsFromDisk")]
pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
&mut self,
filename: P,
) -> crate::error::ImGuiResult<()> {
use crate::error::SafeStringConversion;
let _guard = CTX_MUTEX.lock();
let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
unsafe {
with_bound_context(self.raw, || {
sys::igLoadIniSettingsFromDisk(cstr.as_ptr());
});
}
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
#[doc(alias = "SaveIniSettingsToDisk")]
pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
&mut self,
filename: P,
) -> crate::error::ImGuiResult<()> {
use crate::error::SafeStringConversion;
let _guard = CTX_MUTEX.lock();
let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
unsafe {
with_bound_context(self.raw, || {
sys::igSaveIniSettingsToDisk(cstr.as_ptr());
});
}
Ok(())
}
#[doc(alias = "GetClipboardText")]
pub fn clipboard_text(&self) -> Option<String> {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
let ptr = sys::igGetClipboardText();
if ptr.is_null() {
return None;
}
Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
})
}
}
#[doc(alias = "SetClipboardText")]
pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
let _guard = CTX_MUTEX.lock();
unsafe {
with_bound_context(self.raw, || {
sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
});
}
}
pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
let _guard = CTX_MUTEX.lock();
let clipboard_ctx: Box<UnsafeCell<_>> =
Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
#[cfg(not(target_arch = "wasm32"))]
unsafe {
let platform_io = sys::igGetPlatformIO_ContextPtr(self.raw);
if platform_io.is_null() {
panic!("Context::set_clipboard_backend() requires a valid ImGui context");
}
(*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
(*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
(*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
}
self.clipboard_ctx = clipboard_ctx;
}
}
impl Drop for Context {
fn drop(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
if !self.raw.is_null() {
unregister_user_textures_for_context(self.raw);
crate::platform_io::clear_typed_callbacks_for_context(self.raw);
with_bound_context(self.raw, || {
crate::platform_io::clear_out_param_callbacks_for_current_context();
});
if sys::igGetCurrentContext() == self.raw {
clear_current_context();
}
sys::igDestroyContext(self.raw);
}
}
}
}
#[cfg(test)]
mod tests {
use super::{Context, with_bound_context};
#[test]
fn platform_io_shared_and_mut_views_match() {
let mut ctx = Context::create();
let shared = ctx.platform_io().as_raw();
let mutable = ctx.platform_io_mut().as_raw();
assert_eq!(shared, mutable);
}
#[test]
fn with_bound_context_restores_previous_context_after_panic() {
let ctx_a = Context::create();
let raw_a = ctx_a.raw;
let suspended_a = ctx_a.suspend();
let ctx_b = Context::create();
let raw_b = ctx_b.raw;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
with_bound_context(raw_a, || panic!("forced panic while context is rebound"));
}));
assert!(result.is_err());
assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, raw_b);
drop(ctx_b);
drop(suspended_a);
}
#[test]
fn io_and_platform_io_accessors_use_self_context_not_current_context() {
let mut ctx_a = Context::create();
let marker_a = std::ptr::NonNull::<u8>::dangling().as_ptr().cast();
ctx_a.io_mut().set_backend_language_user_data(marker_a);
let pio_a = ctx_a.platform_io().as_raw();
let suspended_a = ctx_a.suspend();
let mut ctx_b = Context::create();
let marker_b = std::ptr::NonNull::<u16>::dangling().as_ptr().cast();
ctx_b.io_mut().set_backend_language_user_data(marker_b);
let pio_b = ctx_b.platform_io().as_raw();
assert_ne!(marker_a, marker_b);
assert_ne!(pio_a, pio_b);
let ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
assert_eq!(ctx_a.0.io().backend_language_user_data(), marker_a);
assert_eq!(ctx_a.0.platform_io().as_raw(), pio_a);
assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
drop(ctx_b);
drop(ctx_a);
}
#[test]
fn style_and_main_viewport_accessors_use_self_context_not_current_context() {
let mut ctx_a = Context::create();
ctx_a.style_mut().set_alpha(0.25);
let viewport_a = ctx_a.main_viewport().as_raw();
let suspended_a = ctx_a.suspend();
let mut ctx_b = Context::create();
ctx_b.style_mut().set_alpha(0.75);
let viewport_b = ctx_b.main_viewport().as_raw();
assert_ne!(viewport_a, viewport_b);
let mut ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
assert_eq!(ctx_a.0.style().alpha(), 0.25);
assert_eq!(ctx_a.0.main_viewport().as_raw(), viewport_a);
assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
drop(ctx_b);
drop(ctx_a);
}
#[test]
fn io_font_global_scale_uses_owner_context_not_current_context() {
let mut ctx_a = Context::create();
ctx_a.style_mut().set_font_scale_main(1.25);
let suspended_a = ctx_a.suspend();
let mut ctx_b = Context::create();
ctx_b.style_mut().set_font_scale_main(2.0);
let mut ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
assert_eq!(ctx_a.0.io().font_global_scale(), 1.25);
ctx_a.0.io_mut().set_font_global_scale(1.5);
assert_eq!(ctx_a.0.style().font_scale_main(), 1.5);
assert_eq!(ctx_b.style().font_scale_main(), 2.0);
assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
drop(ctx_b);
drop(ctx_a);
}
#[test]
fn frame_lifecycle_requires_receiver_to_be_current_context() {
let ctx_a = Context::create();
let suspended_a = ctx_a.suspend();
let ctx_b = Context::create();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = suspended_a.0.draw_data();
}));
assert!(result.is_err());
assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
drop(ctx_b);
drop(suspended_a);
}
#[cfg(feature = "multi-viewport")]
#[test]
fn platform_io_get_window_pos_and_size_setters_install_handlers() {
unsafe extern "C" fn get_pos(
_viewport: *mut crate::sys::ImGuiViewport,
out_pos: *mut crate::sys::ImVec2,
) {
if let Some(out_pos) = unsafe { out_pos.as_mut() } {
*out_pos = crate::sys::ImVec2 { x: 10.0, y: 20.0 };
}
}
unsafe extern "C" fn get_size(
_viewport: *mut crate::sys::ImGuiViewport,
out_size: *mut crate::sys::ImVec2,
) {
if let Some(out_size) = unsafe { out_size.as_mut() } {
*out_size = crate::sys::ImVec2 { x: 30.0, y: 40.0 };
}
}
unsafe extern "C" fn get_scale(
_viewport: *mut crate::sys::ImGuiViewport,
out_scale: *mut crate::sys::ImVec2,
) {
if let Some(out_scale) = unsafe { out_scale.as_mut() } {
*out_scale = crate::sys::ImVec2 { x: 1.0, y: 2.0 };
}
}
unsafe extern "C" fn get_insets(
_viewport: *mut crate::sys::ImGuiViewport,
out_insets: *mut crate::sys::ImVec4,
) {
if let Some(out_insets) = unsafe { out_insets.as_mut() } {
*out_insets = crate::sys::ImVec4::new(1.0, 2.0, 3.0, 4.0);
}
}
let mut ctx = Context::create();
{
let pio = ctx.platform_io_mut();
pio.set_platform_get_window_pos_raw(Some(get_pos));
pio.set_platform_get_window_size_raw(Some(get_size));
pio.set_platform_get_window_framebuffer_scale_raw(Some(get_scale));
pio.set_platform_get_window_work_area_insets_raw(Some(get_insets));
let raw = unsafe { &*pio.as_raw() };
assert!(raw.Platform_GetWindowPos.is_some());
assert!(raw.Platform_GetWindowSize.is_some());
assert!(raw.Platform_GetWindowFramebufferScale.is_some());
assert!(raw.Platform_GetWindowWorkAreaInsets.is_some());
}
assert!(
ctx.io().backend_language_user_data().is_null(),
"PlatformIO out-param helpers must not occupy BackendLanguageUserData"
);
let pio = ctx.platform_io_mut();
pio.set_platform_get_window_pos_raw(None);
pio.set_platform_get_window_size_raw(None);
pio.set_platform_get_window_framebuffer_scale_raw(None);
pio.set_platform_get_window_work_area_insets_raw(None);
let raw = unsafe { &*pio.as_raw() };
assert!(raw.Platform_GetWindowPos.is_none());
assert!(raw.Platform_GetWindowSize.is_none());
assert!(raw.Platform_GetWindowFramebufferScale.is_none());
assert!(raw.Platform_GetWindowWorkAreaInsets.is_none());
}
#[test]
fn registered_user_texture_token_survives_context_drop() {
let mut ctx = Context::create();
let mut texture = crate::texture::OwnedTextureData::new();
let token = ctx.register_user_texture_token(&mut texture);
drop(ctx);
drop(token);
drop(texture);
}
#[test]
fn registered_user_texture_token_survives_texture_drop() {
let mut ctx = Context::create();
let token = {
let mut texture = crate::texture::OwnedTextureData::new();
ctx.register_user_texture_token(&mut texture)
};
drop(token);
drop(ctx);
}
#[test]
fn user_texture_registration_is_idempotent_and_unregister_is_noop_when_missing() {
let mut ctx = Context::create();
let mut texture = crate::texture::OwnedTextureData::new();
ctx.register_user_texture(&mut texture);
ctx.register_user_texture(&mut texture);
ctx.unregister_user_texture(&mut texture);
ctx.unregister_user_texture(&mut texture);
}
}
#[derive(Debug)]
pub struct SuspendedContext(Context);
#[derive(Clone, Debug)]
pub struct ContextAliveToken(Weak<()>);
impl ContextAliveToken {
pub fn is_alive(&self) -> bool {
self.0.upgrade().is_some()
}
}
impl SuspendedContext {
pub fn try_create() -> crate::error::ImGuiResult<Self> {
Self::try_create_internal(None)
}
pub fn try_create_with_shared_font_atlas(
shared_font_atlas: SharedFontAtlas,
) -> crate::error::ImGuiResult<Self> {
Self::try_create_internal(Some(shared_font_atlas))
}
pub fn create() -> Self {
Self::try_create().expect("Failed to create Dear ImGui context")
}
pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
Self::try_create_with_shared_font_atlas(shared_font_atlas)
.expect("Failed to create Dear ImGui context")
}
fn try_create_internal(
mut shared_font_atlas: Option<SharedFontAtlas>,
) -> crate::error::ImGuiResult<Self> {
let _guard = CTX_MUTEX.lock();
let shared_font_atlas_ptr = match &mut shared_font_atlas {
Some(atlas) => atlas.as_ptr_mut(),
None => ptr::null_mut(),
};
let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
if raw.is_null() {
return Err(crate::error::ImGuiError::ContextCreation {
reason: "ImGui_CreateContext returned null".to_string(),
});
}
let ctx = Context {
raw,
alive: Rc::new(()),
shared_font_atlas,
ini_filename: None,
log_filename: None,
platform_name: None,
renderer_name: None,
clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
ui: crate::ui::Ui::new(),
};
if ctx.is_current_context() {
clear_current_context();
}
Ok(SuspendedContext(ctx))
}
pub fn activate(self) -> Result<Context, SuspendedContext> {
let _guard = CTX_MUTEX.lock();
if no_current_context() {
unsafe {
sys::igSetCurrentContext(self.0.raw);
}
Ok(self.0)
} else {
Err(self)
}
}
}
#[derive(Debug)]
pub struct RegisteredUserTexture {
ctx: *mut sys::ImGuiContext,
tex: *mut sys::ImTextureData,
alive: Weak<()>,
}
impl Drop for RegisteredUserTexture {
fn drop(&mut self) {
if self.ctx.is_null() || self.tex.is_null() || self.alive.upgrade().is_none() {
return;
}
let _guard = CTX_MUTEX.lock();
if let Some(registration) = take_user_texture_registration(self.ctx, self.tex) {
unregister_user_texture_registration(registration);
}
}
}