use imgui::sys as imgui_sys;
use std::sync::Arc;
use std::sync::Mutex;
struct Inner {
context: imgui::Context,
font_atlas_texture: *mut imgui::FontAtlasTexture<'static>,
ui: Option<*mut imgui::Ui<'static>>,
platform: imgui_winit_support::WinitPlatform,
want_capture_keyboard: bool,
want_capture_mouse: bool,
want_set_mouse_pos: bool,
want_text_input: bool,
}
unsafe impl Send for Inner {}
impl Drop for Inner {
fn drop(&mut self) {
let mut ui = None;
std::mem::swap(&mut self.ui, &mut ui);
if let Some(ui) = ui {
let _ui = unsafe { Box::from_raw(ui) };
}
unsafe { Box::from_raw(self.font_atlas_texture) };
}
}
#[derive(Clone)]
pub struct ImguiManager {
inner: Arc<Mutex<Inner>>,
}
impl ImguiManager {
pub fn new(
mut imgui_context: imgui::Context,
platform: imgui_winit_support::WinitPlatform,
) -> Self {
let font_atlas_texture = {
let mut fonts = imgui_context.fonts();
let font_atlas_texture = Box::new(fonts.build_rgba32_texture());
log::info!("Building ImGui font atlas");
let font_atlas_texture: *mut imgui::FontAtlasTexture =
Box::into_raw(font_atlas_texture);
#[allow(clippy::transmute_ptr_to_ptr)]
let font_atlas_texture: *mut imgui::FontAtlasTexture<'static> =
unsafe { std::mem::transmute(font_atlas_texture) };
font_atlas_texture
};
ImguiManager {
inner: Arc::new(Mutex::new(Inner {
context: imgui_context,
font_atlas_texture,
ui: None,
platform,
want_capture_keyboard: false,
want_capture_mouse: false,
want_set_mouse_pos: false,
want_text_input: false,
})),
}
}
pub fn handle_event<T>(
&self,
window: &winit::window::Window,
event: &winit::event::Event<T>,
) {
let mut inner = self.inner.lock().unwrap();
let inner = &mut *inner;
let context = &mut inner.context;
let platform = &mut inner.platform;
match event {
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::ReceivedCharacter(ch),
..
} if *ch == '\u{7f}' => {
}
_ => {
platform.handle_event(context.io_mut(), window, event);
}
}
}
#[allow(dead_code)]
pub fn with_context<F>(
&self,
f: F,
) where
F: FnOnce(&mut imgui::Context),
{
let mut inner = self.inner.lock().unwrap();
(f)(&mut inner.context);
}
pub fn with_ui<F>(
&self,
f: F,
) where
F: FnOnce(&mut imgui::Ui),
{
let inner = self.inner.lock().unwrap();
if inner.ui.is_none() {
log::warn!("Tried to use imgui ui when a frame was not started");
return;
}
if let Some(ui) = inner.ui {
unsafe {
(f)(&mut *ui);
}
}
}
#[allow(dead_code)]
pub fn font_atlas_texture(&self) -> &imgui::FontAtlasTexture {
let inner = self.inner.lock().unwrap();
assert!(!inner.font_atlas_texture.is_null());
unsafe { &*inner.font_atlas_texture }
}
fn take_ui(inner: &mut Inner) -> Option<Box<imgui::Ui<'static>>> {
let mut ui = None;
std::mem::swap(&mut inner.ui, &mut ui);
if let Some(ui) = ui {
return Some(unsafe { Box::from_raw(ui) });
}
None
}
pub fn begin_frame(
&self,
window: &winit::window::Window,
) {
let mut inner_mutex_guard = self.inner.lock().unwrap();
let inner = &mut *inner_mutex_guard;
if inner.ui.is_some() {
log::warn!("a frame is already in progress, starting a new one");
ImguiManager::take_ui(inner);
}
inner
.platform
.prepare_frame(inner.context.io_mut(), window)
.unwrap();
let ui = Box::new(inner.context.frame());
inner.want_capture_keyboard = ui.io().want_capture_keyboard;
inner.want_capture_mouse = ui.io().want_capture_mouse;
inner.want_set_mouse_pos = ui.io().want_set_mouse_pos;
inner.want_text_input = ui.io().want_text_input;
let ui_ptr: *mut imgui::Ui = Box::into_raw(ui);
#[allow(clippy::transmute_ptr_to_ptr)]
let ui_ptr: *mut imgui::Ui<'static> = unsafe { std::mem::transmute(ui_ptr) };
inner.ui = Some(ui_ptr);
}
#[allow(dead_code)]
pub fn is_frame_started(&self) -> bool {
let inner = self.inner.lock().unwrap();
inner.ui.is_some()
}
pub fn render(
&self,
window: &winit::window::Window,
) {
let mut inner = self.inner.lock().unwrap();
if inner.ui.is_none() {
log::warn!("render() was called but a frame was not started");
return;
}
let ui = ImguiManager::take_ui(&mut inner);
if let Some(ui) = ui {
inner.platform.prepare_render(&ui, window);
ui.render();
} else {
log::warn!("ui did not exist");
}
}
#[allow(dead_code)]
pub fn draw_data(&self) -> Option<&imgui::DrawData> {
let inner = self.inner.lock().unwrap();
if inner.ui.is_some() {
log::warn!("get_draw_data() was called but a frame is in progress");
return None;
}
let draw_data = unsafe { imgui_sys::igGetDrawData() };
if draw_data.is_null() {
log::warn!("no draw data available");
return None;
}
let draw_data = unsafe { &*(draw_data as *mut imgui::DrawData) };
Some(draw_data)
}
#[allow(dead_code)]
pub fn want_capture_keyboard(&self) -> bool {
self.inner.lock().unwrap().want_capture_keyboard
}
#[allow(dead_code)]
pub fn want_capture_mouse(&self) -> bool {
self.inner.lock().unwrap().want_capture_mouse
}
#[allow(dead_code)]
pub fn want_set_mouse_pos(&self) -> bool {
self.inner.lock().unwrap().want_set_mouse_pos
}
#[allow(dead_code)]
pub fn want_text_input(&self) -> bool {
self.inner.lock().unwrap().want_text_input
}
}
fn init_imgui(window: &winit::window::Window) -> imgui::Context {
use imgui::Context;
let mut imgui = Context::create();
{
fn imgui_gamma_to_linear(col: [f32; 4]) -> [f32; 4] {
let x = col[0].powf(2.2);
let y = col[1].powf(2.2);
let z = col[2].powf(2.2);
let w = 1.0 - (1.0 - col[3]).powf(2.2);
[x, y, z, w]
}
let style = imgui.style_mut();
for col in 0..style.colors.len() {
style.colors[col] = imgui_gamma_to_linear(style.colors[col]);
}
}
imgui.set_ini_filename(None);
let scale_factor = window.scale_factor().round();
let font_size = (16.0 * scale_factor) as f32;
imgui.fonts().add_font(&[imgui::FontSource::TtfData {
data: include_bytes!("NotoSans-Medium.ttf"),
size_pixels: font_size,
config: None,
}]);
imgui.io_mut().font_global_scale = (1.0 / scale_factor) as f32;
imgui
}
pub fn init_imgui_manager(window: &winit::window::Window) -> ImguiManager {
let mut imgui_context = init_imgui(window);
let mut imgui_platform = imgui_winit_support::WinitPlatform::init(&mut imgui_context);
imgui_platform.attach_window(
imgui_context.io_mut(),
window,
imgui_winit_support::HiDpiMode::Rounded,
);
ImguiManager::new(imgui_context, imgui_platform)
}