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 {
214 let _guard = CTX_MUTEX.lock();
215
216 unsafe {
217 sys::igNewFrame();
218 }
219 &mut self.ui
220 }
221
222 pub fn frame_with<F, R>(&mut self, f: F) -> R
224 where
225 F: FnOnce(&crate::ui::Ui) -> R,
226 {
227 let ui = self.frame();
228 f(ui)
229 }
230
231 pub fn render(&mut self) -> &crate::render::DrawData {
236 let _guard = CTX_MUTEX.lock();
237 unsafe {
238 sys::igRender();
239 let dd = sys::igGetDrawData();
240 if dd.is_null() {
241 panic!("Context::render() returned null draw data");
242 }
243 &*(dd as *const crate::render::DrawData)
244 }
245 }
246
247 pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
252 let _guard = CTX_MUTEX.lock();
253 unsafe {
254 let draw_data = sys::igGetDrawData();
255 if draw_data.is_null() {
256 None
257 } else {
258 let data = &*(draw_data as *const crate::render::DrawData);
259 if data.valid() { Some(data) } else { None }
260 }
261 }
262 }
263
264 pub fn register_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
278 let _guard = CTX_MUTEX.lock();
279 assert!(
280 self.is_current_context(),
281 "Context::register_user_texture() requires the context to be current"
282 );
283 unsafe {
284 sys::igRegisterUserTexture(texture.as_raw_mut());
285 }
286 }
287
288 pub fn register_user_texture_token(
296 &mut self,
297 texture: &mut crate::texture::TextureData,
298 ) -> RegisteredUserTexture {
299 self.register_user_texture(texture);
300 RegisteredUserTexture {
301 ctx: self.raw,
302 tex: texture.as_raw_mut(),
303 }
304 }
305
306 pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
310 let _guard = CTX_MUTEX.lock();
311 assert!(
312 self.is_current_context(),
313 "Context::unregister_user_texture() requires the context to be current"
314 );
315 unsafe {
316 sys::igUnregisterUserTexture(texture.as_raw_mut());
317 }
318 }
319
320 pub fn set_ini_filename<P: Into<PathBuf>>(
326 &mut self,
327 filename: Option<P>,
328 ) -> crate::error::ImGuiResult<()> {
329 use crate::error::SafeStringConversion;
330 let _guard = CTX_MUTEX.lock();
331
332 self.ini_filename = match filename {
333 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
334 None => None,
335 };
336
337 unsafe {
338 let io = sys::igGetIO_Nil();
339 let ptr = self
340 .ini_filename
341 .as_ref()
342 .map(|s| s.as_ptr())
343 .unwrap_or(ptr::null());
344 (*io).IniFilename = ptr;
345 }
346 Ok(())
347 }
348
349 pub fn set_log_filename<P: Into<PathBuf>>(
357 &mut self,
358 filename: Option<P>,
359 ) -> crate::error::ImGuiResult<()> {
360 use crate::error::SafeStringConversion;
361 let _guard = CTX_MUTEX.lock();
362
363 self.log_filename = match filename {
364 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
365 None => None,
366 };
367
368 unsafe {
369 let io = sys::igGetIO_Nil();
370 let ptr = self
371 .log_filename
372 .as_ref()
373 .map(|s| s.as_ptr())
374 .unwrap_or(ptr::null());
375 (*io).LogFilename = ptr;
376 }
377 Ok(())
378 }
379
380 pub fn set_platform_name<S: Into<String>>(
388 &mut self,
389 name: Option<S>,
390 ) -> crate::error::ImGuiResult<()> {
391 use crate::error::SafeStringConversion;
392 let _guard = CTX_MUTEX.lock();
393
394 self.platform_name = match name {
395 Some(n) => Some(n.into().to_cstring_safe()?),
396 None => None,
397 };
398
399 unsafe {
400 let io = sys::igGetIO_Nil();
401 let ptr = self
402 .platform_name
403 .as_ref()
404 .map(|s| s.as_ptr())
405 .unwrap_or(ptr::null());
406 (*io).BackendPlatformName = ptr;
407 }
408 Ok(())
409 }
410
411 pub fn set_renderer_name<S: Into<String>>(
419 &mut self,
420 name: Option<S>,
421 ) -> crate::error::ImGuiResult<()> {
422 use crate::error::SafeStringConversion;
423 let _guard = CTX_MUTEX.lock();
424
425 self.renderer_name = match name {
426 Some(n) => Some(n.into().to_cstring_safe()?),
427 None => None,
428 };
429
430 unsafe {
431 let io = sys::igGetIO_Nil();
432 if io.is_null() {
433 panic!("igGetIO_Nil() returned null");
434 }
435 let ptr = self
436 .renderer_name
437 .as_ref()
438 .map(|s| s.as_ptr())
439 .unwrap_or(ptr::null());
440 (*io).BackendRendererName = ptr;
441 }
442 Ok(())
443 }
444
445 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
452 let _guard = CTX_MUTEX.lock();
453 unsafe {
454 let pio = sys::igGetPlatformIO_Nil();
455 if pio.is_null() {
456 panic!("igGetPlatformIO_Nil() returned null");
457 }
458 crate::platform_io::PlatformIo::from_raw_mut(pio)
459 }
460 }
461
462 #[doc(alias = "GetMainViewport")]
467 pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
468 let _guard = CTX_MUTEX.lock();
469 unsafe {
470 let ptr = sys::igGetMainViewport();
471 if ptr.is_null() {
472 panic!("Context::main_viewport() requires an active ImGui context");
473 }
474 crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
475 }
476 }
477
478 #[cfg(feature = "multi-viewport")]
480 pub fn enable_multi_viewport(&mut self) {
481 crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
483 }
484
485 #[cfg(feature = "multi-viewport")]
490 pub fn update_platform_windows(&mut self) {
491 let _guard = CTX_MUTEX.lock();
492 unsafe {
493 let main_viewport = sys::igGetMainViewport();
495 if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
496 eprintln!("update_platform_windows: main viewport not set up, setting it up now");
497 }
500
501 sys::igUpdatePlatformWindows();
502 }
503 }
504
505 #[cfg(feature = "multi-viewport")]
510 pub fn render_platform_windows_default(&mut self) {
511 let _guard = CTX_MUTEX.lock();
512 unsafe {
513 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
514 }
515 }
516
517 #[cfg(feature = "multi-viewport")]
522 pub fn destroy_platform_windows(&mut self) {
523 let _guard = CTX_MUTEX.lock();
524 unsafe {
525 sys::igDestroyPlatformWindows();
526 }
527 }
528
529 pub fn suspend(self) -> SuspendedContext {
531 let _guard = CTX_MUTEX.lock();
532 assert!(
533 self.is_current_context(),
534 "context to be suspended is not the active context"
535 );
536 clear_current_context();
537 SuspendedContext(self)
538 }
539
540 fn is_current_context(&self) -> bool {
541 let ctx = unsafe { sys::igGetCurrentContext() };
542 self.raw == ctx
543 }
544
545 pub fn push_font(&mut self, font: &Font) {
547 let _guard = CTX_MUTEX.lock();
548 unsafe {
549 sys::igPushFont(font.raw(), 0.0);
550 }
551 }
552
553 #[doc(alias = "PopFont")]
557 pub fn pop_font(&mut self) {
558 let _guard = CTX_MUTEX.lock();
559 unsafe {
560 sys::igPopFont();
561 }
562 }
563
564 #[doc(alias = "GetFont")]
566 pub fn current_font(&self) -> &Font {
567 let _guard = CTX_MUTEX.lock();
568 unsafe { Font::from_raw(sys::igGetFont() as *const _) }
569 }
570
571 #[doc(alias = "GetFontSize")]
573 pub fn current_font_size(&self) -> f32 {
574 let _guard = CTX_MUTEX.lock();
575 unsafe { sys::igGetFontSize() }
576 }
577
578 pub fn font_atlas(&self) -> FontAtlas {
580 let _guard = CTX_MUTEX.lock();
581
582 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
587 unsafe {
588 let io = sys::igGetIO_Nil();
589 let atlas_ptr = (*io).Fonts;
590 assert!(
591 !atlas_ptr.is_null(),
592 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
593 );
594 FontAtlas::from_raw(atlas_ptr)
595 }
596
597 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
599 {
600 panic!(
601 "font_atlas() is not supported on wasm32 targets without \
602 `wasm-font-atlas-experimental` feature; \
603 see docs/WASM.md for current limitations."
604 );
605 }
606
607 #[cfg(not(target_arch = "wasm32"))]
608 unsafe {
609 let io = sys::igGetIO_Nil();
610 let atlas_ptr = (*io).Fonts;
611 FontAtlas::from_raw(atlas_ptr)
612 }
613 }
614
615 pub fn font_atlas_mut(&mut self) -> FontAtlas {
617 let _guard = CTX_MUTEX.lock();
618
619 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
624 unsafe {
625 let io = sys::igGetIO_Nil();
626 let atlas_ptr = (*io).Fonts;
627 assert!(
628 !atlas_ptr.is_null(),
629 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
630 );
631 return FontAtlas::from_raw(atlas_ptr);
632 }
633
634 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
636 {
637 panic!(
638 "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
639 enable `wasm-font-atlas-experimental` to opt-in for experiments."
640 );
641 }
642
643 #[cfg(not(target_arch = "wasm32"))]
644 unsafe {
645 let io = sys::igGetIO_Nil();
646 let atlas_ptr = (*io).Fonts;
647 FontAtlas::from_raw(atlas_ptr)
648 }
649 }
650
651 pub fn fonts(&mut self) -> FontAtlas {
655 self.font_atlas_mut()
656 }
657
658 pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
660 self.shared_font_atlas.clone()
661 }
662
663 #[doc(alias = "LoadIniSettingsFromMemory")]
665 pub fn load_ini_settings(&mut self, data: &str) {
666 let _guard = CTX_MUTEX.lock();
667 unsafe {
668 sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
669 }
670 }
671
672 #[doc(alias = "SaveIniSettingsToMemory")]
674 pub fn save_ini_settings(&mut self, buf: &mut String) {
675 let _guard = CTX_MUTEX.lock();
676 unsafe {
677 let mut out_ini_size: usize = 0;
678 let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
679 if data_ptr.is_null() || out_ini_size == 0 {
680 return;
681 }
682
683 let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
684 if bytes.last() == Some(&0) {
685 bytes = &bytes[..bytes.len().saturating_sub(1)];
686 }
687 buf.push_str(&String::from_utf8_lossy(bytes));
688 }
689 }
690
691 #[cfg(not(target_arch = "wasm32"))]
697 #[doc(alias = "LoadIniSettingsFromDisk")]
698 pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
699 &mut self,
700 filename: P,
701 ) -> crate::error::ImGuiResult<()> {
702 use crate::error::SafeStringConversion;
703 let _guard = CTX_MUTEX.lock();
704 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
705 unsafe { sys::igLoadIniSettingsFromDisk(cstr.as_ptr()) }
706 Ok(())
707 }
708
709 #[cfg(not(target_arch = "wasm32"))]
715 #[doc(alias = "SaveIniSettingsToDisk")]
716 pub fn save_ini_settings_to_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::igSaveIniSettingsToDisk(cstr.as_ptr()) }
724 Ok(())
725 }
726
727 #[doc(alias = "GetClipboardText")]
734 pub fn clipboard_text(&self) -> Option<String> {
735 let _guard = CTX_MUTEX.lock();
736 unsafe {
737 let ptr = sys::igGetClipboardText();
738 if ptr.is_null() {
739 return None;
740 }
741 Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
742 }
743 }
744
745 #[doc(alias = "SetClipboardText")]
752 pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
753 let _guard = CTX_MUTEX.lock();
754 unsafe {
755 sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
756 }
757 }
758
759 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
761 let _guard = CTX_MUTEX.lock();
762
763 let clipboard_ctx: Box<UnsafeCell<_>> =
764 Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
765
766 #[cfg(not(target_arch = "wasm32"))]
775 unsafe {
776 let platform_io = sys::igGetPlatformIO_Nil();
777 if platform_io.is_null() {
778 panic!("Context::set_clipboard_backend() requires an active ImGui context");
779 }
780 (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
781 (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
782 (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
783 }
784
785 self.clipboard_ctx = clipboard_ctx;
786 }
787}
788
789impl Drop for Context {
790 fn drop(&mut self) {
791 let _guard = CTX_MUTEX.lock();
792 unsafe {
793 if !self.raw.is_null() {
794 if sys::igGetCurrentContext() == self.raw {
795 clear_current_context();
796 }
797 sys::igDestroyContext(self.raw);
798 }
799 }
800 }
801}
802
803#[derive(Debug)]
807pub struct SuspendedContext(Context);
808
809#[derive(Clone, Debug)]
811pub struct ContextAliveToken(Weak<()>);
812
813impl ContextAliveToken {
814 pub fn is_alive(&self) -> bool {
816 self.0.upgrade().is_some()
817 }
818}
819
820impl SuspendedContext {
821 pub fn try_create() -> crate::error::ImGuiResult<Self> {
823 Self::try_create_internal(None)
824 }
825
826 pub fn try_create_with_shared_font_atlas(
828 shared_font_atlas: SharedFontAtlas,
829 ) -> crate::error::ImGuiResult<Self> {
830 Self::try_create_internal(Some(shared_font_atlas))
831 }
832
833 pub fn create() -> Self {
835 Self::try_create().expect("Failed to create Dear ImGui context")
836 }
837
838 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
840 Self::try_create_with_shared_font_atlas(shared_font_atlas)
841 .expect("Failed to create Dear ImGui context")
842 }
843
844 fn try_create_internal(
847 mut shared_font_atlas: Option<SharedFontAtlas>,
848 ) -> crate::error::ImGuiResult<Self> {
849 let _guard = CTX_MUTEX.lock();
850
851 let shared_font_atlas_ptr = match &mut shared_font_atlas {
852 Some(atlas) => atlas.as_ptr_mut(),
853 None => ptr::null_mut(),
854 };
855
856 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
857 if raw.is_null() {
858 return Err(crate::error::ImGuiError::ContextCreation {
859 reason: "ImGui_CreateContext returned null".to_string(),
860 });
861 }
862
863 let ctx = Context {
864 raw,
865 alive: Rc::new(()),
866 shared_font_atlas,
867 ini_filename: None,
868 log_filename: None,
869 platform_name: None,
870 renderer_name: None,
871 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
872 ui: crate::ui::Ui::new(),
873 };
874
875 if ctx.is_current_context() {
877 clear_current_context();
878 }
879
880 Ok(SuspendedContext(ctx))
881 }
882
883 pub fn activate(self) -> Result<Context, SuspendedContext> {
888 let _guard = CTX_MUTEX.lock();
889 if no_current_context() {
890 unsafe {
891 sys::igSetCurrentContext(self.0.raw);
892 }
893 Ok(self.0)
894 } else {
895 Err(self)
896 }
897 }
898}
899
900#[derive(Debug)]
909pub struct RegisteredUserTexture {
910 ctx: *mut sys::ImGuiContext,
911 tex: *mut sys::ImTextureData,
912}
913
914impl Drop for RegisteredUserTexture {
915 fn drop(&mut self) {
916 if self.ctx.is_null() || self.tex.is_null() {
917 return;
918 }
919
920 let _guard = CTX_MUTEX.lock();
921 unsafe {
922 let prev = sys::igGetCurrentContext();
925 if prev != self.ctx {
926 sys::igSetCurrentContext(self.ctx);
927 }
928 sys::igUnregisterUserTexture(self.tex);
929 if prev != self.ctx {
930 sys::igSetCurrentContext(prev);
931 }
932 }
933 }
934}
935
936