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 set_ini_filename<P: Into<PathBuf>>(
254 &mut self,
255 filename: Option<P>,
256 ) -> crate::error::ImGuiResult<()> {
257 use crate::error::SafeStringConversion;
258 let _guard = CTX_MUTEX.lock();
259
260 self.ini_filename = match filename {
261 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
262 None => None,
263 };
264
265 unsafe {
266 let io = sys::igGetIO_Nil();
267 let ptr = self
268 .ini_filename
269 .as_ref()
270 .map(|s| s.as_ptr())
271 .unwrap_or(ptr::null());
272 (*io).IniFilename = ptr;
273 }
274 Ok(())
275 }
276
277 pub fn set_log_filename<P: Into<PathBuf>>(
285 &mut self,
286 filename: Option<P>,
287 ) -> crate::error::ImGuiResult<()> {
288 use crate::error::SafeStringConversion;
289 let _guard = CTX_MUTEX.lock();
290
291 self.log_filename = match filename {
292 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
293 None => None,
294 };
295
296 unsafe {
297 let io = sys::igGetIO_Nil();
298 let ptr = self
299 .log_filename
300 .as_ref()
301 .map(|s| s.as_ptr())
302 .unwrap_or(ptr::null());
303 (*io).LogFilename = ptr;
304 }
305 Ok(())
306 }
307
308 pub fn set_platform_name<S: Into<String>>(
316 &mut self,
317 name: Option<S>,
318 ) -> crate::error::ImGuiResult<()> {
319 use crate::error::SafeStringConversion;
320 let _guard = CTX_MUTEX.lock();
321
322 self.platform_name = match name {
323 Some(n) => Some(n.into().to_cstring_safe()?),
324 None => None,
325 };
326
327 unsafe {
328 let io = sys::igGetIO_Nil();
329 let ptr = self
330 .platform_name
331 .as_ref()
332 .map(|s| s.as_ptr())
333 .unwrap_or(ptr::null());
334 (*io).BackendPlatformName = ptr;
335 }
336 Ok(())
337 }
338
339 pub fn set_renderer_name<S: Into<String>>(
347 &mut self,
348 name: Option<S>,
349 ) -> crate::error::ImGuiResult<()> {
350 use crate::error::SafeStringConversion;
351 let _guard = CTX_MUTEX.lock();
352
353 self.renderer_name = match name {
354 Some(n) => Some(n.into().to_cstring_safe()?),
355 None => None,
356 };
357
358 unsafe {
359 let io = sys::igGetIO_Nil();
360 if io.is_null() {
361 panic!("igGetIO_Nil() returned null");
362 }
363 let ptr = self
364 .renderer_name
365 .as_ref()
366 .map(|s| s.as_ptr())
367 .unwrap_or(ptr::null());
368 (*io).BackendRendererName = ptr;
369 }
370 Ok(())
371 }
372
373 #[cfg(feature = "multi-viewport")]
377 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
378 let _guard = CTX_MUTEX.lock();
379 unsafe {
380 let pio = sys::igGetPlatformIO_Nil();
381 if pio.is_null() {
382 panic!("igGetPlatformIO_Nil() returned null");
383 }
384 crate::platform_io::PlatformIo::from_raw_mut(pio)
385 }
386 }
387
388 #[doc(alias = "GetMainViewport")]
393 pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
394 let _guard = CTX_MUTEX.lock();
395 unsafe {
396 let ptr = sys::igGetMainViewport();
397 if ptr.is_null() {
398 panic!("Context::main_viewport() requires an active ImGui context");
399 }
400 crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
401 }
402 }
403
404 #[cfg(feature = "multi-viewport")]
406 pub fn enable_multi_viewport(&mut self) {
407 crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
409 }
410
411 #[cfg(feature = "multi-viewport")]
416 pub fn update_platform_windows(&mut self) {
417 let _guard = CTX_MUTEX.lock();
418 unsafe {
419 let main_viewport = sys::igGetMainViewport();
421 if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
422 eprintln!("update_platform_windows: main viewport not set up, setting it up now");
423 }
426
427 sys::igUpdatePlatformWindows();
428 }
429 }
430
431 #[cfg(feature = "multi-viewport")]
436 pub fn render_platform_windows_default(&mut self) {
437 let _guard = CTX_MUTEX.lock();
438 unsafe {
439 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
440 }
441 }
442
443 #[cfg(feature = "multi-viewport")]
448 pub fn destroy_platform_windows(&mut self) {
449 let _guard = CTX_MUTEX.lock();
450 unsafe {
451 sys::igDestroyPlatformWindows();
452 }
453 }
454
455 pub fn suspend(self) -> SuspendedContext {
457 let _guard = CTX_MUTEX.lock();
458 assert!(
459 self.is_current_context(),
460 "context to be suspended is not the active context"
461 );
462 clear_current_context();
463 SuspendedContext(self)
464 }
465
466 fn is_current_context(&self) -> bool {
467 let ctx = unsafe { sys::igGetCurrentContext() };
468 self.raw == ctx
469 }
470
471 pub fn push_font(&mut self, font: &Font) {
473 let _guard = CTX_MUTEX.lock();
474 unsafe {
475 sys::igPushFont(font.raw(), 0.0);
476 }
477 }
478
479 #[doc(alias = "PopFont")]
483 pub fn pop_font(&mut self) {
484 let _guard = CTX_MUTEX.lock();
485 unsafe {
486 sys::igPopFont();
487 }
488 }
489
490 #[doc(alias = "GetFont")]
492 pub fn current_font(&self) -> &Font {
493 let _guard = CTX_MUTEX.lock();
494 unsafe { Font::from_raw(sys::igGetFont() as *const _) }
495 }
496
497 #[doc(alias = "GetFontSize")]
499 pub fn current_font_size(&self) -> f32 {
500 let _guard = CTX_MUTEX.lock();
501 unsafe { sys::igGetFontSize() }
502 }
503
504 pub fn font_atlas(&self) -> FontAtlas {
506 let _guard = CTX_MUTEX.lock();
507
508 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
513 unsafe {
514 let io = sys::igGetIO_Nil();
515 let atlas_ptr = (*io).Fonts;
516 assert!(
517 !atlas_ptr.is_null(),
518 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
519 );
520 FontAtlas::from_raw(atlas_ptr)
521 }
522
523 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
525 {
526 panic!(
527 "font_atlas() is not supported on wasm32 targets without \
528 `wasm-font-atlas-experimental` feature; \
529 see docs/WASM.md for current limitations."
530 );
531 }
532
533 #[cfg(not(target_arch = "wasm32"))]
534 unsafe {
535 let io = sys::igGetIO_Nil();
536 let atlas_ptr = (*io).Fonts;
537 FontAtlas::from_raw(atlas_ptr)
538 }
539 }
540
541 pub fn font_atlas_mut(&mut self) -> FontAtlas {
543 let _guard = CTX_MUTEX.lock();
544
545 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
550 unsafe {
551 let io = sys::igGetIO_Nil();
552 let atlas_ptr = (*io).Fonts;
553 assert!(
554 !atlas_ptr.is_null(),
555 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
556 );
557 return FontAtlas::from_raw(atlas_ptr);
558 }
559
560 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
562 {
563 panic!(
564 "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
565 enable `wasm-font-atlas-experimental` to opt-in for experiments."
566 );
567 }
568
569 #[cfg(not(target_arch = "wasm32"))]
570 unsafe {
571 let io = sys::igGetIO_Nil();
572 let atlas_ptr = (*io).Fonts;
573 FontAtlas::from_raw(atlas_ptr)
574 }
575 }
576
577 pub fn fonts(&mut self) -> FontAtlas {
581 self.font_atlas_mut()
582 }
583
584 pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
586 self.shared_font_atlas.clone()
587 }
588
589 #[doc(alias = "LoadIniSettingsFromMemory")]
591 pub fn load_ini_settings(&mut self, data: &str) {
592 let _guard = CTX_MUTEX.lock();
593 unsafe {
594 sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
595 }
596 }
597
598 #[doc(alias = "SaveIniSettingsToMemory")]
600 pub fn save_ini_settings(&mut self, buf: &mut String) {
601 let _guard = CTX_MUTEX.lock();
602 unsafe {
603 let mut out_ini_size: usize = 0;
604 let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
605 if data_ptr.is_null() || out_ini_size == 0 {
606 return;
607 }
608
609 let mut bytes =
610 std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size as usize);
611 if bytes.last() == Some(&0) {
612 bytes = &bytes[..bytes.len().saturating_sub(1)];
613 }
614 buf.push_str(&String::from_utf8_lossy(bytes));
615 }
616 }
617
618 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
620 let _guard = CTX_MUTEX.lock();
621
622 let clipboard_ctx: Box<UnsafeCell<_>> =
623 Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
624
625 #[cfg(not(target_arch = "wasm32"))]
634 unsafe {
635 let platform_io = sys::igGetPlatformIO_Nil();
636 if platform_io.is_null() {
637 panic!("Context::set_clipboard_backend() requires an active ImGui context");
638 }
639 (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
640 (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
641 (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
642 }
643
644 self.clipboard_ctx = clipboard_ctx;
645 }
646}
647
648impl Drop for Context {
649 fn drop(&mut self) {
650 let _guard = CTX_MUTEX.lock();
651 unsafe {
652 if !self.raw.is_null() {
653 if sys::igGetCurrentContext() == self.raw {
654 clear_current_context();
655 }
656 sys::igDestroyContext(self.raw);
657 }
658 }
659 }
660}
661
662#[derive(Debug)]
666pub struct SuspendedContext(Context);
667
668impl SuspendedContext {
669 pub fn try_create() -> crate::error::ImGuiResult<Self> {
671 Self::try_create_internal(None)
672 }
673
674 pub fn try_create_with_shared_font_atlas(
676 shared_font_atlas: SharedFontAtlas,
677 ) -> crate::error::ImGuiResult<Self> {
678 Self::try_create_internal(Some(shared_font_atlas))
679 }
680
681 pub fn create() -> Self {
683 Self::try_create().expect("Failed to create Dear ImGui context")
684 }
685
686 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
688 Self::try_create_with_shared_font_atlas(shared_font_atlas)
689 .expect("Failed to create Dear ImGui context")
690 }
691
692 fn try_create_internal(
695 mut shared_font_atlas: Option<SharedFontAtlas>,
696 ) -> crate::error::ImGuiResult<Self> {
697 let _guard = CTX_MUTEX.lock();
698
699 let shared_font_atlas_ptr = match &mut shared_font_atlas {
700 Some(atlas) => atlas.as_ptr_mut(),
701 None => ptr::null_mut(),
702 };
703
704 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
705 if raw.is_null() {
706 return Err(crate::error::ImGuiError::ContextCreation {
707 reason: "ImGui_CreateContext returned null".to_string(),
708 });
709 }
710
711 let ctx = Context {
712 raw,
713 shared_font_atlas,
714 ini_filename: None,
715 log_filename: None,
716 platform_name: None,
717 renderer_name: None,
718 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
719 ui: crate::ui::Ui::new(),
720 };
721
722 if ctx.is_current_context() {
724 clear_current_context();
725 }
726
727 Ok(SuspendedContext(ctx))
728 }
729
730 pub fn activate(self) -> Result<Context, SuspendedContext> {
735 let _guard = CTX_MUTEX.lock();
736 if no_current_context() {
737 unsafe {
738 sys::igSetCurrentContext(self.0.raw);
739 }
740 Ok(self.0)
741 } else {
742 Err(self)
743 }
744 }
745}
746
747