pub mod context;
pub mod helpers;
pub mod input;
pub mod schedule;
pub mod texture;
pub mod viewport;
use bevy_app::{App, Plugin};
use bevy_ecs::resource::Resource;
pub use self::context::{ImguiContexts, ImguiFrameOutput, ImguiFrameState};
pub use self::helpers::configure_example_context;
pub use self::schedule::{ImguiBeginFrame, ImguiEndFrame, ImguiPrimaryContextPass};
#[cfg(feature = "render")]
pub use self::texture::ImguiBevyTextures;
pub use self::texture::ImguiTextureFeedbackQueue;
pub use self::viewport::{
ImguiViewportBridge, ImguiViewportCamera, ImguiViewportCommand, ImguiViewportFeedback,
ImguiViewportId, ImguiViewportSnapshot, ImguiViewportWindow,
};
const MULTI_VIEWPORT_FEATURE_ENABLED: bool = cfg!(feature = "multi-viewport");
const NATIVE_PLATFORM_TARGET: bool = !cfg!(target_arch = "wasm32");
#[derive(Debug, Clone, Default)]
pub struct ImguiPlugin {
config: ImguiBackendConfig,
}
impl ImguiPlugin {
#[must_use]
pub fn new(config: ImguiBackendConfig) -> Self {
Self { config }
}
#[must_use]
pub fn config(&self) -> &ImguiBackendConfig {
&self.config
}
}
impl Plugin for ImguiPlugin {
fn build(&self, app: &mut App) {
if !app.world().contains_resource::<ImguiBackendConfig>() {
app.insert_resource(self.config.clone());
}
if app.world().get_non_send::<ImguiContext>().is_none() {
app.insert_non_send(ImguiContext::new(dear_imgui_rs::Context::create()));
}
schedule::install_imgui_schedules(app);
input::install_input_mapping(app);
context::install_context_lifecycle(app);
viewport::install_viewport_bridge(app);
#[cfg(feature = "render")]
let render_integration_installed = render::install_render_extraction(app);
#[cfg(not(feature = "render"))]
let render_integration_installed = false;
refresh_backend_status(app, render_integration_installed);
}
fn finish(&self, _app: &mut App) {
#[cfg(feature = "render")]
{
let render_integration_installed = render::install_render_extraction(_app);
refresh_backend_status(_app, render_integration_installed);
}
}
}
fn refresh_backend_status(app: &mut App, render_integration_installed: bool) {
let effective_config = app.world().resource::<ImguiBackendConfig>().clone();
sync_backend_context_config(app, &effective_config, render_integration_installed);
app.insert_resource(ImguiBackendStatus::from_config(
&effective_config,
render_integration_installed,
));
}
fn sync_backend_context_config(
app: &mut App,
config: &ImguiBackendConfig,
render_integration_installed: bool,
) {
let Some(mut imgui_context) = app.world_mut().get_non_send_mut::<ImguiContext>() else {
return;
};
let context = imgui_context.context_mut();
let mut config_flags = context.io().config_flags();
if config.docking {
config_flags.insert(dear_imgui_rs::ConfigFlags::DOCKING_ENABLE);
} else {
config_flags.remove(dear_imgui_rs::ConfigFlags::DOCKING_ENABLE);
}
context.io_mut().set_config_flags(config_flags);
let imgui_name = sanitized_imgui_backend_name(&config.name);
context
.set_platform_name(Some(imgui_name.clone()))
.expect("sanitized backend names must be valid C strings");
if !config.multi_viewport || !MULTI_VIEWPORT_FEATURE_ENABLED || !NATIVE_PLATFORM_TARGET {
context
.io_mut()
.set_backend_platform_user_data(std::ptr::null_mut());
clear_platform_backend_handlers(context);
}
context
.io_mut()
.set_backend_renderer_user_data(std::ptr::null_mut());
clear_renderer_backend_handlers(context);
let mut backend_flags = context.io().backend_flags();
if render_integration_installed {
#[cfg(feature = "render")]
render::install_standard_draw_callbacks_for_context(context);
backend_flags.insert(
dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES
| dear_imgui_rs::BackendFlags::RENDERER_HAS_VTX_OFFSET,
);
context
.set_renderer_name(Some(imgui_name))
.expect("sanitized backend names must be valid C strings");
} else {
backend_flags.remove(
dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES
| dear_imgui_rs::BackendFlags::RENDERER_HAS_VTX_OFFSET,
);
context
.set_renderer_name::<String>(None)
.expect("clearing BackendRendererName must not fail");
}
context.io_mut().set_backend_flags(backend_flags);
}
fn sanitized_imgui_backend_name(name: &str) -> String {
name.replace('\0', "?")
}
#[derive(Resource, Debug, Clone, Eq, PartialEq)]
pub struct ImguiBackendConfig {
pub name: String,
pub docking: bool,
pub multi_viewport: bool,
}
impl Default for ImguiBackendConfig {
fn default() -> Self {
Self {
name: "dear-imgui-bevy".to_owned(),
docking: true,
multi_viewport: false,
}
}
}
#[derive(Resource, Debug, Clone, Eq, PartialEq)]
pub struct ImguiBackendStatus {
pub bevy_target: &'static str,
pub rust_target: &'static str,
pub render_feature_enabled: bool,
pub render_integration_installed: bool,
pub multi_viewport_requested: bool,
pub multi_viewport_feature_enabled: bool,
pub native_platform_target: bool,
pub viewport_lifecycle_bridge_enabled: bool,
pub viewport_input_feedback_enabled: bool,
pub viewport_render_routing_enabled: bool,
pub multi_viewport_supported: bool,
}
impl ImguiBackendStatus {
fn from_config(config: &ImguiBackendConfig, render_integration_installed: bool) -> Self {
let viewport_lifecycle_bridge_enabled =
config.multi_viewport && MULTI_VIEWPORT_FEATURE_ENABLED && NATIVE_PLATFORM_TARGET;
let viewport_input_feedback_enabled =
config.multi_viewport && MULTI_VIEWPORT_FEATURE_ENABLED && NATIVE_PLATFORM_TARGET;
let viewport_render_routing_enabled = config.multi_viewport
&& MULTI_VIEWPORT_FEATURE_ENABLED
&& NATIVE_PLATFORM_TARGET
&& render_integration_installed;
Self {
bevy_target: BEVY_TARGET_VERSION,
rust_target: RUST_TARGET_VERSION,
render_feature_enabled: cfg!(feature = "render"),
render_integration_installed,
multi_viewport_requested: config.multi_viewport,
multi_viewport_feature_enabled: MULTI_VIEWPORT_FEATURE_ENABLED,
native_platform_target: NATIVE_PLATFORM_TARGET,
viewport_lifecycle_bridge_enabled,
viewport_input_feedback_enabled,
viewport_render_routing_enabled,
multi_viewport_supported: viewport_lifecycle_bridge_enabled
&& viewport_input_feedback_enabled
&& viewport_render_routing_enabled,
}
}
}
impl Default for ImguiBackendStatus {
fn default() -> Self {
Self::from_config(&ImguiBackendConfig::default(), false)
}
}
pub struct ImguiContext {
context: dear_imgui_rs::Context,
}
impl ImguiContext {
#[must_use]
pub fn new(context: dear_imgui_rs::Context) -> Self {
Self { context }
}
#[must_use]
pub fn context(&self) -> &dear_imgui_rs::Context {
&self.context
}
#[must_use]
pub fn context_mut(&mut self) -> &mut dear_imgui_rs::Context {
&mut self.context
}
#[must_use]
pub fn into_inner(mut self) -> dear_imgui_rs::Context {
self.clear_backend_data();
let this = std::mem::ManuallyDrop::new(self);
unsafe { std::ptr::read(&this.context) }
}
fn clear_backend_data(&mut self) {
self.context
.io_mut()
.set_backend_platform_user_data(std::ptr::null_mut());
self.context
.set_platform_name::<String>(None)
.expect("clearing BackendPlatformName must not fail");
self.context
.io_mut()
.set_backend_renderer_user_data(std::ptr::null_mut());
self.context
.set_renderer_name::<String>(None)
.expect("clearing BackendRendererName must not fail");
clear_renderer_backend_handlers(&mut self.context);
let mut backend_flags = self.context.io().backend_flags();
backend_flags.remove(
dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES
| dear_imgui_rs::BackendFlags::RENDERER_HAS_VTX_OFFSET
| dear_imgui_rs::BackendFlags::HAS_MOUSE_HOVERED_VIEWPORT,
);
#[cfg(feature = "multi-viewport")]
{
backend_flags.remove(
dear_imgui_rs::BackendFlags::PLATFORM_HAS_VIEWPORTS
| dear_imgui_rs::BackendFlags::RENDERER_HAS_VIEWPORTS,
);
self.context.destroy_platform_windows();
}
clear_platform_backend_handlers(&mut self.context);
self.context.io_mut().set_backend_flags(backend_flags);
}
}
pub(crate) fn clear_platform_backend_handlers(context: &mut dear_imgui_rs::Context) {
let platform_io = context.platform_io_mut();
let clipboard_handlers = ClipboardPlatformHandlers::capture(platform_io.as_raw());
#[cfg(feature = "multi-viewport")]
{
platform_io.clear_platform_handlers();
}
#[cfg(not(feature = "multi-viewport"))]
unsafe {
dear_imgui_rs::sys::ImGuiPlatformIO_ClearPlatformHandlers(platform_io.as_raw_mut());
}
clipboard_handlers.restore(platform_io.as_raw_mut());
}
struct ClipboardPlatformHandlers {
get: Option<
unsafe extern "C" fn(ctx: *mut dear_imgui_rs::sys::ImGuiContext) -> *const std::ffi::c_char,
>,
set: Option<
unsafe extern "C" fn(
ctx: *mut dear_imgui_rs::sys::ImGuiContext,
text: *const std::ffi::c_char,
),
>,
user_data: *mut std::ffi::c_void,
}
impl ClipboardPlatformHandlers {
fn capture(platform_io: *const dear_imgui_rs::sys::ImGuiPlatformIO) -> Self {
let platform_io = unsafe { &*platform_io };
Self {
get: platform_io.Platform_GetClipboardTextFn,
set: platform_io.Platform_SetClipboardTextFn,
user_data: platform_io.Platform_ClipboardUserData,
}
}
fn restore(self, platform_io: *mut dear_imgui_rs::sys::ImGuiPlatformIO) {
let platform_io = unsafe { &mut *platform_io };
platform_io.Platform_GetClipboardTextFn = self.get;
platform_io.Platform_SetClipboardTextFn = self.set;
platform_io.Platform_ClipboardUserData = self.user_data;
}
}
fn clear_renderer_backend_handlers(context: &mut dear_imgui_rs::Context) {
let platform_io = context.platform_io_mut();
#[cfg(feature = "multi-viewport")]
{
platform_io.clear_renderer_handlers();
}
#[cfg(not(feature = "multi-viewport"))]
unsafe {
dear_imgui_rs::sys::ImGuiPlatformIO_ClearRendererHandlers(platform_io.as_raw_mut());
}
}
impl Drop for ImguiContext {
fn drop(&mut self) {
self.clear_backend_data();
}
}
pub const BEVY_TARGET_VERSION: &str = "0.19.0-rc.2";
pub const BEVY_TARGET_COMMIT: &str = "a389b928aee5906928a16a7d4e66cb02c7362901";
pub const RUST_TARGET_VERSION: &str = "1.95.0";
pub const WGPU_TARGET_VERSION: &str = "29.0.3";
#[cfg(feature = "render")]
pub mod render;