diff --git a/Cargo.toml b/Cargo.toml index d3185a6..ecf55d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ typed-arena = "2.0.1" serde = { version = "1", features = ["derive"], optional = true } log = "0.4" png = "0.16" -winit = "0.24.0" +#winit = { version = "0.24.0"} rand = { version = "0.7.2", features = ["wasm-bindgen"] } bytemuck = "1" noise = "0.6" @@ -32,4 +32,13 @@ legion = "0.3.1" wgpu = "0.7.0" imgui = "0.7.0" imgui-wgpu = "0.14.0" -imgui-winit-support = "0.7.0" \ No newline at end of file + +winit-19 = { version = ">= 0.16, < 0.20", package = "winit", optional = true } +winit-20 = { version = ">= 0.20, < 0.22", package = "winit", optional = true } +winit-22 = { version = "0.22", package = "winit", optional = true } +winit-23 = { version = "0.23", package = "winit", optional = true } +winit-24 = { version = "0.24", package = "winit", optional = true } + +[features] +default = ["winit-24"] +debug = ["winit-24"] \ No newline at end of file diff --git a/src/camera.rs b/src/camera.rs index 013c050..599fa1e 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -2,8 +2,8 @@ use std::f32::consts::{FRAC_PI_2, PI}; use std::time::{Duration, Instant}; use cgmath::{Decomposed, InnerSpace, Matrix4, Point3, Rad, Vector3}; -use winit::dpi::{LogicalPosition, PhysicalPosition}; -use winit::event::{ElementState, MouseScrollDelta, VirtualKeyCode}; +use winit_24::dpi::{LogicalPosition, PhysicalPosition}; +use winit_24::event::{ElementState, MouseScrollDelta, VirtualKeyCode}; use crate::render::OPENGL_TO_WGPU_MATRIX; diff --git a/src/extended_winit_imgui_support.rs b/src/extended_winit_imgui_support.rs new file mode 100644 index 0000000..7110af2 --- /dev/null +++ b/src/extended_winit_imgui_support.rs @@ -0,0 +1,1165 @@ +//! This crate provides a winit-based backend platform for imgui-rs. +//! +//! A backend platform handles window/input device events and manages their +//! state. +//! +//! # Using the library +//! +//! There are five things you need to do to use this library correctly: +//! +//! 1. Initialize a `WinitPlatform` instance +//! 2. Attach it to a winit `Window` +//! 3. Pass events to the platform (every frame) +//! 4. Call frame preparation callback (every frame) +//! 5. Call render preparation callback (every frame) +//! +//! ## Complete example for winit 0.20+ (without a renderer) +//! +//! ```rust,no_run,ignore +//! # // TODO: Remove ignore when only one winit version is used +//! use imgui::Context; +//! use imgui_winit_support::{HiDpiMode, WinitPlatform}; +//! use std::time::Instant; +//! use winit::event::{Event, WindowEvent}; +//! use winit::event_loop::{ControlFlow, EventLoop}; +//! use winit::window::{Window}; +//! +//! let mut event_loop = EventLoop::new(); +//! let mut window = Window::new(&event_loop).unwrap(); +//! +//! let mut imgui = Context::create(); +//! // configure imgui-rs Context if necessary +//! +//! let mut platform = WinitPlatform::init(&mut imgui); // step 1 +//! platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Default); // step 2 +//! +//! let mut last_frame = Instant::now(); +//! let mut run = true; +//! event_loop.run(move |event, _, control_flow| { +//! match event { +//! Event::NewEvents(_) => { +//! // other application-specific logic +//! last_frame = imgui.io_mut().update_delta_time(last_frame); +//! }, +//! Event::MainEventsCleared => { +//! // other application-specific logic +//! platform.prepare_frame(imgui.io_mut(), &window) // step 4 +//! .expect("Failed to prepare frame"); +//! window.request_redraw(); +//! } +//! Event::RedrawRequested(_) => { +//! let ui = imgui.frame(); +//! // application-specific rendering *under the UI* +//! +//! // construct the UI +//! +//! platform.prepare_render(&ui, &window); // step 5 +//! // render the UI with a renderer +//! let draw_data = ui.render(); +//! // renderer.render(..., draw_data).expect("UI rendering failed"); +//! +//! // application-specific rendering *over the UI* +//! }, +//! Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { +//! *control_flow = ControlFlow::Exit; +//! } +//! // other application-specific event handling +//! event => { +//! platform.handle_event(imgui.io_mut(), &window, &event); // step 3 +//! // other application-specific event handling +//! } +//! } +//! }) +//! ``` +//! +//! ## `winit` versions and features. +//! +//! This crate has several features which control the version of winit which is +//! used. +//! +//! The following versions are supported, controlled by the listed feature. +//! +//! - The `winit-24` feature supports winit versions `0.24`. This is +//! on by default, so to use any other version you need to disable this crates +//! default features. +//! - The `winit-23` feature uses winit versions compatible with `0.23`. +//! - The `winit-22` feature uses winit versions compatible with `0.22`. +//! - The `winit-20` feature should support winit either `0.20` or winit `0.21`. +//! - The `winit-19` feature should support winits older than `0.19` (possibly +//! back to winit 0.16.*, but this isn't regularly tested and may not work). +//! +//! If multiple `winit-*` features are enabled, and it is a debug build (as +//! determined by `debug_assertions`), we will log a warning to stderr during +//! init. This can be disabled by either turning on the `no-warn-on-multiple` +//! feature, fixing the configuration, or disabling `debug_assertions`. +//! +//! Conversely, if no `winit-*` features are enabled, we will fail to compile. +//! This is not an issue generally, as by default we turn on `winit-24`. +//! +//! All of this is in attempt to preserve the additive nature of features (while +//! still helping users notice project configuration issues), however it's done +//! fairly weakly as our this crate's API isn't 100% identical across winit +//! versions. +//! +//! ### Using an older `winit` version +//! +//! To use an older version, you must configure `default-features = false` in +//! your `Cargo.toml`: +//! +//! ```toml +//! [dependencies.imgui-winit-support] +//! version = "0.6" +//! features = ["winit-$YOUR_VERSION_HERE"] +//! default-features = false +//! ``` +//! +//! ### Old `winit` compatibility +//! +//! No guarantee is made on how long this crate will support legacy versions of +//! `winit`, but we'll try to follow these rules: +//! +//! - Versions which are still in widespread use in the ecosystem will be +//! supported while that is true (for example, 0.19 at the time of writing is +//! quite old, but used by the most recent version of several popular crates). +//! +//! - Versions which are not a significant maintenance burden will be supported +//! (for example, supporting versions older than winit 0.19 given that we +//! support 0.19). +//! +//! - Explicitly removing support for a feature-indicated version will be +//! considered a breaking change. +//! +//! - Changing the default feature to the new latest `winit` version is *not* a +//! breaking change. + +#[cfg(feature = "winit-24")] +use winit_24 as winit; + +#[cfg(all(not(feature = "winit-24"), feature = "winit-23"))] +use winit_23 as winit; + +#[cfg(all( +not(any(feature = "winit-24", feature = "winit-23")), +feature = "winit-22", +))] +use winit_22 as winit; + +#[cfg(all( +not(any(feature = "winit-24", feature = "winit-23", feature = "winit-22")), +feature = "winit-20", +))] +use winit_20 as winit; + +#[cfg(all( +not(any( +feature = "winit-24", +feature = "winit-23", +feature = "winit-22", +feature = "winit-20" +)), +feature = "winit-19", +))] +use winit_19 as winit; + +use imgui::{self, BackendFlags, ConfigFlags, Context, ImString, Io, Key, Ui}; +use std::cell::Cell; +use std::cmp::Ordering; +use winit::dpi::{LogicalPosition, LogicalSize}; + +#[cfg(all( +not(any( +feature = "winit-24", +feature = "winit-23", +feature = "winit-22", +feature = "winit-20" +)), +feature = "winit-19", +))] +use winit::{ + DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseCursor, MouseScrollDelta, + TouchPhase, VirtualKeyCode, Window, WindowEvent, +}; + +#[cfg(any( +feature = "winit-20", +feature = "winit-22", +feature = "winit-23", +feature = "winit-24" +))] +use winit::{ + error::ExternalError, + event::{ + DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase, + VirtualKeyCode, WindowEvent, + }, + window::{CursorIcon as MouseCursor, Window}, +}; +use crate::owned_event::{OwnedEvent, OwnedWindowEvent}; + +// Ensure at least one is enabled +#[cfg(not(any( +feature = "winit-19", +feature = "winit-20", +feature = "winit-22", +feature = "winit-23", +feature = "winit-24", +)))] +compile_error!("Please enable at least one version of `winit` (see documentation for details)."); + +// FIXME(thom): make updading winit and adding a new feature less of a hassle here. +fn check_multiple_winits() { + use std::io::Write as _; + use std::sync::atomic::{AtomicBool, Ordering}; + // bail out for release builds or if we've been explicitly disabled. + if cfg!(any(not(debug_assertions), feature = "no-warn-on-multiple")) { + return; + } + let winits_enabled = cfg!(feature = "winit-24") as usize + + cfg!(feature = "winit-23") as usize + + cfg!(feature = "winit-22") as usize + + cfg!(feature = "winit-20") as usize + + cfg!(feature = "winit-19") as usize; + + // Only complain once even if we're called multiple times. + static COMPLAINED: AtomicBool = AtomicBool::new(false); + // Note that the `Ordering` basically doesn't matter here, but even if it + // did, `Relaxed` is still correct because we're only interested in the + // effects on a single atomic variable. + if winits_enabled <= 1 + || COMPLAINED + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_err() + { + return; + } + let mut err = Vec::with_capacity(512); + + // Log the complaint into a buffer first — in practice this is enough to + // ensure atomicity. + let _ = writeln!( + err, + "Warning (imgui-winit-support): More than one `winit-*` version feature is enabled \ + (this likely indicates misconfiguration, see documentation for details)." + ); + let feats = [ + ("winit-24", cfg!(feature = "winit-24"), " (default)"), + ("winit-23", cfg!(feature = "winit-23"), ""), + ("winit-22", cfg!(feature = "winit-22"), ""), + ("winit-20", cfg!(feature = "winit-20"), ""), + ("winit-19", cfg!(feature = "winit-19"), ""), + ]; + for &(name, enabled, extra) in &feats { + if enabled { + let _ = writeln!(err, " `feature = {:?}` is enabled{}", name, extra); + } + } + if cfg!(feature = "winit-24") && winits_enabled == 2 { + let _ = writeln!( + err, + " Perhaps you are missing a `default-features = false`?", + ); + } + let _ = writeln!( + err, + " (Note: This warning is only present in debug builds, and \ + can be disabled using the \"no-warn-on-multiple\" feature)" + ); + let _ = std::io::stderr().write_all(&err); +} + +/// State of a single mouse button. Used so that we can detect cases where mouse +/// press and release occur on the same frame (seems surprisingly frequent on +/// macOS now...) +#[derive(Debug, Clone, Default)] +struct Button { + pressed_this_frame: Cell, + state: Cell, +} + +impl Button { + // we can use this in an array initializer, unlike `Default::default()` or a + // `const fn new()`. + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Button = Self { + pressed_this_frame: Cell::new(false), + state: Cell::new(false), + }; + fn set(&self, pressed: bool) { + self.state.set(pressed); + if pressed { + self.pressed_this_frame.set(true); + } + } + fn get(&self) -> bool { + // If we got a press this frame, record it even if we got a release + // too — this way we don't drop mouse clicks where the release comes + // in on the same frame as the press. (This mirrors what Dear ImGUI + // seems to do in the `imgui_impl_*`) + self.pressed_this_frame.replace(false) || self.state.get() + } +} + +/// winit backend platform state +#[derive(Debug)] +pub struct WinitPlatform { + hidpi_mode: ActiveHiDpiMode, + hidpi_factor: f64, + cursor_cache: Option, + mouse_buttons: [Button; 5], +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +struct CursorSettings { + cursor: Option, + draw_cursor: bool, +} + +fn to_winit_cursor(cursor: imgui::MouseCursor) -> MouseCursor { + match cursor { + imgui::MouseCursor::Arrow => MouseCursor::Default, + imgui::MouseCursor::TextInput => MouseCursor::Text, + imgui::MouseCursor::ResizeAll => MouseCursor::Move, + imgui::MouseCursor::ResizeNS => MouseCursor::NsResize, + imgui::MouseCursor::ResizeEW => MouseCursor::EwResize, + imgui::MouseCursor::ResizeNESW => MouseCursor::NeswResize, + imgui::MouseCursor::ResizeNWSE => MouseCursor::NwseResize, + imgui::MouseCursor::Hand => MouseCursor::Hand, + imgui::MouseCursor::NotAllowed => MouseCursor::NotAllowed, + } +} + +impl CursorSettings { + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + fn apply(&self, window: &Window) { + match self.cursor { + Some(mouse_cursor) if !self.draw_cursor => { + window.hide_cursor(false); + window.set_cursor(to_winit_cursor(mouse_cursor)); + } + _ => window.hide_cursor(true), + } + } + #[cfg(any( + feature = "winit-20", + feature = "winit-22", + feature = "winit-23", + feature = "winit-24" + ))] + fn apply(&self, window: &Window) { + match self.cursor { + Some(mouse_cursor) if !self.draw_cursor => { + window.set_cursor_visible(true); + window.set_cursor_icon(to_winit_cursor(mouse_cursor)); + } + _ => window.set_cursor_visible(false), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +enum ActiveHiDpiMode { + Default, + Rounded, + Locked, +} + +/// DPI factor handling mode. +/// +/// Applications that use imgui-rs might want to customize the used DPI factor and not use +/// directly the value coming from winit. +/// +/// **Note: if you use a mode other than default and the DPI factor is adjusted, winit and imgui-rs +/// will use different logical coordinates, so be careful if you pass around logical size or +/// position values.** +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum HiDpiMode { + /// The DPI factor from winit is used directly without adjustment + Default, + /// The DPI factor from winit is rounded to an integer value. + /// + /// This prevents the user interface from becoming blurry with non-integer scaling. + Rounded, + /// The DPI factor from winit is ignored, and the included value is used instead. + /// + /// This is useful if you want to force some DPI factor (e.g. 1.0) and not care about the value + /// coming from winit. + Locked(f64), +} + +impl HiDpiMode { + fn apply(&self, hidpi_factor: f64) -> (ActiveHiDpiMode, f64) { + match *self { + HiDpiMode::Default => (ActiveHiDpiMode::Default, hidpi_factor), + HiDpiMode::Rounded => (ActiveHiDpiMode::Rounded, hidpi_factor.round()), + HiDpiMode::Locked(value) => (ActiveHiDpiMode::Locked, value), + } + } +} + +impl WinitPlatform { + /// Initializes a winit platform instance and configures imgui. + /// + /// This function configures imgui-rs in the following ways: + /// + /// * backend flags are updated + /// * keys are configured + /// * platform name is set + pub fn init(imgui: &mut Context) -> WinitPlatform { + // noop in non-debug builds, if disabled, or if called a second time. + check_multiple_winits(); + let io = imgui.io_mut(); + io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS); + io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS); + io[Key::Tab] = VirtualKeyCode::Tab as _; + io[Key::LeftArrow] = VirtualKeyCode::Left as _; + io[Key::RightArrow] = VirtualKeyCode::Right as _; + io[Key::UpArrow] = VirtualKeyCode::Up as _; + io[Key::DownArrow] = VirtualKeyCode::Down as _; + io[Key::PageUp] = VirtualKeyCode::PageUp as _; + io[Key::PageDown] = VirtualKeyCode::PageDown as _; + io[Key::Home] = VirtualKeyCode::Home as _; + io[Key::End] = VirtualKeyCode::End as _; + io[Key::Insert] = VirtualKeyCode::Insert as _; + io[Key::Delete] = VirtualKeyCode::Delete as _; + io[Key::Backspace] = VirtualKeyCode::Back as _; + io[Key::Space] = VirtualKeyCode::Space as _; + io[Key::Enter] = VirtualKeyCode::Return as _; + io[Key::Escape] = VirtualKeyCode::Escape as _; + io[Key::KeyPadEnter] = VirtualKeyCode::NumpadEnter as _; + io[Key::A] = VirtualKeyCode::A as _; + io[Key::C] = VirtualKeyCode::C as _; + io[Key::V] = VirtualKeyCode::V as _; + io[Key::X] = VirtualKeyCode::X as _; + io[Key::Y] = VirtualKeyCode::Y as _; + io[Key::Z] = VirtualKeyCode::Z as _; + imgui.set_platform_name(Some(ImString::from(format!( + "imgui-winit-support {}", + env!("CARGO_PKG_VERSION") + )))); + WinitPlatform { + hidpi_mode: ActiveHiDpiMode::Default, + hidpi_factor: 1.0, + cursor_cache: None, + mouse_buttons: [Button::INIT; 5], + } + } + /// Attaches the platform instance to a winit window. + /// + /// This function configures imgui-rs in the following ways: + /// + /// * framebuffer scale (= DPI factor) is set + /// * display size is set + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + pub fn attach_window(&mut self, io: &mut Io, window: &Window, hidpi_mode: HiDpiMode) { + let (hidpi_mode, hidpi_factor) = hidpi_mode.apply(window.get_hidpi_factor()); + self.hidpi_mode = hidpi_mode; + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + if let Some(logical_size) = window.get_inner_size() { + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + } + /// Attaches the platform instance to a winit window. + /// + /// This function configures imgui-rs in the following ways: + /// + /// * framebuffer scale (= DPI factor) is set + /// * display size is set + #[cfg(any( + feature = "winit-20", + feature = "winit-22", + feature = "winit-23", + feature = "winit-24" + ))] + pub fn attach_window(&mut self, io: &mut Io, window: &Window, hidpi_mode: HiDpiMode) { + let (hidpi_mode, hidpi_factor) = hidpi_mode.apply(window.scale_factor()); + self.hidpi_mode = hidpi_mode; + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + let logical_size = window.inner_size().to_logical(hidpi_factor); + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + /// Returns the current DPI factor. + /// + /// The value might not be the same as the winit DPI factor (depends on the used DPI mode) + pub fn hidpi_factor(&self) -> f64 { + self.hidpi_factor + } + /// Scales a logical size coming from winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + pub fn scale_size_from_winit(&self, window: &Window, logical_size: LogicalSize) -> LogicalSize { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_size, + _ => logical_size + .to_physical(window.get_hidpi_factor()) + .to_logical(self.hidpi_factor), + } + } + /// Scales a logical size coming from winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + #[cfg(any( + feature = "winit-20", + feature = "winit-22", + feature = "winit-23", + feature = "winit-24" + ))] + pub fn scale_size_from_winit( + &self, + window: &Window, + logical_size: LogicalSize, + ) -> LogicalSize { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_size, + _ => logical_size + .to_physical::(window.scale_factor()) + .to_logical(self.hidpi_factor), + } + } + /// Scales a logical position coming from winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + pub fn scale_pos_from_winit( + &self, + window: &Window, + logical_pos: LogicalPosition, + ) -> LogicalPosition { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_pos, + _ => logical_pos + .to_physical(window.get_hidpi_factor()) + .to_logical(self.hidpi_factor), + } + } + /// Scales a logical position coming from winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + #[cfg(any( + feature = "winit-20", + feature = "winit-22", + feature = "winit-23", + feature = "winit-24" + ))] + pub fn scale_pos_from_winit( + &self, + window: &Window, + logical_pos: LogicalPosition, + ) -> LogicalPosition { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_pos, + _ => logical_pos + .to_physical::(window.scale_factor()) + .to_logical(self.hidpi_factor), + } + } + /// Scales a logical position for winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + pub fn scale_pos_for_winit( + &self, + window: &Window, + logical_pos: LogicalPosition, + ) -> LogicalPosition { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_pos, + _ => logical_pos + .to_physical(self.hidpi_factor) + .to_logical(window.get_hidpi_factor()), + } + } + /// Scales a logical position for winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + #[cfg(any( + feature = "winit-20", + feature = "winit-22", + feature = "winit-23", + feature = "winit-24" + ))] + pub fn scale_pos_for_winit( + &self, + window: &Window, + logical_pos: LogicalPosition, + ) -> LogicalPosition { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_pos, + _ => logical_pos + .to_physical::(self.hidpi_factor) + .to_logical(window.scale_factor()), + } + } + /// Handles a winit event. + /// + /// This function performs the following actions (depends on the event): + /// + /// * window size / dpi factor changes are applied + /// * keyboard state is updated + /// * mouse state is updated + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + pub fn handle_event(&mut self, io: &mut Io, window: &Window, event: &Event) { + match *event { + Event::WindowEvent { + window_id, + ref event, + } if window_id == window.id() => { + self.handle_window_event(io, window, event); + } + // Track key release events outside our window. If we don't do this, + // we might never see the release event if some other window gets focus. + Event::DeviceEvent { + event: + DeviceEvent::Key(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + .. + }), + .. + } => { + io.keys_down[key as usize] = false; + match key { + VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = false, + VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = false, + VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = false, + VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = false, + _ => (), + } + } + _ => (), + } + } + /// Handles a winit event. + /// + /// This function performs the following actions (depends on the event): + /// + /// * window size / dpi factor changes are applied + /// * keyboard state is updated + /// * mouse state is updated + #[cfg(all( + not(any(feature = "winit-24", feature = "winit-23", feature = "winit-22")), + feature = "winit-20", + ))] + pub fn handle_event(&mut self, io: &mut Io, window: &Window, event: &Event) { + match *event { + Event::WindowEvent { + window_id, + ref event, + } if window_id == window.id() => { + self.handle_window_event(io, window, event); + } + // Track key release events outside our window. If we don't do this, + // we might never see the release event if some other window gets focus. + Event::DeviceEvent { + event: + DeviceEvent::Key(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + .. + }), + .. + } => { + io.keys_down[key as usize] = false; + } + + // We need to track modifiers separately because some system like macOS, will + // not reliably send modifier states during certain events like ScreenCapture. + // Gotta let the people show off their pretty imgui widgets! + Event::DeviceEvent { + event: DeviceEvent::ModifiersChanged(modifiers), + .. + } => { + io.key_shift = modifiers.shift(); + io.key_ctrl = modifiers.ctrl(); + io.key_alt = modifiers.alt(); + io.key_super = modifiers.logo(); + } + _ => (), + } + } + /// Handles a winit event. + /// + /// This function performs the following actions (depends on the event): + /// + /// * window size / dpi factor changes are applied + /// * keyboard state is updated + /// * mouse state is updated + //#[cfg(any(feature = "winit-22", feature = "winit-23", feature = "winit-24"))] + pub fn handle_event(&mut self, io: &mut Io, window: &Window, event: &OwnedEvent) { + match *event { + OwnedEvent::WindowEvent { + window_id, + ref event, + } if window_id == window.id() => { + // We need to track modifiers separately because some system like macOS, will + // not reliably send modifier states during certain events like ScreenCapture. + // Gotta let the people show off their pretty imgui widgets! + if let OwnedWindowEvent::ModifiersChanged(modifiers) = event { + io.key_shift = modifiers.shift(); + io.key_ctrl = modifiers.ctrl(); + io.key_alt = modifiers.alt(); + io.key_super = modifiers.logo(); + } + + self.handle_window_event(io, window, event); + } + // Track key release events outside our window. If we don't do this, + // we might never see the release event if some other window gets focus. + OwnedEvent::DeviceEvent { + event: + DeviceEvent::Key(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + .. + }), + .. + } => { + io.keys_down[key as usize] = false; + } + _ => (), + } + } + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &WindowEvent) { + match *event { + WindowEvent::Resized(logical_size) => { + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + WindowEvent::HiDpiFactorChanged(scale) => { + let hidpi_factor = match self.hidpi_mode { + ActiveHiDpiMode::Default => scale, + ActiveHiDpiMode::Rounded => scale.round(), + _ => return, + }; + // Mouse position needs to be changed while we still have both the old and the new + // values + if io.mouse_pos[0].is_finite() && io.mouse_pos[1].is_finite() { + io.mouse_pos = [ + io.mouse_pos[0] * (hidpi_factor / self.hidpi_factor) as f32, + io.mouse_pos[1] * (hidpi_factor / self.hidpi_factor) as f32, + ]; + } + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + // Window size might change too if we are using DPI rounding + if let Some(logical_size) = window.get_inner_size() { + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state, + .. + }, + .. + } => { + io.keys_down[key as usize] = state == ElementState::Pressed; + } + WindowEvent::ReceivedCharacter(ch) => { + // Exclude the backspace key ('\u{7f}'). Otherwise we will insert this char and then + // delete it. + if ch != '\u{7f}' { + io.add_input_character(ch) + } + } + WindowEvent::CursorMoved { position, .. } => { + let position = self.scale_pos_from_winit(window, position); + io.mouse_pos = [position.x as f32, position.y as f32]; + } + WindowEvent::MouseWheel { + delta, + phase: TouchPhase::Moved, + .. + } => match delta { + MouseScrollDelta::LineDelta(h, v) => { + io.mouse_wheel_h = h; + io.mouse_wheel = v; + } + MouseScrollDelta::PixelDelta(pos) => { + match pos.x.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel_h += 1.0, + Some(Ordering::Less) => io.mouse_wheel_h -= 1.0, + _ => (), + } + match pos.y.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel += 1.0, + Some(Ordering::Less) => io.mouse_wheel -= 1.0, + _ => (), + } + } + }, + WindowEvent::MouseInput { state, button, .. } => { + let pressed = state == ElementState::Pressed; + match button { + MouseButton::Left => self.mouse_buttons[0].set(pressed), + MouseButton::Right => self.mouse_buttons[1].set(pressed), + MouseButton::Middle => self.mouse_buttons[2].set(pressed), + MouseButton::Other(idx @ 0..=4) => { + self.mouse_buttons[idx as usize].set(pressed) + } + _ => (), + } + } + _ => (), + } + } + #[cfg(all( + not(any(feature = "winit-23", feature = "winit-24")), + any(feature = "winit-20", feature = "winit-22") + ))] + fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &WindowEvent) { + 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); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + let hidpi_factor = match self.hidpi_mode { + ActiveHiDpiMode::Default => scale_factor, + ActiveHiDpiMode::Rounded => scale_factor.round(), + _ => return, + }; + // Mouse position needs to be changed while we still have both the old and the new + // values + if io.mouse_pos[0].is_finite() && io.mouse_pos[1].is_finite() { + io.mouse_pos = [ + io.mouse_pos[0] * (hidpi_factor / self.hidpi_factor) as f32, + io.mouse_pos[1] * (hidpi_factor / self.hidpi_factor) as f32, + ]; + } + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + // Window size might change too if we are using DPI rounding + let logical_size = window.inner_size().to_logical(scale_factor); + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state, + .. + }, + .. + } => { + let pressed = state == ElementState::Pressed; + io.keys_down[key as usize] = pressed; + + // This is a bit redundant here, but we'll leave it in. The OS occasionally + // fails to send modifiers keys, but it doesn't seem to send false-positives, + // so double checking isn't terrible in case some system *doesn't* send + // device events sometimes. + match key { + VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = pressed, + VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = pressed, + VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = pressed, + VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = pressed, + _ => (), + } + } + WindowEvent::ReceivedCharacter(ch) => { + // Exclude the backspace key ('\u{7f}'). Otherwise we will insert this char and then + // delete it. + if ch != '\u{7f}' { + io.add_input_character(ch) + } + } + WindowEvent::CursorMoved { position, .. } => { + let position = position.to_logical(window.scale_factor()); + let position = self.scale_pos_from_winit(window, position); + io.mouse_pos = [position.x as f32, position.y as f32]; + } + WindowEvent::MouseWheel { + delta, + phase: TouchPhase::Moved, + .. + } => match delta { + MouseScrollDelta::LineDelta(h, v) => { + io.mouse_wheel_h = h; + io.mouse_wheel = v; + } + MouseScrollDelta::PixelDelta(pos) => { + match pos.x.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel_h += 1.0, + Some(Ordering::Less) => io.mouse_wheel_h -= 1.0, + _ => (), + } + match pos.y.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel += 1.0, + Some(Ordering::Less) => io.mouse_wheel -= 1.0, + _ => (), + } + } + }, + WindowEvent::MouseInput { state, button, .. } => { + let pressed = state == ElementState::Pressed; + match button { + MouseButton::Left => self.mouse_buttons[0].set(pressed), + MouseButton::Right => self.mouse_buttons[1].set(pressed), + MouseButton::Middle => self.mouse_buttons[2].set(pressed), + MouseButton::Other(idx @ 0..=4) => { + self.mouse_buttons[idx as usize].set(pressed) + } + _ => (), + } + } + _ => (), + } + } + + //#[cfg(any(feature = "winit-23", feature = "winit-24"))] + fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &OwnedWindowEvent) { + match *event { + OwnedWindowEvent::Resized(physical_size) => { + let logical_size = physical_size.to_logical(window.scale_factor()); + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + OwnedWindowEvent::ScaleFactorChanged { scale_factor, .. } => { + let hidpi_factor = match self.hidpi_mode { + ActiveHiDpiMode::Default => scale_factor, + ActiveHiDpiMode::Rounded => scale_factor.round(), + _ => return, + }; + // Mouse position needs to be changed while we still have both the old and the new + // values + if io.mouse_pos[0].is_finite() && io.mouse_pos[1].is_finite() { + io.mouse_pos = [ + io.mouse_pos[0] * (hidpi_factor / self.hidpi_factor) as f32, + io.mouse_pos[1] * (hidpi_factor / self.hidpi_factor) as f32, + ]; + } + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + // Window size might change too if we are using DPI rounding + let logical_size = window.inner_size().to_logical(scale_factor); + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + OwnedWindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state, + .. + }, + .. + } => { + let pressed = state == ElementState::Pressed; + io.keys_down[key as usize] = pressed; + + // This is a bit redundant here, but we'll leave it in. The OS occasionally + // fails to send modifiers keys, but it doesn't seem to send false-positives, + // so double checking isn't terrible in case some system *doesn't* send + // device events sometimes. + match key { + VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = pressed, + VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = pressed, + VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = pressed, + VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = pressed, + _ => (), + } + } + OwnedWindowEvent::ReceivedCharacter(ch) => { + // Exclude the backspace key ('\u{7f}'). Otherwise we will insert this char and then + // delete it. + if ch != '\u{7f}' { + io.add_input_character(ch) + } + } + OwnedWindowEvent::CursorMoved { position, .. } => { + let position = position.to_logical(window.scale_factor()); + let position = self.scale_pos_from_winit(window, position); + io.mouse_pos = [position.x as f32, position.y as f32]; + } + OwnedWindowEvent::MouseWheel { + delta, + phase: TouchPhase::Moved, + .. + } => match delta { + MouseScrollDelta::LineDelta(h, v) => { + io.mouse_wheel_h = h; + io.mouse_wheel = v; + } + MouseScrollDelta::PixelDelta(pos) => { + let pos = pos.to_logical::(self.hidpi_factor); + match pos.x.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel_h += 1.0, + Some(Ordering::Less) => io.mouse_wheel_h -= 1.0, + _ => (), + } + match pos.y.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel += 1.0, + Some(Ordering::Less) => io.mouse_wheel -= 1.0, + _ => (), + } + } + }, + OwnedWindowEvent::MouseInput { state, button, .. } => { + let pressed = state == ElementState::Pressed; + match button { + MouseButton::Left => self.mouse_buttons[0].set(pressed), + MouseButton::Right => self.mouse_buttons[1].set(pressed), + MouseButton::Middle => self.mouse_buttons[2].set(pressed), + MouseButton::Other(idx @ 0..=4) => { + self.mouse_buttons[idx as usize].set(pressed) + } + _ => (), + } + } + _ => (), + } + } + /// Frame preparation callback. + /// + /// Call this before calling the imgui-rs context `frame` function. + /// This function performs the following actions: + /// + /// * mouse cursor is repositioned (if requested by imgui-rs) + #[cfg(all( + not(any( + feature = "winit-24", + feature = "winit-23", + feature = "winit-22", + feature = "winit-20" + )), + feature = "winit-19", + ))] + pub fn prepare_frame(&self, io: &mut Io, window: &Window) -> Result<(), String> { + self.copy_mouse_to_io(&mut io.mouse_down); + if io.want_set_mouse_pos { + let logical_pos = self.scale_pos_for_winit( + window, + LogicalPosition::new(f64::from(io.mouse_pos[0]), f64::from(io.mouse_pos[1])), + ); + window.set_cursor_position(logical_pos) + } else { + Ok(()) + } + } + /// Frame preparation callback. + /// + /// Call this before calling the imgui-rs context `frame` function. + /// This function performs the following actions: + /// + /// * mouse cursor is repositioned (if requested by imgui-rs) + #[cfg(any( + feature = "winit-20", + feature = "winit-22", + feature = "winit-23", + feature = "winit-24" + ))] + pub fn prepare_frame(&self, io: &mut Io, window: &Window) -> Result<(), ExternalError> { + self.copy_mouse_to_io(&mut io.mouse_down); + if io.want_set_mouse_pos { + let logical_pos = self.scale_pos_for_winit( + window, + LogicalPosition::new(f64::from(io.mouse_pos[0]), f64::from(io.mouse_pos[1])), + ); + window.set_cursor_position(logical_pos) + } else { + Ok(()) + } + } + + fn copy_mouse_to_io(&self, io_mouse_down: &mut [bool]) { + for (io_down, button) in io_mouse_down.iter_mut().zip(&self.mouse_buttons) { + *io_down = button.get(); + } + } + + /// Render preparation callback. + /// + /// Call this before calling the imgui-rs UI `render_with`/`render` function. + /// This function performs the following actions: + /// + /// * mouse cursor is changed and/or hidden (if requested by imgui-rs) + pub fn prepare_render(&mut self, ui: &Ui, window: &Window) { + let io = ui.io(); + if !io + .config_flags + .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE) + { + let cursor = CursorSettings { + cursor: ui.mouse_cursor(), + draw_cursor: io.mouse_draw_cursor, + }; + if self.cursor_cache != Some(cursor) { + cursor.apply(window); + self.cursor_cache = Some(cursor); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 4de8a2f..3fe39eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ extern crate imgui; extern crate imgui_wgpu; -extern crate imgui_winit_support; extern crate tobj; -extern crate winit; +extern crate winit_24; use std::f32::consts::PI; use std::rc::Rc; @@ -31,13 +30,13 @@ use rapier3d::na::{Isometry, Isometry3, Vector, Vector3}; use rapier3d::pipeline::PhysicsPipeline; use wgpu::{BindGroup, Buffer, TextureView}; use wgpu_subscriber; -use winit::{ +use winit_24::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, }; -use winit::event::DeviceEvent::MouseMotion; -use winit::platform::unix::x11::ffi::Time; -use winit::window::Window; +use winit_24::event::DeviceEvent::MouseMotion; +use winit_24::platform::unix::x11::ffi::Time; +use winit_24::window::Window; use crate::camera::{Camera, CameraController}; use crate::components::{Collider, Color, LoopState, Physics, Position}; @@ -47,7 +46,7 @@ use crate::render::Renderer; use legion::systems::{UnsafeResources, SyncResources}; use std::borrow::Borrow; use imgui::__core::convert::TryInto; -use imgui_winit_support::WinitPlatform; +use crate::extended_winit_imgui_support::WinitPlatform; mod camera; mod components; @@ -56,6 +55,7 @@ mod light; mod owned_event; mod physics; mod render; +mod extended_winit_imgui_support; /* @@ -107,7 +107,6 @@ fn main() { .build(); let mut update_schedule = Schedule::builder() - .add_system(physics::test_unsafe_system()) .add_system(physics::update_camera_system()) .add_system(physics::run_physics_system()) .add_system(physics::update_models_system()) @@ -119,7 +118,7 @@ fn main() { .build(); let event_loop = EventLoop::::with_user_event(); - let mut builder = winit::window::WindowBuilder::new(); + let mut builder = winit_24::window::WindowBuilder::new(); builder = builder.with_title("MVGE"); let window = builder.build(&event_loop).unwrap(); @@ -129,11 +128,11 @@ fn main() { // Load up all the resources { let mut imgui_context = imgui::Context::create(); - let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui_context); + let mut platform = extended_winit_imgui_support::WinitPlatform::init(&mut imgui_context); platform.attach_window( imgui_context.io_mut(), &window, - imgui_winit_support::HiDpiMode::Default, + extended_winit_imgui_support::HiDpiMode::Default, ); // imgui rendering context diff --git a/src/owned_event.rs b/src/owned_event.rs index 63c9f8b..b0986fe 100644 --- a/src/owned_event.rs +++ b/src/owned_event.rs @@ -3,18 +3,20 @@ use std::path::PathBuf; use gilrs::Event as GilEvent; use legion::world::SubWorld; use legion::*; -use winit::dpi::{PhysicalPosition, PhysicalSize}; -use winit::event::DeviceEvent::MouseMotion; -use winit::event::{ +use winit_24::dpi::{PhysicalPosition, PhysicalSize}; +use winit_24::event::DeviceEvent::MouseMotion; +use winit_24::event::{ AxisId, DeviceEvent, DeviceId, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, StartCause, Touch, TouchPhase, WindowEvent, }; -use winit::window::{Theme, WindowId, Window}; +use winit_24::window::{Theme, WindowId, Window}; use crate::camera::{Camera, CameraController}; use crate::owned_event::OwnedWindowEvent::MouseWheel; use crate::{ImguiPlatform, ImguiContext}; use std::sync::{Mutex, Arc}; +use std::cmp::Ordering; +use imgui::Io; #[derive(Clone)] pub enum OwnedUIEvent { @@ -239,6 +241,7 @@ pub enum OwnedWindowEvent { ThemeChanged(Theme), } + /// Because I am a glutton for punishment I am going to just do a mono-event-dispatch-magoooo #[system] #[write_component(Camera)] @@ -250,13 +253,13 @@ pub fn event_dispatch( #[resource] imgui_platform: &mut Arc>, #[resource] winit_window: &mut Window, ) { - use winit::event::Event::DeviceEvent; + use winit_24::event::Event::DeviceEvent; for event in event_stack { match event { OwnedEvent::DeviceEvent { - event: winit::event::DeviceEvent::MouseMotion { delta }, + event: winit_24::event::DeviceEvent::MouseMotion { delta }, .. } => { let mut query = <(&mut CameraController)>::query(); @@ -265,7 +268,7 @@ pub fn event_dispatch( } }, OwnedEvent::DeviceEvent { - event: winit::event::DeviceEvent::Key(keyboard_input), + event: winit_24::event::DeviceEvent::Key(keyboard_input), .. } => { let mut query = <(&mut CameraController)>::query(); @@ -297,6 +300,6 @@ pub fn event_dispatch( let mut imgui_context = &mut imgui_context.lock().unwrap().context; let mut imgui_platform = &mut imgui_platform.lock().unwrap().platform; - imgui_platform.handle_event(imgui_context.io_mut(), &winit_window, &event.into()); + imgui_platform.handle_event(imgui_context.io_mut(), &winit_window, &event); } } diff --git a/src/physics.rs b/src/physics.rs index e6f04e8..f22b04f 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -87,17 +87,6 @@ pub fn run_physics( ); } -#[system] -#[write_component(Camera)] -pub fn test_unsafe(world: &mut SubWorld, - #[resource] loop_state: &mut LoopState, - // #[resource] imgui_context: &mut imgui::Context, -) { - - - -} - #[system] #[write_component(Camera)] diff --git a/src/render.rs b/src/render.rs index ea341f0..a126718 100644 --- a/src/render.rs +++ b/src/render.rs @@ -17,15 +17,14 @@ use wgpu::{ Instance, Queue, Surface, SwapChain, SwapChainDescriptor, SwapChainFrame, TextureView, VertexState, }; -use winit::dpi::PhysicalSize; -use winit::platform::unix::x11::ffi::Time; -use winit::window::Window; +use winit_24::dpi::PhysicalSize; +use winit_24::platform::unix::x11::ffi::Time; +use winit_24::window::Window; use crate::camera::{Camera, CameraController}; use crate::components::{Color, Mesh, Position, RangeCopy}; use crate::geometry::{import_mesh, vertex, Vertex}; use crate::light::{DirectionalLight, LightRaw}; -use imgui_winit_support::WinitPlatform; use imgui::*; use imgui_wgpu::{Renderer as ImguiRenderer, RendererConfig as ImguiRendererConfig}; use std::cell::RefCell;