use parking_lot::ReentrantMutex;
use std::cell::{RefCell, UnsafeCell};
use std::ffi::{CStr, CString};
use std::ops::Drop;
use std::path::PathBuf;
use std::ptr;
use std::rc::Rc;
use crate::clipboard::{ClipboardBackend, ClipboardContext};
use crate::fonts::atlas::{FontAtlas, FontAtlasRefMut, FontId, SharedFontAtlas};
use crate::io::Io;
use crate::style::Style;
use crate::sys;
use crate::Ui;
#[derive(Debug)]
pub struct Context {
raw: *mut sys::ImGuiContext,
shared_font_atlas: Option<Rc<RefCell<SharedFontAtlas>>>,
ini_filename: Option<CString>,
log_filename: Option<CString>,
platform_name: Option<CString>,
renderer_name: Option<CString>,
clipboard_ctx: Box<UnsafeCell<ClipboardContext>>,
}
static CTX_MUTEX: ReentrantMutex<()> = parking_lot::const_reentrant_mutex(());
fn clear_current_context() {
unsafe {
sys::igSetCurrentContext(ptr::null_mut());
}
}
fn no_current_context() -> bool {
let ctx = unsafe { sys::igGetCurrentContext() };
ctx.is_null()
}
impl Context {
#[doc(alias = "CreateContext")]
pub fn create() -> Self {
Self::create_internal(None)
}
#[doc(alias = "CreateContext")]
pub fn create_with_shared_font_atlas(shared_font_atlas: Rc<RefCell<SharedFontAtlas>>) -> Self {
Self::create_internal(Some(shared_font_atlas))
}
pub fn current() -> Self {
let ctx = unsafe { sys::igGetCurrentContext() };
Self {
raw: ctx,
shared_font_atlas: None,
ini_filename: None,
log_filename: None,
platform_name: None,
renderer_name: None,
clipboard_ctx: Box::new(ClipboardContext::dummy().into()),
}
}
#[doc(alias = "CreateContext")]
pub fn suspend(self) -> SuspendedContext {
let _guard = CTX_MUTEX.lock();
assert!(
self.is_current_context(),
"context to be suspended is not the active context"
);
clear_current_context();
SuspendedContext(self)
}
pub fn ini_filename(&self) -> Option<PathBuf> {
let io = self.io();
if io.ini_filename.is_null() {
None
} else {
let s = unsafe { CStr::from_ptr(io.ini_filename) };
Some(PathBuf::from(s.to_str().ok()?))
}
}
pub fn set_ini_filename<T: Into<Option<PathBuf>>>(&mut self, ini_filename: T) {
let ini_filename: Option<PathBuf> = ini_filename.into();
let ini_filename = ini_filename.and_then(|v| CString::new(v.to_str()?).ok());
self.io_mut().ini_filename = ini_filename
.as_ref()
.map(|x| x.as_ptr())
.unwrap_or(ptr::null());
self.ini_filename = ini_filename;
}
pub fn log_filename(&self) -> Option<PathBuf> {
let io = self.io();
if io.log_filename.is_null() {
None
} else {
let cstr = unsafe { CStr::from_ptr(io.log_filename) };
Some(PathBuf::from(cstr.to_str().ok()?))
}
}
pub fn set_log_filename<T: Into<Option<PathBuf>>>(&mut self, log_filename: T) {
let log_filename = log_filename
.into()
.and_then(|v| CString::new(v.to_str()?).ok());
self.io_mut().log_filename = log_filename
.as_ref()
.map(|x| x.as_ptr())
.unwrap_or(ptr::null());
self.log_filename = log_filename;
}
pub fn platform_name(&self) -> Option<&str> {
let io = self.io();
if io.backend_platform_name.is_null() {
None
} else {
let cstr = unsafe { CStr::from_ptr(io.backend_platform_name) };
cstr.to_str().ok()
}
}
pub fn set_platform_name<T: Into<Option<String>>>(&mut self, platform_name: T) {
let platform_name: Option<CString> =
platform_name.into().and_then(|v| CString::new(v).ok());
self.io_mut().backend_platform_name = platform_name
.as_ref()
.map(|x| x.as_ptr())
.unwrap_or(ptr::null());
self.platform_name = platform_name;
}
pub fn renderer_name(&self) -> Option<&str> {
let io = self.io();
if io.backend_renderer_name.is_null() {
None
} else {
let cstr = unsafe { CStr::from_ptr(io.backend_renderer_name) };
cstr.to_str().ok()
}
}
pub fn set_renderer_name<T: Into<Option<String>>>(&mut self, renderer_name: T) {
let renderer_name: Option<CString> =
renderer_name.into().and_then(|v| CString::new(v).ok());
self.io_mut().backend_renderer_name = renderer_name
.as_ref()
.map(|x| x.as_ptr())
.unwrap_or(ptr::null());
self.renderer_name = renderer_name;
}
#[doc(alias = "LoadIniSettingsFromMemory")]
pub fn load_ini_settings(&mut self, data: &str) {
unsafe { sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len()) }
}
#[doc(alias = "SaveInitSettingsToMemory")]
pub fn save_ini_settings(&mut self, buf: &mut String) {
let data = unsafe { CStr::from_ptr(sys::igSaveIniSettingsToMemory(ptr::null_mut())) };
buf.push_str(&data.to_string_lossy());
}
pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
let clipboard_ctx: Box<UnsafeCell<_>> = Box::new(ClipboardContext::new(backend).into());
let io = self.io_mut();
io.set_clipboard_text_fn = Some(crate::clipboard::set_clipboard_text);
io.get_clipboard_text_fn = Some(crate::clipboard::get_clipboard_text);
io.clipboard_user_data = clipboard_ctx.get() as *mut _;
self.clipboard_ctx = clipboard_ctx;
}
fn create_internal(shared_font_atlas: Option<Rc<RefCell<SharedFontAtlas>>>) -> Self {
let _guard = CTX_MUTEX.lock();
assert!(
no_current_context(),
"A new active context cannot be created, because another one already exists"
);
let shared_font_atlas_ptr = match &shared_font_atlas {
Some(shared_font_atlas) => {
let borrowed_font_atlas = shared_font_atlas.borrow();
borrowed_font_atlas.0
}
None => ptr::null_mut(),
};
let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
Context {
raw,
shared_font_atlas,
ini_filename: None,
log_filename: None,
platform_name: None,
renderer_name: None,
clipboard_ctx: Box::new(ClipboardContext::dummy().into()),
}
}
fn is_current_context(&self) -> bool {
let ctx = unsafe { sys::igGetCurrentContext() };
self.raw == ctx
}
}
impl Drop for Context {
#[doc(alias = "DestroyContext")]
fn drop(&mut self) {
let _guard = CTX_MUTEX.lock();
unsafe {
sys::igDestroyContext(self.raw);
}
}
}
#[derive(Debug)]
pub struct SuspendedContext(Context);
impl SuspendedContext {
#[doc(alias = "CreateContext")]
pub fn create() -> Self {
Self::create_internal(None)
}
pub fn create_with_shared_font_atlas(shared_font_atlas: Rc<RefCell<SharedFontAtlas>>) -> Self {
Self::create_internal(Some(shared_font_atlas))
}
#[doc(alias = "SetCurrentContext")]
pub fn activate(self) -> Result<Context, SuspendedContext> {
let _guard = CTX_MUTEX.lock();
if no_current_context() {
unsafe {
sys::igSetCurrentContext(self.0.raw);
}
Ok(self.0)
} else {
Err(self)
}
}
fn create_internal(shared_font_atlas: Option<Rc<RefCell<SharedFontAtlas>>>) -> Self {
let _guard = CTX_MUTEX.lock();
let raw = unsafe { sys::igCreateContext(ptr::null_mut()) };
let ctx = Context {
raw,
shared_font_atlas,
ini_filename: None,
log_filename: None,
platform_name: None,
renderer_name: None,
clipboard_ctx: Box::new(ClipboardContext::dummy().into()),
};
if ctx.is_current_context() {
clear_current_context();
}
SuspendedContext(ctx)
}
}
#[test]
fn test_one_context() {
let _guard = crate::test::TEST_MUTEX.lock();
let _ctx = Context::create();
assert!(!no_current_context());
}
#[test]
fn test_drop_clears_current_context() {
let _guard = crate::test::TEST_MUTEX.lock();
{
let _ctx1 = Context::create();
assert!(!no_current_context());
}
assert!(no_current_context());
{
let _ctx2 = Context::create();
assert!(!no_current_context());
}
assert!(no_current_context());
}
#[test]
fn test_new_suspended() {
let _guard = crate::test::TEST_MUTEX.lock();
let ctx = Context::create();
let _suspended = SuspendedContext::create();
assert!(ctx.is_current_context());
::std::mem::drop(_suspended);
assert!(ctx.is_current_context());
}
#[test]
fn test_suspend() {
let _guard = crate::test::TEST_MUTEX.lock();
let ctx = Context::create();
assert!(!no_current_context());
let _suspended = ctx.suspend();
assert!(no_current_context());
let _ctx2 = Context::create();
}
#[test]
fn test_drop_suspended() {
let _guard = crate::test::TEST_MUTEX.lock();
let suspended = Context::create().suspend();
assert!(no_current_context());
let ctx2 = Context::create();
::std::mem::drop(suspended);
assert!(ctx2.is_current_context());
}
#[test]
fn test_suspend_activate() {
let _guard = crate::test::TEST_MUTEX.lock();
let suspended = Context::create().suspend();
assert!(no_current_context());
let ctx = suspended.activate().unwrap();
assert!(ctx.is_current_context());
}
#[test]
fn test_suspend_failure() {
let _guard = crate::test::TEST_MUTEX.lock();
let suspended = Context::create().suspend();
let _ctx = Context::create();
assert!(suspended.activate().is_err());
}
#[test]
fn test_shared_font_atlas() {
let _guard = crate::test::TEST_MUTEX.lock();
let atlas = Rc::new(RefCell::new(SharedFontAtlas::create()));
let suspended1 = SuspendedContext::create_with_shared_font_atlas(atlas.clone());
let mut ctx2 = Context::create_with_shared_font_atlas(atlas);
{
let _borrow = ctx2.fonts();
}
let _suspended2 = ctx2.suspend();
let mut ctx = suspended1.activate().unwrap();
let _borrow = ctx.fonts();
}
#[test]
#[should_panic]
fn test_shared_font_atlas_borrow_panic() {
let _guard = crate::test::TEST_MUTEX.lock();
let atlas = Rc::new(RefCell::new(SharedFontAtlas::create()));
let _suspended = SuspendedContext::create_with_shared_font_atlas(atlas.clone());
let mut ctx = Context::create_with_shared_font_atlas(atlas.clone());
let _borrow1 = atlas.borrow();
let _borrow2 = ctx.fonts();
}
#[test]
fn test_ini_load_save() {
let (_guard, mut ctx) = crate::test::test_ctx();
let data = "[Window][Debug##Default]
Pos=60,60
Size=400,400
Collapsed=0";
ctx.load_ini_settings(data);
let mut buf = String::new();
ctx.save_ini_settings(&mut buf);
assert_eq!(data.trim(), buf.trim());
}
#[test]
fn test_default_ini_filename() {
let _guard = crate::test::TEST_MUTEX.lock();
let ctx = Context::create();
assert_eq!(ctx.ini_filename(), Some(PathBuf::from("imgui.ini")));
}
#[test]
fn test_set_ini_filename() {
let (_guard, mut ctx) = crate::test::test_ctx();
ctx.set_ini_filename(Some(PathBuf::from("test.ini")));
assert_eq!(ctx.ini_filename(), Some(PathBuf::from("test.ini")));
}
#[test]
fn test_default_log_filename() {
let _guard = crate::test::TEST_MUTEX.lock();
let ctx = Context::create();
assert_eq!(ctx.log_filename(), Some(PathBuf::from("imgui_log.txt")));
}
#[test]
fn test_set_log_filename() {
let (_guard, mut ctx) = crate::test::test_ctx();
ctx.set_log_filename(Some(PathBuf::from("test.log")));
assert_eq!(ctx.log_filename(), Some(PathBuf::from("test.log")));
}
impl Context {
pub fn io(&self) -> &Io {
unsafe {
&*(sys::igGetIO() as *const Io)
}
}
pub fn io_mut(&mut self) -> &mut Io {
unsafe {
&mut *(sys::igGetIO() as *mut Io)
}
}
#[doc(alias = "GetStyle")]
pub fn style(&self) -> &Style {
unsafe {
&*(sys::igGetStyle() as *const Style)
}
}
#[doc(alias = "GetStyle")]
pub fn style_mut(&mut self) -> &mut Style {
unsafe {
&mut *(sys::igGetStyle() as *mut Style)
}
}
pub fn fonts(&mut self) -> FontAtlasRefMut<'_> {
match self.shared_font_atlas {
Some(ref font_atlas) => FontAtlasRefMut::Shared(font_atlas.borrow_mut()),
None => unsafe {
let fonts = &mut *(self.io_mut().fonts as *mut FontAtlas);
FontAtlasRefMut::Owned(fonts)
},
}
}
#[doc(alias = "NewFame")]
pub fn frame(&mut self) -> Ui<'_> {
let default_font = self.io().font_default;
if !default_font.is_null() && self.fonts().get_font(FontId(default_font)).is_none() {
self.io_mut().font_default = ptr::null_mut();
}
let font_atlas = self
.shared_font_atlas
.as_ref()
.map(|font_atlas| font_atlas.borrow_mut());
unsafe {
sys::igNewFrame();
}
Ui {
ctx: self,
font_atlas,
buffer: crate::UiBuffer::new(1024).into(),
}
}
}