1use parking_lot::ReentrantMutex;
9use std::cell::UnsafeCell;
10use std::ffi::CString;
11use std::ops::Drop;
12use std::path::PathBuf;
13use std::ptr;
14use std::rc::{Rc, Weak};
15
16use crate::clipboard::{ClipboardBackend, ClipboardContext};
17use crate::fonts::{Font, FontAtlas, SharedFontAtlas};
18use crate::io::Io;
19
20use crate::sys;
21
22#[derive(Debug)]
48pub struct Context {
49 raw: *mut sys::ImGuiContext,
50 alive: Rc<()>,
51 shared_font_atlas: Option<SharedFontAtlas>,
52 ini_filename: Option<CString>,
53 log_filename: Option<CString>,
54 platform_name: Option<CString>,
55 renderer_name: Option<CString>,
56 clipboard_ctx: Box<UnsafeCell<ClipboardContext>>,
61 ui: crate::ui::Ui,
62}
63
64static CTX_MUTEX: ReentrantMutex<()> = parking_lot::const_reentrant_mutex(());
67
68fn clear_current_context() {
69 unsafe {
70 sys::igSetCurrentContext(ptr::null_mut());
71 }
72}
73
74fn no_current_context() -> bool {
75 let ctx = unsafe { sys::igGetCurrentContext() };
76 ctx.is_null()
77}
78
79impl Context {
80 pub fn try_create() -> crate::error::ImGuiResult<Context> {
84 Self::try_create_internal(None)
85 }
86
87 pub fn try_create_with_shared_font_atlas(
89 shared_font_atlas: SharedFontAtlas,
90 ) -> crate::error::ImGuiResult<Context> {
91 Self::try_create_internal(Some(shared_font_atlas))
92 }
93
94 pub fn create() -> Context {
98 Self::try_create().expect("Failed to create Dear ImGui context")
99 }
100
101 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
103 Self::try_create_with_shared_font_atlas(shared_font_atlas)
104 .expect("Failed to create Dear ImGui context")
105 }
106
107 pub fn as_raw(&self) -> *mut sys::ImGuiContext {
109 self.raw
110 }
111
112 pub fn alive_token(&self) -> ContextAliveToken {
117 ContextAliveToken(Rc::downgrade(&self.alive))
118 }
119
120 fn try_create_internal(
123 mut shared_font_atlas: Option<SharedFontAtlas>,
124 ) -> crate::error::ImGuiResult<Context> {
125 let _guard = CTX_MUTEX.lock();
126
127 if !no_current_context() {
128 return Err(crate::error::ImGuiError::ContextAlreadyActive);
129 }
130
131 let shared_font_atlas_ptr = match &mut shared_font_atlas {
132 Some(atlas) => atlas.as_ptr_mut(),
133 None => ptr::null_mut(),
134 };
135
136 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
138 if raw.is_null() {
139 return Err(crate::error::ImGuiError::ContextCreation {
140 reason: "ImGui_CreateContext returned null".to_string(),
141 });
142 }
143
144 unsafe {
146 sys::igSetCurrentContext(raw);
147 }
148
149 Ok(Context {
150 raw,
151 alive: Rc::new(()),
152 shared_font_atlas,
153 ini_filename: None,
154 log_filename: None,
155 platform_name: None,
156 renderer_name: None,
157 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
158 ui: crate::ui::Ui::new(),
159 })
160 }
161
162 pub fn io_mut(&mut self) -> &mut Io {
164 let _guard = CTX_MUTEX.lock();
165 unsafe {
166 let io_ptr = sys::igGetIO_Nil();
168 if io_ptr.is_null() {
169 panic!("Context::io_mut() requires an active ImGui context");
170 }
171 &mut *(io_ptr as *mut Io)
172 }
173 }
174
175 pub fn io(&self) -> &crate::io::Io {
177 let _guard = CTX_MUTEX.lock();
178 unsafe {
179 let io_ptr = sys::igGetIO_Nil();
181 if io_ptr.is_null() {
182 panic!("Context::io() requires an active ImGui context");
183 }
184 &*(io_ptr as *const crate::io::Io)
185 }
186 }
187
188 pub fn style(&self) -> &crate::style::Style {
190 let _guard = CTX_MUTEX.lock();
191 unsafe {
192 let style_ptr = sys::igGetStyle();
193 if style_ptr.is_null() {
194 panic!("Context::style() requires an active ImGui context");
195 }
196 &*(style_ptr as *const crate::style::Style)
197 }
198 }
199
200 pub fn style_mut(&mut self) -> &mut crate::style::Style {
202 let _guard = CTX_MUTEX.lock();
203 unsafe {
204 let style_ptr = sys::igGetStyle();
205 if style_ptr.is_null() {
206 panic!("Context::style_mut() requires an active ImGui context");
207 }
208 &mut *(style_ptr as *mut crate::style::Style)
209 }
210 }
211
212 pub fn frame(&mut self) -> &mut crate::ui::Ui {
217 let _guard = CTX_MUTEX.lock();
218
219 unsafe {
220 let io = sys::igGetIO_Nil();
225 if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
226 panic!(
227 "Context::frame() called with invalid io.DisplaySize ({}, {}). \
228Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
229If you are using a windowing/event-loop library, prefer a platform backend such as \
230dear-imgui-winit::WinitPlatform::prepare_frame().",
231 (*io).DisplaySize.x,
232 (*io).DisplaySize.y
233 );
234 }
235 sys::igNewFrame();
236 }
237 &mut self.ui
238 }
239
240 pub fn frame_with<F, R>(&mut self, f: F) -> R
242 where
243 F: FnOnce(&crate::ui::Ui) -> R,
244 {
245 let ui = self.frame();
246 f(ui)
247 }
248
249 pub fn render(&mut self) -> &crate::render::DrawData {
254 let _guard = CTX_MUTEX.lock();
255 unsafe {
256 sys::igRender();
257 let dd = sys::igGetDrawData();
258 if dd.is_null() {
259 panic!("Context::render() returned null draw data");
260 }
261 &*(dd as *const crate::render::DrawData)
262 }
263 }
264
265 pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
270 let _guard = CTX_MUTEX.lock();
271 unsafe {
272 let draw_data = sys::igGetDrawData();
273 if draw_data.is_null() {
274 None
275 } else {
276 let data = &*(draw_data as *const crate::render::DrawData);
277 if data.valid() { Some(data) } else { None }
278 }
279 }
280 }
281
282 pub fn register_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
296 let _guard = CTX_MUTEX.lock();
297 assert!(
298 self.is_current_context(),
299 "Context::register_user_texture() requires the context to be current"
300 );
301 unsafe {
302 sys::igRegisterUserTexture(texture.as_raw_mut());
303 }
304 }
305
306 pub fn register_user_texture_token(
314 &mut self,
315 texture: &mut crate::texture::TextureData,
316 ) -> RegisteredUserTexture {
317 self.register_user_texture(texture);
318 RegisteredUserTexture {
319 ctx: self.raw,
320 tex: texture.as_raw_mut(),
321 }
322 }
323
324 pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
328 let _guard = CTX_MUTEX.lock();
329 assert!(
330 self.is_current_context(),
331 "Context::unregister_user_texture() requires the context to be current"
332 );
333 unsafe {
334 sys::igUnregisterUserTexture(texture.as_raw_mut());
335 }
336 }
337
338 pub fn set_ini_filename<P: Into<PathBuf>>(
344 &mut self,
345 filename: Option<P>,
346 ) -> crate::error::ImGuiResult<()> {
347 use crate::error::SafeStringConversion;
348 let _guard = CTX_MUTEX.lock();
349
350 self.ini_filename = match filename {
351 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
352 None => None,
353 };
354
355 unsafe {
356 let io = sys::igGetIO_Nil();
357 let ptr = self
358 .ini_filename
359 .as_ref()
360 .map(|s| s.as_ptr())
361 .unwrap_or(ptr::null());
362 (*io).IniFilename = ptr;
363 }
364 Ok(())
365 }
366
367 pub fn set_log_filename<P: Into<PathBuf>>(
375 &mut self,
376 filename: Option<P>,
377 ) -> crate::error::ImGuiResult<()> {
378 use crate::error::SafeStringConversion;
379 let _guard = CTX_MUTEX.lock();
380
381 self.log_filename = match filename {
382 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
383 None => None,
384 };
385
386 unsafe {
387 let io = sys::igGetIO_Nil();
388 let ptr = self
389 .log_filename
390 .as_ref()
391 .map(|s| s.as_ptr())
392 .unwrap_or(ptr::null());
393 (*io).LogFilename = ptr;
394 }
395 Ok(())
396 }
397
398 pub fn set_platform_name<S: Into<String>>(
406 &mut self,
407 name: Option<S>,
408 ) -> crate::error::ImGuiResult<()> {
409 use crate::error::SafeStringConversion;
410 let _guard = CTX_MUTEX.lock();
411
412 self.platform_name = match name {
413 Some(n) => Some(n.into().to_cstring_safe()?),
414 None => None,
415 };
416
417 unsafe {
418 let io = sys::igGetIO_Nil();
419 let ptr = self
420 .platform_name
421 .as_ref()
422 .map(|s| s.as_ptr())
423 .unwrap_or(ptr::null());
424 (*io).BackendPlatformName = ptr;
425 }
426 Ok(())
427 }
428
429 pub fn set_renderer_name<S: Into<String>>(
437 &mut self,
438 name: Option<S>,
439 ) -> crate::error::ImGuiResult<()> {
440 use crate::error::SafeStringConversion;
441 let _guard = CTX_MUTEX.lock();
442
443 self.renderer_name = match name {
444 Some(n) => Some(n.into().to_cstring_safe()?),
445 None => None,
446 };
447
448 unsafe {
449 let io = sys::igGetIO_Nil();
450 if io.is_null() {
451 panic!("igGetIO_Nil() returned null");
452 }
453 let ptr = self
454 .renderer_name
455 .as_ref()
456 .map(|s| s.as_ptr())
457 .unwrap_or(ptr::null());
458 (*io).BackendRendererName = ptr;
459 }
460 Ok(())
461 }
462
463 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
470 let _guard = CTX_MUTEX.lock();
471 unsafe {
472 let pio = sys::igGetPlatformIO_Nil();
473 if pio.is_null() {
474 panic!("igGetPlatformIO_Nil() returned null");
475 }
476 crate::platform_io::PlatformIo::from_raw_mut(pio)
477 }
478 }
479
480 #[doc(alias = "GetMainViewport")]
485 pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
486 let _guard = CTX_MUTEX.lock();
487 unsafe {
488 let ptr = sys::igGetMainViewport();
489 if ptr.is_null() {
490 panic!("Context::main_viewport() requires an active ImGui context");
491 }
492 crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
493 }
494 }
495
496 #[cfg(feature = "multi-viewport")]
498 pub fn enable_multi_viewport(&mut self) {
499 crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
501 }
502
503 #[cfg(feature = "multi-viewport")]
508 pub fn update_platform_windows(&mut self) {
509 let _guard = CTX_MUTEX.lock();
510 unsafe {
511 let main_viewport = sys::igGetMainViewport();
513 if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
514 eprintln!("update_platform_windows: main viewport not set up, setting it up now");
515 }
518
519 sys::igUpdatePlatformWindows();
520 }
521 }
522
523 #[cfg(feature = "multi-viewport")]
528 pub fn render_platform_windows_default(&mut self) {
529 let _guard = CTX_MUTEX.lock();
530 unsafe {
531 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
532 }
533 }
534
535 #[cfg(feature = "multi-viewport")]
540 pub fn destroy_platform_windows(&mut self) {
541 let _guard = CTX_MUTEX.lock();
542 unsafe {
543 sys::igDestroyPlatformWindows();
544 }
545 }
546
547 pub fn suspend(self) -> SuspendedContext {
549 let _guard = CTX_MUTEX.lock();
550 assert!(
551 self.is_current_context(),
552 "context to be suspended is not the active context"
553 );
554 clear_current_context();
555 SuspendedContext(self)
556 }
557
558 fn is_current_context(&self) -> bool {
559 let ctx = unsafe { sys::igGetCurrentContext() };
560 self.raw == ctx
561 }
562
563 pub fn push_font(&mut self, font: &Font) {
565 let _guard = CTX_MUTEX.lock();
566 unsafe {
567 sys::igPushFont(font.raw(), 0.0);
568 }
569 }
570
571 #[doc(alias = "PopFont")]
575 pub fn pop_font(&mut self) {
576 let _guard = CTX_MUTEX.lock();
577 unsafe {
578 sys::igPopFont();
579 }
580 }
581
582 #[doc(alias = "GetFont")]
584 pub fn current_font(&self) -> &Font {
585 let _guard = CTX_MUTEX.lock();
586 unsafe { Font::from_raw(sys::igGetFont() as *const _) }
587 }
588
589 #[doc(alias = "GetFontSize")]
591 pub fn current_font_size(&self) -> f32 {
592 let _guard = CTX_MUTEX.lock();
593 unsafe { sys::igGetFontSize() }
594 }
595
596 pub fn font_atlas(&self) -> FontAtlas {
598 let _guard = CTX_MUTEX.lock();
599
600 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
605 unsafe {
606 let io = sys::igGetIO_Nil();
607 let atlas_ptr = (*io).Fonts;
608 assert!(
609 !atlas_ptr.is_null(),
610 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
611 );
612 FontAtlas::from_raw(atlas_ptr)
613 }
614
615 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
617 {
618 panic!(
619 "font_atlas() is not supported on wasm32 targets without \
620 `wasm-font-atlas-experimental` feature; \
621 see docs/WASM.md for current limitations."
622 );
623 }
624
625 #[cfg(not(target_arch = "wasm32"))]
626 unsafe {
627 let io = sys::igGetIO_Nil();
628 let atlas_ptr = (*io).Fonts;
629 FontAtlas::from_raw(atlas_ptr)
630 }
631 }
632
633 pub fn font_atlas_mut(&mut self) -> FontAtlas {
635 let _guard = CTX_MUTEX.lock();
636
637 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
642 unsafe {
643 let io = sys::igGetIO_Nil();
644 let atlas_ptr = (*io).Fonts;
645 assert!(
646 !atlas_ptr.is_null(),
647 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
648 );
649 return FontAtlas::from_raw(atlas_ptr);
650 }
651
652 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
654 {
655 panic!(
656 "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
657 enable `wasm-font-atlas-experimental` to opt-in for experiments."
658 );
659 }
660
661 #[cfg(not(target_arch = "wasm32"))]
662 unsafe {
663 let io = sys::igGetIO_Nil();
664 let atlas_ptr = (*io).Fonts;
665 FontAtlas::from_raw(atlas_ptr)
666 }
667 }
668
669 pub fn fonts(&mut self) -> FontAtlas {
673 self.font_atlas_mut()
674 }
675
676 pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
678 self.shared_font_atlas.clone()
679 }
680
681 #[doc(alias = "LoadIniSettingsFromMemory")]
683 pub fn load_ini_settings(&mut self, data: &str) {
684 let _guard = CTX_MUTEX.lock();
685 unsafe {
686 sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
687 }
688 }
689
690 #[doc(alias = "SaveIniSettingsToMemory")]
692 pub fn save_ini_settings(&mut self, buf: &mut String) {
693 let _guard = CTX_MUTEX.lock();
694 unsafe {
695 let mut out_ini_size: usize = 0;
696 let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
697 if data_ptr.is_null() || out_ini_size == 0 {
698 return;
699 }
700
701 let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
702 if bytes.last() == Some(&0) {
703 bytes = &bytes[..bytes.len().saturating_sub(1)];
704 }
705 buf.push_str(&String::from_utf8_lossy(bytes));
706 }
707 }
708
709 #[cfg(not(target_arch = "wasm32"))]
715 #[doc(alias = "LoadIniSettingsFromDisk")]
716 pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
717 &mut self,
718 filename: P,
719 ) -> crate::error::ImGuiResult<()> {
720 use crate::error::SafeStringConversion;
721 let _guard = CTX_MUTEX.lock();
722 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
723 unsafe { sys::igLoadIniSettingsFromDisk(cstr.as_ptr()) }
724 Ok(())
725 }
726
727 #[cfg(not(target_arch = "wasm32"))]
733 #[doc(alias = "SaveIniSettingsToDisk")]
734 pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
735 &mut self,
736 filename: P,
737 ) -> crate::error::ImGuiResult<()> {
738 use crate::error::SafeStringConversion;
739 let _guard = CTX_MUTEX.lock();
740 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
741 unsafe { sys::igSaveIniSettingsToDisk(cstr.as_ptr()) }
742 Ok(())
743 }
744
745 #[doc(alias = "GetClipboardText")]
752 pub fn clipboard_text(&self) -> Option<String> {
753 let _guard = CTX_MUTEX.lock();
754 unsafe {
755 let ptr = sys::igGetClipboardText();
756 if ptr.is_null() {
757 return None;
758 }
759 Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
760 }
761 }
762
763 #[doc(alias = "SetClipboardText")]
770 pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
771 let _guard = CTX_MUTEX.lock();
772 unsafe {
773 sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
774 }
775 }
776
777 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
779 let _guard = CTX_MUTEX.lock();
780
781 let clipboard_ctx: Box<UnsafeCell<_>> =
782 Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
783
784 #[cfg(not(target_arch = "wasm32"))]
793 unsafe {
794 let platform_io = sys::igGetPlatformIO_Nil();
795 if platform_io.is_null() {
796 panic!("Context::set_clipboard_backend() requires an active ImGui context");
797 }
798 (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
799 (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
800 (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
801 }
802
803 self.clipboard_ctx = clipboard_ctx;
804 }
805}
806
807impl Drop for Context {
808 fn drop(&mut self) {
809 let _guard = CTX_MUTEX.lock();
810 unsafe {
811 if !self.raw.is_null() {
812 if sys::igGetCurrentContext() == self.raw {
813 clear_current_context();
814 }
815 sys::igDestroyContext(self.raw);
816 }
817 }
818 }
819}
820
821#[derive(Debug)]
825pub struct SuspendedContext(Context);
826
827#[derive(Clone, Debug)]
829pub struct ContextAliveToken(Weak<()>);
830
831impl ContextAliveToken {
832 pub fn is_alive(&self) -> bool {
834 self.0.upgrade().is_some()
835 }
836}
837
838impl SuspendedContext {
839 pub fn try_create() -> crate::error::ImGuiResult<Self> {
841 Self::try_create_internal(None)
842 }
843
844 pub fn try_create_with_shared_font_atlas(
846 shared_font_atlas: SharedFontAtlas,
847 ) -> crate::error::ImGuiResult<Self> {
848 Self::try_create_internal(Some(shared_font_atlas))
849 }
850
851 pub fn create() -> Self {
853 Self::try_create().expect("Failed to create Dear ImGui context")
854 }
855
856 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
858 Self::try_create_with_shared_font_atlas(shared_font_atlas)
859 .expect("Failed to create Dear ImGui context")
860 }
861
862 fn try_create_internal(
865 mut shared_font_atlas: Option<SharedFontAtlas>,
866 ) -> crate::error::ImGuiResult<Self> {
867 let _guard = CTX_MUTEX.lock();
868
869 let shared_font_atlas_ptr = match &mut shared_font_atlas {
870 Some(atlas) => atlas.as_ptr_mut(),
871 None => ptr::null_mut(),
872 };
873
874 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
875 if raw.is_null() {
876 return Err(crate::error::ImGuiError::ContextCreation {
877 reason: "ImGui_CreateContext returned null".to_string(),
878 });
879 }
880
881 let ctx = Context {
882 raw,
883 alive: Rc::new(()),
884 shared_font_atlas,
885 ini_filename: None,
886 log_filename: None,
887 platform_name: None,
888 renderer_name: None,
889 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
890 ui: crate::ui::Ui::new(),
891 };
892
893 if ctx.is_current_context() {
895 clear_current_context();
896 }
897
898 Ok(SuspendedContext(ctx))
899 }
900
901 pub fn activate(self) -> Result<Context, SuspendedContext> {
906 let _guard = CTX_MUTEX.lock();
907 if no_current_context() {
908 unsafe {
909 sys::igSetCurrentContext(self.0.raw);
910 }
911 Ok(self.0)
912 } else {
913 Err(self)
914 }
915 }
916}
917
918#[derive(Debug)]
927pub struct RegisteredUserTexture {
928 ctx: *mut sys::ImGuiContext,
929 tex: *mut sys::ImTextureData,
930}
931
932impl Drop for RegisteredUserTexture {
933 fn drop(&mut self) {
934 if self.ctx.is_null() || self.tex.is_null() {
935 return;
936 }
937
938 let _guard = CTX_MUTEX.lock();
939 unsafe {
940 let prev = sys::igGetCurrentContext();
943 if prev != self.ctx {
944 sys::igSetCurrentContext(self.ctx);
945 }
946 sys::igUnregisterUserTexture(self.tex);
947 if prev != self.ctx {
948 sys::igSetCurrentContext(prev);
949 }
950 }
951 }
952}
953
954