use instant::Instant;
use dear_imgui_rs::{BackendFlags, ConfigFlags, Context};
use winit::dpi::{LogicalPosition, LogicalSize};
use winit::event::{Event, WindowEvent};
use winit::window::{Window, WindowAttributes};
use crate::cursor::CursorSettings;
use crate::events;
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum HiDpiMode {
#[default]
Default,
Locked(f64),
Rounded,
}
pub struct WinitPlatform {
hidpi_mode: HiDpiMode,
hidpi_factor: f64,
cursor_cache: Option<CursorSettings>,
#[allow(dead_code)]
ime_enabled: bool,
last_frame: Instant,
}
impl WinitPlatform {
pub fn new(imgui_ctx: &mut Context) -> Self {
let _ = imgui_ctx.set_platform_name(Some(format!(
"dear-imgui-winit {}",
env!("CARGO_PKG_VERSION")
)));
let io = imgui_ctx.io_mut();
let mut backend_flags = io.backend_flags();
backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS | BackendFlags::HAS_SET_MOUSE_POS);
#[cfg(feature = "multi-viewport")]
{
let mut config_flags = io.config_flags();
config_flags.insert(dear_imgui_rs::ConfigFlags::VIEWPORTS_ENABLE);
io.set_config_flags(config_flags);
backend_flags.insert(BackendFlags::PLATFORM_HAS_VIEWPORTS);
backend_flags.remove(BackendFlags::HAS_SET_MOUSE_POS);
}
io.set_backend_flags(backend_flags);
Self {
hidpi_mode: HiDpiMode::default(),
hidpi_factor: 1.0,
cursor_cache: None,
ime_enabled: false,
last_frame: Instant::now(),
}
}
pub fn set_hidpi_mode(&mut self, hidpi_mode: HiDpiMode) {
self.hidpi_mode = hidpi_mode;
}
pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
pub fn attach_window(
&mut self,
window: &Window,
hidpi_mode: HiDpiMode,
imgui_ctx: &mut Context,
) {
self.hidpi_mode = hidpi_mode;
self.hidpi_factor = match hidpi_mode {
HiDpiMode::Default => window.scale_factor(),
HiDpiMode::Locked(factor) => factor,
HiDpiMode::Rounded => window.scale_factor().round(),
};
let logical_size = window.inner_size().to_logical(window.scale_factor());
let logical_size = self.scale_size_from_winit(window, logical_size);
let io = imgui_ctx.io_mut();
io.set_display_size([logical_size.width as f32, logical_size.height as f32]);
io.set_display_framebuffer_scale([self.hidpi_factor as f32, self.hidpi_factor as f32]);
}
pub fn handle_event<T>(
&mut self,
imgui_ctx: &mut Context,
window: &Window,
event: &Event<T>,
) -> bool {
match event {
Event::WindowEvent { event, .. } => self.handle_window_event(imgui_ctx, window, event),
Event::DeviceEvent { event, .. } => {
events::handle_device_event(event);
false
}
_ => false,
}
}
fn handle_window_event(
&mut self,
imgui_ctx: &mut Context,
window: &Window,
event: &WindowEvent,
) -> bool {
match event {
WindowEvent::Resized(physical_size) => {
let logical_size = physical_size.to_logical(window.scale_factor());
let logical_size = self.scale_size_from_winit(window, logical_size);
imgui_ctx
.io_mut()
.set_display_size([logical_size.width as f32, logical_size.height as f32]);
false
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let new_hidpi = match self.hidpi_mode {
HiDpiMode::Default => *scale_factor,
HiDpiMode::Locked(factor) => factor,
HiDpiMode::Rounded => scale_factor.round(),
};
{
let io = imgui_ctx.io_mut();
let mouse = io.mouse_pos();
if mouse[0].is_finite() && mouse[1].is_finite() && self.hidpi_factor > 0.0 {
let scale = (new_hidpi / self.hidpi_factor) as f32;
io.set_mouse_pos([mouse[0] * scale, mouse[1] * scale]);
}
}
self.hidpi_factor = new_hidpi;
let logical_size = window.inner_size().to_logical(window.scale_factor());
let logical_size = self.scale_size_from_winit(window, logical_size);
let io = imgui_ctx.io_mut();
io.set_display_size([logical_size.width as f32, logical_size.height as f32]);
io.set_display_framebuffer_scale([
self.hidpi_factor as f32,
self.hidpi_factor as f32,
]);
false
}
WindowEvent::KeyboardInput { event, .. } => {
events::handle_keyboard_input(event, imgui_ctx)
}
WindowEvent::CursorMoved { position, .. } => {
#[cfg(feature = "multi-viewport")]
{
if imgui_ctx
.io()
.config_flags()
.contains(dear_imgui_rs::ConfigFlags::VIEWPORTS_ENABLE)
{
if let Ok(base) = window.inner_position() {
let sx = base.x as f64 + position.x;
let sy = base.y as f64 + position.y;
return events::handle_cursor_moved([sx, sy], imgui_ctx);
}
}
}
let position = position.to_logical(window.scale_factor());
let position = self.scale_pos_from_winit(window, position);
events::handle_cursor_moved([position.x, position.y], imgui_ctx)
}
WindowEvent::MouseInput { button, state, .. } => {
events::handle_mouse_button(*button, *state, imgui_ctx)
}
WindowEvent::MouseWheel { delta, .. } => events::handle_mouse_wheel(*delta, imgui_ctx),
WindowEvent::CursorLeft { .. } => {
imgui_ctx
.io_mut()
.add_mouse_pos_event([-f32::MAX, -f32::MAX]);
false
}
WindowEvent::ModifiersChanged(modifiers) => {
events::handle_modifiers_changed(modifiers, imgui_ctx);
false
}
WindowEvent::Ime(ime) => {
events::handle_ime_event(ime, imgui_ctx);
imgui_ctx.io().want_capture_keyboard()
}
WindowEvent::Touch(touch) => {
events::handle_touch_event(touch, window, imgui_ctx);
imgui_ctx.io().want_capture_mouse()
}
WindowEvent::Focused(focused) => events::handle_focused(*focused, imgui_ctx),
_ => false,
}
}
pub fn prepare_render(&mut self, imgui_ctx: &mut Context, window: &Window) {
let now = Instant::now();
let delta = now - self.last_frame;
let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0;
self.last_frame = now;
imgui_ctx.io_mut().set_delta_time(delta_s);
if imgui_ctx.io().want_set_mouse_pos()
&& !imgui_ctx
.io()
.config_flags()
.contains(ConfigFlags::VIEWPORTS_ENABLE)
{
let pos = imgui_ctx.io().mouse_pos();
let logical_pos = self
.scale_pos_for_winit(window, LogicalPosition::new(pos[0] as f64, pos[1] as f64));
let _ = window.set_cursor_position(logical_pos);
}
}
pub fn prepare_frame(&mut self, window: &Window, imgui_ctx: &mut Context) {
self.prepare_render(imgui_ctx, window);
}
pub fn set_software_cursor_enabled(&mut self, imgui_ctx: &mut Context, enabled: bool) {
imgui_ctx.io_mut().set_mouse_draw_cursor(enabled);
self.cursor_cache = None;
}
pub fn prepare_render_with_ui(&mut self, ui: &dear_imgui_rs::Ui, window: &Window) {
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) {
cursor.apply(window);
self.cursor_cache = Some(cursor);
}
}
}
pub fn scale_size_from_winit(
&self,
window: &Window,
logical_size: LogicalSize<f64>,
) -> LogicalSize<f64> {
match self.hidpi_mode {
HiDpiMode::Default => logical_size,
_ => logical_size
.to_physical::<f64>(window.scale_factor())
.to_logical(self.hidpi_factor),
}
}
pub fn scale_pos_from_winit(
&self,
window: &Window,
logical_pos: LogicalPosition<f64>,
) -> LogicalPosition<f64> {
match self.hidpi_mode {
HiDpiMode::Default => logical_pos,
_ => logical_pos
.to_physical::<f64>(window.scale_factor())
.to_logical(self.hidpi_factor),
}
}
pub fn scale_pos_for_winit(
&self,
window: &Window,
logical_pos: LogicalPosition<f64>,
) -> LogicalPosition<f64> {
match self.hidpi_mode {
HiDpiMode::Default => logical_pos,
_ => logical_pos
.to_physical::<f64>(self.hidpi_factor)
.to_logical(window.scale_factor()),
}
}
pub fn create_window_attributes() -> WindowAttributes {
WindowAttributes::default()
.with_title("Dear ImGui Window")
.with_inner_size(LogicalSize::new(1024.0, 768.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::test_sync::lock_context;
#[test]
fn test_hidpi_mode_default() {
assert_eq!(HiDpiMode::default(), HiDpiMode::Default);
}
#[test]
fn test_platform_creation() {
let _guard = lock_context();
let mut ctx = Context::create();
let platform = WinitPlatform::new(&mut ctx);
assert_eq!(platform.hidpi_mode, HiDpiMode::Default);
assert_eq!(platform.hidpi_factor, 1.0);
assert_eq!(platform.cursor_cache, None);
assert!(!platform.ime_enabled);
}
#[test]
fn test_hidpi_mode_setting() {
let _guard = lock_context();
let mut ctx = Context::create();
let mut platform = WinitPlatform::new(&mut ctx);
platform.set_hidpi_mode(HiDpiMode::Locked(2.0));
assert_eq!(platform.hidpi_mode, HiDpiMode::Locked(2.0));
platform.set_hidpi_mode(HiDpiMode::Rounded);
assert_eq!(platform.hidpi_mode, HiDpiMode::Rounded);
}
#[test]
fn test_window_attributes_creation() {
let attrs = WinitPlatform::create_window_attributes();
let _ = attrs;
}
}