arcdps_imgui/
context.rs
1use parking_lot::ReentrantMutex;
2use std::cell::{RefCell, UnsafeCell};
3use std::ffi::{CStr, CString};
4use std::ops::Drop;
5use std::path::PathBuf;
6use std::ptr;
7use std::rc::Rc;
8
9use crate::clipboard::{ClipboardBackend, ClipboardContext};
10use crate::fonts::atlas::{FontAtlas, FontAtlasRefMut, FontId, SharedFontAtlas};
11use crate::io::Io;
12use crate::style::Style;
13use crate::sys;
14use crate::Ui;
15
16#[derive(Debug)]
51pub struct Context {
52 raw: *mut sys::ImGuiContext,
53 shared_font_atlas: Option<Rc<RefCell<SharedFontAtlas>>>,
54 ini_filename: Option<CString>,
55 log_filename: Option<CString>,
56 platform_name: Option<CString>,
57 renderer_name: Option<CString>,
58 clipboard_ctx: Box<UnsafeCell<ClipboardContext>>,
63}
64
65static CTX_MUTEX: ReentrantMutex<()> = parking_lot::const_reentrant_mutex(());
68
69fn clear_current_context() {
70 unsafe {
71 sys::igSetCurrentContext(ptr::null_mut());
72 }
73}
74fn no_current_context() -> bool {
75 let ctx = unsafe { sys::igGetCurrentContext() };
76 ctx.is_null()
77}
78
79impl Context {
80 #[doc(alias = "CreateContext")]
86 pub fn create() -> Self {
87 Self::create_internal(None)
88 }
89 #[doc(alias = "CreateContext")]
95 pub fn create_with_shared_font_atlas(shared_font_atlas: Rc<RefCell<SharedFontAtlas>>) -> Self {
96 Self::create_internal(Some(shared_font_atlas))
97 }
98 pub fn current() -> Self {
100 let ctx = unsafe { sys::igGetCurrentContext() };
101 Self {
102 raw: ctx,
103 shared_font_atlas: None,
104 ini_filename: None,
105 log_filename: None,
106 platform_name: None,
107 renderer_name: None,
108 clipboard_ctx: Box::new(ClipboardContext::dummy().into()),
109 }
110 }
111 #[doc(alias = "CreateContext")]
113 pub fn suspend(self) -> SuspendedContext {
114 let _guard = CTX_MUTEX.lock();
115 assert!(
116 self.is_current_context(),
117 "context to be suspended is not the active context"
118 );
119 clear_current_context();
120 SuspendedContext(self)
121 }
122 pub fn ini_filename(&self) -> Option<PathBuf> {
124 let io = self.io();
125 if io.ini_filename.is_null() {
126 None
127 } else {
128 let s = unsafe { CStr::from_ptr(io.ini_filename) };
129 Some(PathBuf::from(s.to_str().ok()?))
130 }
131 }
132 pub fn set_ini_filename<T: Into<Option<PathBuf>>>(&mut self, ini_filename: T) {
136 let ini_filename: Option<PathBuf> = ini_filename.into();
137 let ini_filename = ini_filename.and_then(|v| CString::new(v.to_str()?).ok());
138
139 self.io_mut().ini_filename = ini_filename
140 .as_ref()
141 .map(|x| x.as_ptr())
142 .unwrap_or(ptr::null());
143 self.ini_filename = ini_filename;
144 }
145 pub fn log_filename(&self) -> Option<PathBuf> {
148 let io = self.io();
149 if io.log_filename.is_null() {
150 None
151 } else {
152 let cstr = unsafe { CStr::from_ptr(io.log_filename) };
153 Some(PathBuf::from(cstr.to_str().ok()?))
154 }
155 }
156 pub fn set_log_filename<T: Into<Option<PathBuf>>>(&mut self, log_filename: T) {
158 let log_filename = log_filename
159 .into()
160 .and_then(|v| CString::new(v.to_str()?).ok());
161
162 self.io_mut().log_filename = log_filename
163 .as_ref()
164 .map(|x| x.as_ptr())
165 .unwrap_or(ptr::null());
166 self.log_filename = log_filename;
167 }
168 pub fn platform_name(&self) -> Option<&str> {
170 let io = self.io();
171 if io.backend_platform_name.is_null() {
172 None
173 } else {
174 let cstr = unsafe { CStr::from_ptr(io.backend_platform_name) };
175 cstr.to_str().ok()
176 }
177 }
178 pub fn set_platform_name<T: Into<Option<String>>>(&mut self, platform_name: T) {
180 let platform_name: Option<CString> =
181 platform_name.into().and_then(|v| CString::new(v).ok());
182 self.io_mut().backend_platform_name = platform_name
183 .as_ref()
184 .map(|x| x.as_ptr())
185 .unwrap_or(ptr::null());
186 self.platform_name = platform_name;
187 }
188 pub fn renderer_name(&self) -> Option<&str> {
190 let io = self.io();
191 if io.backend_renderer_name.is_null() {
192 None
193 } else {
194 let cstr = unsafe { CStr::from_ptr(io.backend_renderer_name) };
195 cstr.to_str().ok()
196 }
197 }
198 pub fn set_renderer_name<T: Into<Option<String>>>(&mut self, renderer_name: T) {
200 let renderer_name: Option<CString> =
201 renderer_name.into().and_then(|v| CString::new(v).ok());
202
203 self.io_mut().backend_renderer_name = renderer_name
204 .as_ref()
205 .map(|x| x.as_ptr())
206 .unwrap_or(ptr::null());
207
208 self.renderer_name = renderer_name;
209 }
210 #[doc(alias = "LoadIniSettingsFromMemory")]
212 pub fn load_ini_settings(&mut self, data: &str) {
213 unsafe { sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len()) }
214 }
215 #[doc(alias = "SaveInitSettingsToMemory")]
217 pub fn save_ini_settings(&mut self, buf: &mut String) {
218 let data = unsafe { CStr::from_ptr(sys::igSaveIniSettingsToMemory(ptr::null_mut())) };
219 buf.push_str(&data.to_string_lossy());
220 }
221 pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
223 let clipboard_ctx: Box<UnsafeCell<_>> = Box::new(ClipboardContext::new(backend).into());
224 let io = self.io_mut();
225 io.set_clipboard_text_fn = Some(crate::clipboard::set_clipboard_text);
226 io.get_clipboard_text_fn = Some(crate::clipboard::get_clipboard_text);
227
228 io.clipboard_user_data = clipboard_ctx.get() as *mut _;
229 self.clipboard_ctx = clipboard_ctx;
230 }
231 fn create_internal(shared_font_atlas: Option<Rc<RefCell<SharedFontAtlas>>>) -> Self {
232 let _guard = CTX_MUTEX.lock();
233 assert!(
234 no_current_context(),
235 "A new active context cannot be created, because another one already exists"
236 );
237
238 let shared_font_atlas_ptr = match &shared_font_atlas {
239 Some(shared_font_atlas) => {
240 let borrowed_font_atlas = shared_font_atlas.borrow();
241 borrowed_font_atlas.0
242 }
243 None => ptr::null_mut(),
244 };
245 let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
248
249 Context {
250 raw,
251 shared_font_atlas,
252 ini_filename: None,
253 log_filename: None,
254 platform_name: None,
255 renderer_name: None,
256 clipboard_ctx: Box::new(ClipboardContext::dummy().into()),
257 }
258 }
259 fn is_current_context(&self) -> bool {
260 let ctx = unsafe { sys::igGetCurrentContext() };
261 self.raw == ctx
262 }
263}
264
265impl Drop for Context {
266 #[doc(alias = "DestroyContext")]
267 fn drop(&mut self) {
268 let _guard = CTX_MUTEX.lock();
269 unsafe {
272 sys::igDestroyContext(self.raw);
273 }
274 }
275}
276
277#[derive(Debug)]
297pub struct SuspendedContext(Context);
298
299impl SuspendedContext {
300 #[doc(alias = "CreateContext")]
302 pub fn create() -> Self {
303 Self::create_internal(None)
304 }
305 pub fn create_with_shared_font_atlas(shared_font_atlas: Rc<RefCell<SharedFontAtlas>>) -> Self {
307 Self::create_internal(Some(shared_font_atlas))
308 }
309 #[doc(alias = "SetCurrentContext")]
316 pub fn activate(self) -> Result<Context, SuspendedContext> {
317 let _guard = CTX_MUTEX.lock();
318 if no_current_context() {
319 unsafe {
320 sys::igSetCurrentContext(self.0.raw);
321 }
322 Ok(self.0)
323 } else {
324 Err(self)
325 }
326 }
327 fn create_internal(shared_font_atlas: Option<Rc<RefCell<SharedFontAtlas>>>) -> Self {
328 let _guard = CTX_MUTEX.lock();
329 let raw = unsafe { sys::igCreateContext(ptr::null_mut()) };
330 let ctx = Context {
331 raw,
332 shared_font_atlas,
333 ini_filename: None,
334 log_filename: None,
335 platform_name: None,
336 renderer_name: None,
337 clipboard_ctx: Box::new(ClipboardContext::dummy().into()),
338 };
339 if ctx.is_current_context() {
340 clear_current_context();
342 }
343 SuspendedContext(ctx)
344 }
345}
346
347#[test]
348fn test_one_context() {
349 let _guard = crate::test::TEST_MUTEX.lock();
350 let _ctx = Context::create();
351 assert!(!no_current_context());
352}
353
354#[test]
355fn test_drop_clears_current_context() {
356 let _guard = crate::test::TEST_MUTEX.lock();
357 {
358 let _ctx1 = Context::create();
359 assert!(!no_current_context());
360 }
361 assert!(no_current_context());
362 {
363 let _ctx2 = Context::create();
364 assert!(!no_current_context());
365 }
366 assert!(no_current_context());
367}
368
369#[test]
370fn test_new_suspended() {
371 let _guard = crate::test::TEST_MUTEX.lock();
372 let ctx = Context::create();
373 let _suspended = SuspendedContext::create();
374 assert!(ctx.is_current_context());
375 ::std::mem::drop(_suspended);
376 assert!(ctx.is_current_context());
377}
378
379#[test]
380fn test_suspend() {
381 let _guard = crate::test::TEST_MUTEX.lock();
382 let ctx = Context::create();
383 assert!(!no_current_context());
384 let _suspended = ctx.suspend();
385 assert!(no_current_context());
386 let _ctx2 = Context::create();
387}
388
389#[test]
390fn test_drop_suspended() {
391 let _guard = crate::test::TEST_MUTEX.lock();
392 let suspended = Context::create().suspend();
393 assert!(no_current_context());
394 let ctx2 = Context::create();
395 ::std::mem::drop(suspended);
396 assert!(ctx2.is_current_context());
397}
398
399#[test]
400fn test_suspend_activate() {
401 let _guard = crate::test::TEST_MUTEX.lock();
402 let suspended = Context::create().suspend();
403 assert!(no_current_context());
404 let ctx = suspended.activate().unwrap();
405 assert!(ctx.is_current_context());
406}
407
408#[test]
409fn test_suspend_failure() {
410 let _guard = crate::test::TEST_MUTEX.lock();
411 let suspended = Context::create().suspend();
412 let _ctx = Context::create();
413 assert!(suspended.activate().is_err());
414}
415
416#[test]
417fn test_shared_font_atlas() {
418 let _guard = crate::test::TEST_MUTEX.lock();
419 let atlas = Rc::new(RefCell::new(SharedFontAtlas::create()));
420 let suspended1 = SuspendedContext::create_with_shared_font_atlas(atlas.clone());
421 let mut ctx2 = Context::create_with_shared_font_atlas(atlas);
422 {
423 let _borrow = ctx2.fonts();
424 }
425 let _suspended2 = ctx2.suspend();
426 let mut ctx = suspended1.activate().unwrap();
427 let _borrow = ctx.fonts();
428}
429
430#[test]
431#[should_panic]
432fn test_shared_font_atlas_borrow_panic() {
433 let _guard = crate::test::TEST_MUTEX.lock();
434 let atlas = Rc::new(RefCell::new(SharedFontAtlas::create()));
435 let _suspended = SuspendedContext::create_with_shared_font_atlas(atlas.clone());
436 let mut ctx = Context::create_with_shared_font_atlas(atlas.clone());
437 let _borrow1 = atlas.borrow();
438 let _borrow2 = ctx.fonts();
439}
440
441#[test]
442fn test_ini_load_save() {
443 let (_guard, mut ctx) = crate::test::test_ctx();
444 let data = "[Window][Debug##Default]
445Pos=60,60
446Size=400,400
447Collapsed=0";
448 ctx.load_ini_settings(data);
449 let mut buf = String::new();
450 ctx.save_ini_settings(&mut buf);
451 assert_eq!(data.trim(), buf.trim());
452}
453
454#[test]
455fn test_default_ini_filename() {
456 let _guard = crate::test::TEST_MUTEX.lock();
457 let ctx = Context::create();
458 assert_eq!(ctx.ini_filename(), Some(PathBuf::from("imgui.ini")));
459}
460
461#[test]
462fn test_set_ini_filename() {
463 let (_guard, mut ctx) = crate::test::test_ctx();
464 ctx.set_ini_filename(Some(PathBuf::from("test.ini")));
465 assert_eq!(ctx.ini_filename(), Some(PathBuf::from("test.ini")));
466}
467
468#[test]
469fn test_default_log_filename() {
470 let _guard = crate::test::TEST_MUTEX.lock();
471 let ctx = Context::create();
472 assert_eq!(ctx.log_filename(), Some(PathBuf::from("imgui_log.txt")));
473}
474
475#[test]
476fn test_set_log_filename() {
477 let (_guard, mut ctx) = crate::test::test_ctx();
478 ctx.set_log_filename(Some(PathBuf::from("test.log")));
479 assert_eq!(ctx.log_filename(), Some(PathBuf::from("test.log")));
480}
481
482impl Context {
483 pub fn io(&self) -> &Io {
485 unsafe {
486 &*(sys::igGetIO() as *const Io)
488 }
489 }
490 pub fn io_mut(&mut self) -> &mut Io {
492 unsafe {
493 &mut *(sys::igGetIO() as *mut Io)
495 }
496 }
497 #[doc(alias = "GetStyle")]
499 pub fn style(&self) -> &Style {
500 unsafe {
501 &*(sys::igGetStyle() as *const Style)
503 }
504 }
505 #[doc(alias = "GetStyle")]
507 pub fn style_mut(&mut self) -> &mut Style {
508 unsafe {
509 &mut *(sys::igGetStyle() as *mut Style)
511 }
512 }
513 pub fn fonts(&mut self) -> FontAtlasRefMut<'_> {
519 match self.shared_font_atlas {
520 Some(ref font_atlas) => FontAtlasRefMut::Shared(font_atlas.borrow_mut()),
521 None => unsafe {
522 let fonts = &mut *(self.io_mut().fonts as *mut FontAtlas);
524 FontAtlasRefMut::Owned(fonts)
525 },
526 }
527 }
528 #[doc(alias = "NewFame")]
534 pub fn frame(&mut self) -> Ui<'_> {
535 let default_font = self.io().font_default;
537 if !default_font.is_null() && self.fonts().get_font(FontId(default_font)).is_none() {
538 self.io_mut().font_default = ptr::null_mut();
539 }
540 let font_atlas = self
542 .shared_font_atlas
543 .as_ref()
544 .map(|font_atlas| font_atlas.borrow_mut());
545 unsafe {
547 sys::igNewFrame();
548 }
549 Ui {
550 ctx: self,
551 font_atlas,
552 buffer: crate::UiBuffer::new(1024).into(),
553 }
554 }
555}