1pub mod context;
38pub mod helpers;
39pub mod input;
40pub mod schedule;
41pub mod texture;
42pub mod viewport;
43
44use bevy_app::{App, Plugin};
45use bevy_ecs::resource::Resource;
46
47pub use self::context::{ImguiContexts, ImguiFrameOutput, ImguiFrameState};
48pub use self::helpers::configure_example_context;
49pub use self::schedule::{ImguiBeginFrame, ImguiEndFrame, ImguiPrimaryContextPass};
50#[cfg(feature = "render")]
51pub use self::texture::ImguiBevyTextures;
52pub use self::texture::ImguiTextureFeedbackQueue;
53pub use self::viewport::{
54 ImguiViewportBridge, ImguiViewportCamera, ImguiViewportCommand, ImguiViewportFeedback,
55 ImguiViewportId, ImguiViewportSnapshot, ImguiViewportWindow,
56};
57
58const MULTI_VIEWPORT_FEATURE_ENABLED: bool = cfg!(feature = "multi-viewport");
59const NATIVE_PLATFORM_TARGET: bool = !cfg!(target_arch = "wasm32");
60
61#[derive(Debug, Clone, Default)]
66pub struct ImguiPlugin {
67 config: ImguiBackendConfig,
68}
69
70impl ImguiPlugin {
71 #[must_use]
73 pub fn new(config: ImguiBackendConfig) -> Self {
74 Self { config }
75 }
76
77 #[must_use]
79 pub fn config(&self) -> &ImguiBackendConfig {
80 &self.config
81 }
82}
83
84impl Plugin for ImguiPlugin {
85 fn build(&self, app: &mut App) {
86 if !app.world().contains_resource::<ImguiBackendConfig>() {
87 app.insert_resource(self.config.clone());
88 }
89 if app.world().get_non_send::<ImguiContext>().is_none() {
90 app.insert_non_send(ImguiContext::new(dear_imgui_rs::Context::create()));
91 }
92 schedule::install_imgui_schedules(app);
93 input::install_input_mapping(app);
94 context::install_context_lifecycle(app);
95 viewport::install_viewport_bridge(app);
96 #[cfg(feature = "render")]
97 let render_integration_installed = render::install_render_extraction(app);
98 #[cfg(not(feature = "render"))]
99 let render_integration_installed = false;
100 refresh_backend_status(app, render_integration_installed);
101 }
102
103 fn finish(&self, _app: &mut App) {
104 #[cfg(feature = "render")]
105 {
106 let render_integration_installed = render::install_render_extraction(_app);
107 refresh_backend_status(_app, render_integration_installed);
108 }
109 }
110}
111
112fn refresh_backend_status(app: &mut App, render_integration_installed: bool) {
113 let effective_config = app.world().resource::<ImguiBackendConfig>().clone();
114 sync_backend_context_config(app, &effective_config, render_integration_installed);
115 app.insert_resource(ImguiBackendStatus::from_config(
116 &effective_config,
117 render_integration_installed,
118 ));
119}
120
121fn sync_backend_context_config(
122 app: &mut App,
123 config: &ImguiBackendConfig,
124 render_integration_installed: bool,
125) {
126 let Some(mut imgui_context) = app.world_mut().get_non_send_mut::<ImguiContext>() else {
127 return;
128 };
129 let context = imgui_context.context_mut();
130 let mut config_flags = context.io().config_flags();
131 if config.docking {
132 config_flags.insert(dear_imgui_rs::ConfigFlags::DOCKING_ENABLE);
133 } else {
134 config_flags.remove(dear_imgui_rs::ConfigFlags::DOCKING_ENABLE);
135 }
136 context.io_mut().set_config_flags(config_flags);
137
138 let imgui_name = sanitized_imgui_backend_name(&config.name);
139 context
140 .set_platform_name(Some(imgui_name.clone()))
141 .expect("sanitized backend names must be valid C strings");
142 if !config.multi_viewport || !MULTI_VIEWPORT_FEATURE_ENABLED || !NATIVE_PLATFORM_TARGET {
143 context
144 .io_mut()
145 .set_backend_platform_user_data(std::ptr::null_mut());
146 clear_platform_backend_handlers(context);
147 }
148 context
149 .io_mut()
150 .set_backend_renderer_user_data(std::ptr::null_mut());
151 clear_renderer_backend_handlers(context);
152 let mut backend_flags = context.io().backend_flags();
153 if render_integration_installed {
154 #[cfg(feature = "render")]
155 render::install_standard_draw_callbacks_for_context(context);
156 backend_flags.insert(
157 dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES
158 | dear_imgui_rs::BackendFlags::RENDERER_HAS_VTX_OFFSET,
159 );
160 context
161 .set_renderer_name(Some(imgui_name))
162 .expect("sanitized backend names must be valid C strings");
163 } else {
164 backend_flags.remove(
165 dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES
166 | dear_imgui_rs::BackendFlags::RENDERER_HAS_VTX_OFFSET,
167 );
168 context
169 .set_renderer_name::<String>(None)
170 .expect("clearing BackendRendererName must not fail");
171 }
172 context.io_mut().set_backend_flags(backend_flags);
173}
174
175fn sanitized_imgui_backend_name(name: &str) -> String {
176 name.replace('\0', "?")
177}
178
179#[derive(Resource, Debug, Clone, Eq, PartialEq)]
181pub struct ImguiBackendConfig {
182 pub name: String,
184 pub docking: bool,
186 pub multi_viewport: bool,
192}
193
194impl Default for ImguiBackendConfig {
195 fn default() -> Self {
196 Self {
197 name: "dear-imgui-bevy".to_owned(),
198 docking: true,
199 multi_viewport: false,
200 }
201 }
202}
203
204#[derive(Resource, Debug, Clone, Eq, PartialEq)]
206pub struct ImguiBackendStatus {
207 pub bevy_target: &'static str,
209 pub rust_target: &'static str,
211 pub render_feature_enabled: bool,
213 pub render_integration_installed: bool,
215 pub multi_viewport_requested: bool,
217 pub multi_viewport_feature_enabled: bool,
219 pub native_platform_target: bool,
221 pub viewport_lifecycle_bridge_enabled: bool,
223 pub viewport_input_feedback_enabled: bool,
225 pub viewport_render_routing_enabled: bool,
227 pub multi_viewport_supported: bool,
233}
234
235impl ImguiBackendStatus {
236 fn from_config(config: &ImguiBackendConfig, render_integration_installed: bool) -> Self {
237 let viewport_lifecycle_bridge_enabled =
238 config.multi_viewport && MULTI_VIEWPORT_FEATURE_ENABLED && NATIVE_PLATFORM_TARGET;
239 let viewport_input_feedback_enabled =
240 config.multi_viewport && MULTI_VIEWPORT_FEATURE_ENABLED && NATIVE_PLATFORM_TARGET;
241 let viewport_render_routing_enabled = config.multi_viewport
242 && MULTI_VIEWPORT_FEATURE_ENABLED
243 && NATIVE_PLATFORM_TARGET
244 && render_integration_installed;
245
246 Self {
247 bevy_target: BEVY_TARGET_VERSION,
248 rust_target: RUST_TARGET_VERSION,
249 render_feature_enabled: cfg!(feature = "render"),
250 render_integration_installed,
251 multi_viewport_requested: config.multi_viewport,
252 multi_viewport_feature_enabled: MULTI_VIEWPORT_FEATURE_ENABLED,
253 native_platform_target: NATIVE_PLATFORM_TARGET,
254 viewport_lifecycle_bridge_enabled,
255 viewport_input_feedback_enabled,
256 viewport_render_routing_enabled,
257 multi_viewport_supported: viewport_lifecycle_bridge_enabled
258 && viewport_input_feedback_enabled
259 && viewport_render_routing_enabled,
260 }
261 }
262}
263
264impl Default for ImguiBackendStatus {
265 fn default() -> Self {
266 Self::from_config(&ImguiBackendConfig::default(), false)
267 }
268}
269
270pub struct ImguiContext {
276 context: dear_imgui_rs::Context,
277}
278
279impl ImguiContext {
280 #[must_use]
282 pub fn new(context: dear_imgui_rs::Context) -> Self {
283 Self { context }
284 }
285
286 #[must_use]
288 pub fn context(&self) -> &dear_imgui_rs::Context {
289 &self.context
290 }
291
292 #[must_use]
294 pub fn context_mut(&mut self) -> &mut dear_imgui_rs::Context {
295 &mut self.context
296 }
297
298 #[must_use]
300 pub fn into_inner(mut self) -> dear_imgui_rs::Context {
301 self.clear_backend_data();
302 let this = std::mem::ManuallyDrop::new(self);
303 unsafe { std::ptr::read(&this.context) }
306 }
307
308 fn clear_backend_data(&mut self) {
309 self.context
310 .io_mut()
311 .set_backend_platform_user_data(std::ptr::null_mut());
312 self.context
313 .set_platform_name::<String>(None)
314 .expect("clearing BackendPlatformName must not fail");
315 self.context
316 .io_mut()
317 .set_backend_renderer_user_data(std::ptr::null_mut());
318 self.context
319 .set_renderer_name::<String>(None)
320 .expect("clearing BackendRendererName must not fail");
321 clear_renderer_backend_handlers(&mut self.context);
322
323 let mut backend_flags = self.context.io().backend_flags();
324 backend_flags.remove(
325 dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES
326 | dear_imgui_rs::BackendFlags::RENDERER_HAS_VTX_OFFSET
327 | dear_imgui_rs::BackendFlags::HAS_MOUSE_HOVERED_VIEWPORT,
328 );
329 #[cfg(feature = "multi-viewport")]
330 {
331 backend_flags.remove(
332 dear_imgui_rs::BackendFlags::PLATFORM_HAS_VIEWPORTS
333 | dear_imgui_rs::BackendFlags::RENDERER_HAS_VIEWPORTS,
334 );
335 self.context.destroy_platform_windows();
336 }
337 clear_platform_backend_handlers(&mut self.context);
338 self.context.io_mut().set_backend_flags(backend_flags);
339 }
340}
341
342pub(crate) fn clear_platform_backend_handlers(context: &mut dear_imgui_rs::Context) {
343 let platform_io = context.platform_io_mut();
344 let clipboard_handlers = ClipboardPlatformHandlers::capture(platform_io.as_raw());
345 #[cfg(feature = "multi-viewport")]
346 {
347 platform_io.clear_platform_handlers();
348 }
349 #[cfg(not(feature = "multi-viewport"))]
350 unsafe {
351 dear_imgui_rs::sys::ImGuiPlatformIO_ClearPlatformHandlers(platform_io.as_raw_mut());
352 }
353 clipboard_handlers.restore(platform_io.as_raw_mut());
354}
355
356struct ClipboardPlatformHandlers {
357 get: Option<
358 unsafe extern "C" fn(ctx: *mut dear_imgui_rs::sys::ImGuiContext) -> *const std::ffi::c_char,
359 >,
360 set: Option<
361 unsafe extern "C" fn(
362 ctx: *mut dear_imgui_rs::sys::ImGuiContext,
363 text: *const std::ffi::c_char,
364 ),
365 >,
366 user_data: *mut std::ffi::c_void,
367}
368
369impl ClipboardPlatformHandlers {
370 fn capture(platform_io: *const dear_imgui_rs::sys::ImGuiPlatformIO) -> Self {
371 let platform_io = unsafe { &*platform_io };
372 Self {
373 get: platform_io.Platform_GetClipboardTextFn,
374 set: platform_io.Platform_SetClipboardTextFn,
375 user_data: platform_io.Platform_ClipboardUserData,
376 }
377 }
378
379 fn restore(self, platform_io: *mut dear_imgui_rs::sys::ImGuiPlatformIO) {
380 let platform_io = unsafe { &mut *platform_io };
381 platform_io.Platform_GetClipboardTextFn = self.get;
382 platform_io.Platform_SetClipboardTextFn = self.set;
383 platform_io.Platform_ClipboardUserData = self.user_data;
384 }
385}
386
387fn clear_renderer_backend_handlers(context: &mut dear_imgui_rs::Context) {
388 let platform_io = context.platform_io_mut();
389 #[cfg(feature = "multi-viewport")]
390 {
391 platform_io.clear_renderer_handlers();
392 }
393 #[cfg(not(feature = "multi-viewport"))]
394 unsafe {
395 dear_imgui_rs::sys::ImGuiPlatformIO_ClearRendererHandlers(platform_io.as_raw_mut());
396 }
397}
398
399impl Drop for ImguiContext {
400 fn drop(&mut self) {
401 self.clear_backend_data();
402 }
403}
404
405pub const BEVY_TARGET_VERSION: &str = "0.19.0-rc.2";
407pub const BEVY_TARGET_COMMIT: &str = "a389b928aee5906928a16a7d4e66cb02c7362901";
409pub const RUST_TARGET_VERSION: &str = "1.95.0";
411pub const WGPU_TARGET_VERSION: &str = "29.0.3";
413
414#[cfg(feature = "render")]
415pub mod render;