use std::sync::Arc;
use dear_imgui_rs::{ConfigFlags, Context};
use winit::dpi::{LogicalPosition, LogicalSize};
use winit::window::Window;
use crate::cursor::CursorSettings;
use super::WinitPlatformError;
use super::ownership::{WinitPlatform, WinitPlatformControl, platform_control_for_context};
pub(super) type SetImeDataCallback = unsafe extern "C" fn(
*mut dear_imgui_rs::sys::ImGuiContext,
*mut dear_imgui_rs::sys::ImGuiViewport,
*mut dear_imgui_rs::sys::ImGuiPlatformImeData,
);
pub(super) unsafe extern "C" fn imgui_winit_set_ime_data(
ctx: *mut dear_imgui_rs::sys::ImGuiContext,
viewport: *mut dear_imgui_rs::sys::ImGuiViewport,
data: *mut dear_imgui_rs::sys::ImGuiPlatformImeData,
) {
use dear_imgui_rs::sys::{ImGuiPlatformImeData, ImGuiViewport};
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
if viewport.is_null() || data.is_null() {
return;
}
let Some(control) = platform_control_for_context(ctx) else {
return;
};
let _ = control.binding.try_with_bound_context(|| {
if let Err(error) = control.validate_complete_contract_in_current_context() {
control.fail_current_contract(error);
return;
}
#[cfg(feature = "multi-viewport")]
let window = crate::multi_viewport::window_for_viewport(ctx, viewport)
.or_else(|| control.attached_window().ok());
#[cfg(not(feature = "multi-viewport"))]
let window = control.attached_window().ok();
let Some(window) = window else {
return;
};
let ime: &ImGuiPlatformImeData = &*data;
let vp: &ImGuiViewport = &*viewport;
if !ime.WantVisible && !ime.WantTextInput {
return;
}
let line_height = if ime.InputLineHeight > 0.0 {
ime.InputLineHeight
} else {
16.0_f32
};
#[cfg(feature = "multi-viewport")]
let area = if control.has_live_runtime() {
crate::multi_viewport::ime_cursor_area_for_viewport(
&window,
[ime.InputPos.x, ime.InputPos.y],
[vp.Pos.x, vp.Pos.y],
line_height,
)
} else {
Some((
LogicalPosition::new(
f64::from(ime.InputPos.x - vp.Pos.x),
f64::from(ime.InputPos.y - vp.Pos.y),
),
LogicalSize::new(f64::from(line_height), f64::from(line_height)),
))
};
#[cfg(not(feature = "multi-viewport"))]
let area = Some((
LogicalPosition::new(
f64::from(ime.InputPos.x - vp.Pos.x),
f64::from(ime.InputPos.y - vp.Pos.y),
),
LogicalSize::new(f64::from(line_height), f64::from(line_height)),
));
if let Some((position, size)) = area {
window.set_ime_cursor_area(position, size);
}
});
}));
if res.is_err()
&& let Some(control) = platform_control_for_context(ctx)
{
let _ = control.binding.try_with_bound_context(|| {
control.fail_current_contract(WinitPlatformError::CallbackPanicked {
callback: "Platform_SetImeDataFn",
});
});
}
}
pub(super) fn ime_callback_eq(
left: Option<SetImeDataCallback>,
right: Option<SetImeDataCallback>,
) -> bool {
match (left, right) {
(Some(left), Some(right)) => std::ptr::fn_addr_eq(left, right),
(None, None) => true,
_ => false,
}
}
impl WinitPlatformControl {
pub(crate) fn attached_window(&self) -> Result<Arc<Window>, WinitPlatformError> {
self.attached_window
.borrow()
.clone()
.ok_or(WinitPlatformError::WindowNotAttached)
}
pub(crate) fn set_ime_allowed_for_owned_windows(&self, allowed: bool) {
self.ime_allowed.set(allowed);
if let Some(window) = self.attached_window.borrow().as_ref() {
window.set_ime_allowed(allowed);
}
#[cfg(feature = "multi-viewport")]
if let Some(runtime) = self.runtime.borrow().as_ref() {
runtime.set_ime_allowed(allowed);
}
}
#[cfg(feature = "multi-viewport")]
#[cfg(feature = "multi-viewport")]
pub(crate) fn set_cursor_settings(&self, settings: Option<CursorSettings>) {
self.cursor_settings.set(settings);
let Some(settings) = settings else {
return;
};
if let Some(window) = self.attached_window.borrow().as_ref() {
settings.apply(window);
}
if let Some(runtime) = self.runtime.borrow().as_ref() {
runtime.apply_cursor_settings(settings);
}
}
#[cfg(feature = "multi-viewport")]
pub(crate) fn apply_current_window_state(&self, window: &Window) {
window.set_ime_allowed(self.ime_allowed.get());
if let Some(settings) = self.cursor_settings.get() {
settings.apply(window);
}
}
fn ensure_window(&self, window: &Window) -> Result<Arc<Window>, WinitPlatformError> {
let attached = self.attached_window()?;
if std::ptr::eq(Arc::as_ptr(&attached), window) {
Ok(attached)
} else {
Err(WinitPlatformError::WindowMismatch)
}
}
pub(super) fn validate_entry(
&self,
context: &Context,
window: &Window,
) -> Result<(), WinitPlatformError> {
self.ensure_context(context)?;
self.ensure_window(window)?;
self.validate_operational_contract()
}
pub(super) fn validate_window_entry(&self, window: &Window) -> Result<(), WinitPlatformError> {
self.ensure_window(window)?;
self.validate_operational_contract()
}
pub(super) fn attach_window(
&self,
context: &mut Context,
window: Arc<Window>,
) -> Result<(), WinitPlatformError> {
self.ensure_context(context)?;
self.validate_operational_contract()?;
self.binding.try_with_bound_context(|| {
if let Some(existing) = self.attached_window.borrow().as_ref()
&& !Arc::ptr_eq(existing, &window)
{
return Err(WinitPlatformError::WindowMismatch);
}
unsafe {
let platform_io = dear_imgui_rs::sys::igGetPlatformIO_Nil();
(*platform_io).Platform_SetImeDataFn = Some(imgui_winit_set_ime_data);
(*platform_io).Platform_ImeUserData = Arc::as_ptr(&window).cast_mut().cast();
}
self.attached_window.borrow_mut().replace(window);
Ok(())
})?
}
pub(super) fn detach_window(
&self,
context: &mut Context,
window: &Arc<Window>,
) -> Result<(), WinitPlatformError> {
self.ensure_context(context)?;
self.validate_operational_contract()?;
self.binding.try_with_bound_context(|| {
let attached = self.attached_window()?;
if !Arc::ptr_eq(&attached, window) {
return Err(WinitPlatformError::WindowMismatch);
}
self.release_single_touch_in_current_context();
unsafe {
let platform_io = dear_imgui_rs::sys::igGetPlatformIO_Nil();
(*platform_io).Platform_SetImeDataFn = self.baseline_ime_callback.get();
(*platform_io).Platform_ImeUserData = self.baseline_ime_user_data.get();
}
self.attached_window.borrow_mut().take();
Ok(())
})?
}
}
impl WinitPlatform {
pub fn set_ime_allowed(&mut self, allowed: bool) -> Result<(), WinitPlatformError> {
let window = self.control.attached_window()?;
self.control.validate_window_entry(&window)?;
self.control.set_ime_allowed_for_owned_windows(allowed);
self.ime_enabled = allowed;
Ok(())
}
pub fn ime_enabled(&self) -> bool {
self.ime_enabled
}
pub fn set_ime_auto_management(&mut self, enabled: bool) {
self.ime_auto_manage = enabled;
}
pub fn set_software_cursor_enabled(
&mut self,
imgui_ctx: &mut Context,
enabled: bool,
) -> Result<(), WinitPlatformError> {
self.control.ensure_context(imgui_ctx)?;
self.control.validate_operational_contract()?;
imgui_ctx.io_mut().set_mouse_draw_cursor(enabled);
self.cursor_cache = None;
Ok(())
}
pub fn prepare_render(
&mut self,
ui: &dear_imgui_rs::Ui,
window: &Window,
) -> Result<(), WinitPlatformError> {
if ui.context_id() != self.control.binding.id() {
return Err(WinitPlatformError::ContextMismatch);
}
self.control.validate_window_entry(window)?;
if self.ime_auto_manage {
let want_text = ui.io().want_text_input();
if want_text && !self.ime_enabled {
self.control.set_ime_allowed_for_owned_windows(true);
self.ime_enabled = true;
} else if !want_text && self.ime_enabled {
self.control.set_ime_allowed_for_owned_windows(false);
self.ime_enabled = false;
}
}
if !ui
.io()
.config_flags()
.contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
{
let cursor = CursorSettings {
cursor: ui.mouse_cursor(),
draw_cursor: ui.io().mouse_draw_cursor(),
};
if self.cursor_cache != Some(cursor) {
#[cfg(feature = "multi-viewport")]
self.control.set_cursor_settings(Some(cursor));
#[cfg(not(feature = "multi-viewport"))]
cursor.apply(window);
self.cursor_cache = Some(cursor);
}
} else {
self.cursor_cache = None;
#[cfg(feature = "multi-viewport")]
self.control.set_cursor_settings(None);
}
Ok(())
}
}