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 &mut *(io_ptr as *mut Io)
153 }
154 }
155
156 pub fn io(&self) -> &crate::io::Io {
158 let _guard = CTX_MUTEX.lock();
159 unsafe {
160 let io_ptr = sys::igGetIO_Nil();
162 &*(io_ptr as *const crate::io::Io)
163 }
164 }
165
166 pub fn style(&self) -> &crate::style::Style {
168 let _guard = CTX_MUTEX.lock();
169 unsafe {
170 let style_ptr = sys::igGetStyle();
171 &*(style_ptr as *const crate::style::Style)
172 }
173 }
174
175 pub fn style_mut(&mut self) -> &mut crate::style::Style {
177 let _guard = CTX_MUTEX.lock();
178 unsafe {
179 let style_ptr = sys::igGetStyle();
180 &mut *(style_ptr as *mut crate::style::Style)
181 }
182 }
183
184 pub fn frame(&mut self) -> &mut crate::ui::Ui {
186 let _guard = CTX_MUTEX.lock();
187
188 unsafe {
189 sys::igNewFrame();
190 }
191 &mut self.ui
192 }
193
194 pub fn frame_with<F, R>(&mut self, f: F) -> R
196 where
197 F: FnOnce(&crate::ui::Ui) -> R,
198 {
199 let ui = self.frame();
200 f(ui)
201 }
202
203 pub fn render(&mut self) -> &crate::render::DrawData {
208 let _guard = CTX_MUTEX.lock();
209 unsafe {
210 sys::igRender();
211 &*(sys::igGetDrawData() as *const crate::render::DrawData)
212 }
213 }
214
215 pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
220 let _guard = CTX_MUTEX.lock();
221 unsafe {
222 let draw_data = sys::igGetDrawData();
223 if draw_data.is_null() {
224 None
225 } else {
226 let data = &*(draw_data as *const crate::render::DrawData);
227 if data.valid() { Some(data) } else { None }
228 }
229 }
230 }
231
232 pub fn set_ini_filename<P: Into<PathBuf>>(
238 &mut self,
239 filename: Option<P>,
240 ) -> crate::error::ImGuiResult<()> {
241 use crate::error::SafeStringConversion;
242 let _guard = CTX_MUTEX.lock();
243
244 self.ini_filename = match filename {
245 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
246 None => None,
247 };
248
249 unsafe {
250 let io = sys::igGetIO_Nil();
251 let ptr = self
252 .ini_filename
253 .as_ref()
254 .map(|s| s.as_ptr())
255 .unwrap_or(ptr::null());
256 (*io).IniFilename = ptr;
257 }
258 Ok(())
259 }
260
261 pub fn set_log_filename<P: Into<PathBuf>>(
269 &mut self,
270 filename: Option<P>,
271 ) -> crate::error::ImGuiResult<()> {
272 use crate::error::SafeStringConversion;
273 let _guard = CTX_MUTEX.lock();
274
275 self.log_filename = match filename {
276 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
277 None => None,
278 };
279
280 unsafe {
281 let io = sys::igGetIO_Nil();
282 let ptr = self
283 .log_filename
284 .as_ref()
285 .map(|s| s.as_ptr())
286 .unwrap_or(ptr::null());
287 (*io).LogFilename = ptr;
288 }
289 Ok(())
290 }
291
292 pub fn set_platform_name<S: Into<String>>(
300 &mut self,
301 name: Option<S>,
302 ) -> crate::error::ImGuiResult<()> {
303 use crate::error::SafeStringConversion;
304 let _guard = CTX_MUTEX.lock();
305
306 self.platform_name = match name {
307 Some(n) => Some(n.into().to_cstring_safe()?),
308 None => None,
309 };
310
311 unsafe {
312 let io = sys::igGetIO_Nil();
313 let ptr = self
314 .platform_name
315 .as_ref()
316 .map(|s| s.as_ptr())
317 .unwrap_or(ptr::null());
318 (*io).BackendPlatformName = ptr;
319 }
320 Ok(())
321 }
322
323 pub fn set_renderer_name<S: Into<String>>(
331 &mut self,
332 name: Option<S>,
333 ) -> crate::error::ImGuiResult<()> {
334 use crate::error::SafeStringConversion;
335 let _guard = CTX_MUTEX.lock();
336
337 self.renderer_name = match name {
338 Some(n) => Some(n.into().to_cstring_safe()?),
339 None => None,
340 };
341
342 unsafe {
343 let io = sys::igGetIO_Nil();
344 let ptr = self
345 .renderer_name
346 .as_ref()
347 .map(|s| s.as_ptr())
348 .unwrap_or(ptr::null());
349 (*io).BackendRendererName = ptr;
350 }
351 Ok(())
352 }
353
354 #[cfg(feature = "multi-viewport")]
358 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
359 let _guard = CTX_MUTEX.lock();
360 unsafe {
361 let pio = sys::igGetPlatformIO_Nil();
362 crate::platform_io::PlatformIo::from_raw_mut(pio)
363 }
364 }
365
366 #[cfg(feature = "multi-viewport")]
368 pub fn enable_multi_viewport(&mut self) {
369 crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
371 }
372
373 #[cfg(feature = "multi-viewport")]
378 pub fn update_platform_windows(&mut self) {
379 let _guard = CTX_MUTEX.lock();
380 unsafe {
381 let main_viewport = sys::igGetMainViewport();
383 if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
384 eprintln!("update_platform_windows: main viewport not set up, setting it up now");
385 }
388
389 sys::igUpdatePlatformWindows();
390 }
391 }
392
393 #[cfg(feature = "multi-viewport")]
398 pub fn render_platform_windows_default(&mut self) {
399 let _guard = CTX_MUTEX.lock();
400 unsafe {
401 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
402 }
403 }
404
405 #[cfg(feature = "multi-viewport")]
410 pub fn destroy_platform_windows(&mut self) {
411 let _guard = CTX_MUTEX.lock();
412 unsafe {
413 sys::igDestroyPlatformWindows();
414 }
415 }
416
417 pub fn suspend(self) -> SuspendedContext {
419 let _guard = CTX_MUTEX.lock();
420 assert!(
421 self.is_current_context(),
422 "context to be suspended is not the active context"
423 );
424 clear_current_context();
425 SuspendedContext(self)
426 }
427
428 fn is_current_context(&self) -> bool {
429 let ctx = unsafe { sys::igGetCurrentContext() };
430 self.raw == ctx
431 }
432
433 pub fn push_font(&mut self, font: &Font) {
435 let _guard = CTX_MUTEX.lock();
436 unsafe {
437 sys::igPushFont(font.raw(), 0.0);
438 }
439 }
440
441 #[doc(alias = "PopFont")]
445 pub fn pop_font(&mut self) {
446 let _guard = CTX_MUTEX.lock();
447 unsafe {
448 sys::igPopFont();
449 }
450 }
451
452 #[doc(alias = "GetFont")]
454 pub fn current_font(&self) -> &Font {
455 let _guard = CTX_MUTEX.lock();
456 unsafe { Font::from_raw(sys::igGetFont() as *const _) }
457 }
458
459 #[doc(alias = "GetFontSize")]
461 pub fn current_font_size(&self) -> f32 {
462 let _guard = CTX_MUTEX.lock();
463 unsafe { sys::igGetFontSize() }
464 }
465
466 pub fn font_atlas(&self) -> FontAtlas {
468 let _guard = CTX_MUTEX.lock();
469
470 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
475 unsafe {
476 let io = sys::igGetIO_Nil();
477 let atlas_ptr = (*io).Fonts;
478 assert!(
479 !atlas_ptr.is_null(),
480 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
481 );
482 FontAtlas::from_raw(atlas_ptr)
483 }
484
485 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
487 {
488 panic!(
489 "font_atlas() is not supported on wasm32 targets without \
490 `wasm-font-atlas-experimental` feature; \
491 see docs/WASM.md for current limitations."
492 );
493 }
494
495 #[cfg(not(target_arch = "wasm32"))]
496 unsafe {
497 let io = sys::igGetIO_Nil();
498 let atlas_ptr = (*io).Fonts;
499 FontAtlas::from_raw(atlas_ptr)
500 }
501 }
502
503 pub fn font_atlas_mut(&mut self) -> FontAtlas {
505 let _guard = CTX_MUTEX.lock();
506
507 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
512 unsafe {
513 let io = sys::igGetIO_Nil();
514 let atlas_ptr = (*io).Fonts;
515 assert!(
516 !atlas_ptr.is_null(),
517 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
518 );
519 return FontAtlas::from_raw(atlas_ptr);
520 }
521
522 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
524 {
525 panic!(
526 "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
527 enable `wasm-font-atlas-experimental` to opt-in for experiments."
528 );
529 }
530
531 #[cfg(not(target_arch = "wasm32"))]
532 unsafe {
533 let io = sys::igGetIO_Nil();
534 let atlas_ptr = (*io).Fonts;
535 FontAtlas::from_raw(atlas_ptr)
536 }
537 }
538
539 pub fn fonts(&mut self) -> FontAtlas {
543 self.font_atlas_mut()
544 }
545
546 pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
548 self.shared_font_atlas.clone()
549 }
550
551 #[doc(alias = "LoadIniSettingsFromMemory")]
553 pub fn load_ini_settings(&mut self, data: &str) {
554 let _guard = CTX_MUTEX.lock();
555 unsafe {
556 sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
557 }
558 }
559
560 #[doc(alias = "SaveIniSettingsToMemory")]
562 pub fn save_ini_settings(&mut self, buf: &mut String) {
563 let _guard = CTX_MUTEX.lock();
564 unsafe {
565 let data_ptr = sys::igSaveIniSettingsToMemory(ptr::null_mut());
566 if !data_ptr.is_null() {
567 let data = std::ffi::CStr::from_ptr(data_ptr);
568 buf.push_str(&data.to_string_lossy());
569 }
570 }
571 }
572
573 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
575 let clipboard_ctx: Box<UnsafeCell<_>> =
576 Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
577
578 #[cfg(not(target_arch = "wasm32"))]
587 unsafe {
588 let platform_io = sys::igGetPlatformIO_Nil();
589 (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
590 (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
591 (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
592 }
593
594 self.clipboard_ctx = clipboard_ctx;
595 }
596}
597
598impl Drop for Context {
599 fn drop(&mut self) {
600 let _guard = CTX_MUTEX.lock();
601 unsafe {
602 if !self.raw.is_null() {
603 if sys::igGetCurrentContext() == self.raw {
604 clear_current_context();
605 }
606 sys::igDestroyContext(self.raw);
607 }
608 }
609 }
610}
611
612#[derive(Debug)]
616pub struct SuspendedContext(Context);
617
618impl SuspendedContext {
619 pub fn try_create() -> crate::error::ImGuiResult<Self> {
621 Self::try_create_internal(None)
622 }
623
624 pub fn try_create_with_shared_font_atlas(
626 shared_font_atlas: SharedFontAtlas,
627 ) -> crate::error::ImGuiResult<Self> {
628 Self::try_create_internal(Some(shared_font_atlas))
629 }
630
631 pub fn create() -> Self {
633 Self::try_create().expect("Failed to create Dear ImGui context")
634 }
635
636 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
638 Self::try_create_with_shared_font_atlas(shared_font_atlas)
639 .expect("Failed to create Dear ImGui context")
640 }
641
642 fn try_create_internal(
645 mut shared_font_atlas: Option<SharedFontAtlas>,
646 ) -> crate::error::ImGuiResult<Self> {
647 let _guard = CTX_MUTEX.lock();
648
649 let shared_font_atlas_ptr = match &mut shared_font_atlas {
650 Some(atlas) => atlas.as_ptr_mut(),
651 None => ptr::null_mut(),
652 };
653
654 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
655 if raw.is_null() {
656 return Err(crate::error::ImGuiError::ContextCreation {
657 reason: "ImGui_CreateContext returned null".to_string(),
658 });
659 }
660
661 let ctx = Context {
662 raw,
663 shared_font_atlas,
664 ini_filename: None,
665 log_filename: None,
666 platform_name: None,
667 renderer_name: None,
668 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
669 ui: crate::ui::Ui::new(),
670 };
671
672 if ctx.is_current_context() {
674 clear_current_context();
675 }
676
677 Ok(SuspendedContext(ctx))
678 }
679
680 pub fn activate(self) -> Result<Context, SuspendedContext> {
685 let _guard = CTX_MUTEX.lock();
686 if no_current_context() {
687 unsafe {
688 sys::igSetCurrentContext(self.0.raw);
689 }
690 Ok(self.0)
691 } else {
692 Err(self)
693 }
694 }
695}
696
697