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 fn try_create_internal(
108 mut shared_font_atlas: Option<SharedFontAtlas>,
109 ) -> crate::error::ImGuiResult<Context> {
110 let _guard = CTX_MUTEX.lock();
111
112 if !no_current_context() {
113 return Err(crate::error::ImGuiError::ContextAlreadyActive);
114 }
115
116 let shared_font_atlas_ptr = match &mut shared_font_atlas {
117 Some(atlas) => atlas.as_ptr_mut(),
118 None => ptr::null_mut(),
119 };
120
121 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
123 if raw.is_null() {
124 return Err(crate::error::ImGuiError::ContextCreation {
125 reason: "ImGui_CreateContext returned null".to_string(),
126 });
127 }
128
129 unsafe {
131 sys::igSetCurrentContext(raw);
132 }
133
134 Ok(Context {
135 raw,
136 shared_font_atlas,
137 ini_filename: None,
138 log_filename: None,
139 platform_name: None,
140 renderer_name: None,
141 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
142 ui: crate::ui::Ui::new(),
143 })
144 }
145
146 pub fn io_mut(&mut self) -> &mut Io {
148 let _guard = CTX_MUTEX.lock();
149 unsafe {
150 let io_ptr = sys::igGetIO_Nil();
152 if io_ptr.is_null() {
153 panic!("Context::io_mut() requires an active ImGui context");
154 }
155 &mut *(io_ptr as *mut Io)
156 }
157 }
158
159 pub fn io(&self) -> &crate::io::Io {
161 let _guard = CTX_MUTEX.lock();
162 unsafe {
163 let io_ptr = sys::igGetIO_Nil();
165 if io_ptr.is_null() {
166 panic!("Context::io() requires an active ImGui context");
167 }
168 &*(io_ptr as *const crate::io::Io)
169 }
170 }
171
172 pub fn style(&self) -> &crate::style::Style {
174 let _guard = CTX_MUTEX.lock();
175 unsafe {
176 let style_ptr = sys::igGetStyle();
177 if style_ptr.is_null() {
178 panic!("Context::style() requires an active ImGui context");
179 }
180 &*(style_ptr as *const crate::style::Style)
181 }
182 }
183
184 pub fn style_mut(&mut self) -> &mut crate::style::Style {
186 let _guard = CTX_MUTEX.lock();
187 unsafe {
188 let style_ptr = sys::igGetStyle();
189 if style_ptr.is_null() {
190 panic!("Context::style_mut() requires an active ImGui context");
191 }
192 &mut *(style_ptr as *mut crate::style::Style)
193 }
194 }
195
196 pub fn frame(&mut self) -> &mut crate::ui::Ui {
198 let _guard = CTX_MUTEX.lock();
199
200 unsafe {
201 sys::igNewFrame();
202 }
203 &mut self.ui
204 }
205
206 pub fn frame_with<F, R>(&mut self, f: F) -> R
208 where
209 F: FnOnce(&crate::ui::Ui) -> R,
210 {
211 let ui = self.frame();
212 f(ui)
213 }
214
215 pub fn render(&mut self) -> &crate::render::DrawData {
220 let _guard = CTX_MUTEX.lock();
221 unsafe {
222 sys::igRender();
223 let dd = sys::igGetDrawData();
224 if dd.is_null() {
225 panic!("Context::render() returned null draw data");
226 }
227 &*(dd as *const crate::render::DrawData)
228 }
229 }
230
231 pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
236 let _guard = CTX_MUTEX.lock();
237 unsafe {
238 let draw_data = sys::igGetDrawData();
239 if draw_data.is_null() {
240 None
241 } else {
242 let data = &*(draw_data as *const crate::render::DrawData);
243 if data.valid() { Some(data) } else { None }
244 }
245 }
246 }
247
248 pub fn register_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
262 let _guard = CTX_MUTEX.lock();
263 assert!(
264 self.is_current_context(),
265 "Context::register_user_texture() requires the context to be current"
266 );
267 unsafe {
268 sys::igRegisterUserTexture(texture.as_raw_mut());
269 }
270 }
271
272 pub fn register_user_texture_token(
280 &mut self,
281 texture: &mut crate::texture::TextureData,
282 ) -> RegisteredUserTexture {
283 self.register_user_texture(texture);
284 RegisteredUserTexture {
285 ctx: self.raw,
286 tex: texture.as_raw_mut(),
287 }
288 }
289
290 pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
294 let _guard = CTX_MUTEX.lock();
295 assert!(
296 self.is_current_context(),
297 "Context::unregister_user_texture() requires the context to be current"
298 );
299 unsafe {
300 sys::igUnregisterUserTexture(texture.as_raw_mut());
301 }
302 }
303
304 pub fn set_ini_filename<P: Into<PathBuf>>(
310 &mut self,
311 filename: Option<P>,
312 ) -> crate::error::ImGuiResult<()> {
313 use crate::error::SafeStringConversion;
314 let _guard = CTX_MUTEX.lock();
315
316 self.ini_filename = match filename {
317 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
318 None => None,
319 };
320
321 unsafe {
322 let io = sys::igGetIO_Nil();
323 let ptr = self
324 .ini_filename
325 .as_ref()
326 .map(|s| s.as_ptr())
327 .unwrap_or(ptr::null());
328 (*io).IniFilename = ptr;
329 }
330 Ok(())
331 }
332
333 pub fn set_log_filename<P: Into<PathBuf>>(
341 &mut self,
342 filename: Option<P>,
343 ) -> crate::error::ImGuiResult<()> {
344 use crate::error::SafeStringConversion;
345 let _guard = CTX_MUTEX.lock();
346
347 self.log_filename = match filename {
348 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
349 None => None,
350 };
351
352 unsafe {
353 let io = sys::igGetIO_Nil();
354 let ptr = self
355 .log_filename
356 .as_ref()
357 .map(|s| s.as_ptr())
358 .unwrap_or(ptr::null());
359 (*io).LogFilename = ptr;
360 }
361 Ok(())
362 }
363
364 pub fn set_platform_name<S: Into<String>>(
372 &mut self,
373 name: Option<S>,
374 ) -> crate::error::ImGuiResult<()> {
375 use crate::error::SafeStringConversion;
376 let _guard = CTX_MUTEX.lock();
377
378 self.platform_name = match name {
379 Some(n) => Some(n.into().to_cstring_safe()?),
380 None => None,
381 };
382
383 unsafe {
384 let io = sys::igGetIO_Nil();
385 let ptr = self
386 .platform_name
387 .as_ref()
388 .map(|s| s.as_ptr())
389 .unwrap_or(ptr::null());
390 (*io).BackendPlatformName = ptr;
391 }
392 Ok(())
393 }
394
395 pub fn set_renderer_name<S: Into<String>>(
403 &mut self,
404 name: Option<S>,
405 ) -> crate::error::ImGuiResult<()> {
406 use crate::error::SafeStringConversion;
407 let _guard = CTX_MUTEX.lock();
408
409 self.renderer_name = match name {
410 Some(n) => Some(n.into().to_cstring_safe()?),
411 None => None,
412 };
413
414 unsafe {
415 let io = sys::igGetIO_Nil();
416 if io.is_null() {
417 panic!("igGetIO_Nil() returned null");
418 }
419 let ptr = self
420 .renderer_name
421 .as_ref()
422 .map(|s| s.as_ptr())
423 .unwrap_or(ptr::null());
424 (*io).BackendRendererName = ptr;
425 }
426 Ok(())
427 }
428
429 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
436 let _guard = CTX_MUTEX.lock();
437 unsafe {
438 let pio = sys::igGetPlatformIO_Nil();
439 if pio.is_null() {
440 panic!("igGetPlatformIO_Nil() returned null");
441 }
442 crate::platform_io::PlatformIo::from_raw_mut(pio)
443 }
444 }
445
446 #[doc(alias = "GetMainViewport")]
451 pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
452 let _guard = CTX_MUTEX.lock();
453 unsafe {
454 let ptr = sys::igGetMainViewport();
455 if ptr.is_null() {
456 panic!("Context::main_viewport() requires an active ImGui context");
457 }
458 crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
459 }
460 }
461
462 #[cfg(feature = "multi-viewport")]
464 pub fn enable_multi_viewport(&mut self) {
465 crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
467 }
468
469 #[cfg(feature = "multi-viewport")]
474 pub fn update_platform_windows(&mut self) {
475 let _guard = CTX_MUTEX.lock();
476 unsafe {
477 let main_viewport = sys::igGetMainViewport();
479 if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
480 eprintln!("update_platform_windows: main viewport not set up, setting it up now");
481 }
484
485 sys::igUpdatePlatformWindows();
486 }
487 }
488
489 #[cfg(feature = "multi-viewport")]
494 pub fn render_platform_windows_default(&mut self) {
495 let _guard = CTX_MUTEX.lock();
496 unsafe {
497 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
498 }
499 }
500
501 #[cfg(feature = "multi-viewport")]
506 pub fn destroy_platform_windows(&mut self) {
507 let _guard = CTX_MUTEX.lock();
508 unsafe {
509 sys::igDestroyPlatformWindows();
510 }
511 }
512
513 pub fn suspend(self) -> SuspendedContext {
515 let _guard = CTX_MUTEX.lock();
516 assert!(
517 self.is_current_context(),
518 "context to be suspended is not the active context"
519 );
520 clear_current_context();
521 SuspendedContext(self)
522 }
523
524 fn is_current_context(&self) -> bool {
525 let ctx = unsafe { sys::igGetCurrentContext() };
526 self.raw == ctx
527 }
528
529 pub fn push_font(&mut self, font: &Font) {
531 let _guard = CTX_MUTEX.lock();
532 unsafe {
533 sys::igPushFont(font.raw(), 0.0);
534 }
535 }
536
537 #[doc(alias = "PopFont")]
541 pub fn pop_font(&mut self) {
542 let _guard = CTX_MUTEX.lock();
543 unsafe {
544 sys::igPopFont();
545 }
546 }
547
548 #[doc(alias = "GetFont")]
550 pub fn current_font(&self) -> &Font {
551 let _guard = CTX_MUTEX.lock();
552 unsafe { Font::from_raw(sys::igGetFont() as *const _) }
553 }
554
555 #[doc(alias = "GetFontSize")]
557 pub fn current_font_size(&self) -> f32 {
558 let _guard = CTX_MUTEX.lock();
559 unsafe { sys::igGetFontSize() }
560 }
561
562 pub fn font_atlas(&self) -> FontAtlas {
564 let _guard = CTX_MUTEX.lock();
565
566 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
571 unsafe {
572 let io = sys::igGetIO_Nil();
573 let atlas_ptr = (*io).Fonts;
574 assert!(
575 !atlas_ptr.is_null(),
576 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
577 );
578 FontAtlas::from_raw(atlas_ptr)
579 }
580
581 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
583 {
584 panic!(
585 "font_atlas() is not supported on wasm32 targets without \
586 `wasm-font-atlas-experimental` feature; \
587 see docs/WASM.md for current limitations."
588 );
589 }
590
591 #[cfg(not(target_arch = "wasm32"))]
592 unsafe {
593 let io = sys::igGetIO_Nil();
594 let atlas_ptr = (*io).Fonts;
595 FontAtlas::from_raw(atlas_ptr)
596 }
597 }
598
599 pub fn font_atlas_mut(&mut self) -> FontAtlas {
601 let _guard = CTX_MUTEX.lock();
602
603 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
608 unsafe {
609 let io = sys::igGetIO_Nil();
610 let atlas_ptr = (*io).Fonts;
611 assert!(
612 !atlas_ptr.is_null(),
613 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
614 );
615 return FontAtlas::from_raw(atlas_ptr);
616 }
617
618 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
620 {
621 panic!(
622 "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
623 enable `wasm-font-atlas-experimental` to opt-in for experiments."
624 );
625 }
626
627 #[cfg(not(target_arch = "wasm32"))]
628 unsafe {
629 let io = sys::igGetIO_Nil();
630 let atlas_ptr = (*io).Fonts;
631 FontAtlas::from_raw(atlas_ptr)
632 }
633 }
634
635 pub fn fonts(&mut self) -> FontAtlas {
639 self.font_atlas_mut()
640 }
641
642 pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
644 self.shared_font_atlas.clone()
645 }
646
647 #[doc(alias = "LoadIniSettingsFromMemory")]
649 pub fn load_ini_settings(&mut self, data: &str) {
650 let _guard = CTX_MUTEX.lock();
651 unsafe {
652 sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
653 }
654 }
655
656 #[doc(alias = "SaveIniSettingsToMemory")]
658 pub fn save_ini_settings(&mut self, buf: &mut String) {
659 let _guard = CTX_MUTEX.lock();
660 unsafe {
661 let mut out_ini_size: usize = 0;
662 let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
663 if data_ptr.is_null() || out_ini_size == 0 {
664 return;
665 }
666
667 let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
668 if bytes.last() == Some(&0) {
669 bytes = &bytes[..bytes.len().saturating_sub(1)];
670 }
671 buf.push_str(&String::from_utf8_lossy(bytes));
672 }
673 }
674
675 #[cfg(not(target_arch = "wasm32"))]
681 #[doc(alias = "LoadIniSettingsFromDisk")]
682 pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
683 &mut self,
684 filename: P,
685 ) -> crate::error::ImGuiResult<()> {
686 use crate::error::SafeStringConversion;
687 let _guard = CTX_MUTEX.lock();
688 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
689 unsafe { sys::igLoadIniSettingsFromDisk(cstr.as_ptr()) }
690 Ok(())
691 }
692
693 #[cfg(not(target_arch = "wasm32"))]
699 #[doc(alias = "SaveIniSettingsToDisk")]
700 pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
701 &mut self,
702 filename: P,
703 ) -> crate::error::ImGuiResult<()> {
704 use crate::error::SafeStringConversion;
705 let _guard = CTX_MUTEX.lock();
706 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
707 unsafe { sys::igSaveIniSettingsToDisk(cstr.as_ptr()) }
708 Ok(())
709 }
710
711 #[doc(alias = "GetClipboardText")]
718 pub fn clipboard_text(&self) -> Option<String> {
719 let _guard = CTX_MUTEX.lock();
720 unsafe {
721 let ptr = sys::igGetClipboardText();
722 if ptr.is_null() {
723 return None;
724 }
725 Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
726 }
727 }
728
729 #[doc(alias = "SetClipboardText")]
736 pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
737 let _guard = CTX_MUTEX.lock();
738 unsafe {
739 sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
740 }
741 }
742
743 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
745 let _guard = CTX_MUTEX.lock();
746
747 let clipboard_ctx: Box<UnsafeCell<_>> =
748 Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
749
750 #[cfg(not(target_arch = "wasm32"))]
759 unsafe {
760 let platform_io = sys::igGetPlatformIO_Nil();
761 if platform_io.is_null() {
762 panic!("Context::set_clipboard_backend() requires an active ImGui context");
763 }
764 (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
765 (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
766 (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
767 }
768
769 self.clipboard_ctx = clipboard_ctx;
770 }
771}
772
773impl Drop for Context {
774 fn drop(&mut self) {
775 let _guard = CTX_MUTEX.lock();
776 unsafe {
777 if !self.raw.is_null() {
778 if sys::igGetCurrentContext() == self.raw {
779 clear_current_context();
780 }
781 sys::igDestroyContext(self.raw);
782 }
783 }
784 }
785}
786
787#[derive(Debug)]
791pub struct SuspendedContext(Context);
792
793impl SuspendedContext {
794 pub fn try_create() -> crate::error::ImGuiResult<Self> {
796 Self::try_create_internal(None)
797 }
798
799 pub fn try_create_with_shared_font_atlas(
801 shared_font_atlas: SharedFontAtlas,
802 ) -> crate::error::ImGuiResult<Self> {
803 Self::try_create_internal(Some(shared_font_atlas))
804 }
805
806 pub fn create() -> Self {
808 Self::try_create().expect("Failed to create Dear ImGui context")
809 }
810
811 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
813 Self::try_create_with_shared_font_atlas(shared_font_atlas)
814 .expect("Failed to create Dear ImGui context")
815 }
816
817 fn try_create_internal(
820 mut shared_font_atlas: Option<SharedFontAtlas>,
821 ) -> crate::error::ImGuiResult<Self> {
822 let _guard = CTX_MUTEX.lock();
823
824 let shared_font_atlas_ptr = match &mut shared_font_atlas {
825 Some(atlas) => atlas.as_ptr_mut(),
826 None => ptr::null_mut(),
827 };
828
829 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
830 if raw.is_null() {
831 return Err(crate::error::ImGuiError::ContextCreation {
832 reason: "ImGui_CreateContext returned null".to_string(),
833 });
834 }
835
836 let ctx = Context {
837 raw,
838 shared_font_atlas,
839 ini_filename: None,
840 log_filename: None,
841 platform_name: None,
842 renderer_name: None,
843 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
844 ui: crate::ui::Ui::new(),
845 };
846
847 if ctx.is_current_context() {
849 clear_current_context();
850 }
851
852 Ok(SuspendedContext(ctx))
853 }
854
855 pub fn activate(self) -> Result<Context, SuspendedContext> {
860 let _guard = CTX_MUTEX.lock();
861 if no_current_context() {
862 unsafe {
863 sys::igSetCurrentContext(self.0.raw);
864 }
865 Ok(self.0)
866 } else {
867 Err(self)
868 }
869 }
870}
871
872#[derive(Debug)]
881pub struct RegisteredUserTexture {
882 ctx: *mut sys::ImGuiContext,
883 tex: *mut sys::ImTextureData,
884}
885
886impl Drop for RegisteredUserTexture {
887 fn drop(&mut self) {
888 if self.ctx.is_null() || self.tex.is_null() {
889 return;
890 }
891
892 let _guard = CTX_MUTEX.lock();
893 unsafe {
894 let prev = sys::igGetCurrentContext();
897 if prev != self.ctx {
898 sys::igSetCurrentContext(self.ctx);
899 }
900 sys::igUnregisterUserTexture(self.tex);
901 if prev != self.ctx {
902 sys::igSetCurrentContext(prev);
903 }
904 }
905 }
906}
907
908