1use parking_lot::ReentrantMutex;
9use std::cell::{RefCell, 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
68#[derive(Clone)]
69struct UserTextureRegistration {
70 ctx: *mut sys::ImGuiContext,
71 tex: *mut sys::ImTextureData,
72 alive: Weak<()>,
73}
74
75thread_local! {
76 static USER_TEXTURE_REGISTRATIONS: RefCell<Vec<UserTextureRegistration>> = RefCell::new(Vec::new());
77}
78
79fn clear_current_context() {
80 unsafe {
81 sys::igSetCurrentContext(ptr::null_mut());
82 }
83}
84
85fn no_current_context() -> bool {
86 let ctx = unsafe { sys::igGetCurrentContext() };
87 ctx.is_null()
88}
89
90struct BoundContextGuard {
91 prev: *mut sys::ImGuiContext,
92 restore: bool,
93}
94
95impl BoundContextGuard {
96 fn bind(ctx: *mut sys::ImGuiContext) -> Self {
97 unsafe {
98 let prev = sys::igGetCurrentContext();
99 let restore = prev != ctx;
100 if restore {
101 sys::igSetCurrentContext(ctx);
102 }
103 Self { prev, restore }
104 }
105 }
106}
107
108impl Drop for BoundContextGuard {
109 fn drop(&mut self) {
110 if self.restore {
111 unsafe {
112 sys::igSetCurrentContext(self.prev);
113 }
114 }
115 }
116}
117
118fn with_bound_context<R>(ctx: *mut sys::ImGuiContext, f: impl FnOnce() -> R) -> R {
119 let _guard = BoundContextGuard::bind(ctx);
120 f()
121}
122
123fn prune_dead_user_texture_registrations(registrations: &mut Vec<UserTextureRegistration>) {
124 registrations.retain(|registration| registration.alive.upgrade().is_some());
125}
126
127fn is_user_texture_registered(ctx: *mut sys::ImGuiContext, tex: *mut sys::ImTextureData) -> bool {
128 USER_TEXTURE_REGISTRATIONS.with(|registrations| {
129 let mut registrations = registrations.borrow_mut();
130 prune_dead_user_texture_registrations(&mut registrations);
131 registrations
132 .iter()
133 .any(|registration| registration.ctx == ctx && registration.tex == tex)
134 })
135}
136
137fn track_user_texture_registration(
138 ctx: *mut sys::ImGuiContext,
139 tex: *mut sys::ImTextureData,
140 alive: Weak<()>,
141) {
142 USER_TEXTURE_REGISTRATIONS.with(|registrations| {
143 let mut registrations = registrations.borrow_mut();
144 prune_dead_user_texture_registrations(&mut registrations);
145 registrations.push(UserTextureRegistration { ctx, tex, alive });
146 });
147}
148
149fn take_user_texture_registration(
150 ctx: *mut sys::ImGuiContext,
151 tex: *mut sys::ImTextureData,
152) -> Option<UserTextureRegistration> {
153 USER_TEXTURE_REGISTRATIONS.with(|registrations| {
154 let mut registrations = registrations.borrow_mut();
155 prune_dead_user_texture_registrations(&mut registrations);
156 registrations
157 .iter()
158 .position(|registration| registration.ctx == ctx && registration.tex == tex)
159 .map(|index| registrations.remove(index))
160 })
161}
162
163fn unregister_user_texture_registration(registration: UserTextureRegistration) {
164 if registration.ctx.is_null()
165 || registration.tex.is_null()
166 || registration.alive.upgrade().is_none()
167 {
168 return;
169 }
170
171 unsafe {
172 with_bound_context(registration.ctx, || {
173 sys::igUnregisterUserTexture(registration.tex);
174 });
175 }
176}
177
178pub(crate) fn unregister_user_texture_from_all_contexts(tex: *mut sys::ImTextureData) {
179 if tex.is_null() {
180 return;
181 }
182
183 let registrations = USER_TEXTURE_REGISTRATIONS.with(|registrations| {
184 let mut registrations = registrations.borrow_mut();
185 let mut taken = Vec::new();
186 let mut index = 0;
187 while index < registrations.len() {
188 if registrations[index].alive.upgrade().is_none() {
189 registrations.remove(index);
190 } else if registrations[index].tex == tex {
191 taken.push(registrations.remove(index));
192 } else {
193 index += 1;
194 }
195 }
196 taken
197 });
198
199 let _guard = CTX_MUTEX.lock();
200 for registration in registrations {
201 unregister_user_texture_registration(registration);
202 }
203}
204
205fn unregister_user_textures_for_context(ctx: *mut sys::ImGuiContext) {
206 if ctx.is_null() {
207 return;
208 }
209
210 let registrations = USER_TEXTURE_REGISTRATIONS.with(|registrations| {
211 let mut registrations = registrations.borrow_mut();
212 let mut taken = Vec::new();
213 let mut index = 0;
214 while index < registrations.len() {
215 if registrations[index].alive.upgrade().is_none() || registrations[index].ctx == ctx {
216 let registration = registrations.remove(index);
217 if registration.ctx == ctx {
218 taken.push(registration);
219 }
220 } else {
221 index += 1;
222 }
223 }
224 taken
225 });
226
227 for registration in registrations {
228 unregister_user_texture_registration(registration);
229 }
230}
231
232impl Context {
233 pub fn try_create() -> crate::error::ImGuiResult<Context> {
237 Self::try_create_internal(None)
238 }
239
240 pub fn try_create_with_shared_font_atlas(
242 shared_font_atlas: SharedFontAtlas,
243 ) -> crate::error::ImGuiResult<Context> {
244 Self::try_create_internal(Some(shared_font_atlas))
245 }
246
247 pub fn create() -> Context {
251 Self::try_create().expect("Failed to create Dear ImGui context")
252 }
253
254 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
256 Self::try_create_with_shared_font_atlas(shared_font_atlas)
257 .expect("Failed to create Dear ImGui context")
258 }
259
260 pub fn as_raw(&self) -> *mut sys::ImGuiContext {
262 self.raw
263 }
264
265 pub fn alive_token(&self) -> ContextAliveToken {
270 ContextAliveToken(Rc::downgrade(&self.alive))
271 }
272
273 fn io_ptr(&self, caller: &str) -> *mut sys::ImGuiIO {
276 let io = unsafe { sys::igGetIO_ContextPtr(self.raw) };
277 if io.is_null() {
278 panic!("{caller} requires a valid ImGui context");
279 }
280 io
281 }
282
283 fn platform_io_ptr(&self, caller: &str) -> *mut sys::ImGuiPlatformIO {
284 let pio = unsafe { sys::igGetPlatformIO_ContextPtr(self.raw) };
285 if pio.is_null() {
286 panic!("{caller} requires a valid ImGui context");
287 }
288 pio
289 }
290
291 fn assert_current_context(&self, caller: &str) {
292 assert!(
293 self.is_current_context(),
294 "{caller} requires this context to be current"
295 );
296 }
297
298 fn try_create_internal(
299 mut shared_font_atlas: Option<SharedFontAtlas>,
300 ) -> crate::error::ImGuiResult<Context> {
301 let _guard = CTX_MUTEX.lock();
302
303 if !no_current_context() {
304 return Err(crate::error::ImGuiError::ContextAlreadyActive);
305 }
306
307 let shared_font_atlas_ptr = match &mut shared_font_atlas {
308 Some(atlas) => atlas.as_ptr_mut(),
309 None => ptr::null_mut(),
310 };
311
312 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
314 if raw.is_null() {
315 return Err(crate::error::ImGuiError::ContextCreation {
316 reason: "ImGui_CreateContext returned null".to_string(),
317 });
318 }
319
320 unsafe {
322 sys::igSetCurrentContext(raw);
323 }
324
325 Ok(Context {
326 raw,
327 alive: Rc::new(()),
328 shared_font_atlas,
329 ini_filename: None,
330 log_filename: None,
331 platform_name: None,
332 renderer_name: None,
333 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
334 ui: crate::ui::Ui::new(),
335 })
336 }
337
338 pub fn io_mut(&mut self) -> &mut Io {
340 let _guard = CTX_MUTEX.lock();
341 unsafe {
342 let io_ptr = self.io_ptr("Context::io_mut()");
343 &mut *(io_ptr as *mut Io)
344 }
345 }
346
347 pub fn io(&self) -> &crate::io::Io {
349 let _guard = CTX_MUTEX.lock();
350 unsafe {
351 let io_ptr = self.io_ptr("Context::io()");
352 &*(io_ptr as *const crate::io::Io)
353 }
354 }
355
356 pub fn style(&self) -> &crate::style::Style {
358 let _guard = CTX_MUTEX.lock();
359 unsafe {
360 with_bound_context(self.raw, || {
361 let style_ptr = sys::igGetStyle();
362 if style_ptr.is_null() {
363 panic!("Context::style() requires a valid ImGui context");
364 }
365 &*(style_ptr as *const crate::style::Style)
366 })
367 }
368 }
369
370 pub fn style_mut(&mut self) -> &mut crate::style::Style {
372 let _guard = CTX_MUTEX.lock();
373 unsafe {
374 with_bound_context(self.raw, || {
375 let style_ptr = sys::igGetStyle();
376 if style_ptr.is_null() {
377 panic!("Context::style_mut() requires a valid ImGui context");
378 }
379 &mut *(style_ptr as *mut crate::style::Style)
380 })
381 }
382 }
383
384 pub fn frame(&mut self) -> &mut crate::ui::Ui {
389 let _guard = CTX_MUTEX.lock();
390 self.assert_current_context("Context::frame()");
391
392 unsafe {
393 let io = sys::igGetIO_Nil();
398 if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
399 panic!(
400 "Context::frame() called with invalid io.DisplaySize ({}, {}). \
401Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
402If you are using a windowing/event-loop library, prefer a platform backend such as \
403dear-imgui-winit::WinitPlatform::prepare_frame().",
404 (*io).DisplaySize.x,
405 (*io).DisplaySize.y
406 );
407 }
408 sys::igNewFrame();
409 }
410 &mut self.ui
411 }
412
413 pub fn frame_with<F, R>(&mut self, f: F) -> R
415 where
416 F: FnOnce(&crate::ui::Ui) -> R,
417 {
418 let ui = self.frame();
419 f(ui)
420 }
421
422 pub fn render(&mut self) -> &crate::render::DrawData {
427 let _guard = CTX_MUTEX.lock();
428 self.assert_current_context("Context::render()");
429
430 unsafe {
431 sys::igRender();
432 let dd = sys::igGetDrawData();
433 if dd.is_null() {
434 panic!("Context::render() returned null draw data");
435 }
436 &*(dd as *const crate::render::DrawData)
437 }
438 }
439
440 pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
445 let _guard = CTX_MUTEX.lock();
446 self.assert_current_context("Context::draw_data()");
447
448 unsafe {
449 let draw_data = sys::igGetDrawData();
450 if draw_data.is_null() {
451 None
452 } else {
453 let data = &*(draw_data as *const crate::render::DrawData);
454 if data.valid() { Some(data) } else { None }
455 }
456 }
457 }
458
459 pub fn register_user_texture(&mut self, texture: &mut crate::texture::OwnedTextureData) {
471 self.register_user_texture_ptr(texture.as_mut().as_raw_mut());
472 }
473
474 pub unsafe fn register_user_texture_raw(&mut self, texture: &mut crate::texture::TextureData) {
483 self.register_user_texture_ptr(texture.as_raw_mut());
484 }
485
486 fn register_user_texture_ptr(&mut self, texture: *mut sys::ImTextureData) {
487 let _guard = CTX_MUTEX.lock();
488 self.assert_current_context("Context::register_user_texture()");
489 assert!(
490 !texture.is_null(),
491 "Context::register_user_texture() received a null texture"
492 );
493 if is_user_texture_registered(self.raw, texture) {
494 return;
495 }
496 unsafe {
497 sys::igRegisterUserTexture(texture);
498 }
499 track_user_texture_registration(self.raw, texture, Rc::downgrade(&self.alive));
500 }
501
502 pub fn register_user_texture_token(
506 &mut self,
507 texture: &mut crate::texture::OwnedTextureData,
508 ) -> RegisteredUserTexture {
509 self.register_user_texture(texture);
510 RegisteredUserTexture {
511 ctx: self.raw,
512 tex: texture.as_mut().as_raw_mut(),
513 alive: Rc::downgrade(&self.alive),
514 }
515 }
516
517 pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::OwnedTextureData) {
521 self.unregister_user_texture_ptr(texture.as_mut().as_raw_mut());
522 }
523
524 pub unsafe fn unregister_user_texture_raw(
531 &mut self,
532 texture: &mut crate::texture::TextureData,
533 ) {
534 self.unregister_user_texture_ptr(texture.as_raw_mut());
535 }
536
537 fn unregister_user_texture_ptr(&mut self, texture: *mut sys::ImTextureData) {
538 let _guard = CTX_MUTEX.lock();
539 self.assert_current_context("Context::unregister_user_texture()");
540 assert!(
541 !texture.is_null(),
542 "Context::unregister_user_texture() received a null texture"
543 );
544 if let Some(registration) = take_user_texture_registration(self.raw, texture) {
545 unregister_user_texture_registration(registration);
546 }
547 }
548
549 pub fn set_ini_filename<P: Into<PathBuf>>(
555 &mut self,
556 filename: Option<P>,
557 ) -> crate::error::ImGuiResult<()> {
558 use crate::error::SafeStringConversion;
559 let _guard = CTX_MUTEX.lock();
560
561 self.ini_filename = match filename {
562 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
563 None => None,
564 };
565
566 unsafe {
567 let io = self.io_ptr("Context::set_ini_filename()");
568 let ptr = self
569 .ini_filename
570 .as_ref()
571 .map(|s| s.as_ptr())
572 .unwrap_or(ptr::null());
573 (*io).IniFilename = ptr;
574 }
575 Ok(())
576 }
577
578 pub fn set_log_filename<P: Into<PathBuf>>(
586 &mut self,
587 filename: Option<P>,
588 ) -> crate::error::ImGuiResult<()> {
589 use crate::error::SafeStringConversion;
590 let _guard = CTX_MUTEX.lock();
591
592 self.log_filename = match filename {
593 Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
594 None => None,
595 };
596
597 unsafe {
598 let io = self.io_ptr("Context::set_log_filename()");
599 let ptr = self
600 .log_filename
601 .as_ref()
602 .map(|s| s.as_ptr())
603 .unwrap_or(ptr::null());
604 (*io).LogFilename = ptr;
605 }
606 Ok(())
607 }
608
609 pub fn set_platform_name<S: Into<String>>(
617 &mut self,
618 name: Option<S>,
619 ) -> crate::error::ImGuiResult<()> {
620 use crate::error::SafeStringConversion;
621 let _guard = CTX_MUTEX.lock();
622
623 self.platform_name = match name {
624 Some(n) => Some(n.into().to_cstring_safe()?),
625 None => None,
626 };
627
628 unsafe {
629 let io = self.io_ptr("Context::set_platform_name()");
630 let ptr = self
631 .platform_name
632 .as_ref()
633 .map(|s| s.as_ptr())
634 .unwrap_or(ptr::null());
635 (*io).BackendPlatformName = ptr;
636 }
637 Ok(())
638 }
639
640 pub fn set_renderer_name<S: Into<String>>(
648 &mut self,
649 name: Option<S>,
650 ) -> crate::error::ImGuiResult<()> {
651 use crate::error::SafeStringConversion;
652 let _guard = CTX_MUTEX.lock();
653
654 self.renderer_name = match name {
655 Some(n) => Some(n.into().to_cstring_safe()?),
656 None => None,
657 };
658
659 unsafe {
660 let io = self.io_ptr("Context::set_renderer_name()");
661 let ptr = self
662 .renderer_name
663 .as_ref()
664 .map(|s| s.as_ptr())
665 .unwrap_or(ptr::null());
666 (*io).BackendRendererName = ptr;
667 }
668 Ok(())
669 }
670
671 pub fn platform_io(&self) -> &crate::platform_io::PlatformIo {
678 let _guard = CTX_MUTEX.lock();
679 unsafe {
680 let pio = self.platform_io_ptr("Context::platform_io()");
681 crate::platform_io::PlatformIo::from_raw(pio)
682 }
683 }
684
685 pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
690 let _guard = CTX_MUTEX.lock();
691 unsafe {
692 let pio = self.platform_io_ptr("Context::platform_io_mut()");
693 crate::platform_io::PlatformIo::from_raw_mut(pio)
694 }
695 }
696
697 #[doc(alias = "GetMainViewport")]
702 pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
703 let _guard = CTX_MUTEX.lock();
704 unsafe {
705 with_bound_context(self.raw, || {
706 let ptr = sys::igGetMainViewport();
707 if ptr.is_null() {
708 panic!("Context::main_viewport() requires a valid ImGui context");
709 }
710 crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
711 })
712 }
713 }
714
715 #[cfg(feature = "multi-viewport")]
717 pub fn enable_multi_viewport(&mut self) {
718 crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
720 }
721
722 #[cfg(feature = "multi-viewport")]
727 pub fn update_platform_windows(&mut self) {
728 let _guard = CTX_MUTEX.lock();
729 unsafe {
730 with_bound_context(self.raw, || {
731 let main_viewport = sys::igGetMainViewport();
733 if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
734 eprintln!(
735 "update_platform_windows: main viewport not set up, setting it up now"
736 );
737 }
740
741 sys::igUpdatePlatformWindows();
742 });
743 }
744 }
745
746 #[cfg(feature = "multi-viewport")]
751 pub fn render_platform_windows_default(&mut self) {
752 let _guard = CTX_MUTEX.lock();
753 unsafe {
754 with_bound_context(self.raw, || {
755 sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
756 });
757 }
758 }
759
760 #[cfg(feature = "multi-viewport")]
765 pub fn destroy_platform_windows(&mut self) {
766 let _guard = CTX_MUTEX.lock();
767 unsafe {
768 with_bound_context(self.raw, || {
769 sys::igDestroyPlatformWindows();
770 });
771 }
772 }
773
774 pub fn suspend(self) -> SuspendedContext {
776 let _guard = CTX_MUTEX.lock();
777 assert!(
778 self.is_current_context(),
779 "context to be suspended is not the active context"
780 );
781 clear_current_context();
782 SuspendedContext(self)
783 }
784
785 fn is_current_context(&self) -> bool {
786 let ctx = unsafe { sys::igGetCurrentContext() };
787 self.raw == ctx
788 }
789
790 pub fn push_font(&mut self, font: &Font) {
792 let _guard = CTX_MUTEX.lock();
793 unsafe {
794 with_bound_context(self.raw, || {
795 sys::igPushFont(font.raw(), 0.0);
796 });
797 }
798 }
799
800 #[doc(alias = "PopFont")]
804 pub fn pop_font(&mut self) {
805 let _guard = CTX_MUTEX.lock();
806 unsafe {
807 with_bound_context(self.raw, || {
808 sys::igPopFont();
809 });
810 }
811 }
812
813 #[doc(alias = "GetFont")]
815 pub fn current_font(&self) -> &Font {
816 let _guard = CTX_MUTEX.lock();
817 unsafe { with_bound_context(self.raw, || Font::from_raw(sys::igGetFont() as *const _)) }
818 }
819
820 #[doc(alias = "GetFontSize")]
822 pub fn current_font_size(&self) -> f32 {
823 let _guard = CTX_MUTEX.lock();
824 unsafe { with_bound_context(self.raw, || sys::igGetFontSize()) }
825 }
826
827 pub fn font_atlas(&self) -> FontAtlas {
829 let _guard = CTX_MUTEX.lock();
830
831 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
836 unsafe {
837 let io = self.io_ptr("Context::font_atlas()");
838 let atlas_ptr = (*io).Fonts;
839 assert!(
840 !atlas_ptr.is_null(),
841 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
842 );
843 FontAtlas::from_raw(atlas_ptr)
844 }
845
846 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
848 {
849 panic!(
850 "font_atlas() is not supported on wasm32 targets without \
851 `wasm-font-atlas-experimental` feature; \
852 see docs/WASM.md for current limitations."
853 );
854 }
855
856 #[cfg(not(target_arch = "wasm32"))]
857 unsafe {
858 let io = self.io_ptr("Context::font_atlas()");
859 let atlas_ptr = (*io).Fonts;
860 FontAtlas::from_raw(atlas_ptr)
861 }
862 }
863
864 pub fn font_atlas_mut(&mut self) -> FontAtlas {
866 let _guard = CTX_MUTEX.lock();
867
868 #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
873 unsafe {
874 let io = self.io_ptr("Context::font_atlas_mut()");
875 let atlas_ptr = (*io).Fonts;
876 assert!(
877 !atlas_ptr.is_null(),
878 "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
879 );
880 return FontAtlas::from_raw(atlas_ptr);
881 }
882
883 #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
885 {
886 panic!(
887 "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
888 enable `wasm-font-atlas-experimental` to opt-in for experiments."
889 );
890 }
891
892 #[cfg(not(target_arch = "wasm32"))]
893 unsafe {
894 let io = self.io_ptr("Context::font_atlas_mut()");
895 let atlas_ptr = (*io).Fonts;
896 FontAtlas::from_raw(atlas_ptr)
897 }
898 }
899
900 pub fn fonts(&mut self) -> FontAtlas {
904 self.font_atlas_mut()
905 }
906
907 pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
909 self.shared_font_atlas.clone()
910 }
911
912 #[doc(alias = "LoadIniSettingsFromMemory")]
914 pub fn load_ini_settings(&mut self, data: &str) {
915 let _guard = CTX_MUTEX.lock();
916 unsafe {
917 with_bound_context(self.raw, || {
918 sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
919 });
920 }
921 }
922
923 #[doc(alias = "SaveIniSettingsToMemory")]
925 pub fn save_ini_settings(&mut self, buf: &mut String) {
926 let _guard = CTX_MUTEX.lock();
927 unsafe {
928 with_bound_context(self.raw, || {
929 let mut out_ini_size: usize = 0;
930 let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
931 if data_ptr.is_null() || out_ini_size == 0 {
932 return;
933 }
934
935 let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
936 if bytes.last() == Some(&0) {
937 bytes = &bytes[..bytes.len().saturating_sub(1)];
938 }
939 buf.push_str(&String::from_utf8_lossy(bytes));
940 });
941 }
942 }
943
944 #[cfg(not(target_arch = "wasm32"))]
950 #[doc(alias = "LoadIniSettingsFromDisk")]
951 pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
952 &mut self,
953 filename: P,
954 ) -> crate::error::ImGuiResult<()> {
955 use crate::error::SafeStringConversion;
956 let _guard = CTX_MUTEX.lock();
957 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
958 unsafe {
959 with_bound_context(self.raw, || {
960 sys::igLoadIniSettingsFromDisk(cstr.as_ptr());
961 });
962 }
963 Ok(())
964 }
965
966 #[cfg(not(target_arch = "wasm32"))]
972 #[doc(alias = "SaveIniSettingsToDisk")]
973 pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
974 &mut self,
975 filename: P,
976 ) -> crate::error::ImGuiResult<()> {
977 use crate::error::SafeStringConversion;
978 let _guard = CTX_MUTEX.lock();
979 let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
980 unsafe {
981 with_bound_context(self.raw, || {
982 sys::igSaveIniSettingsToDisk(cstr.as_ptr());
983 });
984 }
985 Ok(())
986 }
987
988 #[doc(alias = "GetClipboardText")]
995 pub fn clipboard_text(&self) -> Option<String> {
996 let _guard = CTX_MUTEX.lock();
997 unsafe {
998 with_bound_context(self.raw, || {
999 let ptr = sys::igGetClipboardText();
1000 if ptr.is_null() {
1001 return None;
1002 }
1003 Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
1004 })
1005 }
1006 }
1007
1008 #[doc(alias = "SetClipboardText")]
1015 pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
1016 let _guard = CTX_MUTEX.lock();
1017 unsafe {
1018 with_bound_context(self.raw, || {
1019 sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
1020 });
1021 }
1022 }
1023
1024 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
1026 let _guard = CTX_MUTEX.lock();
1027
1028 let clipboard_ctx: Box<UnsafeCell<_>> =
1029 Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
1030
1031 #[cfg(not(target_arch = "wasm32"))]
1040 unsafe {
1041 let platform_io = sys::igGetPlatformIO_ContextPtr(self.raw);
1042 if platform_io.is_null() {
1043 panic!("Context::set_clipboard_backend() requires a valid ImGui context");
1044 }
1045 (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
1046 (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
1047 (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
1048 }
1049
1050 self.clipboard_ctx = clipboard_ctx;
1051 }
1052}
1053
1054impl Drop for Context {
1055 fn drop(&mut self) {
1056 let _guard = CTX_MUTEX.lock();
1057 unsafe {
1058 if !self.raw.is_null() {
1059 unregister_user_textures_for_context(self.raw);
1060 crate::platform_io::clear_typed_callbacks_for_context(self.raw);
1061 with_bound_context(self.raw, || {
1062 crate::platform_io::clear_out_param_callbacks_for_current_context();
1063 });
1064 if sys::igGetCurrentContext() == self.raw {
1065 clear_current_context();
1066 }
1067 sys::igDestroyContext(self.raw);
1068 }
1069 }
1070 }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::{Context, with_bound_context};
1076
1077 #[test]
1078 fn platform_io_shared_and_mut_views_match() {
1079 let mut ctx = Context::create();
1080 let shared = ctx.platform_io().as_raw();
1081 let mutable = ctx.platform_io_mut().as_raw();
1082 assert_eq!(shared, mutable);
1083 }
1084
1085 #[test]
1086 fn with_bound_context_restores_previous_context_after_panic() {
1087 let ctx_a = Context::create();
1088 let raw_a = ctx_a.raw;
1089 let suspended_a = ctx_a.suspend();
1090 let ctx_b = Context::create();
1091 let raw_b = ctx_b.raw;
1092
1093 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1094 with_bound_context(raw_a, || panic!("forced panic while context is rebound"));
1095 }));
1096
1097 assert!(result.is_err());
1098 assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, raw_b);
1099
1100 drop(ctx_b);
1101 drop(suspended_a);
1102 }
1103
1104 #[test]
1105 fn io_and_platform_io_accessors_use_self_context_not_current_context() {
1106 let mut ctx_a = Context::create();
1107 let marker_a = std::ptr::NonNull::<u8>::dangling().as_ptr().cast();
1108 ctx_a.io_mut().set_backend_language_user_data(marker_a);
1109 let pio_a = ctx_a.platform_io().as_raw();
1110 let suspended_a = ctx_a.suspend();
1111
1112 let mut ctx_b = Context::create();
1113 let marker_b = std::ptr::NonNull::<u16>::dangling().as_ptr().cast();
1114 ctx_b.io_mut().set_backend_language_user_data(marker_b);
1115 let pio_b = ctx_b.platform_io().as_raw();
1116
1117 assert_ne!(marker_a, marker_b);
1118 assert_ne!(pio_a, pio_b);
1119
1120 let ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
1121 assert_eq!(ctx_a.0.io().backend_language_user_data(), marker_a);
1122 assert_eq!(ctx_a.0.platform_io().as_raw(), pio_a);
1123 assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1124
1125 drop(ctx_b);
1126 drop(ctx_a);
1127 }
1128
1129 #[test]
1130 fn style_and_main_viewport_accessors_use_self_context_not_current_context() {
1131 let mut ctx_a = Context::create();
1132 ctx_a.style_mut().set_alpha(0.25);
1133 let viewport_a = ctx_a.main_viewport().as_raw();
1134 let suspended_a = ctx_a.suspend();
1135
1136 let mut ctx_b = Context::create();
1137 ctx_b.style_mut().set_alpha(0.75);
1138 let viewport_b = ctx_b.main_viewport().as_raw();
1139
1140 assert_ne!(viewport_a, viewport_b);
1141
1142 let mut ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
1143 assert_eq!(ctx_a.0.style().alpha(), 0.25);
1144 assert_eq!(ctx_a.0.main_viewport().as_raw(), viewport_a);
1145 assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1146
1147 drop(ctx_b);
1148 drop(ctx_a);
1149 }
1150
1151 #[test]
1152 fn io_font_global_scale_uses_owner_context_not_current_context() {
1153 let mut ctx_a = Context::create();
1154 ctx_a.style_mut().set_font_scale_main(1.25);
1155 let suspended_a = ctx_a.suspend();
1156
1157 let mut ctx_b = Context::create();
1158 ctx_b.style_mut().set_font_scale_main(2.0);
1159
1160 let mut ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
1161 assert_eq!(ctx_a.0.io().font_global_scale(), 1.25);
1162
1163 ctx_a.0.io_mut().set_font_global_scale(1.5);
1164
1165 assert_eq!(ctx_a.0.style().font_scale_main(), 1.5);
1166 assert_eq!(ctx_b.style().font_scale_main(), 2.0);
1167 assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1168
1169 drop(ctx_b);
1170 drop(ctx_a);
1171 }
1172
1173 #[test]
1174 fn frame_lifecycle_requires_receiver_to_be_current_context() {
1175 let ctx_a = Context::create();
1176 let suspended_a = ctx_a.suspend();
1177 let ctx_b = Context::create();
1178
1179 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1180 let _ = suspended_a.0.draw_data();
1181 }));
1182
1183 assert!(result.is_err());
1184 assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1185
1186 drop(ctx_b);
1187 drop(suspended_a);
1188 }
1189
1190 #[cfg(feature = "multi-viewport")]
1191 #[test]
1192 fn platform_io_get_window_pos_and_size_setters_install_handlers() {
1193 unsafe extern "C" fn get_pos(
1194 _viewport: *mut crate::sys::ImGuiViewport,
1195 out_pos: *mut crate::sys::ImVec2,
1196 ) {
1197 if let Some(out_pos) = unsafe { out_pos.as_mut() } {
1198 *out_pos = crate::sys::ImVec2 { x: 10.0, y: 20.0 };
1199 }
1200 }
1201 unsafe extern "C" fn get_size(
1202 _viewport: *mut crate::sys::ImGuiViewport,
1203 out_size: *mut crate::sys::ImVec2,
1204 ) {
1205 if let Some(out_size) = unsafe { out_size.as_mut() } {
1206 *out_size = crate::sys::ImVec2 { x: 30.0, y: 40.0 };
1207 }
1208 }
1209 unsafe extern "C" fn get_scale(
1210 _viewport: *mut crate::sys::ImGuiViewport,
1211 out_scale: *mut crate::sys::ImVec2,
1212 ) {
1213 if let Some(out_scale) = unsafe { out_scale.as_mut() } {
1214 *out_scale = crate::sys::ImVec2 { x: 1.0, y: 2.0 };
1215 }
1216 }
1217 unsafe extern "C" fn get_insets(
1218 _viewport: *mut crate::sys::ImGuiViewport,
1219 out_insets: *mut crate::sys::ImVec4,
1220 ) {
1221 if let Some(out_insets) = unsafe { out_insets.as_mut() } {
1222 *out_insets = crate::sys::ImVec4::new(1.0, 2.0, 3.0, 4.0);
1223 }
1224 }
1225
1226 let mut ctx = Context::create();
1227
1228 {
1229 let pio = ctx.platform_io_mut();
1230 pio.set_platform_get_window_pos_raw(Some(get_pos));
1231 pio.set_platform_get_window_size_raw(Some(get_size));
1232 pio.set_platform_get_window_framebuffer_scale_raw(Some(get_scale));
1233 pio.set_platform_get_window_work_area_insets_raw(Some(get_insets));
1234
1235 let raw = unsafe { &*pio.as_raw() };
1236 assert!(raw.Platform_GetWindowPos.is_some());
1237 assert!(raw.Platform_GetWindowSize.is_some());
1238 assert!(raw.Platform_GetWindowFramebufferScale.is_some());
1239 assert!(raw.Platform_GetWindowWorkAreaInsets.is_some());
1240 }
1241 assert!(
1242 ctx.io().backend_language_user_data().is_null(),
1243 "PlatformIO out-param helpers must not occupy BackendLanguageUserData"
1244 );
1245
1246 let pio = ctx.platform_io_mut();
1247 pio.set_platform_get_window_pos_raw(None);
1248 pio.set_platform_get_window_size_raw(None);
1249 pio.set_platform_get_window_framebuffer_scale_raw(None);
1250 pio.set_platform_get_window_work_area_insets_raw(None);
1251
1252 let raw = unsafe { &*pio.as_raw() };
1253 assert!(raw.Platform_GetWindowPos.is_none());
1254 assert!(raw.Platform_GetWindowSize.is_none());
1255 assert!(raw.Platform_GetWindowFramebufferScale.is_none());
1256 assert!(raw.Platform_GetWindowWorkAreaInsets.is_none());
1257 }
1258
1259 #[test]
1260 fn registered_user_texture_token_survives_context_drop() {
1261 let mut ctx = Context::create();
1262 let mut texture = crate::texture::OwnedTextureData::new();
1263
1264 let token = ctx.register_user_texture_token(&mut texture);
1265 drop(ctx);
1266 drop(token);
1267 drop(texture);
1268 }
1269
1270 #[test]
1271 fn registered_user_texture_token_survives_texture_drop() {
1272 let mut ctx = Context::create();
1273 let token = {
1274 let mut texture = crate::texture::OwnedTextureData::new();
1275 ctx.register_user_texture_token(&mut texture)
1276 };
1277
1278 drop(token);
1279 drop(ctx);
1280 }
1281
1282 #[test]
1283 fn user_texture_registration_is_idempotent_and_unregister_is_noop_when_missing() {
1284 let mut ctx = Context::create();
1285 let mut texture = crate::texture::OwnedTextureData::new();
1286
1287 ctx.register_user_texture(&mut texture);
1288 ctx.register_user_texture(&mut texture);
1289 ctx.unregister_user_texture(&mut texture);
1290 ctx.unregister_user_texture(&mut texture);
1291 }
1292}
1293
1294#[derive(Debug)]
1298pub struct SuspendedContext(Context);
1299
1300#[derive(Clone, Debug)]
1302pub struct ContextAliveToken(Weak<()>);
1303
1304impl ContextAliveToken {
1305 pub fn is_alive(&self) -> bool {
1307 self.0.upgrade().is_some()
1308 }
1309}
1310
1311impl SuspendedContext {
1312 pub fn try_create() -> crate::error::ImGuiResult<Self> {
1314 Self::try_create_internal(None)
1315 }
1316
1317 pub fn try_create_with_shared_font_atlas(
1319 shared_font_atlas: SharedFontAtlas,
1320 ) -> crate::error::ImGuiResult<Self> {
1321 Self::try_create_internal(Some(shared_font_atlas))
1322 }
1323
1324 pub fn create() -> Self {
1326 Self::try_create().expect("Failed to create Dear ImGui context")
1327 }
1328
1329 pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
1331 Self::try_create_with_shared_font_atlas(shared_font_atlas)
1332 .expect("Failed to create Dear ImGui context")
1333 }
1334
1335 fn try_create_internal(
1338 mut shared_font_atlas: Option<SharedFontAtlas>,
1339 ) -> crate::error::ImGuiResult<Self> {
1340 let _guard = CTX_MUTEX.lock();
1341
1342 let shared_font_atlas_ptr = match &mut shared_font_atlas {
1343 Some(atlas) => atlas.as_ptr_mut(),
1344 None => ptr::null_mut(),
1345 };
1346
1347 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
1348 if raw.is_null() {
1349 return Err(crate::error::ImGuiError::ContextCreation {
1350 reason: "ImGui_CreateContext returned null".to_string(),
1351 });
1352 }
1353
1354 let ctx = Context {
1355 raw,
1356 alive: Rc::new(()),
1357 shared_font_atlas,
1358 ini_filename: None,
1359 log_filename: None,
1360 platform_name: None,
1361 renderer_name: None,
1362 clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
1363 ui: crate::ui::Ui::new(),
1364 };
1365
1366 if ctx.is_current_context() {
1368 clear_current_context();
1369 }
1370
1371 Ok(SuspendedContext(ctx))
1372 }
1373
1374 pub fn activate(self) -> Result<Context, SuspendedContext> {
1379 let _guard = CTX_MUTEX.lock();
1380 if no_current_context() {
1381 unsafe {
1382 sys::igSetCurrentContext(self.0.raw);
1383 }
1384 Ok(self.0)
1385 } else {
1386 Err(self)
1387 }
1388 }
1389}
1390
1391#[derive(Debug)]
1396pub struct RegisteredUserTexture {
1397 ctx: *mut sys::ImGuiContext,
1398 tex: *mut sys::ImTextureData,
1399 alive: Weak<()>,
1400}
1401
1402impl Drop for RegisteredUserTexture {
1403 fn drop(&mut self) {
1404 if self.ctx.is_null() || self.tex.is_null() || self.alive.upgrade().is_none() {
1405 return;
1406 }
1407
1408 let _guard = CTX_MUTEX.lock();
1409 if let Some(registration) = take_user_texture_registration(self.ctx, self.tex) {
1410 unregister_user_texture_registration(registration);
1411 }
1412 }
1413}
1414
1415