1use parking_lot::ReentrantMutex;
9use std::cell::UnsafeCell;
10use std::ffi::CString;
11use std::ops::Drop;
12use std::path::PathBuf;
13use std::ptr;
14
15use crate::clipboard::{ClipboardBackend, ClipboardContext};
16use crate::fonts::{Font, FontAtlas, SharedFontAtlas};
17use crate::io::Io;
18
19use crate::sys;
20
21#[derive(Debug)]
47pub struct Context {
48 raw: *mut sys::ImGuiContext,
49 shared_font_atlas: Option<SharedFontAtlas>,
50 ini_filename: Option<CString>,
51 log_filename: Option<CString>,
52 platform_name: Option<CString>,
53 renderer_name: Option<CString>,
54 clipboard_ctx: Box<UnsafeCell<ClipboardContext>>,
59 ui: crate::ui::Ui,
60}
61
62static CTX_MUTEX: ReentrantMutex<()> = parking_lot::const_reentrant_mutex(());
65
66fn clear_current_context() {
67 unsafe {
68 sys::igSetCurrentContext(ptr::null_mut());
69 }
70}
71
72fn no_current_context() -> bool {
73 let ctx = unsafe { sys::igGetCurrentContext() };
74 ctx.is_null()
75}
76
77impl Context {
78 pub fn try_create() -> crate::error::ImGuiResult<Context> {
82 Self::try_create_internal(None)
83 }
84
85 pub fn try_create_with_shared_font_atlas(
87 shared_font_atlas: SharedFontAtlas,
88 ) -> crate::error::ImGuiResult<Context> {
89 Self::try_create_internal(Some(shared_font_atlas))
90 }
91
92 pub fn create() -> Context {
96 Self::try_create().expect("Failed to create Dear ImGui context")
97 }
98
99 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
101 Self::try_create_with_shared_font_atlas(shared_font_atlas)
102 .expect("Failed to create Dear ImGui context")
103 }
104
105 pub fn as_raw(&self) -> *mut sys::ImGuiContext {
107 self.raw
108 }
109
110 fn try_create_internal(
113 mut shared_font_atlas: Option<SharedFontAtlas>,
114 ) -> crate::error::ImGuiResult<Context> {
115 let _guard = CTX_MUTEX.lock();
116
117 if !no_current_context() {
118 return Err(crate::error::ImGuiError::ContextAlreadyActive);
119 }
120
121 let shared_font_atlas_ptr = match &mut shared_font_atlas {
122 Some(atlas) => atlas.as_ptr_mut(),
123 None => ptr::null_mut(),
124 };
125
126 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
128 if raw.is_null() {
129 return Err(crate::error::ImGuiError::ContextCreation {
130 reason: "ImGui_CreateContext returned null".to_string(),
131 });
132 }
133
134 unsafe {
136 sys::igSetCurrentContext(raw);
137 }
138
139 Ok(Context {
140 raw,
141 shared_font_atlas,
142 ini_filename: None,
143 log_filename: None,
144 platform_name: None,
145 renderer_name: None,
146 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
147 ui: crate::ui::Ui::new(),
148 })
149 }
150
151 pub fn io_mut(&mut self) -> &mut Io {
153 let _guard = CTX_MUTEX.lock();
154 unsafe {
155 let io_ptr = sys::igGetIO_Nil();
157 if io_ptr.is_null() {
158 panic!("Context::io_mut() requires an active ImGui context");
159 }
160 &mut *(io_ptr as *mut Io)
161 }
162 }
163
164 pub fn io(&self) -> &crate::io::Io {
166 let _guard = CTX_MUTEX.lock();
167 unsafe {
168 let io_ptr = sys::igGetIO_Nil();
170 if io_ptr.is_null() {
171 panic!("Context::io() requires an active ImGui context");
172 }
173 &*(io_ptr as *const crate::io::Io)
174 }
175 }
176
177 pub fn style(&self) -> &crate::style::Style {
179 let _guard = CTX_MUTEX.lock();
180 unsafe {
181 let style_ptr = sys::igGetStyle();
182 if style_ptr.is_null() {
183 panic!("Context::style() requires an active ImGui context");
184 }
185 &*(style_ptr as *const crate::style::Style)
186 }
187 }
188
189 pub fn style_mut(&mut self) -> &mut crate::style::Style {
191 let _guard = CTX_MUTEX.lock();
192 unsafe {
193 let style_ptr = sys::igGetStyle();
194 if style_ptr.is_null() {
195 panic!("Context::style_mut() requires an active ImGui context");
196 }
197 &mut *(style_ptr as *mut crate::style::Style)
198 }
199 }
200
201 pub fn frame(&mut self) -> &mut crate::ui::Ui {
203 let _guard = CTX_MUTEX.lock();
204
205 unsafe {
206 sys::igNewFrame();
207 }
208 &mut self.ui
209 }
210
211 pub fn frame_with<F, R>(&mut self, f: F) -> R
213 where
214 F: FnOnce(&crate::ui::Ui) -> R,
215 {
216 let ui = self.frame();
217 f(ui)
218 }
219
220 pub fn render(&mut self) -> &crate::render::DrawData {
225 let _guard = CTX_MUTEX.lock();
226 unsafe {
227 sys::igRender();
228 let dd = sys::igGetDrawData();
229 if dd.is_null() {
230 panic!("Context::render() returned null draw data");
231 }
232 &*(dd as *const crate::render::DrawData)
233 }
234 }
235
236 pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
241 let _guard = CTX_MUTEX.lock();
242 unsafe {
243 let draw_data = sys::igGetDrawData();
244 if draw_data.is_null() {
245 None
246 } else {
247 let data = &*(draw_data as *const crate::render::DrawData);
248 if data.valid() { Some(data) } else { None }
249 }
250 }
251 }
252
253 pub fn register_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
267 let _guard = CTX_MUTEX.lock();
268 assert!(
269 self.is_current_context(),
270 "Context::register_user_texture() requires the context to be current"
271 );
272 unsafe {
273 sys::igRegisterUserTexture(texture.as_raw_mut());
274 }
275 }
276
277 pub fn register_user_texture_token(
285 &mut self,
286 texture: &mut crate::texture::TextureData,
287 ) -> RegisteredUserTexture {
288 self.register_user_texture(texture);
289 RegisteredUserTexture {
290 ctx: self.raw,
291 tex: texture.as_raw_mut(),
292 }
293 }
294
295 pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
299 let _guard = CTX_MUTEX.lock();
300 assert!(
301 self.is_current_context(),
302 "Context::unregister_user_texture() requires the context to be current"
303 );
304 unsafe {
305 sys::igUnregisterUserTexture(texture.as_raw_mut());
306 }
307 }
308
309 pub fn set_ini_filename<P: Into<PathBuf>>(
315 &mut self,
316 filename: Option<P>,
317 ) -> crate::error::ImGuiResult<()> {
318 use crate::error::SafeStringConversion;
319 let _guard = CTX_MUTEX.lock();
320
321 self.ini_filename = match filename {
322 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
323 None => None,
324 };
325
326 unsafe {
327 let io = sys::igGetIO_Nil();
328 let ptr = self
329 .ini_filename
330 .as_ref()
331 .map(|s| s.as_ptr())
332 .unwrap_or(ptr::null());
333 (*io).IniFilename = ptr;
334 }
335 Ok(())
336 }
337
338 pub fn set_log_filename<P: Into<PathBuf>>(
346 &mut self,
347 filename: Option<P>,
348 ) -> crate::error::ImGuiResult<()> {
349 use crate::error::SafeStringConversion;
350 let _guard = CTX_MUTEX.lock();
351
352 self.log_filename = match filename {
353 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
354 None => None,
355 };
356
357 unsafe {
358 let io = sys::igGetIO_Nil();
359 let ptr = self
360 .log_filename
361 .as_ref()
362 .map(|s| s.as_ptr())
363 .unwrap_or(ptr::null());
364 (*io).LogFilename = ptr;
365 }
366 Ok(())
367 }
368
369 pub fn set_platform_name<S: Into<String>>(
377 &mut self,
378 name: Option<S>,
379 ) -> crate::error::ImGuiResult<()> {
380 use crate::error::SafeStringConversion;
381 let _guard = CTX_MUTEX.lock();
382
383 self.platform_name = match name {
384 Some(n) => Some(n.into().to_cstring_safe()?),
385 None => None,
386 };
387
388 unsafe {
389 let io = sys::igGetIO_Nil();
390 let ptr = self
391 .platform_name
392 .as_ref()
393 .map(|s| s.as_ptr())
394 .unwrap_or(ptr::null());
395 (*io).BackendPlatformName = ptr;
396 }
397 Ok(())
398 }
399
400 pub fn set_renderer_name<S: Into<String>>(
408 &mut self,
409 name: Option<S>,
410 ) -> crate::error::ImGuiResult<()> {
411 use crate::error::SafeStringConversion;
412 let _guard = CTX_MUTEX.lock();
413
414 self.renderer_name = match name {
415 Some(n) => Some(n.into().to_cstring_safe()?),
416 None => None,
417 };
418
419 unsafe {
420 let io = sys::igGetIO_Nil();
421 if io.is_null() {
422 panic!("igGetIO_Nil() returned null");
423 }
424 let ptr = self
425 .renderer_name
426 .as_ref()
427 .map(|s| s.as_ptr())
428 .unwrap_or(ptr::null());
429 (*io).BackendRendererName = ptr;
430 }
431 Ok(())
432 }
433
434 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
441 let _guard = CTX_MUTEX.lock();
442 unsafe {
443 let pio = sys::igGetPlatformIO_Nil();
444 if pio.is_null() {
445 panic!("igGetPlatformIO_Nil() returned null");
446 }
447 crate::platform_io::PlatformIo::from_raw_mut(pio)
448 }
449 }
450
451 #[doc(alias = "GetMainViewport")]
456 pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
457 let _guard = CTX_MUTEX.lock();
458 unsafe {
459 let ptr = sys::igGetMainViewport();
460 if ptr.is_null() {
461 panic!("Context::main_viewport() requires an active ImGui context");
462 }
463 crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
464 }
465 }
466
467 #[cfg(feature = "multi-viewport")]
469 pub fn enable_multi_viewport(&mut self) {
470 crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
472 }
473
474 #[cfg(feature = "multi-viewport")]
479 pub fn update_platform_windows(&mut self) {
480 let _guard = CTX_MUTEX.lock();
481 unsafe {
482 let main_viewport = sys::igGetMainViewport();
484 if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
485 eprintln!("update_platform_windows: main viewport not set up, setting it up now");
486 }
489
490 sys::igUpdatePlatformWindows();
491 }
492 }
493
494 #[cfg(feature = "multi-viewport")]
499 pub fn render_platform_windows_default(&mut self) {
500 let _guard = CTX_MUTEX.lock();
501 unsafe {
502 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
503 }
504 }
505
506 #[cfg(feature = "multi-viewport")]
511 pub fn destroy_platform_windows(&mut self) {
512 let _guard = CTX_MUTEX.lock();
513 unsafe {
514 sys::igDestroyPlatformWindows();
515 }
516 }
517
518 pub fn suspend(self) -> SuspendedContext {
520 let _guard = CTX_MUTEX.lock();
521 assert!(
522 self.is_current_context(),
523 "context to be suspended is not the active context"
524 );
525 clear_current_context();
526 SuspendedContext(self)
527 }
528
529 fn is_current_context(&self) -> bool {
530 let ctx = unsafe { sys::igGetCurrentContext() };
531 self.raw == ctx
532 }
533
534 pub fn push_font(&mut self, font: &Font) {
536 let _guard = CTX_MUTEX.lock();
537 unsafe {
538 sys::igPushFont(font.raw(), 0.0);
539 }
540 }
541
542 #[doc(alias = "PopFont")]
546 pub fn pop_font(&mut self) {
547 let _guard = CTX_MUTEX.lock();
548 unsafe {
549 sys::igPopFont();
550 }
551 }
552
553 #[doc(alias = "GetFont")]
555 pub fn current_font(&self) -> &Font {
556 let _guard = CTX_MUTEX.lock();
557 unsafe { Font::from_raw(sys::igGetFont() as *const _) }
558 }
559
560 #[doc(alias = "GetFontSize")]
562 pub fn current_font_size(&self) -> f32 {
563 let _guard = CTX_MUTEX.lock();
564 unsafe { sys::igGetFontSize() }
565 }
566
567 pub fn font_atlas(&self) -> FontAtlas {
569 let _guard = CTX_MUTEX.lock();
570
571 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
576 unsafe {
577 let io = sys::igGetIO_Nil();
578 let atlas_ptr = (*io).Fonts;
579 assert!(
580 !atlas_ptr.is_null(),
581 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
582 );
583 FontAtlas::from_raw(atlas_ptr)
584 }
585
586 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
588 {
589 panic!(
590 "font_atlas() is not supported on wasm32 targets without \
591 `wasm-font-atlas-experimental` feature; \
592 see docs/WASM.md for current limitations."
593 );
594 }
595
596 #[cfg(not(target_arch = "wasm32"))]
597 unsafe {
598 let io = sys::igGetIO_Nil();
599 let atlas_ptr = (*io).Fonts;
600 FontAtlas::from_raw(atlas_ptr)
601 }
602 }
603
604 pub fn font_atlas_mut(&mut self) -> FontAtlas {
606 let _guard = CTX_MUTEX.lock();
607
608 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
613 unsafe {
614 let io = sys::igGetIO_Nil();
615 let atlas_ptr = (*io).Fonts;
616 assert!(
617 !atlas_ptr.is_null(),
618 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
619 );
620 return FontAtlas::from_raw(atlas_ptr);
621 }
622
623 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
625 {
626 panic!(
627 "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
628 enable `wasm-font-atlas-experimental` to opt-in for experiments."
629 );
630 }
631
632 #[cfg(not(target_arch = "wasm32"))]
633 unsafe {
634 let io = sys::igGetIO_Nil();
635 let atlas_ptr = (*io).Fonts;
636 FontAtlas::from_raw(atlas_ptr)
637 }
638 }
639
640 pub fn fonts(&mut self) -> FontAtlas {
644 self.font_atlas_mut()
645 }
646
647 pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
649 self.shared_font_atlas.clone()
650 }
651
652 #[doc(alias = "LoadIniSettingsFromMemory")]
654 pub fn load_ini_settings(&mut self, data: &str) {
655 let _guard = CTX_MUTEX.lock();
656 unsafe {
657 sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
658 }
659 }
660
661 #[doc(alias = "SaveIniSettingsToMemory")]
663 pub fn save_ini_settings(&mut self, buf: &mut String) {
664 let _guard = CTX_MUTEX.lock();
665 unsafe {
666 let mut out_ini_size: usize = 0;
667 let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
668 if data_ptr.is_null() || out_ini_size == 0 {
669 return;
670 }
671
672 let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
673 if bytes.last() == Some(&0) {
674 bytes = &bytes[..bytes.len().saturating_sub(1)];
675 }
676 buf.push_str(&String::from_utf8_lossy(bytes));
677 }
678 }
679
680 #[cfg(not(target_arch = "wasm32"))]
686 #[doc(alias = "LoadIniSettingsFromDisk")]
687 pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
688 &mut self,
689 filename: P,
690 ) -> crate::error::ImGuiResult<()> {
691 use crate::error::SafeStringConversion;
692 let _guard = CTX_MUTEX.lock();
693 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
694 unsafe { sys::igLoadIniSettingsFromDisk(cstr.as_ptr()) }
695 Ok(())
696 }
697
698 #[cfg(not(target_arch = "wasm32"))]
704 #[doc(alias = "SaveIniSettingsToDisk")]
705 pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
706 &mut self,
707 filename: P,
708 ) -> crate::error::ImGuiResult<()> {
709 use crate::error::SafeStringConversion;
710 let _guard = CTX_MUTEX.lock();
711 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
712 unsafe { sys::igSaveIniSettingsToDisk(cstr.as_ptr()) }
713 Ok(())
714 }
715
716 #[doc(alias = "GetClipboardText")]
723 pub fn clipboard_text(&self) -> Option<String> {
724 let _guard = CTX_MUTEX.lock();
725 unsafe {
726 let ptr = sys::igGetClipboardText();
727 if ptr.is_null() {
728 return None;
729 }
730 Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
731 }
732 }
733
734 #[doc(alias = "SetClipboardText")]
741 pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
742 let _guard = CTX_MUTEX.lock();
743 unsafe {
744 sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
745 }
746 }
747
748 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
750 let _guard = CTX_MUTEX.lock();
751
752 let clipboard_ctx: Box<UnsafeCell<_>> =
753 Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
754
755 #[cfg(not(target_arch = "wasm32"))]
764 unsafe {
765 let platform_io = sys::igGetPlatformIO_Nil();
766 if platform_io.is_null() {
767 panic!("Context::set_clipboard_backend() requires an active ImGui context");
768 }
769 (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
770 (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
771 (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
772 }
773
774 self.clipboard_ctx = clipboard_ctx;
775 }
776}
777
778impl Drop for Context {
779 fn drop(&mut self) {
780 let _guard = CTX_MUTEX.lock();
781 unsafe {
782 if !self.raw.is_null() {
783 if sys::igGetCurrentContext() == self.raw {
784 clear_current_context();
785 }
786 sys::igDestroyContext(self.raw);
787 }
788 }
789 }
790}
791
792#[derive(Debug)]
796pub struct SuspendedContext(Context);
797
798impl SuspendedContext {
799 pub fn try_create() -> crate::error::ImGuiResult<Self> {
801 Self::try_create_internal(None)
802 }
803
804 pub fn try_create_with_shared_font_atlas(
806 shared_font_atlas: SharedFontAtlas,
807 ) -> crate::error::ImGuiResult<Self> {
808 Self::try_create_internal(Some(shared_font_atlas))
809 }
810
811 pub fn create() -> Self {
813 Self::try_create().expect("Failed to create Dear ImGui context")
814 }
815
816 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
818 Self::try_create_with_shared_font_atlas(shared_font_atlas)
819 .expect("Failed to create Dear ImGui context")
820 }
821
822 fn try_create_internal(
825 mut shared_font_atlas: Option<SharedFontAtlas>,
826 ) -> crate::error::ImGuiResult<Self> {
827 let _guard = CTX_MUTEX.lock();
828
829 let shared_font_atlas_ptr = match &mut shared_font_atlas {
830 Some(atlas) => atlas.as_ptr_mut(),
831 None => ptr::null_mut(),
832 };
833
834 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
835 if raw.is_null() {
836 return Err(crate::error::ImGuiError::ContextCreation {
837 reason: "ImGui_CreateContext returned null".to_string(),
838 });
839 }
840
841 let ctx = Context {
842 raw,
843 shared_font_atlas,
844 ini_filename: None,
845 log_filename: None,
846 platform_name: None,
847 renderer_name: None,
848 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
849 ui: crate::ui::Ui::new(),
850 };
851
852 if ctx.is_current_context() {
854 clear_current_context();
855 }
856
857 Ok(SuspendedContext(ctx))
858 }
859
860 pub fn activate(self) -> Result<Context, SuspendedContext> {
865 let _guard = CTX_MUTEX.lock();
866 if no_current_context() {
867 unsafe {
868 sys::igSetCurrentContext(self.0.raw);
869 }
870 Ok(self.0)
871 } else {
872 Err(self)
873 }
874 }
875}
876
877#[derive(Debug)]
886pub struct RegisteredUserTexture {
887 ctx: *mut sys::ImGuiContext,
888 tex: *mut sys::ImTextureData,
889}
890
891impl Drop for RegisteredUserTexture {
892 fn drop(&mut self) {
893 if self.ctx.is_null() || self.tex.is_null() {
894 return;
895 }
896
897 let _guard = CTX_MUTEX.lock();
898 unsafe {
899 let prev = sys::igGetCurrentContext();
902 if prev != self.ctx {
903 sys::igSetCurrentContext(self.ctx);
904 }
905 sys::igUnregisterUserTexture(self.tex);
906 if prev != self.ctx {
907 sys::igSetCurrentContext(prev);
908 }
909 }
910 }
911}
912
913