diff --git a/CHANGELOG.md b/CHANGELOG.md index b5dc45d9d6..8ab20df4a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Unreleased - **Breaking:** Removed `VirtualKeyCode::LMenu` and `VirtualKeyCode::RMenu`; Windows now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead. +- On X11, exiting fullscreen no longer leaves the window in the monitor's top left corner. +- **Breaking:** `Window::hidpi_factor` has been renamed to `Window::get_hidpi_factor` for better consistency. `WindowEvent::HiDPIFactorChanged` has been renamed to `WindowEvent::HiDpiFactorChanged`. DPI factors are always represented as `f64` instead of `f32` now. +- The Windows backend is now DPI aware. `WindowEvent::HiDpiFactorChanged` is implemented, and `MonitorId::get_hidpi_factor` and `Window::hidpi_factor` return accurate values. +- Implemented `WindowEvent::HiDpiFactorChanged` on X11. +- On macOS, `Window::set_cursor_position` is now relative to the client area. +- On macOS, setting the maximum and minimum dimensions now applies to the client area dimensions rather than to the window dimensions. +- On iOS, `MonitorId::get_dimensions` has been implemented and both `MonitorId::get_hidpi_factor` and `Window::get_hidpi_factor` return accurate values. +- On Emscripten, `MonitorId::get_hidpi_factor` now returns the same value as `Window::get_hidpi_factor` (it previously would always return 1.0). +- **Breaking:** The entire API for sizes, positions, etc. has changed. In the majority of cases, winit produces and consumes positions and sizes as `LogicalPosition` and `LogicalSize`, respectively. The notable exception is `MonitorId` methods, which deal in `PhysicalPosition` and `PhysicalSize`. See the documentation for specifics and explanations of the types. Additionally, winit automatically conserves logical size when the DPI factor changes. +- **Breaking:** All deprecated methods have been removed. For `Window::platform_display` and `Window::platform_window`, switch to the appropriate platform-specific `WindowExt` methods. For `Window::get_inner_size_points` and `Window::get_inner_size_pixels`, use the `LogicalSize` returned by `Window::get_inner_size` and convert as needed. +- HiDPI support for Wayland. # Version 0.15.1 (2018-06-13) diff --git a/Cargo.toml b/Cargo.toml index 42d212e04f..d7257077e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ features = [ "objbase", "processthreadsapi", "shellapi", + "shellscalingapi", "shobjidl_core", "unknwnbase", "windowsx", diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs index 4a5076e69f..06f4aa18d2 100644 --- a/examples/min_max_size.rs +++ b/examples/min_max_size.rs @@ -1,5 +1,7 @@ extern crate winit; +use winit::dpi::LogicalSize; + fn main() { let mut events_loop = winit::EventsLoop::new(); @@ -7,8 +9,8 @@ fn main() { .build(&events_loop) .unwrap(); - window.set_min_dimensions(Some((400, 200))); - window.set_max_dimensions(Some((800, 400))); + window.set_min_dimensions(Some(LogicalSize::new(400.0, 200.0))); + window.set_max_dimensions(Some(LogicalSize::new(800.0, 400.0))); events_loop.run_forever(|event| { println!("{:?}", event); diff --git a/examples/resizable.rs b/examples/resizable.rs index f1557445eb..749e852121 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -7,7 +7,7 @@ fn main() { let window = winit::WindowBuilder::new() .with_title("Hit space to toggle resizability.") - .with_dimensions(400, 200) + .with_dimensions((400, 200).into()) .with_resizable(resizable) .build(&events_loop) .unwrap(); diff --git a/src/dpi.rs b/src/dpi.rs new file mode 100644 index 0000000000..141d0f3301 --- /dev/null +++ b/src/dpi.rs @@ -0,0 +1,307 @@ + +//! DPI is important, so read the docs for this module if you don't want to be confused. +//! +//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all +//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical +//! pixels, as do any context-related functions in `glutin`. +//! +//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the +//! concepts. +//! +//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of +//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two +//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the +//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays. +//! +//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the +//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the +//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably +//! small. +//! +//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100 +//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI factor. On a "typical" +//! desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical pixels. However, +//! a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels. Ideally, the +//! button now has approximately the same perceived size across varying displays. +//! +//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users +//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in +//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application. +//! +//! There are two ways to get the DPI factor: either by calling +//! [`MonitorId::get_hidpi_factor`](../struct.MonitorId.html#method.get_hidpi_factor), or +//! [`Window::get_hidpi_factor`](../struct.Window.html#method.get_hidpi_factor). You'll almost always use the latter, +//! which is basically equivalent to `window.get_current_monitor().get_hidpi_factor()` anyway. +//! +//! Here's an overview of what sort of DPI factors you can expect, and where they come from: +//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings. +//! While users are free to select any option they want, they're only given a selection of "nice" DPI factors, i.e. +//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out. +//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0. +//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any +//! display to use that 2.0 DPI factor, given the use of the command line. +//! - **X11:** On X11, we calcuate the DPI factor based on the millimeter dimensions provided by XRandR. This can +//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be +//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended. +//! - **Wayland:** On Wayland, DPI factors are very much at the discretion of the user. +//! - **iOS:** DPI factors are both constant and device-specific on iOS. +//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0. +//! +//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This +//! may be surprising on X11, but is quite standard elsewhere. Physical size changes produce a +//! [`Resized`](../enum.WindowEvent.html#variant.Resized) event, even on platforms where no resize actually occurs, +//! such as macOS and Wayland. As a result, it's not necessary to separately handle +//! [`HiDpiFactorChanged`](../enum.WindowEvent.html#variant.HiDpiFactorChanged) if you're only listening for size. +//! +//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your +//! framebuffer's size should be in physical pixels. + +/// Checks that the DPI factor is a normal positive `f64`. +/// +/// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from +/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; +/// otherwise, you risk panics. +#[inline] +pub fn validate_hidpi_factor(dpi_factor: f64) -> bool { + dpi_factor.is_sign_positive() && dpi_factor.is_normal() +} + +/// A position represented in logical pixels. +/// +/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, +/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which +/// does the rounding for you. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct LogicalPosition { + pub x: f64, + pub y: f64, +} + +impl LogicalPosition { + #[inline] + pub fn new(x: f64, y: f64) -> Self { + LogicalPosition { x, y } + } + + #[inline] + pub fn from_physical>(physical: T, dpi_factor: f64) -> Self { + physical.into().to_logical(dpi_factor) + } + + #[inline] + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { + assert!(validate_hidpi_factor(dpi_factor)); + let x = self.x * dpi_factor; + let y = self.y * dpi_factor; + PhysicalPosition::new(x, y) + } +} + +impl From<(f64, f64)> for LogicalPosition { + #[inline] + fn from((x, y): (f64, f64)) -> Self { + Self::new(x, y) + } +} + +impl From<(i32, i32)> for LogicalPosition { + #[inline] + fn from((x, y): (i32, i32)) -> Self { + Self::new(x as f64, y as f64) + } +} + +impl Into<(f64, f64)> for LogicalPosition { + #[inline] + fn into(self) -> (f64, f64) { + (self.x, self.y) + } +} + +impl Into<(i32, i32)> for LogicalPosition { + /// Note that this rounds instead of truncating. + #[inline] + fn into(self) -> (i32, i32) { + (self.x.round() as _, self.y.round() as _) + } +} + +/// A position represented in physical pixels. +/// +/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, +/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which +/// does the rounding for you. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PhysicalPosition { + pub x: f64, + pub y: f64, +} + +impl PhysicalPosition { + #[inline] + pub fn new(x: f64, y: f64) -> Self { + PhysicalPosition { x, y } + } + + #[inline] + pub fn from_logical>(logical: T, dpi_factor: f64) -> Self { + logical.into().to_physical(dpi_factor) + } + + #[inline] + pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { + assert!(validate_hidpi_factor(dpi_factor)); + let x = self.x / dpi_factor; + let y = self.y / dpi_factor; + LogicalPosition::new(x, y) + } +} + +impl From<(f64, f64)> for PhysicalPosition { + #[inline] + fn from((x, y): (f64, f64)) -> Self { + Self::new(x, y) + } +} + +impl From<(i32, i32)> for PhysicalPosition { + #[inline] + fn from((x, y): (i32, i32)) -> Self { + Self::new(x as f64, y as f64) + } +} + +impl Into<(f64, f64)> for PhysicalPosition { + #[inline] + fn into(self) -> (f64, f64) { + (self.x, self.y) + } +} + +impl Into<(i32, i32)> for PhysicalPosition { + /// Note that this rounds instead of truncating. + #[inline] + fn into(self) -> (i32, i32) { + (self.x.round() as _, self.y.round() as _) + } +} + +/// A size represented in logical pixels. +/// +/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, +/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which +/// does the rounding for you. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct LogicalSize { + pub width: f64, + pub height: f64, +} + +impl LogicalSize { + #[inline] + pub fn new(width: f64, height: f64) -> Self { + LogicalSize { width, height } + } + + #[inline] + pub fn from_physical>(physical: T, dpi_factor: f64) -> Self { + physical.into().to_logical(dpi_factor) + } + + #[inline] + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { + assert!(validate_hidpi_factor(dpi_factor)); + let width = self.width * dpi_factor; + let height = self.height * dpi_factor; + PhysicalSize::new(width, height) + } +} + +impl From<(f64, f64)> for LogicalSize { + #[inline] + fn from((width, height): (f64, f64)) -> Self { + Self::new(width, height) + } +} + +impl From<(u32, u32)> for LogicalSize { + #[inline] + fn from((width, height): (u32, u32)) -> Self { + Self::new(width as f64, height as f64) + } +} + +impl Into<(f64, f64)> for LogicalSize { + #[inline] + fn into(self) -> (f64, f64) { + (self.width, self.height) + } +} + +impl Into<(u32, u32)> for LogicalSize { + /// Note that this rounds instead of truncating. + #[inline] + fn into(self) -> (u32, u32) { + (self.width.round() as _, self.height.round() as _) + } +} + +/// A size represented in physical pixels. +/// +/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, +/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which +/// does the rounding for you. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PhysicalSize { + pub width: f64, + pub height: f64, +} + +impl PhysicalSize { + #[inline] + pub fn new(width: f64, height: f64) -> Self { + PhysicalSize { width, height } + } + + #[inline] + pub fn from_logical>(logical: T, dpi_factor: f64) -> Self { + logical.into().to_physical(dpi_factor) + } + + #[inline] + pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { + assert!(validate_hidpi_factor(dpi_factor)); + let width = self.width / dpi_factor; + let height = self.height / dpi_factor; + LogicalSize::new(width, height) + } +} + +impl From<(f64, f64)> for PhysicalSize { + #[inline] + fn from((width, height): (f64, f64)) -> Self { + Self::new(width, height) + } +} + +impl From<(u32, u32)> for PhysicalSize { + #[inline] + fn from((width, height): (u32, u32)) -> Self { + Self::new(width as f64, height as f64) + } +} + +impl Into<(f64, f64)> for PhysicalSize { + #[inline] + fn into(self) -> (f64, f64) { + (self.width, self.height) + } +} + +impl Into<(u32, u32)> for PhysicalSize { + /// Note that this rounds instead of truncating. + #[inline] + fn into(self) -> (u32, u32) { + (self.width.round() as _, self.height.round() as _) + } +} diff --git a/src/events.rs b/src/events.rs index 2436796b0b..5a90a373df 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; -use {WindowId, DeviceId}; + +use {DeviceId, LogicalPosition, LogicalSize, WindowId}; /// Describes a generic event. #[derive(Clone, Debug)] @@ -23,12 +24,11 @@ pub enum Event { /// Describes an event from a `Window`. #[derive(Clone, Debug)] pub enum WindowEvent { - /// The size of the window has changed. Contains the client area's new dimensions. - Resized(u32, u32), + Resized(LogicalSize), /// The position of the window has changed. Contains the window's new position. - Moved(i32, i32), + Moved(LogicalPosition), /// The window has been requested to close. CloseRequested, @@ -63,7 +63,7 @@ pub enum WindowEvent { /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. - position: (f64, f64), + position: LogicalPosition, modifiers: ModifiersState }, @@ -96,14 +96,16 @@ pub enum WindowEvent { /// Touch event has been received Touch(Touch), - /// DPI scaling factor of the window has changed. + /// The DPI factor of the window has changed. + /// + /// The following user actions can cause DPI changes: /// - /// The following actions cause DPI changes: + /// * Changing the display's resolution. + /// * Changing the display's DPI factor (e.g. in Control Panel on Windows). + /// * Moving the window to a display with a different DPI factor. /// - /// * A user changes the resolution. - /// * A user changes the desktop scaling value (e.g. in Control Panel on Windows). - /// * A user moves the application window to a display with a different DPI. - HiDPIFactorChanged(f32), + /// For more information about DPI in general, see the [`dpi`](dpi/index.html) module. + HiDpiFactorChanged(f64), } /// Represents raw hardware events that are not associated with any particular window. @@ -197,7 +199,7 @@ pub enum TouchPhase { pub struct Touch { pub device_id: DeviceId, pub phase: TouchPhase, - pub location: (f64,f64), + pub location: LogicalPosition, /// unique identifier of a finger. pub id: u64 } @@ -242,7 +244,7 @@ pub enum MouseScrollDelta { /// Scroll events are expressed as a PixelDelta if /// supported by the device (eg. a touchpad) and /// platform. - PixelDelta(f32, f32) + PixelDelta(LogicalPosition), } /// Symbolic name for a keyboard key. diff --git a/src/lib.rs b/src/lib.rs index fec7474a11..44a1cb4ad9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,14 +35,18 @@ //! //! ```no_run //! use winit::{Event, WindowEvent}; +//! use winit::dpi::LogicalSize; //! # use winit::EventsLoop; //! # let mut events_loop = EventsLoop::new(); //! //! loop { //! events_loop.poll_events(|event| { //! match event { -//! Event::WindowEvent { event: WindowEvent::Resized(w, h), .. } => { -//! println!("The window was resized to {}x{}", w, h); +//! Event::WindowEvent { +//! event: WindowEvent::Resized(LogicalSize { width, height }), +//! .. +//! } => { +//! println!("The window was resized to {}x{}", width, height); //! }, //! _ => () //! } @@ -80,6 +84,7 @@ //! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window. //! +#[allow(unused_imports)] #[macro_use] extern crate lazy_static; extern crate libc; @@ -106,14 +111,16 @@ extern crate percent_encoding; #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] extern crate smithay_client_toolkit as sctk; +pub(crate) use dpi::*; // TODO: Actually change the imports throughout the codebase. pub use events::*; pub use window::{AvailableMonitorsIter, MonitorId}; pub use icon::*; -mod platform; +pub mod dpi; mod events; -mod window; mod icon; +mod platform; +mod window; pub mod os; @@ -227,6 +234,11 @@ impl EventsLoop { /// Calls `callback` every time an event is received. If no event is available, sleeps the /// current thread and waits for an event. If the callback returns `ControlFlow::Break` then /// `run_forever` will immediately return. + /// + /// # Danger! + /// + /// The callback is run after *every* event, so if its execution time is non-trivial the event queue may not empty + /// at a sufficient rate. Rendering in the callback with vsync enabled **will** cause significant lag. #[inline] pub fn run_forever(&mut self, callback: F) where F: FnMut(Event) -> ControlFlow @@ -403,23 +415,23 @@ impl Default for CursorState { } /// Attributes to use when creating a window. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct WindowAttributes { /// The dimensions of the window. If this is `None`, some platform-specific dimensions will be /// used. /// /// The default is `None`. - pub dimensions: Option<(u32, u32)>, + pub dimensions: Option, /// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved). /// /// The default is `None`. - pub min_dimensions: Option<(u32, u32)>, + pub min_dimensions: Option, /// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform. /// /// The default is `None`. - pub max_dimensions: Option<(u32, u32)>, + pub max_dimensions: Option, /// Whether the window is resizable or not. /// diff --git a/src/os/ios.rs b/src/os/ios.rs new file mode 100644 index 0000000000..5090272c9f --- /dev/null +++ b/src/os/ios.rs @@ -0,0 +1,43 @@ +#![cfg(target_os = "ios")] + +use std::os::raw::c_void; + +use {MonitorId, Window}; + +/// Additional methods on `Window` that are specific to iOS. +pub trait WindowExt { + /// Returns a pointer to the `UIWindow` that is used by this window. + /// + /// The pointer will become invalid when the `Window` is destroyed. + fn get_uiwindow(&self) -> *mut c_void; + + /// Returns a pointer to the `UIView` that is used by this window. + /// + /// The pointer will become invalid when the `Window` is destroyed. + fn get_uiview(&self) -> *mut c_void; +} + +impl WindowExt for Window { + #[inline] + fn get_uiwindow(&self) -> *mut c_void { + self.window.get_uiwindow() as _ + } + + #[inline] + fn get_uiview(&self) -> *mut c_void { + self.window.get_uiview() as _ + } +} + +/// Additional methods on `MonitorId` that are specific to iOS. +pub trait MonitorIdExt { + /// Returns a pointer to the `UIScreen` that is used by this monitor. + fn get_uiscreen(&self) -> *mut c_void; +} + +impl MonitorIdExt for MonitorId { + #[inline] + fn get_uiscreen(&self) -> *mut c_void { + self.inner.get_uiscreen() as _ + } +} diff --git a/src/os/macos.rs b/src/os/macos.rs index c560336c22..776c10ffb8 100644 --- a/src/os/macos.rs +++ b/src/os/macos.rs @@ -3,7 +3,7 @@ use std::convert::From; use std::os::raw::c_void; use cocoa::appkit::NSApplicationActivationPolicy; -use {MonitorId, Window, WindowBuilder}; +use {LogicalSize, MonitorId, Window, WindowBuilder}; /// Additional methods on `Window` that are specific to MacOS. pub trait WindowExt { @@ -86,7 +86,7 @@ pub trait WindowBuilderExt { /// Makes the window content appear behind the titlebar. fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder; /// Build window with `resizeIncrements` property. Values must not be 0. - fn with_resize_increments(self, width_inc: u32, height_inc: u32) -> WindowBuilder; + fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; } impl WindowBuilderExt for WindowBuilder { @@ -133,8 +133,8 @@ impl WindowBuilderExt for WindowBuilder { } #[inline] - fn with_resize_increments(mut self, width_inc: u32, height_inc: u32) -> WindowBuilder { - self.platform_specific.resize_increments = Some((width_inc, height_inc)); + fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { + self.platform_specific.resize_increments = Some(increments.into()); self } } diff --git a/src/os/mod.rs b/src/os/mod.rs index 9e759d0427..2496769563 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -3,6 +3,7 @@ //! Contains the follow modules: //! //! - `android` +//! - `ios` //! - `macos` //! - `unix` //! - `windows` @@ -10,6 +11,7 @@ //! However only the module corresponding to the platform you're compiling to will be available. //! pub mod android; +pub mod ios; pub mod macos; pub mod unix; pub mod windows; diff --git a/src/os/unix.rs b/src/os/unix.rs index 95ef8b4603..fc2d9fddd2 100644 --- a/src/os/unix.rs +++ b/src/os/unix.rs @@ -1,14 +1,20 @@ #![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] use std::os::raw; -use std::sync::Arc; use std::ptr; -use EventsLoop; -use MonitorId; -use Window; -use platform::EventsLoop as LinuxEventsLoop; -use platform::Window as LinuxWindow; -use WindowBuilder; +use std::sync::Arc; + +use { + EventsLoop, + LogicalSize, + MonitorId, + Window, + WindowBuilder, +}; +use platform::{ + EventsLoop as LinuxEventsLoop, + Window as LinuxWindow, +}; use platform::x11::XConnection; use platform::x11::ffi::XVisualInfo; @@ -72,6 +78,7 @@ impl EventsLoopExt for EventsLoop { } #[inline] + #[doc(hidden)] fn get_xlib_xconnection(&self) -> Option> { self.events_loop.x_connection().cloned() } @@ -93,6 +100,7 @@ pub trait WindowExt { fn get_xlib_screen_id(&self) -> Option; + #[doc(hidden)] fn get_xlib_xconnection(&self) -> Option>; /// Set window urgency hint (`XUrgencyHint`). Only relevant on X. @@ -155,6 +163,7 @@ impl WindowExt for Window { } #[inline] + #[doc(hidden)] fn get_xlib_xconnection(&self) -> Option> { match self.window { LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()), @@ -211,9 +220,9 @@ pub trait WindowBuilderExt { /// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11. fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder; /// Build window with resize increment hint. Only implemented on X11. - fn with_resize_increments(self, width_inc: u32, height_inc: u32) -> WindowBuilder; + fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; /// Build window with base size hint. Only implemented on X11. - fn with_base_size(self, base_width: u32, base_height: u32) -> WindowBuilder; + fn with_base_size(self, base_size: LogicalSize) -> WindowBuilder; } impl WindowBuilderExt for WindowBuilder { @@ -250,14 +259,14 @@ impl WindowBuilderExt for WindowBuilder { } #[inline] - fn with_resize_increments(mut self, width_inc: u32, height_inc: u32) -> WindowBuilder { - self.platform_specific.resize_increments = Some((width_inc, height_inc)); + fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { + self.platform_specific.resize_increments = Some(increments.into()); self } #[inline] - fn with_base_size(mut self, base_width: u32, base_height: u32) -> WindowBuilder { - self.platform_specific.base_size = Some((base_width, base_height)); + fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder { + self.platform_specific.base_size = Some(base_size.into()); self } } diff --git a/src/os/windows.rs b/src/os/windows.rs index 15c80a5598..d9f427b2be 100644 --- a/src/os/windows.rs +++ b/src/os/windows.rs @@ -5,7 +5,25 @@ use std::os::raw::c_void; use libc; use winapi::shared::windef::HWND; -use {DeviceId, Icon, MonitorId, Window, WindowBuilder}; +use {DeviceId, EventsLoop, Icon, MonitorId, Window, WindowBuilder}; +use platform::EventsLoop as WindowsEventsLoop; + +/// Additional methods on `EventsLoop` that are specific to Windows. +pub trait EventsLoopExt { + /// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's + /// undesirable, you can create an `EventsLoop` using this function instead. + fn new_dpi_unaware() -> Self where Self: Sized; +} + +impl EventsLoopExt for EventsLoop { + #[inline] + fn new_dpi_unaware() -> Self { + EventsLoop { + events_loop: WindowsEventsLoop::with_dpi_awareness(false), + _marker: ::std::marker::PhantomData, + } + } +} /// Additional methods on `Window` that are specific to Windows. pub trait WindowExt { diff --git a/src/platform/android/mod.rs b/src/platform/android/mod.rs index ad08e51276..66724b4b63 100644 --- a/src/platform/android/mod.rs +++ b/src/platform/android/mod.rs @@ -2,24 +2,34 @@ extern crate android_glue; -use libc; -use std::sync::mpsc::{Receiver, channel}; +mod ffi; + +use std::cell::RefCell; +use std::collections::VecDeque; +use std::fmt; use std::os::raw::c_void; -use {CreationError, Event, WindowEvent, MouseCursor}; +use std::sync::mpsc::{Receiver, channel}; + +use { + CreationError, + CursorState, + Event, + LogicalPosition, + LogicalSize, + MouseCursor, + PhysicalPosition, + PhysicalSize, + WindowAttributes, + WindowEvent, + WindowId as RootWindowId, +}; use CreationError::OsError; -use WindowId as RootWindowId; use events::{Touch, TouchPhase}; use window::MonitorId as RootMonitorId; -use std::collections::VecDeque; -use std::cell::RefCell; - -use CursorState; -use WindowAttributes; - pub struct EventsLoop { event_rx: Receiver, - suspend_callback: RefCell ()>>> + suspend_callback: RefCell ()>>>, } #[derive(Clone)] @@ -31,13 +41,13 @@ impl EventsLoop { android_glue::add_sender(tx); EventsLoop { event_rx: rx, - suspend_callback: RefCell::new(None), + suspend_callback: Default::default(), } } #[inline] pub fn get_available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::new(); + let mut rb = VecDeque::with_capacity(1); rb.push_back(MonitorId); rb } @@ -47,14 +57,17 @@ impl EventsLoop { MonitorId } - pub fn poll_events(&mut self, mut callback: F) where F: FnMut(::Event) { while let Ok(event) = self.event_rx.try_recv() { - let e = match event{ android_glue::Event::EventMotion(motion) => { + let dpi_factor = MonitorId.get_hidpi_factor(); + let location = LogicalPosition::from_physical( + (motion.x as f64, motion.y as f64), + dpi_factor, + ); Some(Event::WindowEvent { window_id: RootWindowId(WindowId), event: WindowEvent::Touch(Touch { @@ -64,7 +77,7 @@ impl EventsLoop { android_glue::MotionAction::Up => TouchPhase::Ended, android_glue::MotionAction::Cancel => TouchPhase::Cancelled, }, - location: (motion.x as f64, motion.y as f64), + location, id: motion.pointer_id as u64, device_id: DEVICE_ID, }), @@ -91,11 +104,12 @@ impl EventsLoop { if native_window.is_null() { None } else { - let w = unsafe { ffi::ANativeWindow_getWidth(native_window as *const _) } as u32; - let h = unsafe { ffi::ANativeWindow_getHeight(native_window as *const _) } as u32; + let dpi_factor = MonitorId.get_hidpi_factor(); + let physical_size = MonitorId.get_dimensions(); + let size = LogicalSize::from_physical(physical_size, dpi_factor); Some(Event::WindowEvent { window_id: RootWindowId(WindowId), - event: WindowEvent::Resized(w, h), + event: WindowEvent::Resized(size), }) } }, @@ -164,10 +178,29 @@ pub struct Window { native_window: *const c_void, } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct MonitorId; -mod ffi; +impl fmt::Debug for MonitorId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[derive(Debug)] + struct MonitorId { + name: Option, + dimensions: PhysicalSize, + position: PhysicalPosition, + hidpi_factor: f64, + } + + let monitor_id_proxy = MonitorId { + name: self.get_name(), + dimensions: self.get_dimensions(), + position: self.get_position(), + hidpi_factor: self.get_hidpi_factor(), + }; + + monitor_id_proxy.fmt(f) + } +} impl MonitorId { #[inline] @@ -176,21 +209,24 @@ impl MonitorId { } #[inline] - pub fn get_dimensions(&self) -> (u32, u32) { + pub fn get_dimensions(&self) -> PhysicalSize { unsafe { let window = android_glue::get_native_window(); - (ffi::ANativeWindow_getWidth(window) as u32, ffi::ANativeWindow_getHeight(window) as u32) + ( + ffi::ANativeWindow_getWidth(window) as f64, + ffi::ANativeWindow_getHeight(window) as f64, + ).into() } } #[inline] - pub fn get_position(&self) -> (i32, i32) { + pub fn get_position(&self) -> PhysicalPosition { // Android assumes single screen - (0, 0) + (0, 0).into() } #[inline] - pub fn get_hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { 1.0 } } @@ -205,10 +241,6 @@ impl Window { _: PlatformSpecificWindowBuilderAttributes) -> Result { - // not implemented - assert!(win_attribs.min_dimensions.is_none()); - assert!(win_attribs.max_dimensions.is_none()); - let native_window = unsafe { android_glue::get_native_window() }; if native_window.is_null() { return Err(OsError(format!("Android's native window is null"))); @@ -228,98 +260,103 @@ impl Window { #[inline] pub fn set_title(&self, _: &str) { + // N/A } #[inline] pub fn show(&self) { + // N/A } #[inline] pub fn hide(&self) { + // N/A } #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { + pub fn get_position(&self) -> Option { + // N/A None } #[inline] - pub fn get_inner_position(&self) -> Option<(i32, i32)> { + pub fn get_inner_position(&self) -> Option { + // N/A None } #[inline] - pub fn set_position(&self, _x: i32, _y: i32) { + pub fn set_position(&self, _position: LogicalPosition) { + // N/A } #[inline] - pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + pub fn set_min_dimensions(&self, _dimensions: Option) { + // N/A + } #[inline] - pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + pub fn set_max_dimensions(&self, _dimensions: Option) { + // N/A + } #[inline] pub fn set_resizable(&self, _resizable: bool) { // N/A } - + #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { + pub fn get_inner_size(&self) -> Option { if self.native_window.is_null() { None } else { - Some(( - unsafe { ffi::ANativeWindow_getWidth(self.native_window as *const _) } as u32, - unsafe { ffi::ANativeWindow_getHeight(self.native_window as *const _) } as u32 - )) + let dpi_factor = self.get_hidpi_factor(); + let physical_size = self.get_current_monitor().get_dimensions(); + Some(LogicalSize::from_physical(physical_size, dpi_factor)) } } #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { + pub fn get_outer_size(&self) -> Option { self.get_inner_size() } #[inline] - pub fn set_inner_size(&self, _x: u32, _y: u32) { - } - - #[inline] - pub fn platform_display(&self) -> *mut libc::c_void { - unimplemented!(); + pub fn set_inner_size(&self, _size: LogicalSize) { + // N/A } #[inline] - pub fn platform_window(&self) -> *mut libc::c_void { - unimplemented!() + pub fn get_hidpi_factor(&self) -> f64 { + self.get_current_monitor().get_hidpi_factor() } #[inline] pub fn set_cursor(&self, _: MouseCursor) { + // N/A } #[inline] pub fn set_cursor_state(&self, _state: CursorState) -> Result<(), String> { + // N/A Ok(()) } #[inline] - pub fn hidpi_factor(&self) -> f32 { - 1.0 - } - - #[inline] - pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ()> { + // N/A Ok(()) } #[inline] pub fn set_maximized(&self, _maximized: bool) { + // N/A // Android has single screen maximized apps so nothing to do } #[inline] pub fn set_fullscreen(&self, _monitor: Option) { + // N/A // Android has single screen maximized apps so nothing to do } @@ -339,15 +376,16 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _x: i32, _y: i32) { + pub fn set_ime_spot(&self, _spot: LogicalPosition) { // N/A } #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { - RootMonitorId{inner: MonitorId} + RootMonitorId { inner: MonitorId } } + #[inline] pub fn id(&self) -> WindowId { WindowId } diff --git a/src/platform/emscripten/ffi.rs b/src/platform/emscripten/ffi.rs index f1d12c14e3..8da2663636 100644 --- a/src/platform/emscripten/ffi.rs +++ b/src/platform/emscripten/ffi.rs @@ -1,6 +1,4 @@ -#![allow(dead_code)] -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] +#![allow(dead_code, non_camel_case_types, non_snake_case)] use std::os::raw::{c_int, c_char, c_void, c_ulong, c_double, c_long, c_ushort}; #[cfg(test)] diff --git a/src/platform/emscripten/mod.rs b/src/platform/emscripten/mod.rs index 9bd7f636fb..b31ad00dff 100644 --- a/src/platform/emscripten/mod.rs +++ b/src/platform/emscripten/mod.rs @@ -2,16 +2,21 @@ mod ffi; -use std::mem; +use std::{mem, ptr, str}; +use std::cell::RefCell; +use std::collections::VecDeque; use std::os::raw::{c_char, c_void, c_double, c_ulong, c_int}; -use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, Arc}; -use std::cell::RefCell; -use std::collections::VecDeque; + +use {LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; const DOCUMENT_NAME: &'static str = "#document\0"; +fn get_hidpi_factor() -> f64 { + unsafe { ffi::emscripten_get_device_pixel_ratio() as f64 } +} + #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes; @@ -34,18 +39,18 @@ impl MonitorId { } #[inline] - pub fn get_position(&self) -> (i32, i32) { + pub fn get_position(&self) -> PhysicalPosition { unimplemented!() } #[inline] - pub fn get_dimensions(&self) -> (u32, u32) { - (0, 0) + pub fn get_dimensions(&self) -> PhysicalSize { + (0, 0).into() } #[inline] - pub fn get_hidpi_factor(&self) -> f32 { - 1.0 + pub fn get_hidpi_factor(&self) -> f64 { + get_hidpi_factor() } } @@ -90,17 +95,19 @@ impl EventsLoop { } } + #[inline] pub fn interrupt(&self) { self.interrupted.store(true, Ordering::Relaxed); } + #[inline] pub fn create_proxy(&self) -> EventsLoopProxy { unimplemented!() } #[inline] pub fn get_available_monitors(&self) -> VecDeque { - let mut list = VecDeque::new(); + let mut list = VecDeque::with_capacity(1); list.push_back(MonitorId); list } @@ -115,7 +122,7 @@ impl EventsLoop { { let ref mut window = *self.window.lock().unwrap(); if let &mut Some(ref mut window) = window { - while let Some(event) = window.events.borrow_mut().pop_front() { + while let Some(event) = window.events.lock().unwrap().pop_front() { callback(event) } } @@ -144,7 +151,7 @@ pub struct WindowId(usize); pub struct Window2 { cursor_state: Mutex<::CursorState>, is_fullscreen: bool, - events: Box>>, + events: Box>>, } pub struct Window { @@ -176,7 +183,7 @@ extern "C" fn mouse_callback( event_queue: *mut c_void) -> ffi::EM_BOOL { unsafe { - let queue: &RefCell> = mem::transmute(event_queue); + let queue: &Mutex> = mem::transmute(event_queue); let modifiers = ::ModifiersState { shift: (*event).shiftKey == ffi::EM_TRUE, @@ -187,15 +194,20 @@ extern "C" fn mouse_callback( match event_type { ffi::EMSCRIPTEN_EVENT_MOUSEMOVE => { - queue.borrow_mut().push_back(::Event::WindowEvent { + let dpi_factor = get_hidpi_factor(); + let position = LogicalPosition::from_physical( + ((*event).canvasX as f64, (*event).canvasY as f64), + dpi_factor, + ); + queue.lock().unwrap().push_back(::Event::WindowEvent { window_id: ::WindowId(WindowId(0)), event: ::WindowEvent::CursorMoved { device_id: ::DeviceId(DeviceId), - position: ((*event).canvasX as f64, (*event).canvasY as f64), + position, modifiers: modifiers, } }); - queue.borrow_mut().push_back(::Event::DeviceEvent { + queue.lock().unwrap().push_back(::Event::DeviceEvent { device_id: ::DeviceId(DeviceId), event: ::DeviceEvent::MouseMotion { delta: ((*event).movementX as f64, (*event).movementY as f64), @@ -215,7 +227,7 @@ extern "C" fn mouse_callback( ffi::EMSCRIPTEN_EVENT_MOUSEUP => ::ElementState::Released, _ => unreachable!(), }; - queue.borrow_mut().push_back(::Event::WindowEvent { + queue.lock().unwrap().push_back(::Event::WindowEvent { window_id: ::WindowId(WindowId(0)), event: ::WindowEvent::MouseInput { device_id: ::DeviceId(DeviceId), @@ -238,7 +250,7 @@ extern "C" fn keyboard_callback( event_queue: *mut c_void) -> ffi::EM_BOOL { unsafe { - let queue: &RefCell> = mem::transmute(event_queue); + let queue: &Mutex> = mem::transmute(event_queue); let modifiers = ::ModifiersState { shift: (*event).shiftKey == ffi::EM_TRUE, @@ -249,7 +261,7 @@ extern "C" fn keyboard_callback( match event_type { ffi::EMSCRIPTEN_EVENT_KEYDOWN => { - queue.borrow_mut().push_back(::Event::WindowEvent { + queue.lock().unwrap().push_back(::Event::WindowEvent { window_id: ::WindowId(WindowId(0)), event: ::WindowEvent::KeyboardInput { device_id: ::DeviceId(DeviceId), @@ -263,7 +275,7 @@ extern "C" fn keyboard_callback( }); }, ffi::EMSCRIPTEN_EVENT_KEYUP => { - queue.borrow_mut().push_back(::Event::WindowEvent { + queue.lock().unwrap().push_back(::Event::WindowEvent { window_id: ::WindowId(WindowId(0)), event: ::WindowEvent::KeyboardInput { device_id: ::DeviceId(DeviceId), @@ -289,7 +301,7 @@ extern fn touch_callback( event_queue: *mut c_void) -> ffi::EM_BOOL { unsafe { - let queue: &RefCell> = mem::transmute(event_queue); + let queue: &Mutex> = mem::transmute(event_queue); let phase = match event_type { ffi::EMSCRIPTEN_EVENT_TOUCHSTART => ::TouchPhase::Started, @@ -302,13 +314,18 @@ extern fn touch_callback( for touch in 0..(*event).numTouches as usize { let touch = (*event).touches[touch]; if touch.isChanged == ffi::EM_TRUE { - queue.borrow_mut().push_back(::Event::WindowEvent { + let dpi_factor = get_hidpi_factor(); + let location = LogicalPosition::from_physical( + (touch.canvasX as f64, touch.canvasY as f64), + dpi_factor, + ); + queue.lock().unwrap().push_back(::Event::WindowEvent { window_id: ::WindowId(WindowId(0)), event: ::WindowEvent::Touch(::Touch { device_id: ::DeviceId(DeviceId), phase, id: touch.identifier as u64, - location: (touch.canvasX as f64, touch.canvasY as f64), + location, }), }); } @@ -356,8 +373,8 @@ impl Window { } let w = Window2 { - cursor_state: Mutex::new(::CursorState::Normal), - events: Box::new(RefCell::new(VecDeque::new())), + cursor_state: Default::default(), + events: Default::default(), is_fullscreen: attribs.fullscreen.is_some(), }; @@ -395,8 +412,8 @@ impl Window { em_try(ffi::emscripten_set_fullscreenchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(fullscreen_callback))) .map_err(|e| ::CreationError::OsError(e))?; } - } else if let Some((w, h)) = attribs.dimensions { - window.set_inner_size(w, h); + } else if let Some(size) = attribs.dimensions { + window.set_inner_size(size); } *events_loop.window.lock().unwrap() = Some(window.window.clone()); @@ -413,22 +430,22 @@ impl Window { } #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { - Some((0, 0)) + pub fn get_position(&self) -> Option { + Some((0, 0).into()) } #[inline] - pub fn get_inner_position(&self) -> Option<(i32, i32)> { - Some((0, 0)) + pub fn get_inner_position(&self) -> Option { + Some((0, 0).into()) } #[inline] - pub fn set_position(&self, _: i32, _: i32) { + pub fn set_position(&self, _: LogicalPosition) { } - pub fn get_inner_size(&self) -> Option<(u32, u32)> { + #[inline] + pub fn get_inner_size(&self) -> Option { unsafe { - use std::{mem, ptr}; let mut width = 0; let mut height = 0; let mut fullscreen = 0; @@ -438,31 +455,42 @@ impl Window { { None } else { - Some((width as u32, height as u32)) + let dpi_factor = self.get_hidpi_factor(); + let logical = LogicalSize::from_physical((width as u32, height as u32), dpi_factor); + Some(logical) } } } #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { + pub fn get_outer_size(&self) -> Option { self.get_inner_size() } #[inline] - pub fn set_inner_size(&self, width: u32, height: u32) { + pub fn set_inner_size(&self, size: LogicalSize) { unsafe { - use std::ptr; - ffi::emscripten_set_element_css_size(ptr::null(), width as c_double, height - as c_double); + let dpi_factor = self.get_hidpi_factor(); + let physical = PhysicalSize::from_logical(size, dpi_factor); + let (width, height): (u32, u32) = physical.into(); + ffi::emscripten_set_element_css_size( + ptr::null(), + width as c_double, + height as c_double, + ); } } #[inline] - pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + pub fn set_min_dimensions(&self, _dimensions: Option) { + // N/A + } #[inline] - pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } - + pub fn set_max_dimensions(&self, _dimensions: Option) { + // N/A + } + #[inline] pub fn set_resizable(&self, _resizable: bool) { // N/A @@ -473,16 +501,6 @@ impl Window { #[inline] pub fn hide(&self) {} - #[inline] - pub fn platform_display(&self) -> *mut ::libc::c_void { - unimplemented!() - } - - #[inline] - pub fn platform_window(&self) -> *mut ::libc::c_void { - unimplemented!() - } - #[inline] pub fn set_cursor(&self, _cursor: ::MouseCursor) {} @@ -524,12 +542,12 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f32 { - unsafe { ffi::emscripten_get_device_pixel_ratio() as f32 } + pub fn get_hidpi_factor(&self) -> f64 { + get_hidpi_factor() } #[inline] - pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ()> { Err(()) } @@ -559,7 +577,7 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _x: i32, _y: i32) { + pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) { // N/A } @@ -612,7 +630,6 @@ fn error_to_str(code: ffi::EMSCRIPTEN_RESULT) -> &'static str { } fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) -> u8 { - use std::str; let slice = &input[0..input.iter().take_while(|x| **x != 0).count()]; let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) }; let key = match maybe_key { @@ -629,7 +646,6 @@ fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) -> fn key_translate_virt(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES], location: c_ulong) -> Option<::VirtualKeyCode> { - use std::str; let slice = &input[0..input.iter().take_while(|x| **x != 0).count()]; let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) }; let key = match maybe_key { diff --git a/src/platform/ios/ffi.rs b/src/platform/ios/ffi.rs index cadc56231a..d3f0a8a836 100644 --- a/src/platform/ios/ffi.rs +++ b/src/platform/ios/ffi.rs @@ -1,22 +1,23 @@ +#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] + use std::ffi::CString; +use std::mem; +use std::os::raw::*; -use libc; -use objc::runtime::{ Object, Class }; +use objc::runtime::{Class, Object}; -#[allow(non_camel_case_types)] pub type id = *mut Object; - -#[allow(non_camel_case_types)] -#[allow(non_upper_case_globals)] pub const nil: id = 0 as id; -pub type CFStringRef = *const libc::c_void; +pub type CFStringRef = *const c_void; pub type CFTimeInterval = f64; pub type Boolean = u32; -#[allow(non_upper_case_globals)] pub const kCFRunLoopRunHandledSource: i32 = 4; +pub const UIViewAutoresizingFlexibleWidth: NSUInteger = 1 << 1; +pub const UIViewAutoresizingFlexibleHeight: NSUInteger = 1 << 4; + #[cfg(target_pointer_width = "32")] pub type CGFloat = f32; #[cfg(target_pointer_width = "64")] @@ -38,14 +39,14 @@ pub struct CGPoint { #[derive(Debug, Clone)] pub struct CGRect { pub origin: CGPoint, - pub size: CGSize + pub size: CGSize, } #[repr(C)] #[derive(Debug, Clone)] pub struct CGSize { pub width: CGFloat, - pub height: CGFloat + pub height: CGFloat, } #[link(name = "UIKit", kind = "framework")] @@ -55,15 +56,24 @@ extern { pub static kCFRunLoopDefaultMode: CFStringRef; // int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName ); - pub fn UIApplicationMain(argc: libc::c_int, argv: *const libc::c_char, principalClassName: id, delegateClassName: id) -> libc::c_int; + pub fn UIApplicationMain( + argc: c_int, + argv: *const c_char, + principalClassName: id, + delegateClassName: id, + ) -> c_int; // SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled ); - pub fn CFRunLoopRunInMode(mode: CFStringRef, seconds: CFTimeInterval, returnAfterSourceHandled: Boolean) -> i32; + pub fn CFRunLoopRunInMode( + mode: CFStringRef, + seconds: CFTimeInterval, + returnAfterSourceHandled: Boolean, + ) -> i32; } extern { - pub fn setjmp(env: *mut libc::c_void) -> libc::c_int; - pub fn longjmp(env: *mut libc::c_void, val: libc::c_int); + pub fn setjmp(env: *mut c_void) -> c_int; + pub fn longjmp(env: *mut c_void, val: c_int); } pub trait NSString: Sized { @@ -71,17 +81,14 @@ pub trait NSString: Sized { msg_send![class("NSString"), alloc] } - #[allow(non_snake_case)] - unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id; - #[allow(non_snake_case)] + unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id; unsafe fn stringByAppendingString_(self, other: id) -> id; unsafe fn init_str(self, string: &str) -> Self; - #[allow(non_snake_case)] - unsafe fn UTF8String(self) -> *const libc::c_char; + unsafe fn UTF8String(self) -> *const c_char; } impl NSString for id { - unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id { + unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id { msg_send![self, initWithUTF8String:c_string as id] } @@ -94,7 +101,7 @@ impl NSString for id { self.initWithUTF8String_(cstring.as_ptr()) } - unsafe fn UTF8String(self) -> *const libc::c_char { + unsafe fn UTF8String(self) -> *const c_char { msg_send![self, UTF8String] } } @@ -102,6 +109,6 @@ impl NSString for id { #[inline] pub fn class(name: &str) -> *mut Class { unsafe { - ::std::mem::transmute(Class::get(name)) + mem::transmute(Class::get(name)) } } diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index e135585102..bf06f4b308 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -19,7 +19,7 @@ //! //! ```rust, ignore //! #[no_mangle] -//! pub extern fn start_glutin_app() { +//! pub extern fn start_winit_app() { //! start_inner() //! } //! @@ -29,13 +29,13 @@ //! //! ``` //! -//! Compile project and then drag resulting .a into Xcode project. Add glutin.h to xcode. +//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode. //! //! ```ignore -//! void start_glutin_app(); +//! void start_winit_app(); //! ``` //! -//! Use start_glutin_app inside your xcode's main function. +//! Use start_winit_app inside your xcode's main function. //! //! //! # App lifecycle and events @@ -45,7 +45,7 @@ //! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/). //! //! -//! This is how those event are represented in glutin: +//! This is how those event are represented in winit: //! //! - applicationDidBecomeActive is Focused(true) //! - applicationWillResignActive is Focused(false) @@ -60,100 +60,147 @@ #![cfg(target_os = "ios")] +use std::{fmt, mem, ptr}; use std::collections::VecDeque; -use std::ptr; -use std::mem; -use std::os::raw::c_void; - -use libc; -use libc::c_int; -use objc::runtime::{Class, Object, Sel, BOOL, YES }; -use objc::declare::{ ClassDecl }; - -use { CreationError, CursorState, MouseCursor, WindowAttributes }; -use WindowId as RootEventId; -use WindowEvent; -use Event; -use events::{ Touch, TouchPhase }; +use std::os::raw::*; + +use objc::declare::ClassDecl; +use objc::runtime::{BOOL, Class, Object, Sel, YES}; + +use { + CreationError, + CursorState, + Event, + LogicalPosition, + LogicalSize, + MouseCursor, + PhysicalPosition, + PhysicalSize, + WindowAttributes, + WindowEvent, + WindowId as RootEventId, +}; +use events::{Touch, TouchPhase}; use window::MonitorId as RootMonitorId; mod ffi; use self::ffi::{ - setjmp, - UIApplicationMain, CFTimeInterval, CFRunLoopRunInMode, + CGFloat, + CGPoint, + CGRect, + id, kCFRunLoopDefaultMode, kCFRunLoopRunHandledSource, - id, + longjmp, nil, NSString, - CGFloat, - longjmp, - CGRect, - CGPoint + setjmp, + UIApplicationMain, + UIViewAutoresizingFlexibleWidth, + UIViewAutoresizingFlexibleHeight, }; -static mut jmpbuf: [c_int;27] = [0;27]; - -#[derive(Debug, Clone)] -pub struct MonitorId; +static mut JMPBUF: [c_int; 27] = [0; 27]; pub struct Window { - delegate_state: *mut DelegateState + delegate_state: *mut DelegateState, } -#[derive(Clone)] -pub struct WindowProxy; +unsafe impl Send for Window {} +unsafe impl Sync for Window {} #[derive(Debug)] struct DelegateState { events_queue: VecDeque, window: id, controller: id, - size: (u32,u32), - scale: f32 + view: id, + size: LogicalSize, + scale: f64, } - impl DelegateState { - #[inline] - fn new(window: id, controller:id, size: (u32,u32), scale: f32) -> DelegateState { + fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState { DelegateState { events_queue: VecDeque::new(), - window: window, - controller: controller, - size: size, - scale: scale + window, + controller, + view, + size, + scale, } } } +impl Drop for DelegateState { + fn drop(&mut self) { + unsafe { + let _: () = msg_send![self.window, release]; + let _: () = msg_send![self.controller, release]; + let _: () = msg_send![self.view, release]; + } + } +} + +#[derive(Clone)] +pub struct MonitorId; + +impl fmt::Debug for MonitorId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[derive(Debug)] + struct MonitorId { + name: Option, + dimensions: PhysicalSize, + position: PhysicalPosition, + hidpi_factor: f64, + } + + let monitor_id_proxy = MonitorId { + name: self.get_name(), + dimensions: self.get_dimensions(), + position: self.get_position(), + hidpi_factor: self.get_hidpi_factor(), + }; + + monitor_id_proxy.fmt(f) + } +} + impl MonitorId { + #[inline] + pub fn get_uiscreen(&self) -> id { + let class = Class::get("UIScreen").expect("Failed to get class `UIScreen`"); + unsafe { msg_send![class, mainScreen] } + } + #[inline] pub fn get_name(&self) -> Option { Some("Primary".to_string()) } #[inline] - pub fn get_dimensions(&self) -> (u32, u32) { - unimplemented!() + pub fn get_dimensions(&self) -> PhysicalSize { + let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] }; + (bounds.size.width as f64, bounds.size.height as f64).into() } #[inline] - pub fn get_position(&self) -> (i32, i32) { + pub fn get_position(&self) -> PhysicalPosition { // iOS assumes single screen - (0, 0) + (0, 0).into() } #[inline] - pub fn get_hidpi_factor(&self) -> f32 { - 1.0 + pub fn get_hidpi_factor(&self) -> f64 { + let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] }; + scale as f64 } } pub struct EventsLoop { - delegate_state: *mut DelegateState + delegate_state: *mut DelegateState, } #[derive(Clone)] @@ -162,30 +209,26 @@ pub struct EventsLoopProxy; impl EventsLoop { pub fn new() -> EventsLoop { unsafe { - if setjmp(mem::transmute(&mut jmpbuf)) != 0 { - let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication]; + if setjmp(mem::transmute(&mut JMPBUF)) != 0 { + let app_class = Class::get("UIApplication").expect("Failed to get class `UIApplication`"); + let app: id = msg_send![app_class, sharedApplication]; let delegate: id = msg_send![app, delegate]; - let state: *mut c_void = *(&*delegate).get_ivar("glutinState"); - let state = state as *mut DelegateState; - - let events_loop = EventsLoop { - delegate_state: state - }; - - return events_loop; + let state: *mut c_void = *(&*delegate).get_ivar("winitState"); + let delegate_state = state as *mut DelegateState; + return EventsLoop { delegate_state }; } } - create_delegate_class(); create_view_class(); + create_delegate_class(); start_app(); - panic!("Couldn't create UIApplication") + panic!("Couldn't create `UIApplication`!") } #[inline] pub fn get_available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::new(); + let mut rb = VecDeque::with_capacity(1); rb.push_back(MonitorId); rb } @@ -207,7 +250,7 @@ impl EventsLoop { } // jump hack, so we won't quit on willTerminate event before processing it - if setjmp(mem::transmute(&mut jmpbuf)) != 0 { + if setjmp(mem::transmute(&mut JMPBUF)) != 0 { if let Some(event) = state.events_queue.pop_front() { callback(event); return; @@ -262,111 +305,120 @@ pub struct DeviceId; #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes; +// TODO: AFAIK transparency is enabled by default on iOS, +// so to be consistent with other platforms we have to change that. impl Window { - pub fn new(ev: &EventsLoop, _: WindowAttributes, _: PlatformSpecificWindowBuilderAttributes) - -> Result - { - Ok(Window { - delegate_state: ev.delegate_state, - }) + pub fn new( + ev: &EventsLoop, + _attributes: WindowAttributes, + _pl_alltributes: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + Ok(Window { delegate_state: ev.delegate_state }) } #[inline] - pub fn set_title(&self, _: &str) { + pub fn get_uiwindow(&self) -> id { + unsafe { (*self.delegate_state).window } + } + + #[inline] + pub fn get_uiview(&self) -> id { + unsafe { (*self.delegate_state).view } + } + + #[inline] + pub fn set_title(&self, _title: &str) { + // N/A } #[inline] pub fn show(&self) { + // N/A } #[inline] pub fn hide(&self) { + // N/A } #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { + pub fn get_position(&self) -> Option { + // N/A None } #[inline] - pub fn get_inner_position(&self) -> Option<(i32, i32)> { + pub fn get_inner_position(&self) -> Option { + // N/A None } #[inline] - pub fn set_position(&self, _x: i32, _y: i32) { + pub fn set_position(&self, _position: LogicalPosition) { + // N/A } #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { + pub fn get_inner_size(&self) -> Option { unsafe { Some((&*self.delegate_state).size) } } #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { + pub fn get_outer_size(&self) -> Option { self.get_inner_size() } #[inline] - pub fn set_inner_size(&self, _x: u32, _y: u32) { + pub fn set_inner_size(&self, _size: LogicalSize) { + // N/A } #[inline] - pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { } - - #[inline] - pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } - - #[inline] - pub fn set_resizable(&self, _resizable: bool) { + pub fn set_min_dimensions(&self, _dimensions: Option) { // N/A } - - #[inline] - pub fn platform_display(&self) -> *mut libc::c_void { - unimplemented!(); - } #[inline] - pub fn platform_window(&self) -> *mut libc::c_void { - unimplemented!() + pub fn set_max_dimensions(&self, _dimensions: Option) { + // N/A } #[inline] - pub fn set_window_resize_callback(&mut self, _: Option) { + pub fn set_resizable(&self, _resizable: bool) { + // N/A } #[inline] - pub fn set_cursor(&self, _: MouseCursor) { + pub fn set_cursor(&self, _cursor: MouseCursor) { + // N/A } #[inline] - pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> { + pub fn set_cursor_state(&self, _cursor_state: CursorState) -> Result<(), String> { + // N/A Ok(()) } #[inline] - pub fn hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { unsafe { (&*self.delegate_state) }.scale } #[inline] - pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { - unimplemented!(); - } - - #[inline] - pub fn create_window_proxy(&self) -> WindowProxy { - WindowProxy + pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ()> { + // N/A + Ok(()) } #[inline] pub fn set_maximized(&self, _maximized: bool) { + // N/A // iOS has single screen maximized apps so nothing to do } #[inline] pub fn set_fullscreen(&self, _monitor: Option) { + // N/A // iOS has single screen maximized apps so nothing to do } @@ -386,13 +438,13 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _x: i32, _y: i32) { + pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) { // N/A } #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { - RootMonitorId{inner: MonitorId} + RootMonitorId { inner: MonitorId } } #[inline] @@ -403,26 +455,32 @@ impl Window { fn create_delegate_class() { extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL { + let screen_class = Class::get("UIScreen").expect("Failed to get class `UIScreen`"); + let window_class = Class::get("UIWindow").expect("Failed to get class `UIWindow`"); + let controller_class = Class::get("MainViewController").expect("Failed to get class `MainViewController`"); + let view_class = Class::get("MainView").expect("Failed to get class `MainView`"); unsafe { - let main_screen: id = msg_send![Class::get("UIScreen").unwrap(), mainScreen]; + let main_screen: id = msg_send![screen_class, mainScreen]; let bounds: CGRect = msg_send![main_screen, bounds]; let scale: CGFloat = msg_send![main_screen, nativeScale]; - let window: id = msg_send![Class::get("UIWindow").unwrap(), alloc]; + let window: id = msg_send![window_class, alloc]; let window: id = msg_send![window, initWithFrame:bounds.clone()]; - let size = (bounds.size.width as u32, bounds.size.height as u32); + let size = (bounds.size.width as f64, bounds.size.height as f64).into(); - let view_controller: id = msg_send![Class::get("MainViewController").unwrap(), alloc]; + let view_controller: id = msg_send![controller_class, alloc]; let view_controller: id = msg_send![view_controller, init]; + let view: id = msg_send![view_class, alloc]; + let view: id = msg_send![view, initForGl:&bounds]; + let _: () = msg_send![window, setRootViewController:view_controller]; let _: () = msg_send![window, makeKeyAndVisible]; - let state = Box::new(DelegateState::new(window, view_controller, size, scale as f32)); + let state = Box::new(DelegateState::new(window, view_controller, view, size, scale as f64)); let state_ptr: *mut DelegateState = mem::transmute(state); - this.set_ivar("glutinState", state_ptr as *mut c_void); - + this.set_ivar("winitState", state_ptr as *mut c_void); let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0]; } @@ -430,12 +488,12 @@ fn create_delegate_class() { } extern fn post_launch(_: &Object, _: Sel, _: id) { - unsafe { longjmp(mem::transmute(&mut jmpbuf),1); } + unsafe { longjmp(mem::transmute(&mut JMPBUF),1); } } extern fn did_become_active(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); state.events_queue.push_back(Event::WindowEvent { window_id: RootEventId(WindowId), @@ -446,7 +504,7 @@ fn create_delegate_class() { extern fn will_resign_active(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); state.events_queue.push_back(Event::WindowEvent { window_id: RootEventId(WindowId), @@ -457,7 +515,7 @@ fn create_delegate_class() { extern fn will_enter_foreground(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); state.events_queue.push_back(Event::Suspended(false)); } @@ -465,7 +523,7 @@ fn create_delegate_class() { extern fn did_enter_background(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); state.events_queue.push_back(Event::Suspended(true)); } @@ -473,7 +531,7 @@ fn create_delegate_class() { extern fn will_terminate(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); // push event to the front to garantee that we'll process it // immidiatly after jump @@ -481,13 +539,13 @@ fn create_delegate_class() { window_id: RootEventId(WindowId), event: WindowEvent::Destroyed, }); - longjmp(mem::transmute(&mut jmpbuf),1); + longjmp(mem::transmute(&mut JMPBUF),1); } } extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); let touches_enum: id = msg_send![touches, objectEnumerator]; @@ -506,7 +564,7 @@ fn create_delegate_class() { event: WindowEvent::Touch(Touch { device_id: DEVICE_ID, id: touch_id, - location: (location.x as f64, location.y as f64), + location: (location.x as f64, location.y as f64).into(), phase: match phase { 0 => TouchPhase::Started, 1 => TouchPhase::Moved, @@ -521,8 +579,8 @@ fn create_delegate_class() { } } - let ui_responder = Class::get("UIResponder").unwrap(); - let mut decl = ClassDecl::new("AppDelegate", ui_responder).unwrap(); + let ui_responder = Class::get("UIResponder").expect("Failed to get class `UIResponder`"); + let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`"); unsafe { decl.add_method(sel!(application:didFinishLaunchingWithOptions:), @@ -560,17 +618,45 @@ fn create_delegate_class() { decl.add_method(sel!(postLaunch:), post_launch as extern fn(&Object, Sel, id)); - decl.add_ivar::<*mut c_void>("glutinState"); + decl.add_ivar::<*mut c_void>("winitState"); decl.register(); } } -fn create_view_class() { - let ui_view_controller = Class::get("UIViewController").unwrap(); - let decl = ClassDecl::new("MainViewController", ui_view_controller).unwrap(); - +// TODO: winit shouldn't contain GL-specfiic code +pub fn create_view_class() { + let superclass = Class::get("UIViewController").expect("Failed to get class `UIViewController`"); + let decl = ClassDecl::new("MainViewController", superclass).expect("Failed to declare class `MainViewController`"); decl.register(); + + extern fn init_for_gl(this: &Object, _: Sel, frame: *const c_void) -> id { + unsafe { + let bounds = frame as *const CGRect; + let view: id = msg_send![this, initWithFrame:(*bounds).clone()]; + + let mask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + let _: () = msg_send![view, setAutoresizingMask:mask]; + let _: () = msg_send![view, setAutoresizesSubviews:YES]; + + let layer: id = msg_send![view, layer]; + let _ : () = msg_send![layer, setOpaque:YES]; + + view + } + } + + extern fn layer_class(_: &Class, _: Sel) -> *const Class { + unsafe { mem::transmute(Class::get("CAEAGLLayer").expect("Failed to get class `CAEAGLLayer`")) } + } + + let superclass = Class::get("GLKView").expect("Failed to get class `GLKView`"); + let mut decl = ClassDecl::new("MainView", superclass).expect("Failed to declare class `MainView`"); + unsafe { + decl.add_method(sel!(initForGl:), init_for_gl as extern fn(&Object, Sel, *const c_void) -> id); + decl.add_class_method(sel!(layerClass), layer_class as extern fn(&Class, Sel) -> *const Class); + decl.register(); + } } #[inline] diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 2083462352..e49c688d99 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -8,15 +8,16 @@ use std::sync::Arc; use sctk::reexports::client::ConnectError; -// `std::os::raw::c_void` and `libc::c_void` are NOT interchangeable! -use libc; - use { CreationError, CursorState, EventsLoopClosed, Icon, + LogicalPosition, + LogicalSize, MouseCursor, + PhysicalPosition, + PhysicalSize, ControlFlow, WindowAttributes, }; @@ -57,19 +58,19 @@ thread_local!( pub enum Window { X(x11::Window), - Wayland(wayland::Window) + Wayland(wayland::Window), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum WindowId { X(x11::WindowId), - Wayland(wayland::WindowId) + Wayland(wayland::WindowId), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DeviceId { X(x11::DeviceId), - Wayland(wayland::DeviceId) + Wayland(wayland::DeviceId), } #[derive(Debug, Clone)] @@ -96,7 +97,7 @@ impl MonitorId { } #[inline] - pub fn get_dimensions(&self) -> (u32, u32) { + pub fn get_dimensions(&self) -> PhysicalSize { match self { &MonitorId::X(ref m) => m.get_dimensions(), &MonitorId::Wayland(ref m) => m.get_dimensions(), @@ -104,7 +105,7 @@ impl MonitorId { } #[inline] - pub fn get_position(&self) -> (i32, i32) { + pub fn get_position(&self) -> PhysicalPosition { match self { &MonitorId::X(ref m) => m.get_position(), &MonitorId::Wayland(ref m) => m.get_position(), @@ -112,10 +113,10 @@ impl MonitorId { } #[inline] - pub fn get_hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { match self { &MonitorId::X(ref m) => m.get_hidpi_factor(), - &MonitorId::Wayland(ref m) => m.get_hidpi_factor(), + &MonitorId::Wayland(ref m) => m.get_hidpi_factor() as f64, } } } @@ -141,7 +142,7 @@ impl Window { pub fn id(&self) -> WindowId { match self { &Window::X(ref w) => WindowId::X(w.id()), - &Window::Wayland(ref w) => WindowId::Wayland(w.id()) + &Window::Wayland(ref w) => WindowId::Wayland(w.id()), } } @@ -149,7 +150,7 @@ impl Window { pub fn set_title(&self, title: &str) { match self { &Window::X(ref w) => w.set_title(title), - &Window::Wayland(ref w) => w.set_title(title) + &Window::Wayland(ref w) => w.set_title(title), } } @@ -157,7 +158,7 @@ impl Window { pub fn show(&self) { match self { &Window::X(ref w) => w.show(), - &Window::Wayland(ref w) => w.show() + &Window::Wayland(ref w) => w.show(), } } @@ -165,20 +166,20 @@ impl Window { pub fn hide(&self) { match self { &Window::X(ref w) => w.hide(), - &Window::Wayland(ref w) => w.hide() + &Window::Wayland(ref w) => w.hide(), } } #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { + pub fn get_position(&self) -> Option { match self { &Window::X(ref w) => w.get_position(), - &Window::Wayland(ref w) => w.get_position() + &Window::Wayland(ref w) => w.get_position(), } } #[inline] - pub fn get_inner_position(&self) -> Option<(i32, i32)> { + pub fn get_inner_position(&self) -> Option { match self { &Window::X(ref m) => m.get_inner_position(), &Window::Wayland(ref m) => m.get_inner_position(), @@ -186,53 +187,53 @@ impl Window { } #[inline] - pub fn set_position(&self, x: i32, y: i32) { + pub fn set_position(&self, position: LogicalPosition) { match self { - &Window::X(ref w) => w.set_position(x, y), - &Window::Wayland(ref w) => w.set_position(x, y) + &Window::X(ref w) => w.set_position(position), + &Window::Wayland(ref w) => w.set_position(position), } } #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { + pub fn get_inner_size(&self) -> Option { match self { &Window::X(ref w) => w.get_inner_size(), - &Window::Wayland(ref w) => w.get_inner_size() + &Window::Wayland(ref w) => w.get_inner_size(), } } #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { + pub fn get_outer_size(&self) -> Option { match self { &Window::X(ref w) => w.get_outer_size(), - &Window::Wayland(ref w) => w.get_outer_size() + &Window::Wayland(ref w) => w.get_outer_size(), } } #[inline] - pub fn set_inner_size(&self, x: u32, y: u32) { + pub fn set_inner_size(&self, size: LogicalSize) { match self { - &Window::X(ref w) => w.set_inner_size(x, y), - &Window::Wayland(ref w) => w.set_inner_size(x, y) + &Window::X(ref w) => w.set_inner_size(size), + &Window::Wayland(ref w) => w.set_inner_size(size), } } #[inline] - pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + pub fn set_min_dimensions(&self, dimensions: Option) { match self { &Window::X(ref w) => w.set_min_dimensions(dimensions), - &Window::Wayland(ref w) => w.set_min_dimensions(dimensions) + &Window::Wayland(ref w) => w.set_min_dimensions(dimensions), } } #[inline] - pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + pub fn set_max_dimensions(&self, dimensions: Option) { match self { &Window::X(ref w) => w.set_max_dimensions(dimensions), - &Window::Wayland(ref w) => w.set_max_dimensions(dimensions) + &Window::Wayland(ref w) => w.set_max_dimensions(dimensions), } } - + #[inline] pub fn set_resizable(&self, resizable: bool) { match self { @@ -258,34 +259,18 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { match self { - &Window::X(ref w) => w.hidpi_factor(), - &Window::Wayland(ref w) => w.hidpi_factor() - } - } - - #[inline] - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { - match self { - &Window::X(ref w) => w.set_cursor_position(x, y), - &Window::Wayland(ref w) => w.set_cursor_position(x, y) - } - } - - #[inline] - pub fn platform_display(&self) -> *mut libc::c_void { - match self { - &Window::X(ref w) => w.platform_display(), - &Window::Wayland(ref w) => w.get_display().c_ptr() as *mut _ + &Window::X(ref w) => w.get_hidpi_factor(), + &Window::Wayland(ref w) => w.hidpi_factor() as f64, } } #[inline] - pub fn platform_window(&self) -> *mut libc::c_void { + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ()> { match self { - &Window::X(ref w) => w.platform_window(), - &Window::Wayland(ref w) => w.get_surface().c_ptr() as *mut _ + &Window::X(ref w) => w.set_cursor_position(position), + &Window::Wayland(ref w) => w.set_cursor_position(position), } } @@ -330,9 +315,9 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, x: i32, y: i32) { + pub fn set_ime_spot(&self, position: LogicalPosition) { match self { - &Window::X(ref w) => w.send_xim_spot(x as i16, y as i16), + &Window::X(ref w) => w.set_ime_spot(position), &Window::Wayland(_) => (), } } @@ -447,14 +432,17 @@ r#"Failed to initialize any backend! #[inline] pub fn get_available_monitors(&self) -> VecDeque { match *self { - EventsLoop::Wayland(ref evlp) => evlp.get_available_monitors() - .into_iter() - .map(MonitorId::Wayland) - .collect(), - EventsLoop::X(ref evlp) => x11::get_available_monitors(evlp.x_connection()) - .into_iter() - .map(MonitorId::X) - .collect(), + EventsLoop::Wayland(ref evlp) => evlp + .get_available_monitors() + .into_iter() + .map(MonitorId::Wayland) + .collect(), + EventsLoop::X(ref evlp) => evlp + .x_connection() + .get_available_monitors() + .into_iter() + .map(MonitorId::X) + .collect(), } } @@ -462,7 +450,7 @@ r#"Failed to initialize any backend! pub fn get_primary_monitor(&self) -> MonitorId { match *self { EventsLoop::Wayland(ref evlp) => MonitorId::Wayland(evlp.get_primary_monitor()), - EventsLoop::X(ref evlp) => MonitorId::X(x11::get_primary_monitor(evlp.x_connection())), + EventsLoop::X(ref evlp) => MonitorId::X(evlp.x_connection().get_primary_monitor()), } } diff --git a/src/platform/linux/wayland/event_loop.rs b/src/platform/linux/wayland/event_loop.rs index c124bec53f..99a34cac94 100644 --- a/src/platform/linux/wayland/event_loop.rs +++ b/src/platform/linux/wayland/event_loop.rs @@ -4,7 +4,7 @@ use std::fmt; use std::sync::{Arc, Mutex, Weak}; use std::sync::atomic::{AtomicBool, Ordering}; -use {ControlFlow, EventsLoopClosed}; +use {ControlFlow, EventsLoopClosed, PhysicalPosition, PhysicalSize}; use super::WindowId; use super::window::WindowStore; @@ -248,16 +248,21 @@ impl EventsLoop { } // process pending resize/refresh self.store.lock().unwrap().for_each( - |newsize, refresh, frame_refresh, closed, wid, frame| { + |newsize, size, new_dpi, refresh, frame_refresh, closed, wid, frame| { if let Some(frame) = frame { if let Some((w, h)) = newsize { - frame.resize(w as u32, h as u32); + frame.resize(w, h); frame.refresh(); - sink.send_event(::WindowEvent::Resized(w as u32, h as u32), wid); + let logical_size = ::LogicalSize::new(w as f64, h as f64); + sink.send_event(::WindowEvent::Resized(logical_size), wid); + *size = (w, h); } else if frame_refresh { frame.refresh(); } } + if let Some(dpi) = new_dpi { + sink.send_event(::WindowEvent::HiDpiFactorChanged(dpi as f64), wid); + } if refresh { sink.send_event(::WindowEvent::Refresh, wid); } @@ -434,9 +439,9 @@ impl fmt::Debug for MonitorId { struct MonitorId { name: Option, native_identifier: u32, - dimensions: (u32, u32), - position: (i32, i32), - hidpi_factor: f32, + dimensions: PhysicalSize, + position: PhysicalPosition, + hidpi_factor: i32, } let monitor_id_proxy = MonitorId { @@ -463,7 +468,7 @@ impl MonitorId { self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0) } - pub fn get_dimensions(&self) -> (u32, u32) { + pub fn get_dimensions(&self) -> PhysicalSize { match self.mgr.with_info(&self.proxy, |_, info| { info.modes .iter() @@ -472,19 +477,20 @@ impl MonitorId { }) { Some(Some((w, h))) => (w as u32, h as u32), _ => (0, 0), - } + }.into() } - pub fn get_position(&self) -> (i32, i32) { + pub fn get_position(&self) -> PhysicalPosition { self.mgr .with_info(&self.proxy, |_, info| info.location) .unwrap_or((0, 0)) + .into() } #[inline] - pub fn get_hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> i32 { self.mgr - .with_info(&self.proxy, |_, info| info.scale_factor as f32) - .unwrap_or(1.0) + .with_info(&self.proxy, |_, info| info.scale_factor) + .unwrap_or(1) } } diff --git a/src/platform/linux/wayland/pointer.rs b/src/platform/linux/wayland/pointer.rs index 48f2bdac2f..51b02f89a9 100644 --- a/src/platform/linux/wayland/pointer.rs +++ b/src/platform/linux/wayland/pointer.rs @@ -42,7 +42,7 @@ pub fn implement_pointer( sink.send_event( WindowEvent::CursorMoved { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), - position: (surface_x, surface_y), + position: (surface_x, surface_y).into(), // TODO: replace dummy value with actual modifier state modifiers: ModifiersState::default(), }, @@ -71,7 +71,7 @@ pub fn implement_pointer( sink.send_event( WindowEvent::CursorMoved { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), - position: (surface_x, surface_y), + position: (surface_x, surface_y).into(), // TODO: replace dummy value with actual modifier state modifiers: ModifiersState::default(), }, @@ -117,7 +117,7 @@ pub fn implement_pointer( sink.send_event( WindowEvent::MouseWheel { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), - delta: MouseScrollDelta::PixelDelta(x as f32, y as f32), + delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()), phase: TouchPhase::Moved, // TODO: replace dummy value with actual modifier state modifiers: ModifiersState::default(), @@ -158,7 +158,7 @@ pub fn implement_pointer( sink.send_event( WindowEvent::MouseWheel { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), - delta: MouseScrollDelta::PixelDelta(x as f32, y as f32), + delta: MouseScrollDelta::PixelDelta((x as f64, y as f64).into()), phase: axis_state, // TODO: replace dummy value with actual modifier state modifiers: ModifiersState::default(), diff --git a/src/platform/linux/wayland/touch.rs b/src/platform/linux/wayland/touch.rs index bd29afe2e2..01416b9cfd 100644 --- a/src/platform/linux/wayland/touch.rs +++ b/src/platform/linux/wayland/touch.rs @@ -34,7 +34,7 @@ pub(crate) fn implement_touch( WindowEvent::Touch(::Touch { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), phase: TouchPhase::Started, - location: (x, y), + location: (x, y).into(), id: id as u64, }), wid, @@ -54,7 +54,7 @@ pub(crate) fn implement_touch( WindowEvent::Touch(::Touch { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), phase: TouchPhase::Ended, - location: pt.location, + location: pt.location.into(), id: id as u64, }), pt.wid, @@ -69,7 +69,7 @@ pub(crate) fn implement_touch( WindowEvent::Touch(::Touch { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), phase: TouchPhase::Moved, - location: (x, y), + location: (x, y).into(), id: id as u64, }), pt.wid, @@ -82,7 +82,7 @@ pub(crate) fn implement_touch( WindowEvent::Touch(::Touch { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), phase: TouchPhase::Cancelled, - location: pt.location, + location: pt.location.into(), id: pt.id as u64, }), pt.wid, diff --git a/src/platform/linux/wayland/window.rs b/src/platform/linux/wayland/window.rs index ae722759db..eddeae07c6 100644 --- a/src/platform/linux/wayland/window.rs +++ b/src/platform/linux/wayland/window.rs @@ -1,12 +1,12 @@ use std::sync::{Arc, Mutex, Weak}; -use {CreationError, CursorState, MouseCursor, WindowAttributes}; +use {CreationError, CursorState, MouseCursor, WindowAttributes, LogicalPosition, LogicalSize}; use platform::MonitorId as PlatformMonitorId; use window::MonitorId as RootMonitorId; use sctk::window::{BasicFrame, Event as WEvent, Window as SWindow}; use sctk::reexports::client::{Display, Proxy}; -use sctk::reexports::client::protocol::{wl_seat, wl_surface}; +use sctk::reexports::client::protocol::{wl_seat, wl_surface, wl_output}; use sctk::reexports::client::protocol::wl_compositor::RequestsTrait as CompositorRequests; use sctk::reexports::client::protocol::wl_surface::RequestsTrait as SurfaceRequests; @@ -15,7 +15,7 @@ use super::{make_wid, EventsLoop, MonitorId, WindowId}; pub struct Window { surface: Proxy, frame: Arc>>, - monitors: Arc>>, + monitors: Arc>, size: Arc>, kill_switch: (Arc>, Arc>), display: Arc, @@ -24,23 +24,42 @@ pub struct Window { impl Window { pub fn new(evlp: &EventsLoop, attributes: WindowAttributes) -> Result { - let (width, height) = attributes.dimensions.unwrap_or((800, 600)); + // TODO: Update for new DPI API + //let (width, height) = attributes.dimensions.unwrap_or((800, 600)); + let (width, height) = (64, 64); // Create the window let size = Arc::new(Mutex::new((width, height))); // monitor tracking - let monitor_list = Arc::new(Mutex::new(Vec::new())); + let monitor_list = Arc::new(Mutex::new(MonitorList::new())); let surface = evlp.env.compositor.create_surface().unwrap().implement({ let list = monitor_list.clone(); let omgr = evlp.env.outputs.clone(); - move |event, _| match event { - wl_surface::Event::Enter { output } => list.lock().unwrap().push(MonitorId { - proxy: output, - mgr: omgr.clone(), - }), + let window_store = evlp.store.clone(); + move |event, surface: Proxy| match event { + wl_surface::Event::Enter { output } => { + let dpi_change = list.lock().unwrap().add_output(MonitorId { + proxy: output, + mgr: omgr.clone(), + }); + if let Some(dpi) = dpi_change { + if surface.version() >= 3 { + // without version 3 we can't be dpi aware + window_store.lock().unwrap().dpi_change(&surface, dpi); + surface.set_buffer_scale(dpi); + } + } + }, wl_surface::Event::Leave { output } => { - list.lock().unwrap().retain(|m| !m.proxy.equals(&output)); + let dpi_change = list.lock().unwrap().del_output(&output); + if let Some(dpi) = dpi_change { + if surface.version() >= 3 { + // without version 3 we can't be dpi aware + window_store.lock().unwrap().dpi_change(&surface, dpi); + surface.set_buffer_scale(dpi); + } + } } } }); @@ -59,7 +78,7 @@ impl Window { let mut store = window_store.lock().unwrap(); for window in &mut store.windows { if window.surface.equals(&my_surface) { - window.newsize = new_size.map(|(w, h)| (w as i32, h as i32)); + window.newsize = new_size; window.need_refresh = true; *(window.need_frame_refresh.lock().unwrap()) = true; return; @@ -107,8 +126,9 @@ impl Window { frame.set_decorate(attributes.decorations); // min-max dimensions - frame.set_min_size(attributes.min_dimensions); - frame.set_max_size(attributes.max_dimensions); + // TODO: Update for new DPI API + //frame.set_min_size(attributes.min_dimensions); + //frame.set_max_size(attributes.max_dimensions); let kill_switch = Arc::new(Mutex::new(false)); let need_frame_refresh = Arc::new(Mutex::new(true)); @@ -117,11 +137,14 @@ impl Window { evlp.store.lock().unwrap().windows.push(InternalWindow { closed: false, newsize: None, + size: size.clone(), need_refresh: false, need_frame_refresh: need_frame_refresh.clone(), surface: surface.clone(), kill_switch: kill_switch.clone(), frame: Arc::downgrade(&frame), + current_dpi: 1, + new_dpi: None, }); evlp.evq.borrow_mut().sync_roundtrip().unwrap(); @@ -156,48 +179,49 @@ impl Window { } #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { + pub fn get_position(&self) -> Option { // Not possible with wayland None } #[inline] - pub fn get_inner_position(&self) -> Option<(i32, i32)> { + pub fn get_inner_position(&self) -> Option { // Not possible with wayland None } #[inline] - pub fn set_position(&self, _x: i32, _y: i32) { + pub fn set_position(&self, _pos: LogicalPosition) { // Not possible with wayland } - pub fn get_inner_size(&self) -> Option<(u32, u32)> { - Some(self.size.lock().unwrap().clone()) + pub fn get_inner_size(&self) -> Option { + Some(self.size.lock().unwrap().clone().into()) } #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { + pub fn get_outer_size(&self) -> Option { let (w, h) = self.size.lock().unwrap().clone(); // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); - Some((w as u32, h as u32)) + Some((w, h).into()) } #[inline] // NOTE: This will only resize the borders, the contents must be updated by the user - pub fn set_inner_size(&self, x: u32, y: u32) { - self.frame.lock().unwrap().resize(x, y); - *(self.size.lock().unwrap()) = (x, y); + pub fn set_inner_size(&self, size: LogicalSize) { + let (w, h) = size.into(); + self.frame.lock().unwrap().resize(w, h); + *(self.size.lock().unwrap()) = (w, h); } #[inline] - pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { - self.frame.lock().unwrap().set_min_size(dimensions); + pub fn set_min_dimensions(&self, dimensions: Option) { + self.frame.lock().unwrap().set_min_size(dimensions.map(Into::into)); } #[inline] - pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { - self.frame.lock().unwrap().set_max_size(dimensions); + pub fn set_max_dimensions(&self, dimensions: Option) { + self.frame.lock().unwrap().set_max_size(dimensions.map(Into::into)); } #[inline] @@ -222,14 +246,8 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f32 { - let mut factor: f32 = 1.0; - let guard = self.monitors.lock().unwrap(); - for monitor_id in guard.iter() { - let hidpif = monitor_id.get_hidpi_factor(); - factor = factor.max(hidpif); - } - factor + pub fn hidpi_factor(&self) -> i32 { + self.monitors.lock().unwrap().compute_hidpi_factor() } pub fn set_decorations(&self, decorate: bool) { @@ -260,7 +278,7 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { + pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ()> { // TODO: not yet possible on wayland Err(()) } @@ -277,7 +295,7 @@ impl Window { // we don't know how much each monitor sees us so... // just return the most recent one ? let guard = self.monitors.lock().unwrap(); - guard.last().unwrap().clone() + guard.monitors.last().unwrap().clone() } } @@ -294,12 +312,15 @@ impl Drop for Window { struct InternalWindow { surface: Proxy, - newsize: Option<(i32, i32)>, + newsize: Option<(u32, u32)>, + size: Arc>, need_refresh: bool, need_frame_refresh: Arc>, closed: bool, kill_switch: Arc>, frame: Weak>>, + current_dpi: i32, + new_dpi: Option } pub struct WindowStore { @@ -345,24 +366,84 @@ impl WindowStore { } } + fn dpi_change(&mut self, surface: &Proxy, new: i32) { + for window in &mut self.windows { + if surface.equals(&window.surface) { + window.new_dpi = Some(new); + } + } + } + pub fn for_each(&mut self, mut f: F) where - F: FnMut(Option<(i32, i32)>, bool, bool, bool, WindowId, Option<&mut SWindow>), + F: FnMut(Option<(u32, u32)>, &mut (u32, u32), Option, bool, bool, bool, WindowId, Option<&mut SWindow>), { for window in &mut self.windows { let opt_arc = window.frame.upgrade(); let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); f( window.newsize.take(), + &mut *(window.size.lock().unwrap()), + window.new_dpi, window.need_refresh, ::std::mem::replace(&mut *window.need_frame_refresh.lock().unwrap(), false), window.closed, make_wid(&window.surface), opt_mutex_lock.as_mut().map(|m| &mut **m), ); + if let Some(dpi) = window.new_dpi.take() { + window.current_dpi = dpi; + } window.need_refresh = false; // avoid re-spamming the event window.closed = false; } } } + +/* + * Monitor list with some covenience method to compute DPI + */ + +struct MonitorList { + monitors: Vec +} + +impl MonitorList { + fn new() -> MonitorList { + MonitorList { + monitors: Vec::new() + } + } + + fn compute_hidpi_factor(&self) -> i32 { + let mut factor = 1; + for monitor_id in &self.monitors { + let monitor_dpi = monitor_id.get_hidpi_factor(); + if monitor_dpi > factor { factor = monitor_dpi; } + } + factor + } + + fn add_output(&mut self, monitor: MonitorId) -> Option { + let old_dpi = self.compute_hidpi_factor(); + let monitor_dpi = monitor.get_hidpi_factor(); + self.monitors.push(monitor); + if monitor_dpi > old_dpi { + Some(monitor_dpi) + } else { + None + } + } + + fn del_output(&mut self, output: &Proxy) -> Option { + let old_dpi = self.compute_hidpi_factor(); + self.monitors.retain(|m| !m.proxy.equals(output)); + let new_dpi = self.compute_hidpi_factor(); + if new_dpi != old_dpi { + Some(new_dpi) + } else { + None + } + } +} diff --git a/src/platform/linux/x11/mod.rs b/src/platform/linux/x11/mod.rs index 85100a7803..b5f9dde34f 100644 --- a/src/platform/linux/x11/mod.rs +++ b/src/platform/linux/x11/mod.rs @@ -9,13 +9,7 @@ mod dnd; mod ime; pub mod util; -pub use self::monitor::{ - MonitorId, - get_available_monitors, - get_monitor_for_window, - get_primary_monitor, - invalidate_cached_monitor_list, -}; +pub use self::monitor::MonitorId; pub use self::window::UnownedWindow; pub use self::xdisplay::{XConnection, XNotSupported, XError}; @@ -29,7 +23,6 @@ use std::sync::{Arc, mpsc, Weak}; use std::sync::atomic::{self, AtomicBool}; use libc::{self, setlocale, LC_CTYPE}; -use parking_lot::Mutex; use { ControlFlow, @@ -38,6 +31,8 @@ use { Event, EventsLoopClosed, KeyboardInput, + LogicalPosition, + LogicalSize, WindowAttributes, WindowEvent, }; @@ -92,7 +87,7 @@ impl EventsLoop { result.expect("Failed to set input method destruction callback") }); - let randr_event_offset = monitor::select_input(&xconn, root) + let randr_event_offset = xconn.select_xrandr_input(root) .expect("Failed to query XRandR extension"); let xi2ext = unsafe { @@ -394,6 +389,13 @@ impl EventsLoop { } ffi::ConfigureNotify => { + #[derive(Debug, Default)] + struct Events { + resized: Option, + moved: Option, + dpi_changed: Option, + } + let xev: &ffi::XConfigureEvent = xev.as_ref(); let xwindow = xev.window; let events = self.with_window(xwindow, |window| { @@ -406,9 +408,11 @@ impl EventsLoop { // that has a position relative to the parent window. let is_synthetic = xev.send_event == ffi::True; + // These are both in physical space. let new_inner_size = (xev.width as u32, xev.height as u32); let new_inner_position = (xev.x as i32, xev.y as i32); + let monitor = window.get_current_monitor(); // This must be done *before* locking! let mut shared_state_lock = window.shared_state.lock(); let (resized, moved) = { @@ -431,14 +435,33 @@ impl EventsLoop { (resized, moved) }; - let capacity = resized as usize + moved as usize; - let mut events = Vec::with_capacity(capacity); + // This is a hack to ensure that the DPI adjusted resize is actually applied on all WMs. KWin + // doesn't need this, but Xfwm does. + if let Some(adjusted_size) = shared_state_lock.dpi_adjusted { + let rounded_size = (adjusted_size.0.round() as u32, adjusted_size.1.round() as u32); + if new_inner_size == rounded_size { + // When this finally happens, the event will not be synthetic. + shared_state_lock.dpi_adjusted = None; + } else { + unsafe { + (self.xconn.xlib.XResizeWindow)( + self.xconn.display, + xwindow, + rounded_size.0 as c_uint, + rounded_size.1 as c_uint, + ); + } + } + } + + let mut events = Events::default(); if resized { - events.push(WindowEvent::Resized(new_inner_size.0, new_inner_size.1)); + let logical_size = LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor); + events.resized = Some(WindowEvent::Resized(logical_size)); } - if moved || shared_state_lock.position.is_none() { + let new_outer_position = if moved || shared_state_lock.position.is_none() { // We need to convert client area position to window position. let frame_extents = shared_state_lock.frame_extents .as_ref() @@ -451,19 +474,59 @@ impl EventsLoop { let outer = frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1); shared_state_lock.position = Some(outer); if moved { - events.push(WindowEvent::Moved(outer.0, outer.1)); + let logical_position = LogicalPosition::from_physical(outer, monitor.hidpi_factor); + events.moved = Some(WindowEvent::Moved(logical_position)); } + outer + } else { + shared_state_lock.position.unwrap() + }; + + // If we don't use the existing adjusted value when available, then the user can screw up the + // resizing by dragging across monitors *without* dropping the window. + let (width, height) = shared_state_lock.dpi_adjusted + .unwrap_or_else(|| (xev.width as f64, xev.height as f64)); + let last_hidpi_factor = if shared_state_lock.is_new_window { + shared_state_lock.is_new_window = false; + 1.0 + } else { + shared_state_lock.last_monitor + .as_ref() + .map(|last_monitor| last_monitor.hidpi_factor) + .unwrap_or(1.0) + }; + let new_hidpi_factor = { + let window_rect = util::Rect::new(new_outer_position, new_inner_size); + let monitor = self.xconn.get_monitor_for_window(Some(window_rect)); + let new_hidpi_factor = monitor.hidpi_factor; + shared_state_lock.last_monitor = Some(monitor); + new_hidpi_factor + }; + if last_hidpi_factor != new_hidpi_factor { + events.dpi_changed = Some(WindowEvent::HiDpiFactorChanged(new_hidpi_factor)); + let (new_width, new_height, flusher) = window.adjust_for_dpi( + last_hidpi_factor, + new_hidpi_factor, + width, + height, + ); + flusher.queue(); + shared_state_lock.dpi_adjusted = Some((new_width, new_height)); } events }); if let Some(events) = events { - for event in events { - callback(Event::WindowEvent { - window_id: mkwid(xwindow), - event, - }); + let window_id = mkwid(xwindow); + if let Some(event) = events.resized { + callback(Event::WindowEvent { window_id, event }); + } + if let Some(event) = events.moved { + callback(Event::WindowEvent { window_id, event }); + } + if let Some(event) = events.dpi_changed { + callback(Event::WindowEvent { window_id, event }); } } } @@ -694,14 +757,25 @@ impl EventsLoop { util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) }); if cursor_moved == Some(true) { - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position: new_cursor_pos, - modifiers, - }, + let dpi_factor = self.with_window(xev.event, |window| { + window.get_hidpi_factor() }); + if let Some(dpi_factor) = dpi_factor { + let position = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } else { + return; + } } else if cursor_moved.is_none() { return; } @@ -782,19 +856,29 @@ impl EventsLoop { event: CursorEntered { device_id }, }); - let new_cursor_pos = (xev.event_x, xev.event_y); - // The mods field on this event isn't actually populated, so query the // pointer device. In the future, we can likely remove this round-trip by // relying on Xkb for modifier values. let modifiers = self.xconn.query_pointer(xev.event, xev.deviceid) .expect("Failed to query pointer device").get_modifier_state(); - callback(Event::WindowEvent { window_id, event: CursorMoved { - device_id, - position: new_cursor_pos, - modifiers, - }}) + let dpi_factor = self.with_window(xev.event, |window| { + window.get_hidpi_factor() + }); + if let Some(dpi_factor) = dpi_factor { + let position = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } } ffi::XI_Leave => { let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; @@ -812,7 +896,12 @@ impl EventsLoop { ffi::XI_FocusIn => { let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; - if !self.window_exists(xev.event) { return; } + let dpi_factor = match self.with_window(xev.event, |window| { + window.get_hidpi_factor() + }) { + Some(dpi_factor) => dpi_factor, + None => return, + }; let window_id = mkwid(xev.event); self.ime @@ -830,11 +919,15 @@ impl EventsLoop { .map(|device| device.attachment) .unwrap_or(2); + let position = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); callback(Event::WindowEvent { window_id, event: CursorMoved { device_id: mkdid(pointer_id), - position: (xev.event_x, xev.event_y), + position, modifiers: ModifiersState::from(xev.mods), } }); @@ -861,15 +954,24 @@ impl EventsLoop { ffi::XI_TouchEnd => TouchPhase::Ended, _ => unreachable!() }; - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Touch(Touch { - device_id: mkdid(xev.deviceid), - phase, - location: (xev.event_x, xev.event_y), - id: xev.detail as u64, - }, - )}) + let dpi_factor = self.with_window(xev.event, |window| { + window.get_hidpi_factor() + }); + if let Some(dpi_factor) = dpi_factor { + let location = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Touch(Touch { + device_id: mkdid(xev.deviceid), + phase, + location, + id: xev.detail as u64, + }), + }) + } } ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => { @@ -986,7 +1088,44 @@ impl EventsLoop { _ => { if event_type == self.randr_event_offset { // In the future, it would be quite easy to emit monitor hotplug events. - monitor::invalidate_cached_monitor_list(); + let prev_list = monitor::invalidate_cached_monitor_list(); + if let Some(prev_list) = prev_list { + let new_list = self.xconn.get_available_monitors(); + for new_monitor in new_list { + prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| { + if new_monitor.hidpi_factor != prev_monitor.hidpi_factor { + for (window_id, window) in self.windows.borrow().iter() { + if let Some(window) = window.upgrade() { + // Check if the window is on this monitor + let monitor = window.get_current_monitor(); + if monitor.name == new_monitor.name { + callback(Event::WindowEvent { + window_id: mkwid(window_id.0), + event: WindowEvent::HiDpiFactorChanged( + new_monitor.hidpi_factor + ), + }); + let (width, height) = match window.get_inner_size_physical() { + Some(result) => result, + None => continue, + }; + let (_, _, flusher) = window.adjust_for_dpi( + prev_monitor.hidpi_factor, + new_monitor.hidpi_factor, + width as f64, + height as f64, + ); + flusher.queue(); + } + } + } + } + }); + } + } } }, } @@ -1110,16 +1249,13 @@ pub struct WindowId(ffi::Window); #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(c_int); -pub struct Window { - pub window: Arc, - ime_sender: Mutex, -} +pub struct Window(Arc); impl Deref for Window { type Target = UnownedWindow; #[inline] fn deref(&self) -> &UnownedWindow { - &*self.window + &*self.0 } } @@ -1130,35 +1266,19 @@ impl Window { pl_attribs: PlatformSpecificWindowBuilderAttributes ) -> Result { let window = Arc::new(UnownedWindow::new(&event_loop, attribs, pl_attribs)?); - event_loop.windows .borrow_mut() .insert(window.id(), Arc::downgrade(&window)); - - event_loop.ime - .borrow_mut() - .create_context(window.id().0) - .expect("Failed to create input context"); - - Ok(Window { - window, - ime_sender: Mutex::new(event_loop.ime_sender.clone()), - }) - } - - #[inline] - pub fn send_xim_spot(&self, x: i16, y: i16) { - let _ = self.ime_sender - .lock() - .send((self.window.id().0, x, y)); + Ok(Window(window)) } } impl Drop for Window { fn drop(&mut self) { - let xconn = &self.window.xconn; + let window = self.deref(); + let xconn = &window.xconn; unsafe { - (xconn.xlib.XDestroyWindow)(xconn.display, self.window.id().0); + (xconn.xlib.XDestroyWindow)(xconn.display, window.id().0); // If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about. let _ = xconn.check_errors(); } diff --git a/src/platform/linux/x11/monitor.rs b/src/platform/linux/x11/monitor.rs index 62311e4b9d..3d1d7d7da9 100644 --- a/src/platform/linux/x11/monitor.rs +++ b/src/platform/linux/x11/monitor.rs @@ -1,8 +1,9 @@ use std::os::raw::*; -use std::sync::Arc; use parking_lot::Mutex; +use {PhysicalPosition, PhysicalSize}; +use super::{util, XConnection, XError}; use super::ffi::{ RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, @@ -11,7 +12,6 @@ use super::ffi::{ Window, XRRScreenResources, }; -use super::{util, XConnection, XError}; // Used to test XRandR < 1.5 code path. This should always be committed as false. const FORCE_RANDR_COMPAT: bool = false; @@ -45,7 +45,7 @@ pub struct MonitorId { /// The actual id id: u32, /// The name of the monitor - name: String, + pub(crate) name: String, /// The size of the monitor dimensions: (u32, u32), /// The position of the monitor in the X screen @@ -53,14 +53,14 @@ pub struct MonitorId { /// If the monitor is the primary one primary: bool, /// The DPI scale factor - pub(crate) hidpi_factor: f32, + pub(crate) hidpi_factor: f64, /// Used to determine which windows are on this monitor pub(crate) rect: util::Rect, } impl MonitorId { fn from_repr( - xconn: &Arc, + xconn: &XConnection, resources: *mut XRRScreenResources, id: u32, repr: util::MonitorRepr, @@ -89,182 +89,181 @@ impl MonitorId { self.id as u32 } - pub fn get_dimensions(&self) -> (u32, u32) { - self.dimensions + pub fn get_dimensions(&self) -> PhysicalSize { + self.dimensions.into() } - pub fn get_position(&self) -> (i32, i32) { - self.position + pub fn get_position(&self) -> PhysicalPosition { + self.position.into() } #[inline] - pub fn get_hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { self.hidpi_factor } } -pub fn get_monitor_for_window( - xconn: &Arc, - window_rect: Option, -) -> MonitorId { - let monitors = get_available_monitors(xconn); - let default = monitors - .get(0) - .expect("[winit] Failed to find any monitors using XRandR."); +impl XConnection { + pub fn get_monitor_for_window(&self, window_rect: Option) -> MonitorId { + let monitors = self.get_available_monitors(); + let default = monitors + .get(0) + .expect("[winit] Failed to find any monitors using XRandR."); - let window_rect = match window_rect { - Some(rect) => rect, - None => return default.to_owned(), - }; + let window_rect = match window_rect { + Some(rect) => rect, + None => return default.to_owned(), + }; - let mut largest_overlap = 0; - let mut matched_monitor = default; - for monitor in &monitors { - let overlapping_area = window_rect.get_overlapping_area(&monitor.rect); - if overlapping_area > largest_overlap { - largest_overlap = overlapping_area; - matched_monitor = &monitor; + let mut largest_overlap = 0; + let mut matched_monitor = default; + for monitor in &monitors { + let overlapping_area = window_rect.get_overlapping_area(&monitor.rect); + if overlapping_area > largest_overlap { + largest_overlap = overlapping_area; + matched_monitor = &monitor; + } } - } - matched_monitor.to_owned() -} + matched_monitor.to_owned() + } -fn query_monitor_list(xconn: &Arc) -> Vec { - unsafe { - let root = (xconn.xlib.XDefaultRootWindow)(xconn.display); - // WARNING: this function is supposedly very slow, on the order of hundreds of ms. - // Upon failure, `resources` will be null. - let resources = (xconn.xrandr.XRRGetScreenResources)(xconn.display, root); - if resources.is_null() { - panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist."); - } + fn query_monitor_list(&self) -> Vec { + unsafe { + let root = (self.xlib.XDefaultRootWindow)(self.display); + // WARNING: this function is supposedly very slow, on the order of hundreds of ms. + // Upon failure, `resources` will be null. + let resources = (self.xrandr.XRRGetScreenResources)(self.display, root); + if resources.is_null() { + panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist."); + } - let mut available; - let mut has_primary = false; + let mut available; + let mut has_primary = false; - if xconn.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT { - // We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and - // videowalls. - let xrandr_1_5 = xconn.xrandr_1_5.as_ref().unwrap(); - let mut monitor_count = 0; - let monitors = (xrandr_1_5.XRRGetMonitors)(xconn.display, root, 1, &mut monitor_count); - assert!(monitor_count >= 0); - available = Vec::with_capacity(monitor_count as usize); - for monitor_index in 0..monitor_count { - let monitor = monitors.offset(monitor_index as isize); - let is_primary = (*monitor).primary != 0; - has_primary |= is_primary; - available.push(MonitorId::from_repr( - xconn, - resources, - monitor_index as u32, - monitor.into(), - is_primary, - )); - } - (xrandr_1_5.XRRFreeMonitors)(monitors); - } else { - // We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and - // videowall setups will also show monitors that aren't in the logical groups the user - // cares about. - let primary = (xconn.xrandr.XRRGetOutputPrimary)(xconn.display, root); - available = Vec::with_capacity((*resources).ncrtc as usize); - for crtc_index in 0..(*resources).ncrtc { - let crtc_id = *((*resources).crtcs.offset(crtc_index as isize)); - let crtc = (xconn.xrandr.XRRGetCrtcInfo)(xconn.display, resources, crtc_id); - let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0; - if is_active { - let crtc = util::MonitorRepr::from(crtc); - let is_primary = crtc.get_output() == primary; + if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT { + // We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and + // videowalls. + let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap(); + let mut monitor_count = 0; + let monitors = (xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count); + assert!(monitor_count >= 0); + available = Vec::with_capacity(monitor_count as usize); + for monitor_index in 0..monitor_count { + let monitor = monitors.offset(monitor_index as isize); + let is_primary = (*monitor).primary != 0; has_primary |= is_primary; available.push(MonitorId::from_repr( - xconn, + self, resources, - crtc_id as u32, - crtc, + monitor_index as u32, + monitor.into(), is_primary, )); } - (xconn.xrandr.XRRFreeCrtcInfo)(crtc); + (xrandr_1_5.XRRFreeMonitors)(monitors); + } else { + // We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and + // videowall setups will also show monitors that aren't in the logical groups the user + // cares about. + let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root); + available = Vec::with_capacity((*resources).ncrtc as usize); + for crtc_index in 0..(*resources).ncrtc { + let crtc_id = *((*resources).crtcs.offset(crtc_index as isize)); + let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); + let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0; + if is_active { + let crtc = util::MonitorRepr::from(crtc); + let is_primary = crtc.get_output() == primary; + has_primary |= is_primary; + available.push(MonitorId::from_repr( + self, + resources, + crtc_id as u32, + crtc, + is_primary, + )); + } + (self.xrandr.XRRFreeCrtcInfo)(crtc); + } } - } - // If no monitors were detected as being primary, we just pick one ourselves! - if !has_primary { - if let Some(ref mut fallback) = available.first_mut() { - // Setting this here will come in handy if we ever add an `is_primary` method. - fallback.primary = true; + // If no monitors were detected as being primary, we just pick one ourselves! + if !has_primary { + if let Some(ref mut fallback) = available.first_mut() { + // Setting this here will come in handy if we ever add an `is_primary` method. + fallback.primary = true; + } } - } - (xconn.xrandr.XRRFreeScreenResources)(resources); - available + (self.xrandr.XRRFreeScreenResources)(resources); + available + } } -} -pub fn get_available_monitors(xconn: &Arc) -> Vec { - let mut monitors_lock = MONITORS.lock(); - (*monitors_lock) - .as_ref() - .cloned() - .or_else(|| { - let monitors = Some(query_monitor_list(xconn)); - if !DISABLE_MONITOR_LIST_CACHING { - (*monitors_lock) = monitors.clone(); - } - monitors - }) - .unwrap() -} + pub fn get_available_monitors(&self) -> Vec { + let mut monitors_lock = MONITORS.lock(); + (*monitors_lock) + .as_ref() + .cloned() + .or_else(|| { + let monitors = Some(self.query_monitor_list()); + if !DISABLE_MONITOR_LIST_CACHING { + (*monitors_lock) = monitors.clone(); + } + monitors + }) + .unwrap() + } -#[inline] -pub fn get_primary_monitor(xconn: &Arc) -> MonitorId { - get_available_monitors(xconn) - .into_iter() - .find(|monitor| monitor.primary) - .expect("[winit] Failed to find any monitors using XRandR.") -} + #[inline] + pub fn get_primary_monitor(&self) -> MonitorId { + self.get_available_monitors() + .into_iter() + .find(|monitor| monitor.primary) + .expect("[winit] Failed to find any monitors using XRandR.") + } -pub fn select_input(xconn: &Arc, root: Window) -> Result { - { - let mut version_lock = XRANDR_VERSION.lock(); - if version_lock.is_none() { - let mut major = 0; - let mut minor = 0; - let has_extension = unsafe { - (xconn.xrandr.XRRQueryVersion)( - xconn.display, - &mut major, - &mut minor, - ) - }; - if has_extension != True { - panic!("[winit] XRandR extension not available."); + pub fn select_xrandr_input(&self, root: Window) -> Result { + { + let mut version_lock = XRANDR_VERSION.lock(); + if version_lock.is_none() { + let mut major = 0; + let mut minor = 0; + let has_extension = unsafe { + (self.xrandr.XRRQueryVersion)( + self.display, + &mut major, + &mut minor, + ) + }; + if has_extension != True { + panic!("[winit] XRandR extension not available."); + } + *version_lock = Some((major, minor)); } - *version_lock = Some((major, minor)); } - } - let mut event_offset = 0; - let mut error_offset = 0; - let status = unsafe { - (xconn.xrandr.XRRQueryExtension)( - xconn.display, - &mut event_offset, - &mut error_offset, - ) - }; + let mut event_offset = 0; + let mut error_offset = 0; + let status = unsafe { + (self.xrandr.XRRQueryExtension)( + self.display, + &mut event_offset, + &mut error_offset, + ) + }; - if status != True { - xconn.check_errors()?; - unreachable!("[winit] `XRRQueryExtension` failed but no error was received."); - } + if status != True { + self.check_errors()?; + unreachable!("[winit] `XRRQueryExtension` failed but no error was received."); + } - let mask = RRCrtcChangeNotifyMask - | RROutputPropertyNotifyMask - | RRScreenChangeNotifyMask; - unsafe { (xconn.xrandr.XRRSelectInput)(xconn.display, root, mask) }; + let mask = RRCrtcChangeNotifyMask + | RROutputPropertyNotifyMask + | RRScreenChangeNotifyMask; + unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) }; - Ok(event_offset) + Ok(event_offset) + } } diff --git a/src/platform/linux/x11/util/geometry.rs b/src/platform/linux/x11/util/geometry.rs index 3c4bd9bdab..943255b870 100644 --- a/src/platform/linux/x11/util/geometry.rs +++ b/src/platform/linux/x11/util/geometry.rs @@ -1,6 +1,7 @@ use std::cmp; use super::*; +use {LogicalPosition, LogicalSize}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Rect { @@ -78,6 +79,24 @@ impl FrameExtents { pub fn from_border(border: c_ulong) -> Self { Self::new(border, border, border, border) } + + pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents { + let logicalize = |value: c_ulong| value as f64 / factor; + LogicalFrameExtents { + left: logicalize(self.left), + right: logicalize(self.right), + top: logicalize(self.top), + bottom: logicalize(self.bottom), + } + } +} + +#[derive(Debug, Clone)] +pub struct LogicalFrameExtents { + pub left: f64, + pub right: f64, + pub top: f64, + pub bottom: f64, } #[derive(Debug, Clone, PartialEq)] @@ -103,6 +122,16 @@ impl FrameExtentsHeuristic { } } + pub fn inner_pos_to_outer_logical(&self, mut logical: LogicalPosition, factor: f64) -> LogicalPosition { + use self::FrameExtentsHeuristicPath::*; + if self.heuristic_path != UnsupportedBordered { + let frame_extents = self.frame_extents.as_logical(factor); + logical.x -= frame_extents.left; + logical.y -= frame_extents.top; + } + logical + } + pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) { ( width.saturating_add( @@ -113,6 +142,13 @@ impl FrameExtentsHeuristic { ), ) } + + pub fn inner_size_to_outer_logical(&self, mut logical: LogicalSize, factor: f64) -> LogicalSize { + let frame_extents = self.frame_extents.as_logical(factor); + logical.width += frame_extents.left + frame_extents.right; + logical.height += frame_extents.top + frame_extents.bottom; + logical + } } impl XConnection { diff --git a/src/platform/linux/x11/util/hint.rs b/src/platform/linux/x11/util/hint.rs index 0727af6e76..80887ea47b 100644 --- a/src/platform/linux/x11/util/hint.rs +++ b/src/platform/linux/x11/util/hint.rs @@ -68,6 +68,99 @@ impl WindowType { } } +pub struct NormalHints<'a> { + size_hints: XSmartPointer<'a, ffi::XSizeHints>, +} + +impl<'a> NormalHints<'a> { + pub fn new(xconn: &'a XConnection) -> Self { + NormalHints { size_hints: xconn.alloc_size_hints() } + } + + pub fn has_flag(&self, flag: c_long) -> bool { + has_flag(self.size_hints.flags, flag) + } + + fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> { + if self.has_flag(flag) { + Some((*field1 as _, *field2 as _)) + } else { + None + } + } + + pub fn get_size(&self) -> Option<(u32, u32)> { + self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height) + } + + // WARNING: This hint is obsolete + pub fn set_size(&mut self, size: Option<(u32, u32)>) { + if let Some((width, height)) = size { + self.size_hints.flags |= ffi::PSize; + self.size_hints.width = width as c_int; + self.size_hints.height = height as c_int; + } else { + self.size_hints.flags &= !ffi::PSize; + } + } + + pub fn get_max_size(&self) -> Option<(u32, u32)> { + self.getter(ffi::PMaxSize, &self.size_hints.max_width, &self.size_hints.max_height) + } + + pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) { + if let Some((max_width, max_height)) = max_size { + self.size_hints.flags |= ffi::PMaxSize; + self.size_hints.max_width = max_width as c_int; + self.size_hints.max_height = max_height as c_int; + } else { + self.size_hints.flags &= !ffi::PMaxSize; + } + } + + pub fn get_min_size(&self) -> Option<(u32, u32)> { + self.getter(ffi::PMinSize, &self.size_hints.min_width, &self.size_hints.min_height) + } + + pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) { + if let Some((min_width, min_height)) = min_size { + self.size_hints.flags |= ffi::PMinSize; + self.size_hints.min_width = min_width as c_int; + self.size_hints.min_height = min_height as c_int; + } else { + self.size_hints.flags &= !ffi::PMinSize; + } + } + + pub fn get_resize_increments(&self) -> Option<(u32, u32)> { + self.getter(ffi::PResizeInc, &self.size_hints.width_inc, &self.size_hints.height_inc) + } + + pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) { + if let Some((width_inc, height_inc)) = resize_increments { + self.size_hints.flags |= ffi::PResizeInc; + self.size_hints.width_inc = width_inc as c_int; + self.size_hints.height_inc = height_inc as c_int; + } else { + self.size_hints.flags &= !ffi::PResizeInc; + } + } + + pub fn get_base_size(&self) -> Option<(u32, u32)> { + self.getter(ffi::PBaseSize, &self.size_hints.base_width, &self.size_hints.base_height) + } + + pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) { + if let Some((base_width, base_height)) = base_size { + self.size_hints.flags |= ffi::PBaseSize; + self.size_hints.base_width = base_width as c_int; + self.size_hints.base_height = base_height as c_int; + } else { + self.size_hints.flags &= !ffi::PBaseSize; + } + } +} + impl XConnection { pub fn get_wm_hints(&self, window: ffi::Window) -> Result, XError> { let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) }; @@ -90,4 +183,29 @@ impl XConnection { } Flusher::new(self) } + + pub fn get_normal_hints(&self, window: ffi::Window) -> Result { + let size_hints = self.alloc_size_hints(); + let mut supplied_by_user: c_long = unsafe { mem::uninitialized() }; + unsafe { + (self.xlib.XGetWMNormalHints)( + self.display, + window, + size_hints.ptr, + &mut supplied_by_user, + ); + } + self.check_errors().map(|_| NormalHints { size_hints }) + } + + pub fn set_normal_hints(&self, window: ffi::Window, normal_hints: NormalHints) -> Flusher { + unsafe { + (self.xlib.XSetWMNormalHints)( + self.display, + window, + normal_hints.size_hints.ptr, + ); + } + Flusher::new(self) + } } diff --git a/src/platform/linux/x11/util/mod.rs b/src/platform/linux/x11/util/mod.rs index a0e3df70bd..ccef6e4065 100644 --- a/src/platform/linux/x11/util/mod.rs +++ b/src/platform/linux/x11/util/mod.rs @@ -27,10 +27,16 @@ pub use self::wm::*; use std::mem; use std::ptr; +use std::ops::BitAnd; use std::os::raw::*; use super::{ffi, XConnection, XError}; +pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B { + let b_ptr = a as *const _ as *const B; + unsafe { &*b_ptr } +} + pub fn maybe_change(field: &mut Option, value: T) -> bool { let wrapped = Some(value); if *field != wrapped { @@ -41,6 +47,13 @@ pub fn maybe_change(field: &mut Option, value: T) -> bool { } } +pub fn has_flag(bitset: T, flag: T) -> bool +where T: + Copy + PartialEq + BitAnd +{ + bitset & flag == flag +} + #[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."] pub struct Flusher<'a> { xconn: &'a XConnection, diff --git a/src/platform/linux/x11/util/randr.rs b/src/platform/linux/x11/util/randr.rs index dccfbdd395..2e4c55a13c 100644 --- a/src/platform/linux/x11/util/randr.rs +++ b/src/platform/linux/x11/util/randr.rs @@ -1,21 +1,48 @@ use std::{env, slice}; use std::str::FromStr; +use validate_hidpi_factor; use super::*; -use super::ffi::{ - RROutput, - XRRCrtcInfo, - XRRMonitorInfo, - XRRScreenResources, -}; + +pub fn calc_dpi_factor( + (width_px, height_px): (u32, u32), + (width_mm, height_mm): (u64, u64), +) -> f64 { + // Override DPI if `WINIT_HIDPI_FACTOR` variable is set + let dpi_override = env::var("WINIT_HIDPI_FACTOR") + .ok() + .and_then(|var| f64::from_str(&var).ok()); + if let Some(dpi_override) = dpi_override { + if !validate_hidpi_factor(dpi_override) { + panic!( + "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`", + dpi_override, + ); + } + return dpi_override; + } + + // See http://xpra.org/trac/ticket/728 for more information. + if width_mm == 0 || width_mm == 0 { + return 1.0; + } + + let ppmm = ( + (width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64) + ).sqrt(); + // Quantize 1/12 step size + let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0); + assert!(validate_hidpi_factor(dpi_factor)); + dpi_factor +} pub enum MonitorRepr { - Monitor(*mut XRRMonitorInfo), - Crtc(*mut XRRCrtcInfo), + Monitor(*mut ffi::XRRMonitorInfo), + Crtc(*mut ffi::XRRCrtcInfo), } impl MonitorRepr { - pub unsafe fn get_output(&self) -> RROutput { + pub unsafe fn get_output(&self) -> ffi::RROutput { match *self { // Same member names, but different locations within the struct... MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)), @@ -38,47 +65,20 @@ impl MonitorRepr { } } -impl From<*mut XRRMonitorInfo> for MonitorRepr { - fn from(monitor: *mut XRRMonitorInfo) -> Self { +impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr { + fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self { MonitorRepr::Monitor(monitor) } } -impl From<*mut XRRCrtcInfo> for MonitorRepr { - fn from(crtc: *mut XRRCrtcInfo) -> Self { +impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr { + fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self { MonitorRepr::Crtc(crtc) } } -pub fn calc_dpi_factor( - (width_px, height_px): (u32, u32), - (width_mm, height_mm): (u64, u64), -) -> f64 { - // Override DPI if `WINIT_HIDPI_FACTOR` variable is set - if let Ok(dpi_factor_str) = env::var("WINIT_HIDPI_FACTOR") { - if let Ok(dpi_factor) = f64::from_str(&dpi_factor_str) { - if dpi_factor <= 0. { - panic!("Expected `WINIT_HIDPI_FACTOR` to be bigger than 0, got '{}'", dpi_factor); - } - - return dpi_factor; - } - } - - // See http://xpra.org/trac/ticket/728 for more information - if width_mm == 0 || width_mm == 0 { - return 1.0; - } - - let ppmm = ( - (width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64) - ).sqrt(); - // Quantize 1/12 step size - ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0) -} - impl XConnection { - pub unsafe fn get_output_info(&self, resources: *mut XRRScreenResources, repr: &MonitorRepr) -> (String, f32) { + pub unsafe fn get_output_info(&self, resources: *mut ffi::XRRScreenResources, repr: &MonitorRepr) -> (String, f64) { let output_info = (self.xrandr.XRRGetOutputInfo)( self.display, resources, @@ -92,7 +92,7 @@ impl XConnection { let hidpi_factor = calc_dpi_factor( repr.get_dimensions(), ((*output_info).mm_width as u64, (*output_info).mm_height as u64), - ) as f32; + ); (self.xrandr.XRRFreeOutputInfo)(output_info); (name, hidpi_factor) } diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index affa075f08..e4d0ef9fca 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -7,15 +7,14 @@ use std::sync::Arc; use libc; use parking_lot::Mutex; -use {CursorState, Icon, MouseCursor, WindowAttributes}; +use {CursorState, Icon, LogicalPosition, LogicalSize, MouseCursor, WindowAttributes}; use CreationError::{self, OsError}; use platform::MonitorId as PlatformMonitorId; use platform::PlatformSpecificWindowBuilderAttributes; use platform::x11::MonitorId as X11MonitorId; -use platform::x11::monitor::get_monitor_for_window; use window::MonitorId as RootMonitorId; -use super::{ffi, util, XConnection, XError, WindowId, EventsLoop}; +use super::{ffi, util, ImeSender, XConnection, XError, WindowId, EventsLoop}; unsafe extern "C" fn visibility_predicate( _display: *mut ffi::Display, @@ -29,7 +28,8 @@ unsafe extern "C" fn visibility_predicate( #[derive(Debug, Default)] pub struct SharedState { - pub multitouch: bool, + // Window creation assumes a DPI factor of 1.0, so we use this flag to handle that special case. + pub is_new_window: bool, pub cursor_pos: Option<(f64, f64)>, pub size: Option<(u32, u32)>, pub position: Option<(i32, i32)>, @@ -37,9 +37,19 @@ pub struct SharedState { pub inner_position_rel_parent: Option<(i32, i32)>, pub last_monitor: Option, pub dpi_adjusted: Option<(f64, f64)>, + // Used to restore position after exiting fullscreen. + pub restore_position: Option<(i32, i32)>, pub frame_extents: Option, - pub min_dimensions: Option<(u32, u32)>, - pub max_dimensions: Option<(u32, u32)>, + pub min_dimensions: Option, + pub max_dimensions: Option, +} + +impl SharedState { + fn new() -> Mutex { + let mut shared_state = SharedState::default(); + shared_state.is_new_window = true; + Mutex::new(shared_state) + } } unsafe impl Send for UnownedWindow {} @@ -52,6 +62,7 @@ pub struct UnownedWindow { screen_id: i32, // never changes cursor: Mutex, cursor_state: Mutex, + ime_sender: Mutex, pub multitouch: bool, // never changes pub shared_state: Mutex, } @@ -65,15 +76,20 @@ impl UnownedWindow { let xconn = &event_loop.xconn; let root = event_loop.root; + let max_dimensions: Option<(u32, u32)> = window_attrs.max_dimensions.map(Into::into); + let min_dimensions: Option<(u32, u32)> = window_attrs.min_dimensions.map(Into::into); + let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints - let mut dimensions = window_attrs.dimensions.unwrap_or((800, 600)); - if let Some(max) = window_attrs.max_dimensions { + let mut dimensions = window_attrs.dimensions + .map(Into::into) + .unwrap_or((800, 600)); + if let Some(max) = max_dimensions { dimensions.0 = cmp::min(dimensions.0, max.0); dimensions.1 = cmp::min(dimensions.1, max.1); } - if let Some(min) = window_attrs.min_dimensions { + if let Some(min) = min_dimensions { dimensions.0 = cmp::max(dimensions.0, min.0); dimensions.1 = cmp::max(dimensions.1, min.1); } @@ -145,8 +161,9 @@ impl UnownedWindow { screen_id, cursor: Default::default(), cursor_state: Default::default(), + ime_sender: Mutex::new(event_loop.ime_sender.clone()), multitouch: window_attrs.multitouch, - shared_state: Default::default(), + shared_state: SharedState::new(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window @@ -218,46 +235,24 @@ impl UnownedWindow { // set size hints { - (*window.shared_state.lock()).min_dimensions = window_attrs.min_dimensions; - (*window.shared_state.lock()).max_dimensions = window_attrs.max_dimensions; let mut min_dimensions = window_attrs.min_dimensions; let mut max_dimensions = window_attrs.max_dimensions; if !window_attrs.resizable && !util::wm_name_is_one_of(&["Xfwm4"]) { - max_dimensions = Some(dimensions); - min_dimensions = Some(dimensions); - } + max_dimensions = Some(dimensions.into()); + min_dimensions = Some(dimensions.into()); - let mut size_hints = xconn.alloc_size_hints(); - (*size_hints).flags = ffi::PSize; - (*size_hints).width = dimensions.0 as c_int; - (*size_hints).height = dimensions.1 as c_int; - if let Some((min_width, min_height)) = min_dimensions { - (*size_hints).flags |= ffi::PMinSize; - (*size_hints).min_width = min_width as c_int; - (*size_hints).min_height = min_height as c_int; - } - if let Some((max_width, max_height)) = max_dimensions { - (*size_hints).flags |= ffi::PMaxSize; - (*size_hints).max_width = max_width as c_int; - (*size_hints).max_height = max_height as c_int; - } - if let Some((width_inc, height_inc)) = pl_attribs.resize_increments { - (*size_hints).flags |= ffi::PResizeInc; - (*size_hints).width_inc = width_inc as c_int; - (*size_hints).height_inc = height_inc as c_int; + let mut shared_state_lock = window.shared_state.lock(); + shared_state_lock.min_dimensions = window_attrs.min_dimensions; + shared_state_lock.max_dimensions = window_attrs.max_dimensions; } - if let Some((base_width, base_height)) = pl_attribs.base_size { - (*size_hints).flags |= ffi::PBaseSize; - (*size_hints).base_width = base_width as c_int; - (*size_hints).base_height = base_height as c_int; - } - unsafe { - (xconn.xlib.XSetWMNormalHints)( - xconn.display, - window.xwindow, - size_hints.ptr, - ); - }//.queue(); + + let mut normal_hints = util::NormalHints::new(xconn); + normal_hints.set_size(Some(dimensions)); + normal_hints.set_min_size(min_dimensions.map(Into::into)); + normal_hints.set_max_size(max_dimensions.map(Into::into)); + normal_hints.set_resize_increments(pl_attribs.resize_increments); + normal_hints.set_base_size(pl_attribs.base_size); + xconn.set_normal_hints(window.xwindow, normal_hints).queue(); } // Set window icons @@ -291,7 +286,7 @@ impl UnownedWindow { &mut supported_ptr, ); if supported_ptr == ffi::False { - return Err(OsError(format!("XkbSetDetectableAutoRepeat failed"))); + return Err(OsError(format!("`XkbSetDetectableAutoRepeat` failed"))); } } @@ -315,6 +310,15 @@ impl UnownedWindow { }; xconn.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask).queue(); + { + let result = event_loop.ime + .borrow_mut() + .create_context(window.xwindow); + if let Err(err) = result { + return Err(OsError(format!("Failed to create input context: {:?}", err))); + } + } + // These properties must be set after mapping if window_attrs.maximized { window.set_maximized_inner(window_attrs.maximized).queue(); @@ -355,6 +359,16 @@ impl UnownedWindow { )) } + fn logicalize_coords(&self, (x, y): (i32, i32)) -> LogicalPosition { + let dpi = self.get_hidpi_factor(); + LogicalPosition::from_physical((x, y), dpi) + } + + fn logicalize_size(&self, (width, height): (u32, u32)) -> LogicalSize { + let dpi = self.get_hidpi_factor(); + LogicalSize::from_physical((width, height), dpi) + } + fn set_pid(&self) -> Option { let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") }; let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") }; @@ -400,6 +414,7 @@ impl UnownedWindow { ) } + #[inline] pub fn set_urgent(&self, is_urgent: bool) { let mut wm_hints = self.xconn.get_wm_hints(self.xwindow).expect("`XGetWMHints` failed"); if is_urgent { @@ -439,17 +454,24 @@ impl UnownedWindow { fn set_fullscreen_inner(&self, monitor: Option) -> util::Flusher { match monitor { None => { - self.set_fullscreen_hint(false) + let flusher = self.set_fullscreen_hint(false); + if let Some(position) = self.shared_state.lock().restore_position.take() { + self.set_position_inner(position.0, position.1).queue(); + } + flusher }, Some(RootMonitorId { inner: PlatformMonitorId::X(monitor) }) => { - let screenpos = monitor.get_position(); - self.set_position(screenpos.0 as i32, screenpos.1 as i32); + let window_position = self.get_position_physical(); + self.shared_state.lock().restore_position = window_position; + let monitor_origin: (i32, i32) = monitor.get_position().into(); + self.set_position_inner(monitor_origin.0, monitor_origin.1).queue(); self.set_fullscreen_hint(true) } _ => unreachable!(), } } + #[inline] pub fn set_fullscreen(&self, monitor: Option) { self.set_fullscreen_inner(monitor) .flush() @@ -459,15 +481,26 @@ impl UnownedWindow { fn get_rect(&self) -> Option { // TODO: This might round-trip more times than needed. - if let (Some(position), Some(size)) = (self.get_position(), self.get_outer_size()) { + if let (Some(position), Some(size)) = (self.get_position_physical(), self.get_outer_size_physical()) { Some(util::Rect::new(position, size)) } else { None } } + #[inline] pub fn get_current_monitor(&self) -> X11MonitorId { - get_monitor_for_window(&self.xconn, self.get_rect()).to_owned() + let monitor = self.shared_state + .lock() + .last_monitor + .as_ref() + .cloned(); + monitor + .unwrap_or_else(|| { + let monitor = self.xconn.get_monitor_for_window(self.get_rect()).to_owned(); + self.shared_state.lock().last_monitor = Some(monitor.clone()); + monitor + }) } fn set_maximized_inner(&self, maximized: bool) -> util::Flusher { @@ -476,6 +509,7 @@ impl UnownedWindow { self.set_netwm(maximized.into(), (horz_atom as c_long, vert_atom as c_long, 0, 0)) } + #[inline] pub fn set_maximized(&self, maximized: bool) { self.set_maximized_inner(maximized) .flush() @@ -503,6 +537,7 @@ impl UnownedWindow { } } + #[inline] pub fn set_title(&self, title: &str) { self.set_title_inner(title) .flush() @@ -526,6 +561,7 @@ impl UnownedWindow { ) } + #[inline] pub fn set_decorations(&self, decorations: bool) { self.set_decorations_inner(decorations) .flush() @@ -538,6 +574,7 @@ impl UnownedWindow { self.set_netwm(always_on_top.into(), (above_atom as c_long, 0, 0, 0)) } + #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { self.set_always_on_top_inner(always_on_top) .flush() @@ -568,6 +605,7 @@ impl UnownedWindow { ) } + #[inline] pub fn set_window_icon(&self, icon: Option) { match icon { Some(icon) => self.set_icon_inner(icon), @@ -575,6 +613,7 @@ impl UnownedWindow { }.flush().expect("Failed to set icons"); } + #[inline] pub fn show(&self) { unsafe { (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); @@ -583,6 +622,7 @@ impl UnownedWindow { } } + #[inline] pub fn hide(&self) { unsafe { (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); @@ -596,31 +636,46 @@ impl UnownedWindow { (*self.shared_state.lock()).frame_extents = Some(extents); } - pub fn invalidate_cached_frame_extents(&self) { + pub(crate) fn invalidate_cached_frame_extents(&self) { (*self.shared_state.lock()).frame_extents.take(); } + pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> { + let extents = (*self.shared_state.lock()).frame_extents.clone(); + if let Some(extents) = extents { + self.get_inner_position_physical() + .map(|(x, y)| extents.inner_pos_to_outer(x, y)) + } else { + self.update_cached_frame_extents(); + self.get_position_physical() + } + } + #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { + pub fn get_position(&self) -> Option { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { - self.get_inner_position().map(|(x, y)| - extents.inner_pos_to_outer(x, y) - ) + self.get_inner_position() + .map(|logical| extents.inner_pos_to_outer_logical(logical, self.get_hidpi_factor())) } else { self.update_cached_frame_extents(); self.get_position() } } - #[inline] - pub fn get_inner_position(&self) -> Option<(i32, i32)> { - self.xconn.translate_coords(self.xwindow, self.root ) + pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> { + self.xconn.translate_coords(self.xwindow, self.root) .ok() .map(|coords| (coords.x_rel_root, coords.y_rel_root)) } - pub fn set_position(&self, mut x: i32, mut y: i32) { + #[inline] + pub fn get_inner_position(&self) -> Option { + self.get_inner_position_physical() + .map(|coords| self.logicalize_coords(coords)) + } + + pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher { // There are a few WMs that set client area position rather than window position, so // we'll translate for consistency. if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) { @@ -630,7 +685,7 @@ impl UnownedWindow { y += extents.frame_extents.top as i32; } else { self.update_cached_frame_extents(); - self.set_position(x, y) + return self.set_position_inner(x, y); } } unsafe { @@ -640,32 +695,58 @@ impl UnownedWindow { x as c_int, y as c_int, ); - self.xconn.flush_requests() - }.expect("Failed to call XMoveWindow"); + } + util::Flusher::new(&self.xconn) + } + + pub(crate) fn set_position_physical(&self, x: i32, y: i32) { + self.set_position_inner(x, y) + .flush() + .expect("Failed to call `XMoveWindow`"); } #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { + pub fn set_position(&self, logical_position: LogicalPosition) { + let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into(); + self.set_position_physical(x, y); + } + + pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> { self.xconn.get_geometry(self.xwindow) .ok() .map(|geo| (geo.width, geo.height)) } #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { - let extents = (*self.shared_state.lock()).frame_extents.clone(); + pub fn get_inner_size(&self) -> Option { + self.get_inner_size_physical() + .map(|size| self.logicalize_size(size)) + } + + pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> { + let extents = self.shared_state.lock().frame_extents.clone(); if let Some(extents) = extents { - self.get_inner_size().map(|(w, h)| - extents.inner_size_to_outer(w, h) - ) + self.get_inner_size_physical() + .map(|(w, h)| extents.inner_size_to_outer(w, h)) } else { self.update_cached_frame_extents(); - self.get_outer_size() + self.get_outer_size_physical() } } #[inline] - pub fn set_inner_size(&self, width: u32, height: u32) { + pub fn get_outer_size(&self) -> Option { + let extents = self.shared_state.lock().frame_extents.clone(); + if let Some(extents) = extents { + self.get_inner_size() + .map(|logical| extents.inner_size_to_outer_logical(logical, self.get_hidpi_factor())) + } else { + self.update_cached_frame_extents(); + self.get_outer_size() + } + } + + pub(crate) fn set_inner_size_physical(&self, width: u32, height: u32) { unsafe { (self.xconn.xlib.XResizeWindow)( self.xconn.display, @@ -674,62 +755,86 @@ impl UnownedWindow { height as c_uint, ); self.xconn.flush_requests() - }.expect("Failed to call XResizeWindow"); + }.expect("Failed to call `XResizeWindow`"); } - unsafe fn update_normal_hints(&self, callback: F) -> Result<(), XError> - where F: FnOnce(*mut ffi::XSizeHints) -> () + #[inline] + pub fn set_inner_size(&self, logical_size: LogicalSize) { + let dpi_factor = self.get_hidpi_factor(); + let (width, height) = logical_size.to_physical(dpi_factor).into(); + self.set_inner_size_physical(width, height); + } + + fn update_normal_hints(&self, callback: F) -> Result<(), XError> + where F: FnOnce(&mut util::NormalHints) -> () { - let size_hints = self.xconn.alloc_size_hints(); - let mut flags: c_long = mem::uninitialized(); - (self.xconn.xlib.XGetWMNormalHints)( - self.xconn.display, - self.xwindow, - size_hints.ptr, - &mut flags, - ); - self.xconn.check_errors()?; + let mut normal_hints = self.xconn.get_normal_hints(self.xwindow)?; + callback(&mut normal_hints); + self.xconn.set_normal_hints(self.xwindow, normal_hints).flush() + } - callback(size_hints.ptr); + pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + self.update_normal_hints(|normal_hints| normal_hints.set_min_size(dimensions)) + .expect("Failed to call `XSetWMNormalHints`"); + } - (self.xconn.xlib.XSetWMNormalHints)( - self.xconn.display, - self.xwindow, - size_hints.ptr, - ); - self.xconn.flush_requests()?; + #[inline] + pub fn set_min_dimensions(&self, logical_dimensions: Option) { + self.shared_state.lock().min_dimensions = logical_dimensions; + let physical_dimensions = logical_dimensions.map(|logical_dimensions| { + logical_dimensions.to_physical(self.get_hidpi_factor()).into() + }); + self.set_min_dimensions_physical(physical_dimensions); + } - Ok(()) + pub(crate) fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + self.update_normal_hints(|normal_hints| normal_hints.set_max_size(dimensions)) + .expect("Failed to call `XSetWMNormalHints`"); } - pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { - (*self.shared_state.lock()).min_dimensions = dimensions; - unsafe { - self.update_normal_hints(|size_hints| { - if let Some((width, height)) = dimensions { - (*size_hints).flags |= ffi::PMinSize; - (*size_hints).min_width = width as c_int; - (*size_hints).min_height = height as c_int; - } else { - (*size_hints).flags &= !ffi::PMinSize; - } - }) - }.expect("Failed to call XSetWMNormalHints"); + #[inline] + pub fn set_max_dimensions(&self, logical_dimensions: Option) { + self.shared_state.lock().max_dimensions = logical_dimensions; + let physical_dimensions = logical_dimensions.map(|logical_dimensions| { + logical_dimensions.to_physical(self.get_hidpi_factor()).into() + }); + self.set_max_dimensions_physical(physical_dimensions); } - pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { - (*self.shared_state.lock()).max_dimensions = dimensions; + pub(crate) fn adjust_for_dpi( + &self, + old_dpi_factor: f64, + new_dpi_factor: f64, + width: f64, + height: f64, + ) -> (f64, f64, util::Flusher) { + let scale_factor = new_dpi_factor / old_dpi_factor; + let new_width = width * scale_factor; + let new_height = height * scale_factor; + self.update_normal_hints(|normal_hints| { + let dpi_adjuster = |(width, height): (u32, u32)| -> (u32, u32) { + let new_width = width as f64 * scale_factor; + let new_height = height as f64 * scale_factor; + (new_width.round() as u32, new_height.round() as u32) + }; + let max_size = normal_hints.get_max_size().map(&dpi_adjuster); + let min_size = normal_hints.get_min_size().map(&dpi_adjuster); + let resize_increments = normal_hints.get_resize_increments().map(&dpi_adjuster); + let base_size = normal_hints.get_base_size().map(&dpi_adjuster); + normal_hints.set_max_size(max_size); + normal_hints.set_min_size(min_size); + normal_hints.set_resize_increments(resize_increments); + normal_hints.set_base_size(base_size); + }).expect("Failed to update normal hints"); unsafe { - self.update_normal_hints(|size_hints| { - if let Some((width, height)) = dimensions { - (*size_hints).flags |= ffi::PMaxSize; - (*size_hints).max_width = width as c_int; - (*size_hints).max_height = height as c_int; - } else { - (*size_hints).flags &= !ffi::PMaxSize; - } - }) - }.expect("Failed to call XSetWMNormalHints"); + (self.xconn.xlib.XResizeWindow)( + self.xconn.display, + self.xwindow, + new_width.round() as c_uint, + new_height.round() as c_uint, + ); + } + (new_width, new_height, util::Flusher::new(&self.xconn)) } pub fn set_resizable(&self, resizable: bool) { @@ -739,24 +844,26 @@ impl UnownedWindow { // the lesser of two evils and do nothing. return; } - if resizable { - let min_dimensions = (*self.shared_state.lock()).min_dimensions; - let max_dimensions = (*self.shared_state.lock()).max_dimensions; - self.set_min_dimensions(min_dimensions); - self.set_max_dimensions(max_dimensions); + + let (logical_min, logical_max) = if resizable { + let shared_state_lock = self.shared_state.lock(); + (shared_state_lock.min_dimensions, shared_state_lock.max_dimensions) } else { - unsafe { - self.update_normal_hints(|size_hints| { - (*size_hints).flags |= ffi::PMinSize | ffi::PMaxSize; - if let Some((width, height)) = self.get_inner_size() { - (*size_hints).min_width = width as c_int; - (*size_hints).min_height = height as c_int; - (*size_hints).max_width = width as c_int; - (*size_hints).max_height = height as c_int; - } - }) - }.expect("Failed to call XSetWMNormalHints"); - } + let window_size = self.get_inner_size(); + (window_size.clone(), window_size) + }; + + let dpi_factor = self.get_hidpi_factor(); + let min_dimensions = logical_min + .map(|logical_size| logical_size.to_physical(dpi_factor)) + .map(Into::into); + let max_dimensions = logical_max + .map(|logical_size| logical_size.to_physical(dpi_factor)) + .map(Into::into); + self.update_normal_hints(|normal_hints| { + normal_hints.set_min_size(min_dimensions); + normal_hints.set_max_size(max_dimensions); + }).expect("Failed to call `XSetWMNormalHints`"); } #[inline] @@ -774,21 +881,12 @@ impl UnownedWindow { Arc::clone(&self.xconn) } - #[inline] - pub fn platform_display(&self) -> *mut libc::c_void { - self.xconn.display as _ - } - #[inline] pub fn get_xlib_window(&self) -> c_ulong { self.xwindow } #[inline] - pub fn platform_window(&self) -> *mut libc::c_void { - self.xwindow as _ - } - pub fn get_xcb_connection(&self) -> *mut c_void { unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ @@ -886,6 +984,7 @@ impl UnownedWindow { } } + #[inline] pub fn set_cursor(&self, cursor: MouseCursor) { *self.cursor.lock() = cursor; if *self.cursor_state.lock() != CursorState::Hide { @@ -931,6 +1030,7 @@ impl UnownedWindow { Some(cursor) } + #[inline] pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { use CursorState::*; @@ -994,11 +1094,12 @@ impl UnownedWindow { } } - pub fn hidpi_factor(&self) -> f32 { + #[inline] + pub fn get_hidpi_factor(&self) -> f64 { self.get_current_monitor().hidpi_factor } - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { + pub(crate) fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ()> { unsafe { (self.xconn.xlib.XWarpPointer)( self.xconn.display, @@ -1015,6 +1116,24 @@ impl UnownedWindow { } } + #[inline] + pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), ()> { + let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into(); + self.set_cursor_position_physical(x, y) + } + + pub(crate) fn set_ime_spot_physical(&self, x: i32, y: i32) { + let _ = self.ime_sender + .lock() + .send((self.xwindow, x as i16, y as i16)); + } + + #[inline] + pub fn set_ime_spot(&self, logical_spot: LogicalPosition) { + let (x, y) = logical_spot.to_physical(self.get_hidpi_factor()).into(); + self.set_ime_spot_physical(x, y); + } + #[inline] pub fn id(&self) -> WindowId { WindowId(self.xwindow) } } diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index f084e46aff..b498bc3e69 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -377,14 +377,16 @@ impl EventsLoop { } else { window.view.convertPoint_fromView_(window_point, cocoa::base::nil) }; - let view_rect = NSView::frame(*window.view); - let scale_factor = window.hidpi_factor(); - let x = (scale_factor * view_point.x as f32) as f64; - let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64; - let window_event = WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event_mods(ns_event) }; + let view_rect = NSView::frame(*window.view); + let x = view_point.x as f64; + let y = (view_rect.size.height - view_point.y) as f64; + let window_event = WindowEvent::CursorMoved { + device_id: DEVICE_ID, + position: (x, y).into(), + modifiers: event_mods(ns_event), + }; let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event }; - self.shared.pending_events.lock().unwrap().push_back(event); Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID })) }, @@ -402,27 +404,25 @@ impl EventsLoop { None => return None, }; - let scale_factor = window.hidpi_factor(); - let mut events = std::collections::VecDeque::with_capacity(3); - let delta_x = (scale_factor * ns_event.deltaX() as f32) as f64; + let delta_x = ns_event.deltaX() as f64; if delta_x != 0.0 { let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x }; - let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event }; + let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event }; events.push_back(event); } - let delta_y = (scale_factor * ns_event.deltaY() as f32) as f64; + let delta_y = ns_event.deltaY() as f64; if delta_y != 0.0 { let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y }; - let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event }; + let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event }; events.push_back(event); } if delta_x != 0.0 || delta_y != 0.0 { let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) }; - let event = Event::DeviceEvent{ device_id: DEVICE_ID, event: motion_event }; + let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event }; events.push_back(event); } @@ -433,19 +433,22 @@ impl EventsLoop { appkit::NSScrollWheel => { // If none of the windows received the scroll, return `None`. - let window = match maybe_window { - Some(window) => window, - None => return None, - }; + if maybe_window.is_none() { + return None; + } use events::MouseScrollDelta::{LineDelta, PixelDelta}; - let scale_factor = window.hidpi_factor(); let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { - PixelDelta(scale_factor * ns_event.scrollingDeltaX() as f32, - scale_factor * ns_event.scrollingDeltaY() as f32) + PixelDelta(( + ns_event.scrollingDeltaX() as f64, + ns_event.scrollingDeltaY() as f64, + ).into()) } else { - LineDelta(scale_factor * ns_event.scrollingDeltaX() as f32, - scale_factor * ns_event.scrollingDeltaY() as f32) + // TODO: This is probably wrong + LineDelta( + ns_event.scrollingDeltaX() as f32, + ns_event.scrollingDeltaY() as f32, + ) }; let phase = match ns_event.phase() { NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, @@ -456,11 +459,15 @@ impl EventsLoop { device_id: DEVICE_ID, event: DeviceEvent::MouseWheel { delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { - PixelDelta(ns_event.scrollingDeltaX() as f32, - ns_event.scrollingDeltaY() as f32) + PixelDelta(( + ns_event.scrollingDeltaX() as f64, + ns_event.scrollingDeltaY() as f64, + ).into()) } else { - LineDelta(ns_event.scrollingDeltaX() as f32, - ns_event.scrollingDeltaY() as f32) + LineDelta( + ns_event.scrollingDeltaX() as f32, + ns_event.scrollingDeltaY() as f32, + ) }, } }); diff --git a/src/platform/macos/monitor.rs b/src/platform/macos/monitor.rs index 41f723b5fa..0e8c246160 100644 --- a/src/platform/macos/monitor.rs +++ b/src/platform/macos/monitor.rs @@ -1,9 +1,12 @@ +use std::collections::VecDeque; +use std::fmt; + use cocoa::appkit::NSScreen; use cocoa::base::{id, nil}; use cocoa::foundation::{NSString, NSUInteger}; use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; -use std::collections::VecDeque; -use std::fmt; + +use {PhysicalPosition, PhysicalSize}; use super::EventsLoop; use super::window::IdRef; @@ -39,9 +42,9 @@ impl fmt::Debug for MonitorId { struct MonitorId { name: Option, native_identifier: u32, - dimensions: (u32, u32), - position: (i32, i32), - hidpi_factor: f32, + dimensions: PhysicalSize, + position: PhysicalPosition, + hidpi_factor: f64, } let monitor_id_proxy = MonitorId { @@ -68,30 +71,32 @@ impl MonitorId { self.0 } - pub fn get_dimensions(&self) -> (u32, u32) { + pub fn get_dimensions(&self) -> PhysicalSize { let MonitorId(display_id) = *self; let display = CGDisplay::new(display_id); - let dimension = { - let height = display.pixels_high(); - let width = display.pixels_wide(); - (width as u32, height as u32) - }; - dimension + let height = display.pixels_high(); + let width = display.pixels_wide(); + PhysicalSize::from_logical( + (width as f64, height as f64), + self.get_hidpi_factor(), + ) } #[inline] - pub fn get_position(&self) -> (i32, i32) { + pub fn get_position(&self) -> PhysicalPosition { let bounds = unsafe { CGDisplayBounds(self.get_native_identifier()) }; - (bounds.origin.x as i32, bounds.origin.y as i32) + PhysicalPosition::from_logical( + (bounds.origin.x as f64, bounds.origin.y as f64), + self.get_hidpi_factor(), + ) } - pub fn get_hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { let screen = match self.get_nsscreen() { Some(screen) => screen, None => return 1.0, // default to 1.0 when we can't find the screen }; - - unsafe { NSScreen::backingScaleFactor(screen) as f32 } + unsafe { NSScreen::backingScaleFactor(screen) as f64 } } pub(crate) fn get_nsscreen(&self) -> Option { diff --git a/src/platform/macos/util.rs b/src/platform/macos/util.rs index 298362718a..be99c05588 100644 --- a/src/platform/macos/util.rs +++ b/src/platform/macos/util.rs @@ -14,8 +14,8 @@ pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { // For consistency with other platforms, this will... // 1. translate the bottom-left window corner into the top-left window corner // 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one -pub fn bottom_left_to_top_left(rect: NSRect) -> i32 { - (CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)) as _ +pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { + CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) } pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) { diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 7ce3de0dae..c5a4b41a15 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -22,7 +22,7 @@ use platform::platform::window::{get_window_id, IdRef}; struct ViewState { window: id, shared: Weak, - ime_spot: Option<(i32, i32)>, + ime_spot: Option<(f64, f64)>, raw_characters: Option, last_insert: Option, } @@ -43,7 +43,7 @@ pub fn new_view(window: id, shared: Weak) -> IdRef { } } -pub fn set_ime_spot(view: id, input_context: id, x: i32, y: i32) { +pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) { unsafe { let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); @@ -51,8 +51,8 @@ pub fn set_ime_spot(view: id, input_context: id, x: i32, y: i32) { state.window, NSWindow::frame(state.window), ); - let base_x = content_rect.origin.x as i32; - let base_y = (content_rect.origin.y + content_rect.size.height) as i32; + let base_x = content_rect.origin.x as f64; + let base_y = (content_rect.origin.y + content_rect.size.height) as f64; state.ime_spot = Some((base_x + x, base_y - y)); let _: () = msg_send![input_context, invalidateCharacterCoordinates]; } @@ -249,7 +249,7 @@ extern fn first_rect_for_character_range( ); let x = content_rect.origin.x; let y = util::bottom_left_to_top_left(content_rect); - (x as i32, y as i32) + (x, y) }); NSRect::new( @@ -527,15 +527,14 @@ fn mouse_motion(this: &Object, event: id) { return; } - let scale_factor = NSWindow::backingScaleFactor(state.window) as f64; - let x = scale_factor * view_point.x as f64; - let y = scale_factor * (view_rect.size.height as f64 - view_point.y as f64); + let x = view_point.x as f64; + let y = view_rect.size.height as f64 - view_point.y as f64; let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.window)), event: WindowEvent::CursorMoved { device_id: DEVICE_ID, - position: (x, y), + position: (x, y).into(), modifiers: event_mods(event), }, }; diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 2db5743b4d..06a8f978a8 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,40 +1,52 @@ -use {CreationError, Event, WindowEvent, WindowId, MouseCursor, CursorState}; -use CreationError::OsError; -use libc; - -use WindowAttributes; -use os::macos::ActivationPolicy; -use os::macos::WindowExt; - -use objc; -use objc::runtime::{Class, Object, Sel, BOOL, YES, NO}; -use objc::declare::ClassDecl; +use std; +use std::ops::Deref; +use std::os::raw::c_void; +use std::sync::Weak; +use std::cell::{Cell, RefCell}; use cocoa; -use cocoa::appkit::{self, NSApplication, NSColor, NSScreen, NSView, NSWindow, NSWindowButton, - NSWindowStyleMask}; +use cocoa::appkit::{ + self, + CGFloat, + NSApplication, + NSColor, + NSScreen, + NSView, + NSWindow, + NSWindowButton, + NSWindowStyleMask, +}; use cocoa::base::{id, nil}; -use cocoa::foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString, NSAutoreleasePool}; +use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}; use core_graphics::display::CGDisplay; -use std; -use std::ops::Deref; -use std::os::raw::c_void; -use std::sync::Weak; -use std::cell::{Cell, RefCell}; +use objc; +use objc::runtime::{Class, Object, Sel, BOOL, YES, NO}; +use objc::declare::ClassDecl; -use super::events_loop::{EventsLoop, Shared}; -use platform::platform::ffi; -use platform::platform::util; +use { + CreationError, + CursorState, + Event, + LogicalPosition, + LogicalSize, + MouseCursor, + WindowAttributes, + WindowEvent, + WindowId, +}; +use CreationError::OsError; +use os::macos::{ActivationPolicy, WindowExt}; +use platform::platform::{ffi, util}; +use platform::platform::events_loop::{EventsLoop, Shared}; use platform::platform::view::{new_view, set_ime_spot}; - use window::MonitorId as RootMonitorId; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(pub usize); -struct DelegateState { +pub struct DelegateState { view: IdRef, window: IdRef, shared: Weak, @@ -47,8 +59,11 @@ struct DelegateState { // see comments of `window_did_fail_to_enter_fullscreen` handle_with_fullscreen: bool, - // During windowDidResize, we use this to only send Moved if the position changed. - previous_position: Option<(i32, i32)>, + // During `windowDidResize`, we use this to only send Moved if the position changed. + previous_position: Option<(f64, f64)>, + + // Used to prevent redundant events. + previous_dpi_factor: f64, } impl DelegateState { @@ -150,48 +165,44 @@ pub struct WindowDelegate { } impl WindowDelegate { - /// Get the delegate class, initiailizing it neccessary - fn class() -> *const Class { - use std::os::raw::c_void; - - // Emits an event via the `EventsLoop`'s callback or stores it in the pending queue. - unsafe fn emit_event(state: &mut DelegateState, window_event: WindowEvent) { - let window_id = get_window_id(*state.window); - let event = Event::WindowEvent { - window_id: WindowId(window_id), - event: window_event, - }; - - if let Some(shared) = state.shared.upgrade() { - shared.call_user_callback_with_event_or_store_in_pending(event); - } + // Emits an event via the `EventsLoop`'s callback or stores it in the pending queue. + pub fn emit_event(state: &mut DelegateState, window_event: WindowEvent) { + let window_id = get_window_id(*state.window); + let event = Event::WindowEvent { + window_id: WindowId(window_id), + event: window_event, + }; + if let Some(shared) = state.shared.upgrade() { + shared.call_user_callback_with_event_or_store_in_pending(event); } + } - // Called when the window is resized or when the window was moved to a different screen. - unsafe fn emit_resize_event(state: &mut DelegateState) { - let rect = NSView::frame(*state.view); - let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32; - let width = (scale_factor * rect.size.width as f32) as u32; - let height = (scale_factor * rect.size.height as f32) as u32; - emit_event(state, WindowEvent::Resized(width, height)); - } + pub fn emit_resize_event(state: &mut DelegateState) { + let rect = unsafe { NSView::frame(*state.view) }; + let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); + WindowDelegate::emit_event(state, WindowEvent::Resized(size)); + } - unsafe fn emit_move_event(state: &mut DelegateState) { - let frame_rect = NSWindow::frame(*state.window); - let x = frame_rect.origin.x as _; - let y = util::bottom_left_to_top_left(frame_rect); - let moved = state.previous_position != Some((x, y)); - if moved { - state.previous_position = Some((x, y)); - emit_event(state, WindowEvent::Moved(x, y)); - } + pub fn emit_move_event(state: &mut DelegateState) { + let rect = unsafe { NSWindow::frame(*state.window) }; + let x = rect.origin.x as f64; + let y = util::bottom_left_to_top_left(rect); + let moved = state.previous_position != Some((x, y)); + if moved { + state.previous_position = Some((x, y)); + WindowDelegate::emit_event(state, WindowEvent::Moved((x, y).into())); } + } + + /// Get the delegate class, initiailizing it neccessary + fn class() -> *const Class { + use std::os::raw::c_void; extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, WindowEvent::CloseRequested); + WindowDelegate::emit_event(state, WindowEvent::CloseRequested); } NO } @@ -201,7 +212,7 @@ impl WindowDelegate { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, WindowEvent::Destroyed); + WindowDelegate::emit_event(state, WindowEvent::Destroyed); // Remove the window from the shared state. if let Some(shared) = state.shared.upgrade() { @@ -215,8 +226,8 @@ impl WindowDelegate { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_resize_event(state); - emit_move_event(state); + WindowDelegate::emit_resize_event(state); + WindowDelegate::emit_move_event(state); } } @@ -225,7 +236,7 @@ impl WindowDelegate { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_move_event(state); + WindowDelegate::emit_move_event(state); } } @@ -233,18 +244,26 @@ impl WindowDelegate { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_resize_event(state); - let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32; - emit_event(state, WindowEvent::HiDPIFactorChanged(scale_factor)); + let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64; + if state.previous_dpi_factor != dpi_factor { + state.previous_dpi_factor = dpi_factor; + WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor)); + WindowDelegate::emit_resize_event(state); + } } } + // This will always be called before `window_did_change_screen`. extern fn window_did_change_backing_properties(this: &Object, _:Sel, _:id) { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32; - emit_event(state, WindowEvent::HiDPIFactorChanged(scale_factor)); + let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64; + if state.previous_dpi_factor != dpi_factor { + state.previous_dpi_factor = dpi_factor; + WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor)); + WindowDelegate::emit_resize_event(state); + } } } @@ -254,7 +273,7 @@ impl WindowDelegate { // lost focus let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, WindowEvent::Focused(true)); + WindowDelegate::emit_event(state, WindowEvent::Focused(true)); } } @@ -262,7 +281,7 @@ impl WindowDelegate { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, WindowEvent::Focused(false)); + WindowDelegate::emit_event(state, WindowEvent::Focused(false)); } } @@ -285,7 +304,7 @@ impl WindowDelegate { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, WindowEvent::HoveredFile(PathBuf::from(path))); + WindowDelegate::emit_event(state, WindowEvent::HoveredFile(PathBuf::from(path))); } }; @@ -314,7 +333,7 @@ impl WindowDelegate { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, WindowEvent::DroppedFile(PathBuf::from(path))); + WindowDelegate::emit_event(state, WindowEvent::DroppedFile(PathBuf::from(path))); } }; @@ -329,7 +348,7 @@ impl WindowDelegate { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, WindowEvent::HoveredFileCancelled); + WindowDelegate::emit_event(state, WindowEvent::HoveredFileCancelled); } } @@ -500,7 +519,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub titlebar_hidden: bool, pub titlebar_buttons_hidden: bool, pub fullsize_content_view: bool, - pub resize_increments: Option<(u32, u32)>, + pub resize_increments: Option, } pub struct Window2 { @@ -617,12 +636,11 @@ impl Window2 { app.activateIgnoringOtherApps_(YES); - if let Some((width, height)) = win_attribs.min_dimensions { - nswindow_set_min_dimensions(window.0, width.into(), height.into()); + if let Some(dimensions) = win_attribs.min_dimensions { + nswindow_set_min_dimensions(window.0, dimensions); } - - if let Some((width, height)) = win_attribs.max_dimensions { - nswindow_set_max_dimensions(window.0, width.into(), height.into()); + if let Some(dimensions) = win_attribs.max_dimensions { + nswindow_set_max_dimensions(window.0, dimensions); } use cocoa::foundation::NSArray; @@ -631,7 +649,9 @@ impl Window2 { registerForDraggedTypes:NSArray::arrayWithObject(nil, appkit::NSFilenamesPboardType)]; } - let ds = DelegateState { + let dpi_factor = unsafe { NSWindow::backingScaleFactor(*window) as f64 }; + + let mut delegate_state = DelegateState { view: view.clone(), window: window.clone(), shared, @@ -640,13 +660,19 @@ impl Window2 { save_style_mask: Cell::new(None), handle_with_fullscreen: win_attribs.fullscreen.is_some(), previous_position: None, + previous_dpi_factor: dpi_factor, }; - ds.win_attribs.borrow_mut().fullscreen = None; + delegate_state.win_attribs.borrow_mut().fullscreen = None; + + if dpi_factor != 1.0 { + WindowDelegate::emit_event(&mut delegate_state, WindowEvent::HiDpiFactorChanged(dpi_factor)); + WindowDelegate::emit_resize_event(&mut delegate_state); + } let window = Window2 { view: view, window: window, - delegate: WindowDelegate::new(ds), + delegate: WindowDelegate::new(delegate_state), input_context, }; @@ -729,8 +755,10 @@ impl Window2 { let frame = match screen { Some(screen) => appkit::NSScreen::frame(screen), None => { - let (width, height) = attrs.dimensions.unwrap_or((800, 600)); - NSRect::new(NSPoint::new(0., 0.), NSSize::new(width as f64, height as f64)) + let (width, height) = attrs.dimensions + .map(|logical| (logical.width, logical.height)) + .unwrap_or((800.0, 600.0)); + NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height)) } }; @@ -799,9 +827,10 @@ impl Window2 { let _: () = msg_send![*window, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel]; } - if let Some((x, y)) = pl_attrs.resize_increments { - if x >= 1 && y >= 1 { - let size = NSSize::new(x as _, y as _); + if let Some(increments) = pl_attrs.resize_increments { + let (x, y) = (increments.width, increments.height); + if x >= 1.0 && y >= 1.0 { + let size = NSSize::new(x as CGFloat, y as CGFloat); window.setResizeIncrements_(size); } } @@ -843,15 +872,15 @@ impl Window2 { unsafe { NSWindow::orderOut_(*self.window, nil); } } - pub fn get_position(&self) -> Option<(i32, i32)> { + pub fn get_position(&self) -> Option { let frame_rect = unsafe { NSWindow::frame(*self.window) }; Some(( - frame_rect.origin.x as i32, + frame_rect.origin.x as f64, util::bottom_left_to_top_left(frame_rect), - )) + ).into()) } - pub fn get_inner_position(&self) -> Option<(i32, i32)> { + pub fn get_inner_position(&self) -> Option { let content_rect = unsafe { NSWindow::contentRectForFrameRect_( *self.window, @@ -859,18 +888,18 @@ impl Window2 { ) }; Some(( - content_rect.origin.x as i32, + content_rect.origin.x as f64, util::bottom_left_to_top_left(content_rect), - )) + ).into()) } - pub fn set_position(&self, x: i32, y: i32) { + pub fn set_position(&self, position: LogicalPosition) { let dummy = NSRect::new( NSPoint::new( - x as f64, + position.x, // While it's true that we're setting the top-left position, it still needs to be // in a bottom-left coordinate system. - CGDisplay::main().pixels_high() as f64 - y as f64, + CGDisplay::main().pixels_high() as f64 - position.y, ), NSSize::new(0f64, 0f64), ); @@ -880,42 +909,35 @@ impl Window2 { } #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { - let factor = self.hidpi_factor() as f64; // API convention is that size is in physical pixels - unsafe { - let view_frame = NSView::frame(*self.view); - Some(((view_frame.size.width*factor) as u32, (view_frame.size.height*factor) as u32)) - } + pub fn get_inner_size(&self) -> Option { + let view_frame = unsafe { NSView::frame(*self.view) }; + Some((view_frame.size.width as f64, view_frame.size.height as f64).into()) } #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { - let factor = self.hidpi_factor() as f64; // API convention is that size is in physical pixels - unsafe { - let window_frame = NSWindow::frame(*self.window); - Some(((window_frame.size.width*factor) as u32, (window_frame.size.height*factor) as u32)) - } + pub fn get_outer_size(&self) -> Option { + let view_frame = unsafe { NSWindow::frame(*self.window) }; + Some((view_frame.size.width as f64, view_frame.size.height as f64).into()) } #[inline] - pub fn set_inner_size(&self, width: u32, height: u32) { - let factor = self.hidpi_factor() as f64; // API convention is that size is in physical pixels + pub fn set_inner_size(&self, size: LogicalSize) { unsafe { - NSWindow::setContentSize_(*self.window, NSSize::new((width as f64)/factor, (height as f64)/factor)); + NSWindow::setContentSize_(*self.window, NSSize::new(size.width as CGFloat, size.height as CGFloat)); } } - pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + pub fn set_min_dimensions(&self, dimensions: Option) { unsafe { - let (width, height) = dimensions.unwrap_or((0, 0)); - nswindow_set_min_dimensions(self.window.0, width.into(), height.into()); + let dimensions = dimensions.unwrap_or_else(|| (0, 0).into()); + nswindow_set_min_dimensions(self.window.0, dimensions); } } - pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + pub fn set_max_dimensions(&self, dimensions: Option) { unsafe { - let (width, height) = dimensions.unwrap_or((!0, !0)); - nswindow_set_max_dimensions(self.window.0, width.into(), height.into()); + let dimensions = dimensions.unwrap_or_else(|| (!0, !0).into()); + nswindow_set_max_dimensions(self.window.0, dimensions); } } @@ -934,16 +956,6 @@ impl Window2 { } // Otherwise, we don't change the mask until we exit fullscreen. } - #[inline] - pub fn platform_display(&self) -> *mut libc::c_void { - unimplemented!() - } - - #[inline] - pub fn platform_window(&self) -> *mut libc::c_void { - *self.window as *mut libc::c_void - } - pub fn set_cursor(&self, cursor: MouseCursor) { let cursor_name = match cursor { MouseCursor::Arrow | MouseCursor::Default => "arrowCursor", @@ -1005,24 +1017,24 @@ impl Window2 { } #[inline] - pub fn hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { unsafe { - NSWindow::backingScaleFactor(*self.window) as f32 + NSWindow::backingScaleFactor(*self.window) as f64 } } #[inline] - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { - let (window_x, window_y) = self.get_position().unwrap_or((0, 0)); - let (cursor_x, cursor_y) = (window_x + x, window_y + y); - - // TODO: Check for errors. - let _ = CGDisplay::warp_mouse_cursor_position(appkit::CGPoint { - x: cursor_x as appkit::CGFloat, - y: cursor_y as appkit::CGFloat, - }); - let _ = CGDisplay::associate_mouse_and_mouse_cursor_position(true); - + pub fn set_cursor_position(&self, cursor_position: LogicalPosition) -> Result<(), ()> { + let window_position = self.get_inner_position() + .expect("`get_inner_position` failed"); + let point = appkit::CGPoint { + x: (cursor_position.x + window_position.x) as CGFloat, + y: (cursor_position.y + window_position.y) as CGFloat, + }; + CGDisplay::warp_mouse_cursor_position(point) + .expect("`CGWarpMouseCursorPosition` failed"); + CGDisplay::associate_mouse_and_mouse_cursor_position(true) + .expect("`CGAssociateMouseAndMouseCursorPosition` failed"); Ok(()) } @@ -1131,8 +1143,8 @@ impl Window2 { } #[inline] - pub fn set_ime_spot(&self, x: i32, y: i32) { - set_ime_spot(*self.view, *self.input_context, x, y); + pub fn set_ime_spot(&self, logical_spot: LogicalPosition) { + set_ime_spot(*self.view, *self.input_context, logical_spot.x, logical_spot.y); } #[inline] @@ -1149,51 +1161,50 @@ pub fn get_window_id(window_cocoa_id: id) -> Id { Id(window_cocoa_id as *const objc::runtime::Object as usize) } -unsafe fn nswindow_set_min_dimensions( - window: V, min_width: f64, min_height: f64) -{ +unsafe fn nswindow_set_min_dimensions(window: V, mut min_size: LogicalSize) { + let mut current_rect = NSWindow::frame(window); + let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); + // Convert from client area size to window size + min_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 + min_size.height += (current_rect.size.height - content_rect.size.height) as f64; window.setMinSize_(NSSize { - width: min_width, - height: min_height, + width: min_size.width as CGFloat, + height: min_size.height as CGFloat, }); // If necessary, resize the window to match constraint - let mut current_rect = NSWindow::frame(window); - if current_rect.size.width < min_width { - current_rect.size.width = min_width; + if current_rect.size.width < min_size.width { + current_rect.size.width = min_size.width; window.setFrame_display_(current_rect, 0) } - if current_rect.size.height < min_height { - // The origin point of a rectangle is at its bottom left in Cocoa. To - // ensure the window's top-left point remains the same: - current_rect.origin.y += - current_rect.size.height - min_height; - - current_rect.size.height = min_height; + if current_rect.size.height < min_size.height { + // The origin point of a rectangle is at its bottom left in Cocoa. + // To ensure the window's top-left point remains the same: + current_rect.origin.y += current_rect.size.height - min_size.height; + current_rect.size.height = min_size.height; window.setFrame_display_(current_rect, 0) } } -unsafe fn nswindow_set_max_dimensions( - window: V, max_width: f64, max_height: f64) -{ +unsafe fn nswindow_set_max_dimensions(window: V, mut max_size: LogicalSize) { + let mut current_rect = NSWindow::frame(window); + let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); + // Convert from client area size to window size + max_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 + max_size.height += (current_rect.size.height - content_rect.size.height) as f64; window.setMaxSize_(NSSize { - width: max_width, - height: max_height, + width: max_size.width as CGFloat, + height: max_size.height as CGFloat, }); // If necessary, resize the window to match constraint - let mut current_rect = NSWindow::frame(window); - if current_rect.size.width > max_width { - current_rect.size.width = max_width; + if current_rect.size.width > max_size.width { + current_rect.size.width = max_size.width; window.setFrame_display_(current_rect, 0) } - if current_rect.size.height > max_height { - // The origin point of a rectangle is at its bottom left in - // Cocoa. To ensure the window's top-left point remains the - // same: - current_rect.origin.y += - current_rect.size.height - max_height; - - current_rect.size.height = max_height; + if current_rect.size.height > max_size.height { + // The origin point of a rectangle is at its bottom left in Cocoa. + // To ensure the window's top-left point remains the same: + current_rect.origin.y += current_rect.size.height - max_size.height; + current_rect.size.height = max_size.height; window.setFrame_display_(current_rect, 0) } } diff --git a/src/platform/windows/dpi.rs b/src/platform/windows/dpi.rs new file mode 100644 index 0000000000..9238d20c5b --- /dev/null +++ b/src/platform/windows/dpi.rs @@ -0,0 +1,196 @@ +#![allow(non_snake_case, unused_unsafe)] + +use std::mem; +use std::os::raw::c_void; +use std::sync::{Once, ONCE_INIT}; + +use winapi::shared::minwindef::{BOOL, UINT, FALSE}; +use winapi::shared::windef::{ + DPI_AWARENESS_CONTEXT, + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, + HDC, + HMONITOR, + HWND, +}; +use winapi::shared::winerror::S_OK; +use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA}; +use winapi::um::shellscalingapi::{ + MDT_EFFECTIVE_DPI, + MONITOR_DPI_TYPE, + PROCESS_DPI_AWARENESS, + PROCESS_PER_MONITOR_DPI_AWARE, +}; +use winapi::um::wingdi::{GetDeviceCaps, LOGPIXELSX}; +use winapi::um::winnt::{HRESULT, LPCSTR}; +use winapi::um::winuser::{self, MONITOR_DEFAULTTONEAREST}; + +const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _; + +type SetProcessDPIAware = unsafe extern "system" fn () -> BOOL; +type SetProcessDpiAwareness = unsafe extern "system" fn ( + value: PROCESS_DPI_AWARENESS, +) -> HRESULT; +type SetProcessDpiAwarenessContext = unsafe extern "system" fn ( + value: DPI_AWARENESS_CONTEXT, +) -> BOOL; +type GetDpiForWindow = unsafe extern "system" fn (hwnd: HWND) -> UINT; +type GetDpiForMonitor = unsafe extern "system" fn ( + hmonitor: HMONITOR, + dpi_type: MONITOR_DPI_TYPE, + dpi_x: *mut UINT, + dpi_y: *mut UINT, +) -> HRESULT; +type EnableNonClientDpiScaling = unsafe extern "system" fn (hwnd: HWND) -> BOOL; + +// Helper function to dynamically load function pointer. +// `library` and `function` must be zero-terminated. +fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> { + assert_eq!(library.chars().last(), Some('\0')); + assert_eq!(function.chars().last(), Some('\0')); + + // Library names we will use are ASCII so we can use the A version to avoid string conversion. + let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) }; + if module.is_null() { + return None; + } + + let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) }; + if function_ptr.is_null() { + return None; + } + + Some(function_ptr as _) +} + +macro_rules! get_function { + ($lib:expr, $func:ident) => { + get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')) + .map(|f| unsafe { mem::transmute::<*const _, $func>(f) }) + } +} + +lazy_static! { + static ref GET_DPI_FOR_WINDOW: Option = get_function!( + "user32.dll", + GetDpiForWindow + ); + static ref GET_DPI_FOR_MONITOR: Option = get_function!( + "shcore.dll", + GetDpiForMonitor + ); + static ref ENABLE_NON_CLIENT_DPI_SCALING: Option = get_function!( + "user32.dll", + EnableNonClientDpiScaling + ); +} + +pub fn become_dpi_aware(enable: bool) { + if !enable { return; } + static ENABLE_DPI_AWARENESS: Once = ONCE_INIT; + ENABLE_DPI_AWARENESS.call_once(|| { unsafe { + if let Some(SetProcessDpiAwarenessContext) = get_function!( + "user32.dll", + SetProcessDpiAwarenessContext + ) { + // We are on Windows 10 Anniversary Update (1607) or later. + if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + == FALSE { + // V2 only works with Windows 10 Creators Update (1703). Try using the older + // V1 if we can't set V2. + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + } + } else if let Some(SetProcessDpiAwareness) = get_function!( + "shcore.dll", + SetProcessDpiAwareness + ) { + // We are on Windows 8.1 or later. + SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + } else if let Some(SetProcessDPIAware) = get_function!( + "user32.dll", + SetProcessDPIAware + ) { + // We are on Vista or later. + SetProcessDPIAware(); + } + } }); +} + +pub fn enable_non_client_dpi_scaling(hwnd: HWND) { + unsafe { + if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING { + EnableNonClientDpiScaling(hwnd); + } + } +} + +pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option { + unsafe { + if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let mut dpi_x = 0; + let mut dpi_y = 0; + if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK { + // MSDN says that "the values of *dpiX and *dpiY are identical. You only need to + // record one of the values to determine the DPI and respond appropriately". + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx + return Some(dpi_x as u32) + } + } + } + None +} + +pub const BASE_DPI: u32 = 96; +pub fn dpi_to_scale_factor(dpi: u32) -> f64 { + dpi as f64 / BASE_DPI as f64 +} + +pub unsafe fn get_window_dpi(hwnd: HWND, hdc: HDC) -> u32 { + if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { + // We are on Windows 10 Anniversary Update (1607) or later. + match GetDpiForWindow(hwnd) { + 0 => BASE_DPI, // 0 is returned if hwnd is invalid + dpi => dpi as u32, + } + } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let monitor = winuser::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if monitor.is_null() { + return BASE_DPI; + } + + let mut dpi_x = 0; + let mut dpi_y = 0; + if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK { + dpi_x as u32 + } else { + BASE_DPI + } + } else { + // We are on Vista or later. + if winuser::IsProcessDPIAware() != FALSE { + // If the process is DPI aware, then scaling must be handled by the application using + // this DPI value. + GetDeviceCaps(hdc, LOGPIXELSX) as u32 + } else { + // If the process is DPI unaware, then scaling is performed by the OS; we thus return + // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the + // application and the WM. + BASE_DPI + } + } +} + +// Use this when you have both the HWND and HDC on hand (i.e. window methods) +pub fn get_window_scale_factor(hwnd: HWND, hdc: HDC) -> f64 { + dpi_to_scale_factor(unsafe { get_window_dpi(hwnd, hdc) }) +} + +// Use this when you only have the HWND (i.e. event handling) +pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 { + let hdc = unsafe { winuser::GetDC(hwnd) }; + if hdc.is_null() { + panic!("[winit] `GetDC` returned null!"); + } + unsafe { get_window_scale_factor(hwnd, hdc) } +} diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index b31f238876..1f51ac1dd7 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -12,42 +12,56 @@ //! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to //! add a `WindowState` entry to a list of window to be used by the callback. +use std::{mem, ptr, thread}; use std::cell::RefCell; use std::collections::HashMap; use std::ffi::OsString; -use std::mem; use std::os::windows::ffi::OsStringExt; use std::os::windows::io::AsRawHandle; -use std::ptr; -use std::sync::mpsc; -use std::sync::Arc; -use std::sync::Barrier; -use std::sync::Mutex; -use std::sync::Condvar; -use std::thread; - -use winapi::shared::minwindef::{LOWORD, HIWORD, DWORD, WPARAM, LPARAM, INT, UINT, LRESULT, MAX_PATH}; +use std::sync::{Arc, Barrier, Condvar, mpsc, Mutex}; + +use winapi::ctypes::c_int; +use winapi::shared::minwindef::{ + BOOL, + DWORD, + HIWORD, + INT, + LOWORD, + LPARAM, + LRESULT, + MAX_PATH, + UINT, + WPARAM, +}; use winapi::shared::windef::{HWND, POINT, RECT}; use winapi::shared::windowsx; use winapi::um::{winuser, shellapi, processthreadsapi}; -use winapi::um::winnt::{LONG, SHORT}; - -use events::DeviceEvent; +use winapi::um::winnt::{LONG, LPCSTR, SHORT}; + +use { + ControlFlow, + CursorState, + Event, + EventsLoopClosed, + KeyboardInput, + LogicalPosition, + LogicalSize, + PhysicalSize, + WindowEvent, + WindowId as SuperWindowId, +}; +use events::{DeviceEvent, Touch, TouchPhase}; use platform::platform::{event, Cursor, WindowId, DEVICE_ID, wrap_device_id, util}; +use platform::platform::dpi::{ + become_dpi_aware, + dpi_to_scale_factor, + enable_non_client_dpi_scaling, + get_hwnd_scale_factor, +}; use platform::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey}; -use platform::platform::raw_input::*; +use platform::platform::raw_input::{get_raw_input_data, get_raw_mouse_button_state}; use platform::platform::window::adjust_size; -use ControlFlow; -use CursorState; -use Event; -use EventsLoopClosed; -use KeyboardInput; -use WindowAttributes; -use WindowEvent; -use WindowId as SuperWindowId; -use events::{Touch, TouchPhase}; - /// Contains saved window info for switching between fullscreen #[derive(Clone)] pub struct SavedWindowInfo { @@ -57,6 +71,11 @@ pub struct SavedWindowInfo { pub ex_style: LONG, /// Window position and size pub rect: RECT, + // Since a window can be fullscreened to a different monitor, a DPI change can be triggered. This could result in + // the window being automitcally resized to smaller/larger than it was supposed to be restored to, so we thus must + // check if the post-fullscreen DPI matches the pre-fullscreen DPI. + pub is_fullscreen: bool, + pub dpi_factor: Option, } /// Contains information about states and the window that the callback is going to use. @@ -67,11 +86,28 @@ pub struct WindowState { /// Cursor state to set at the next `WM_SETCURSOR` event received. pub cursor_state: CursorState, /// Used by `WM_GETMINMAXINFO`. - pub attributes: WindowAttributes, + pub max_size: Option, + pub min_size: Option, /// Will contain `true` if the mouse is hovering the window. pub mouse_in_window: bool, /// Saved window info for fullscreen restored pub saved_window_info: Option, + // This is different from the value in `SavedWindowInfo`! That one represents the DPI saved upon entering + // fullscreen. This will always be the most recent DPI for the window. + pub dpi_factor: f64, +} + +impl WindowState { + pub fn update_min_max(&mut self, old_dpi_factor: f64, new_dpi_factor: f64) { + let scale_factor = new_dpi_factor / old_dpi_factor; + let dpi_adjuster = |mut physical_size: PhysicalSize| -> PhysicalSize { + physical_size.width *= scale_factor; + physical_size.height *= scale_factor; + physical_size + }; + self.max_size = self.max_size.map(&dpi_adjuster); + self.min_size = self.min_size.map(&dpi_adjuster); + } } /// Dummy object that allows inserting a window's state. @@ -98,11 +134,17 @@ pub struct EventsLoop { // Variable that contains the block state of the win32 event loop thread during a WM_SIZE event. // The mutex's value is `true` when it's blocked, and should be set to false when it's done // blocking. That's done by the parent thread when it receives a Resized event. - win32_block_loop: Arc<(Mutex, Condvar)> + win32_block_loop: Arc<(Mutex, Condvar)>, } impl EventsLoop { pub fn new() -> EventsLoop { + Self::with_dpi_awareness(true) + } + + pub fn with_dpi_awareness(dpi_aware: bool) -> EventsLoop { + become_dpi_aware(dpi_aware); + // The main events transfer channel. let (tx, rx) = mpsc::channel(); let win32_block_loop = Arc::new((Mutex::new(false), Condvar::new())); @@ -171,7 +213,7 @@ impl EventsLoop { EventsLoop { thread_id, receiver: rx, - win32_block_loop + win32_block_loop, } } @@ -289,22 +331,21 @@ impl EventsLoopProxy { where F: FnMut(Inserter) + Send + 'static, { - unsafe { - // We are using double-boxing here because it make casting back much easier - let boxed = Box::new(function) as Box; - let boxed2 = Box::new(boxed); - let raw = Box::into_raw(boxed2); + // We are using double-boxing here because it make casting back much easier + let double_box = Box::new(Box::new(function) as Box); + let raw = Box::into_raw(double_box); - let res = winuser::PostThreadMessageA( + let res = unsafe { + winuser::PostThreadMessageA( self.thread_id, *EXEC_MSG_ID, raw as *mut () as usize as WPARAM, 0, - ); - // PostThreadMessage can only fail if the thread ID is invalid (which shouldn't happen - // as the events loop is still alive) or if the queue is full. - assert!(res != 0, "PostThreadMessage failed; is the messages queue full?"); - } + ) + }; + // PostThreadMessage can only fail if the thread ID is invalid (which shouldn't happen as + // the events loop is still alive) or if the queue is full. + assert!(res != 0, "PostThreadMessage failed; is the messages queue full?"); } } @@ -313,7 +354,7 @@ lazy_static! { // WPARAM and LPARAM are unused. static ref WAKEUP_MSG_ID: u32 = { unsafe { - winuser::RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr() as *const i8) + winuser::RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr() as LPCSTR) } }; // Message sent when we want to execute a closure in the thread. @@ -321,14 +362,21 @@ lazy_static! { // and LPARAM is unused. static ref EXEC_MSG_ID: u32 = { unsafe { - winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as *const i8) + winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as LPCSTR) } }; // Message sent by a `Window` when it wants to be destroyed by the main thread. // WPARAM and LPARAM are unused. pub static ref DESTROY_MSG_ID: u32 = { unsafe { - winuser::RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr() as *const i8) + winuser::RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr() as LPCSTR) + } + }; + // Message sent by a `Window` after creation if it has a DPI != 96. + // WPARAM is the the DPI (u32). LOWORD of LPARAM is width, and HIWORD is height. + pub static ref INITIAL_DPI_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::InitialDpiMsg\0".as_ptr() as LPCSTR) } }; } @@ -385,11 +433,18 @@ unsafe fn release_mouse() { // // Returning 0 tells the Win32 API that the message has been processed. // FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary -pub unsafe extern "system" fn callback(window: HWND, msg: UINT, - wparam: WPARAM, lparam: LPARAM) - -> LRESULT -{ +pub unsafe extern "system" fn callback( + window: HWND, + msg: UINT, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { match msg { + winuser::WM_NCCREATE => { + enable_non_client_dpi_scaling(window); + winuser::DefWindowProcW(window, msg, wparam, lparam) + }, + winuser::WM_CLOSE => { use events::WindowEvent::CloseRequested; send_event(Event::WindowEvent { @@ -427,9 +482,14 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, let windowpos = lparam as *const winuser::WINDOWPOS; if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE { + let dpi_factor = get_hwnd_scale_factor(window); + let logical_position = LogicalPosition::from_physical( + ((*windowpos).x, (*windowpos).y), + dpi_factor, + ); send_event(Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), - event: Moved((*windowpos).x, (*windowpos).y), + event: Moved(logical_position), }); } @@ -448,9 +508,11 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, let mut context_stash = context_stash.borrow_mut(); let cstash = context_stash.as_mut().unwrap(); + let dpi_factor = get_hwnd_scale_factor(window); + let logical_size = LogicalSize::from_physical((w, h), dpi_factor); let event = Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), - event: Resized(w, h), + event: Resized(logical_size), }; // If this window has been inserted into the window map, the resize event happened @@ -528,10 +590,12 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, let x = windowsx::GET_X_LPARAM(lparam) as f64; let y = windowsx::GET_Y_LPARAM(lparam) as f64; + let dpi_factor = get_hwnd_scale_factor(window); + let position = LogicalPosition::from_physical((x, y), dpi_factor); send_event(Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event::get_key_mods() }, + event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, }); 0 @@ -869,10 +933,17 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, let mut inputs = Vec::with_capacity( pcount ); inputs.set_len( pcount ); let htouch = lparam as winuser::HTOUCHINPUT; - if winuser::GetTouchInputInfo( htouch, pcount as UINT, - inputs.as_mut_ptr(), - mem::size_of::() as INT ) > 0 { + if winuser::GetTouchInputInfo( + htouch, + pcount as UINT, + inputs.as_mut_ptr(), + mem::size_of::() as INT, + ) > 0 { + let dpi_factor = get_hwnd_scale_factor(window); for input in &inputs { + let x = (input.x as f64) / 100f64; + let y = (input.y as f64) / 100f64; + let location = LogicalPosition::from_physical((x, y), dpi_factor); send_event( Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), event: WindowEvent::Touch(Touch { @@ -886,8 +957,7 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, } else { continue; }, - location: ((input.x as f64) / 100f64, - (input.y as f64) / 100f64), + location, id: input.dwID as u64, device_id: DEVICE_ID, }) @@ -907,11 +977,14 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, let x = windowsx::GET_X_LPARAM(lparam) as f64; let y = windowsx::GET_Y_LPARAM(lparam) as f64; + let dpi_factor = get_hwnd_scale_factor(window); + let position = LogicalPosition::from_physical((x, y), dpi_factor); send_event(Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position: (x, y), modifiers: event::get_key_mods() }, + event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, }); + 0 }, @@ -985,18 +1058,15 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, if let Some(wstash) = cstash.windows.get(&window) { let window_state = wstash.lock().unwrap(); - if window_state.attributes.min_dimensions.is_some() || - window_state.attributes.max_dimensions.is_some() { - + if window_state.min_size.is_some() || window_state.max_size.is_some() { let style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; let ex_style = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; - - if let Some(min_dimensions) = window_state.attributes.min_dimensions { - let (width, height) = adjust_size(min_dimensions, style, ex_style); + if let Some(min_size) = window_state.min_size { + let (width, height) = adjust_size(min_size, style, ex_style); (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32 }; } - if let Some(max_dimensions) = window_state.attributes.max_dimensions { - let (width, height) = adjust_size(max_dimensions, style, ex_style); + if let Some(max_size) = window_state.max_size { + let (width, height) = adjust_size(max_size, style, ex_style); (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32 }; } } @@ -1007,10 +1077,115 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, 0 }, + // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change + // DPI, therefore all applications are closed while DPI is changing. + winuser::WM_DPICHANGED => { + use events::WindowEvent::HiDpiFactorChanged; + + // This message actually provides two DPI values - x and y. However MSDN says that + // "you only need to use either the X-axis or the Y-axis value when scaling your + // application since they are the same". + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx + let new_dpi_x = u32::from(LOWORD(wparam as DWORD)); + let new_dpi_factor = dpi_to_scale_factor(new_dpi_x); + + let suppress_resize = CONTEXT_STASH.with(|context_stash| { + context_stash + .borrow() + .as_ref() + .and_then(|cstash| cstash.windows.get(&window)) + .map(|window_state_mutex| { + let mut window_state = window_state_mutex.lock().unwrap(); + let suppress_resize = window_state.saved_window_info + .as_mut() + .map(|saved_window_info| { + let dpi_changed = if !saved_window_info.is_fullscreen { + saved_window_info.dpi_factor.take() != Some(new_dpi_factor) + } else { + false + }; + !dpi_changed || saved_window_info.is_fullscreen + }) + .unwrap_or(false); + // Now we adjust the min/max dimensions for the new DPI. + if !suppress_resize { + let old_dpi_factor = window_state.dpi_factor; + window_state.update_min_max(old_dpi_factor, new_dpi_factor); + } + window_state.dpi_factor = new_dpi_factor; + suppress_resize + }) + .unwrap_or(false) + }); + + // This prevents us from re-applying DPI adjustment to the restored size after exiting + // fullscreen (the restored size is already DPI adjusted). + if !suppress_resize { + // Resize window to the size suggested by Windows. + let rect = &*(lparam as *const RECT); + winuser::SetWindowPos( + window, + ptr::null_mut(), + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, + ); + } + + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: HiDpiFactorChanged(new_dpi_factor), + }); + + 0 + }, + _ => { if msg == *DESTROY_MSG_ID { winuser::DestroyWindow(window); 0 + } else if msg == *INITIAL_DPI_MSG_ID { + use events::WindowEvent::HiDpiFactorChanged; + let scale_factor = dpi_to_scale_factor(wparam as u32); + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: HiDpiFactorChanged(scale_factor), + }); + // Automatically resize for actual DPI + let width = LOWORD(lparam as DWORD) as u32; + let height = HIWORD(lparam as DWORD) as u32; + let (adjusted_width, adjusted_height): (u32, u32) = PhysicalSize::from_logical( + (width, height), + scale_factor, + ).into(); + // We're not done yet! `SetWindowPos` needs the window size, not the client area size. + let mut rect = RECT { + top: 0, + left: 0, + bottom: adjusted_height as LONG, + right: adjusted_width as LONG, + }; + let dw_style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; + let b_menu = !winuser::GetMenu(window).is_null() as BOOL; + let dw_style_ex = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; + winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); + let outer_x = (rect.right - rect.left).abs() as c_int; + let outer_y = (rect.top - rect.bottom).abs() as c_int; + winuser::SetWindowPos( + window, + ptr::null_mut(), + 0, + 0, + outer_x, + outer_y, + winuser::SWP_NOMOVE + | winuser::SWP_NOREPOSITION + | winuser::SWP_NOZORDER + | winuser::SWP_NOACTIVATE, + ); + 0 } else { winuser::DefWindowProcW(window, msg, wparam, lparam) } diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 6307f9a3ab..69c96d68f8 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -47,6 +47,7 @@ pub struct WindowId(HWND); unsafe impl Send for WindowId {} unsafe impl Sync for WindowId {} +mod dpi; mod event; mod events_loop; mod icon; diff --git a/src/platform/windows/monitor.rs b/src/platform/windows/monitor.rs index 296f52dbda..83d54f57df 100644 --- a/src/platform/windows/monitor.rs +++ b/src/platform/windows/monitor.rs @@ -1,38 +1,31 @@ -use winapi::ctypes::wchar_t; -use winapi::shared::minwindef::{DWORD, LPARAM, BOOL, TRUE}; -use winapi::shared::windef::{HMONITOR, HDC, LPRECT, HWND}; +use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE}; +use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT}; use winapi::um::winuser; -use std::collections::VecDeque; use std::{mem, ptr}; +use std::collections::VecDeque; +use {PhysicalPosition, PhysicalSize}; use super::{EventsLoop, util}; +use platform::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi}; /// Win32 implementation of the main `MonitorId` object. #[derive(Debug, Clone)] pub struct MonitorId { - /// The system name of the adapter. - adapter_name: [wchar_t; 32], - /// Monitor handle. hmonitor: HMonitor, - /// The system name of the monitor. monitor_name: String, - /// True if this is the primary monitor. primary: bool, - /// The position of the monitor in pixels on the desktop. /// /// A window that is positioned at these coordinates will overlap the monitor. position: (i32, i32), - /// The current resolution in pixels on the monitor. dimensions: (u32, u32), - - /// DPI scaling factor. - hidpi_factor: f32, + /// DPI scale factor. + hidpi_factor: f64, } // Send is not implemented for HMONITOR, we have to wrap it and implement it manually. @@ -44,122 +37,109 @@ struct HMonitor(HMONITOR); unsafe impl Send for HMonitor {} -unsafe extern "system" fn monitor_enum_proc(hmonitor: HMONITOR, _: HDC, place: LPRECT, data: LPARAM) -> BOOL { +unsafe extern "system" fn monitor_enum_proc( + hmonitor: HMONITOR, + _hdc: HDC, + _place: LPRECT, + data: LPARAM, +) -> BOOL { let monitors = data as *mut VecDeque; - - let place = *place; - let position = (place.left as i32, place.top as i32); - let dimensions = ((place.right - place.left) as u32, (place.bottom - place.top) as u32); - - let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed(); - monitor_info.cbSize = mem::size_of::() as DWORD; - if winuser::GetMonitorInfoW(hmonitor, &mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO) == 0 { - // Some error occurred, just skip this monitor and go on. - return TRUE; - } - - (*monitors).push_back(MonitorId { - adapter_name: monitor_info.szDevice, - hmonitor: HMonitor(hmonitor), - monitor_name: util::wchar_to_string(&monitor_info.szDevice), - primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0, - position, - dimensions, - hidpi_factor: 1.0, - }); - - // TRUE means continue enumeration. - TRUE + (*monitors).push_back(MonitorId::from_hmonitor(hmonitor)); + TRUE // continue enumeration } impl EventsLoop { + // TODO: Investigate opportunities for caching pub fn get_available_monitors(&self) -> VecDeque { + let mut monitors: VecDeque = VecDeque::new(); unsafe { - let mut result: VecDeque = VecDeque::new(); - winuser::EnumDisplayMonitors(ptr::null_mut(), ptr::null_mut(), Some(monitor_enum_proc), &mut result as *mut _ as LPARAM); - result + winuser::EnumDisplayMonitors( + ptr::null_mut(), + ptr::null_mut(), + Some(monitor_enum_proc), + &mut monitors as *mut _ as LPARAM, + ); } + monitors } - pub fn get_current_monitor(handle: HWND) -> MonitorId { - unsafe { - let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed(); - monitor_info.cbSize = mem::size_of::() as DWORD; - - let hmonitor = winuser::MonitorFromWindow(handle, winuser::MONITOR_DEFAULTTONEAREST); - - winuser::GetMonitorInfoW( - hmonitor, - &mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO, - ); - - let place = monitor_info.rcMonitor; - let position = (place.left as i32, place.top as i32); - let dimensions = ( - (place.right - place.left) as u32, - (place.bottom - place.top) as u32, - ); - - MonitorId { - adapter_name: monitor_info.szDevice, - hmonitor: super::monitor::HMonitor(hmonitor), - monitor_name: util::wchar_to_string(&monitor_info.szDevice), - primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0, - position, - dimensions, - hidpi_factor: 1.0, - } - } + pub fn get_current_monitor(hwnd: HWND) -> MonitorId { + let hmonitor = unsafe { + winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) + }; + MonitorId::from_hmonitor(hmonitor) } pub fn get_primary_monitor(&self) -> MonitorId { - // we simply get all available monitors and return the one with the `MONITORINFOF_PRIMARY` flag - // TODO: it is possible to query the win32 API for the primary monitor, this should be done - // instead - for monitor in self.get_available_monitors().into_iter() { - if monitor.primary { - return monitor; - } - } + const ORIGIN: POINT = POINT { x: 0, y: 0 }; + let hmonitor = unsafe { + winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) + }; + MonitorId::from_hmonitor(hmonitor) + } +} - panic!("Failed to find the primary monitor") +fn get_monitor_info(hmonitor: HMONITOR) -> Result { + let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() }; + monitor_info.cbSize = mem::size_of::() as DWORD; + let status = unsafe { + winuser::GetMonitorInfoW( + hmonitor, + &mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO, + ) + }; + if status == 0 { + Err(util::WinError::from_last_error()) + } else { + Ok(monitor_info) } } impl MonitorId { - /// See the docs if the crate root file. + pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self { + let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed"); + let place = monitor_info.rcMonitor; + let dimensions = ( + (place.right - place.left) as u32, + (place.bottom - place.top) as u32, + ); + MonitorId { + hmonitor: HMonitor(hmonitor), + monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()), + primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY), + position: (place.left as i32, place.top as i32), + dimensions, + hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)), + } + } + #[inline] pub fn get_name(&self) -> Option { Some(self.monitor_name.clone()) } - /// See the docs of the crate root file. #[inline] pub fn get_native_identifier(&self) -> String { self.monitor_name.clone() } - /// See the docs of the crate root file. #[inline] pub fn get_hmonitor(&self) -> HMONITOR { self.hmonitor.0 } - /// See the docs of the crate root file. #[inline] - pub fn get_dimensions(&self) -> (u32, u32) { - // TODO: retrieve the dimensions every time this is called - self.dimensions + pub fn get_dimensions(&self) -> PhysicalSize { + self.dimensions.into() } - /// A window that is positioned at these coordinates will overlap the monitor. #[inline] - pub fn get_position(&self) -> (i32, i32) { - self.position + pub fn get_position(&self) -> PhysicalPosition { + self.position.into() } #[inline] - pub fn get_hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { self.hidpi_factor } } diff --git a/src/platform/windows/util.rs b/src/platform/windows/util.rs index 31d9b18bb8..d872cc0c3d 100644 --- a/src/platform/windows/util.rs +++ b/src/platform/windows/util.rs @@ -1,9 +1,9 @@ -use std::{self, mem, ptr}; +use std::{self, mem, ptr, slice}; use std::ops::BitAnd; use winapi::ctypes::wchar_t; use winapi::shared::minwindef::DWORD; -use winapi::shared::windef::RECT; +use winapi::shared::windef::{HWND, RECT}; use winapi::um::errhandlingapi::GetLastError; use winapi::um::winbase::{ FormatMessageW, @@ -19,6 +19,7 @@ use winapi::um::winnt::{ LANG_NEUTRAL, SUBLANG_DEFAULT, }; +use winapi::um::winuser; pub fn has_flag(bitset: T, flag: T) -> bool where T: @@ -28,9 +29,22 @@ where T: } pub fn wchar_to_string(wchar: &[wchar_t]) -> String { - String::from_utf16_lossy(wchar) - .trim_right_matches(0 as char) - .to_string() + String::from_utf16_lossy(wchar).to_string() +} + +pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String { + let len = unsafe { lstrlenW(wchar) } as usize; + let wchar_slice = unsafe { slice::from_raw_parts(wchar, len) }; + wchar_to_string(wchar_slice) +} + +pub fn get_window_rect(hwnd: HWND) -> Option { + let mut rect: RECT = unsafe { mem::uninitialized() }; + if unsafe { winuser::GetWindowRect(hwnd, &mut rect) } != 0 { + Some(rect) + } else { + None + } } // This won't be needed anymore if we just add a derive to winapi. diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index ae87e4d29c..516ec93322 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -1,29 +1,34 @@ #![cfg(target_os = "windows")] -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::ffi::OsStr; use std::{io, mem, ptr}; -use std::os::raw; use std::os::windows::ffi::OsStrExt; use std::sync::{Arc, Mutex}; use std::sync::mpsc::channel; -use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE, UINT}; +use winapi::ctypes::c_int; +use winapi::shared::minwindef::{BOOL, DWORD, FALSE, LPARAM, TRUE, UINT, WORD, WPARAM}; use winapi::shared::windef::{HDC, HWND, LPPOINT, POINT, RECT}; use winapi::um::{combaseapi, dwmapi, libloaderapi, winuser}; use winapi::um::objbase::{COINIT_MULTITHREADED}; use winapi::um::shobjidl_core::{CLSID_TaskbarList, ITaskbarList2}; use winapi::um::winnt::{LONG, LPCWSTR}; -use CreationError; -use CursorState; -use Icon; -use MonitorId as RootMonitorId; -use MouseCursor; -use WindowAttributes; - +use { + CreationError, + CursorState, + Icon, + LogicalPosition, + LogicalSize, + MonitorId as RootMonitorId, + MouseCursor, + PhysicalSize, + WindowAttributes, +}; use platform::platform::{Cursor, EventsLoop, PlatformSpecificWindowBuilderAttributes, WindowId}; -use platform::platform::events_loop::{self, DESTROY_MSG_ID}; +use platform::platform::dpi::{BASE_DPI, dpi_to_scale_factor, get_window_dpi, get_window_scale_factor}; +use platform::platform::events_loop::{self, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID}; use platform::platform::icon::{self, IconType, WinIcon}; use platform::platform::raw_input::register_all_mice_and_keyboards_for_raw_input; use platform::platform::util; @@ -33,6 +38,12 @@ pub struct Window { /// Main handle for the window. window: WindowWrapper, + decorations: Cell, + maximized: Cell, + resizable: Cell, + fullscreen: RefCell>, + always_on_top: Cell, + /// The current window state. window_state: Arc>, @@ -56,19 +67,16 @@ unsafe impl Sync for Window {} // and it added fifty pixels to the top. // From this we can perform the reverse calculation: Instead of expanding the rectangle, we shrink it. unsafe fn unjust_window_rect(prc: &mut RECT, style: DWORD, ex_style: DWORD) -> BOOL { - let mut rc: RECT = mem::zeroed(); - + let mut rc: RECT = mem::uninitialized(); winuser::SetRectEmpty(&mut rc); - - let frc = winuser::AdjustWindowRectEx(&mut rc, style, 0, ex_style); - if frc != 0 { + let status = winuser::AdjustWindowRectEx(&mut rc, style, 0, ex_style); + if status != 0 { prc.left -= rc.left; prc.top -= rc.top; prc.right -= rc.right; prc.bottom -= rc.bottom; } - - frc + status } impl Window { @@ -78,16 +86,13 @@ impl Window { pl_attr: PlatformSpecificWindowBuilderAttributes, ) -> Result { let (tx, rx) = channel(); - let proxy = events_loop.create_proxy(); - events_loop.execute_in_thread(move |inserter| { // We dispatch an `init` function because of code style. // First person to remove the need for cloning here gets a cookie! let win = unsafe { init(w_attr.clone(), pl_attr.clone(), inserter, proxy.clone()) }; let _ = tx.send(win); }); - rx.recv().unwrap() } @@ -115,168 +120,193 @@ impl Window { } } - /// See the docs in the crate root file. - pub fn get_position(&self) -> Option<(i32, i32)> { - let mut rect: RECT = unsafe { mem::uninitialized() }; - - if unsafe { winuser::GetWindowRect(self.window.0, &mut rect) } != 0 { - Some((rect.left as i32, rect.top as i32)) - } else { - None - } + pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> { + util::get_window_rect(self.window.0) + .map(|rect| (rect.left as i32, rect.top as i32)) } - pub fn get_inner_position(&self) -> Option<(i32, i32)> { - use std::mem; + #[inline] + pub fn get_position(&self) -> Option { + self.get_position_physical() + .map(|physical_position| { + let dpi_factor = self.get_hidpi_factor(); + LogicalPosition::from_physical(physical_position, dpi_factor) + }) + } + pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> { let mut position: POINT = unsafe { mem::zeroed() }; if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 { return None; } - Some((position.x, position.y)) } - /// See the docs in the crate root file. - pub fn set_position(&self, x: i32, y: i32) { + #[inline] + pub fn get_inner_position(&self) -> Option { + self.get_inner_position_physical() + .map(|physical_position| { + let dpi_factor = self.get_hidpi_factor(); + LogicalPosition::from_physical(physical_position, dpi_factor) + }) + } + + pub(crate) fn set_position_physical(&self, x: i32, y: i32) { unsafe { - winuser::SetWindowPos(self.window.0, ptr::null_mut(), x as raw::c_int, y as raw::c_int, - 0, 0, winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOSIZE); + winuser::SetWindowPos( + self.window.0, + ptr::null_mut(), + x as c_int, + y as c_int, + 0, + 0, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOSIZE, + ); winuser::UpdateWindow(self.window.0); } } - /// See the docs in the crate root file. #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { - let mut rect: RECT = unsafe { mem::uninitialized() }; + pub fn set_position(&self, logical_position: LogicalPosition) { + let dpi_factor = self.get_hidpi_factor(); + let (x, y) = logical_position.to_physical(dpi_factor).into(); + self.set_position_physical(x, y); + } + pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> { + let mut rect: RECT = unsafe { mem::uninitialized() }; if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 { - return None + return None; } - Some(( (rect.right - rect.left) as u32, - (rect.bottom - rect.top) as u32 + (rect.bottom - rect.top) as u32, )) } - /// See the docs in the crate root file. #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { - let mut rect: RECT = unsafe { mem::uninitialized() }; + pub fn get_inner_size(&self) -> Option { + self.get_inner_size_physical() + .map(|physical_size| { + let dpi_factor = self.get_hidpi_factor(); + LogicalSize::from_physical(physical_size, dpi_factor) + }) + } - if unsafe { winuser::GetWindowRect(self.window.0, &mut rect) } == 0 { - return None - } + pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> { + util::get_window_rect(self.window.0) + .map(|rect| ( + (rect.right - rect.left) as u32, + (rect.bottom - rect.top) as u32, + )) + } - Some(( - (rect.right - rect.left) as u32, - (rect.bottom - rect.top) as u32 - )) + #[inline] + pub fn get_outer_size(&self) -> Option { + self.get_outer_size_physical() + .map(|physical_size| { + let dpi_factor = self.get_hidpi_factor(); + LogicalSize::from_physical(physical_size, dpi_factor) + }) } - /// See the docs in the crate root file. - pub fn set_inner_size(&self, x: u32, y: u32) { + pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) { unsafe { - // Calculate the outer size based upon the specified inner size - let mut rect = RECT { top: 0, left: 0, bottom: y as LONG, right: x as LONG }; + let mut rect = RECT { + top: 0, + left: 0, + bottom: y as LONG, + right: x as LONG, + }; let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD; let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL; let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD; winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); - let outer_x = (rect.right - rect.left).abs() as raw::c_int; - let outer_y = (rect.top - rect.bottom).abs() as raw::c_int; - - winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE); + let outer_x = (rect.right - rect.left).abs() as c_int; + let outer_y = (rect.top - rect.bottom).abs() as c_int; + winuser::SetWindowPos( + self.window.0, + ptr::null_mut(), + 0, + 0, + outer_x, + outer_y, + winuser::SWP_ASYNCWINDOWPOS + | winuser::SWP_NOZORDER + | winuser::SWP_NOREPOSITION + | winuser::SWP_NOMOVE, + ); winuser::UpdateWindow(self.window.0); } } - /// See the docs in the crate root file. #[inline] - pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { - let mut window_state = self.window_state.lock().unwrap(); - window_state.attributes.min_dimensions = dimensions; + pub fn set_inner_size(&self, logical_size: LogicalSize) { + let dpi_factor = self.get_hidpi_factor(); + let (width, height) = logical_size.to_physical(dpi_factor).into(); + self.set_inner_size_physical(width, height); + } + pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + self.window_state.lock().unwrap().min_size = dimensions.map(Into::into); // Make windows re-check the window size bounds. - if let Some(inner_size) = self.get_inner_size() { - unsafe { - let mut rect = RECT { top: 0, left: 0, bottom: inner_size.1 as LONG, right: inner_size.0 as LONG }; - let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD; - let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL; - let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD; - winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); - let outer_x = (rect.right - rect.left).abs() as raw::c_int; - let outer_y = (rect.top - rect.bottom).abs() as raw::c_int; - - winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE); - } - } + self.get_inner_size_physical() + .map(|(width, height)| self.set_inner_size_physical(width, height)); } - /// See the docs in the crate root file. #[inline] - pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { - let mut window_state = self.window_state.lock().unwrap(); - window_state.attributes.max_dimensions = dimensions; + pub fn set_min_dimensions(&self, logical_size: Option) { + let physical_size = logical_size.map(|logical_size| { + let dpi_factor = self.get_hidpi_factor(); + logical_size.to_physical(dpi_factor).into() + }); + self.set_min_dimensions_physical(physical_size); + } + pub fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) { + self.window_state.lock().unwrap().max_size = dimensions.map(Into::into); // Make windows re-check the window size bounds. - if let Some(inner_size) = self.get_inner_size() { - unsafe { - let mut rect = RECT { top: 0, left: 0, bottom: inner_size.1 as LONG, right: inner_size.0 as LONG }; - let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD; - let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL; - let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD; - winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); - let outer_x = (rect.right - rect.left).abs() as raw::c_int; - let outer_y = (rect.top - rect.bottom).abs() as raw::c_int; - - winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE); - } - } + self.get_inner_size_physical() + .map(|(width, height)| self.set_inner_size_physical(width, height)); + } + + #[inline] + pub fn set_max_dimensions(&self, logical_size: Option) { + let physical_size = logical_size.map(|logical_size| { + let dpi_factor = self.get_hidpi_factor(); + logical_size.to_physical(dpi_factor).into() + }); + self.set_max_dimensions_physical(physical_size); } #[inline] pub fn set_resizable(&self, resizable: bool) { - if let Ok(mut window_state) = self.window_state.lock() { - if window_state.attributes.resizable == resizable { - return; - } - if window_state.attributes.fullscreen.is_some() { - window_state.attributes.resizable = resizable; - return; - } - let window = self.window.clone(); - let mut style = unsafe { - winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE) - }; - if resizable { - style |= winuser::WS_SIZEBOX as LONG; - } else { - style &= !winuser::WS_SIZEBOX as LONG; - } - unsafe { - winuser::SetWindowLongW( - window.0, - winuser::GWL_STYLE, - style as _, - ); - }; - window_state.attributes.resizable = resizable; + if resizable == self.resizable.get() { + return; + } + if self.fullscreen.borrow().is_some() { + // If we're in fullscreen, update stored configuration but don't apply anything. + self.resizable.replace(resizable); + return; } - } - // TODO: remove - pub fn platform_display(&self) -> *mut ::libc::c_void { - panic!() // Deprecated function ; we don't care anymore - } - // TODO: remove - pub fn platform_window(&self) -> *mut ::libc::c_void { - self.window.0 as *mut ::libc::c_void + let mut style = unsafe { + winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE) + }; + if resizable { + style |= winuser::WS_SIZEBOX as LONG; + } else { + style &= !winuser::WS_SIZEBOX as LONG; + } + + unsafe { + winuser::SetWindowLongW( + self.window.0, + winuser::GWL_STYLE, + style as _, + ); + }; + self.resizable.replace(resizable); } /// Returns the `hwnd` of this window. @@ -411,29 +441,30 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f32 { - 1.0 + pub fn get_hidpi_factor(&self) -> f64 { + get_window_scale_factor(self.window.0, self.window.1) } - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { - let mut point = POINT { - x: x, - y: y, - }; - + fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ()> { + let mut point = POINT { x, y }; unsafe { if winuser::ClientToScreen(self.window.0, &mut point) == 0 { return Err(()); } - if winuser::SetCursorPos(point.x, point.y) == 0 { return Err(()); } } - Ok(()) } + #[inline] + pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), ()> { + let dpi_factor = self.get_hidpi_factor(); + let (x, y) = logical_position.to_physical(dpi_factor).into(); + self.set_cursor_position_physical(x, y) + } + #[inline] pub fn id(&self) -> WindowId { WindowId(self.window.0) @@ -441,18 +472,13 @@ impl Window { #[inline] pub fn set_maximized(&self, maximized: bool) { - let mut window_state = self.window_state.lock().unwrap(); - - window_state.attributes.maximized = maximized; - // we only maximized if we are not in fullscreen - if window_state.attributes.fullscreen.is_some() { - return; - } + self.maximized.replace(maximized); + // We only maximize if we're not in fullscreen. + if self.fullscreen.borrow().is_some() { return; } let window = self.window.clone(); unsafe { - // And because ShowWindow will resize the window - // We call it in the main thread + // `ShowWindow` resizes the window, so it must be called from the main thread. self.events_loop_proxy.execute_in_thread(move |_| { winuser::ShowWindow( window.0, @@ -469,15 +495,15 @@ impl Window { unsafe fn set_fullscreen_style(&self) -> (LONG, LONG) { let mut window_state = self.window_state.lock().unwrap(); - if window_state.attributes.fullscreen.is_none() || window_state.saved_window_info.is_none() { - let mut rect: RECT = mem::zeroed(); - - winuser::GetWindowRect(self.window.0, &mut rect); - + if self.fullscreen.borrow().is_none() || window_state.saved_window_info.is_none() { + let rect = util::get_window_rect(self.window.0).expect("`GetWindowRect` failed"); + let dpi_factor = Some(self.get_hidpi_factor()); window_state.saved_window_info = Some(events_loop::SavedWindowInfo { style: winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE), ex_style: winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE), rect, + is_fullscreen: true, + dpi_factor, }); } @@ -485,15 +511,14 @@ impl Window { let mut placement: winuser::WINDOWPLACEMENT = mem::zeroed(); placement.length = mem::size_of::() as u32; winuser::GetWindowPlacement(self.window.0, &mut placement); - window_state.attributes.maximized = - placement.showCmd == (winuser::SW_SHOWMAXIMIZED as u32); + self.maximized.replace(placement.showCmd == (winuser::SW_SHOWMAXIMIZED as u32)); let saved_window_info = window_state.saved_window_info.as_ref().unwrap(); (saved_window_info.style, saved_window_info.ex_style) } unsafe fn restore_saved_window(&self) { - let window_state = self.window_state.lock().unwrap(); + let mut window_state = self.window_state.lock().unwrap(); // 'saved_window_info' can be None if the window has never been // in fullscreen mode before this method gets called. @@ -504,18 +529,21 @@ impl Window { // Reset original window style and size. The multiple window size/moves // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be // repainted. Better-looking methods welcome. + { + let saved_window_info = window_state.saved_window_info.as_mut().unwrap(); + saved_window_info.is_fullscreen = false; + } let saved_window_info = window_state.saved_window_info.as_ref().unwrap(); let rect = saved_window_info.rect.clone(); let window = self.window.clone(); let (mut style, ex_style) = (saved_window_info.style, saved_window_info.ex_style); - let maximized = window_state.attributes.maximized; - let resizable = window_state.attributes.resizable; + let maximized = self.maximized.get(); + let resizable = self.resizable.get(); - // On restore, resize to the previous saved rect size. - // And because SetWindowPos will resize the window - // We call it in the main thread + // We're restoring the window to its size and position from before being fullscreened. + // `ShowWindow` resizes the window, so it must be called from the main thread. self.events_loop_proxy.execute_in_thread(move |_| { if resizable { style |= winuser::WS_SIZEBOX as LONG; @@ -532,11 +560,13 @@ impl Window { rect.top, rect.right - rect.left, rect.bottom - rect.top, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE - | winuser::SWP_FRAMECHANGED, + winuser::SWP_ASYNCWINDOWPOS + | winuser::SWP_NOZORDER + | winuser::SWP_NOACTIVATE + | winuser::SWP_FRAMECHANGED, ); - // if it was set to maximized when it were fullscreened, we restore it as well + // We apply any requested changes to maximization state that occurred while we were in fullscreen. winuser::ShowWindow( window.0, if maximized { @@ -555,8 +585,8 @@ impl Window { unsafe { match &monitor { &Some(RootMonitorId { ref inner }) => { - let pos = inner.get_position(); - let dim = inner.get_dimensions(); + let (x, y): (i32, i32) = inner.get_position().into(); + let (width, height): (u32, u32) = inner.get_dimensions().into(); let window = self.window.clone(); let (style, ex_style) = self.set_fullscreen_style(); @@ -582,10 +612,10 @@ impl Window { winuser::SetWindowPos( window.0, ptr::null_mut(), - pos.0, - pos.1, - dim.0 as i32, - dim.1 as i32, + x as c_int, + y as c_int, + width as c_int, + height as c_int, winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE | winuser::SWP_FRAMECHANGED, @@ -600,122 +630,119 @@ impl Window { } } - let mut window_state = self.window_state.lock().unwrap(); - window_state.attributes.fullscreen = monitor; + self.fullscreen.replace(monitor); } #[inline] pub fn set_decorations(&self, decorations: bool) { - if let Ok(mut window_state) = self.window_state.lock() { - if window_state.attributes.decorations == decorations { - return; - } - - let style_flags = (winuser::WS_CAPTION | winuser::WS_THICKFRAME) as LONG; - let ex_style_flags = (winuser::WS_EX_WINDOWEDGE) as LONG; + if self.decorations.get() == decorations { + return; + } - // if we are in fullscreen mode, we only change the saved window info - if window_state.attributes.fullscreen.is_some() { - { - let mut saved = window_state.saved_window_info.as_mut().unwrap(); + let style_flags = (winuser::WS_CAPTION | winuser::WS_THICKFRAME) as LONG; + let ex_style_flags = (winuser::WS_EX_WINDOWEDGE) as LONG; - unsafe { - unjust_window_rect(&mut saved.rect, saved.style as _, saved.ex_style as _); - } + // if we are in fullscreen mode, we only change the saved window info + if self.fullscreen.borrow().is_some() { + { + let mut window_state = self.window_state.lock().unwrap(); + let saved = window_state.saved_window_info.as_mut().unwrap(); - if decorations { - saved.style = saved.style | style_flags; - saved.ex_style = saved.ex_style | ex_style_flags; - } else { - saved.style = saved.style & !style_flags; - saved.ex_style = saved.ex_style & !ex_style_flags; - } - - unsafe { - winuser::AdjustWindowRectEx( - &mut saved.rect, - saved.style as _, - 0, - saved.ex_style as _, - ); - } + unsafe { + unjust_window_rect(&mut saved.rect, saved.style as _, saved.ex_style as _); } - window_state.attributes.decorations = decorations; - return; - } - - unsafe { - let mut rect: RECT = mem::zeroed(); - winuser::GetWindowRect(self.window.0, &mut rect); - - let mut style = winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE); - let mut ex_style = winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE); - unjust_window_rect(&mut rect, style as _, ex_style as _); - if decorations { - style = style | style_flags; - ex_style = ex_style | ex_style_flags; + saved.style = saved.style | style_flags; + saved.ex_style = saved.ex_style | ex_style_flags; } else { - style = style & !style_flags; - ex_style = ex_style & !ex_style_flags; + saved.style = saved.style & !style_flags; + saved.ex_style = saved.ex_style & !ex_style_flags; } - let window = self.window.clone(); - - self.events_loop_proxy.execute_in_thread(move |_| { - winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style); - winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style); - winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _); - - winuser::SetWindowPos( - window.0, - ptr::null_mut(), - rect.left, - rect.top, - rect.right - rect.left, - rect.bottom - rect.top, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE - | winuser::SWP_FRAMECHANGED, + unsafe { + winuser::AdjustWindowRectEx( + &mut saved.rect, + saved.style as _, + 0, + saved.ex_style as _, ); - }); + } } - window_state.attributes.decorations = decorations; + self.decorations.replace(decorations); + return; } - } - #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { - if let Ok(mut window_state) = self.window_state.lock() { - if window_state.attributes.always_on_top == always_on_top { - return; + unsafe { + let mut rect: RECT = mem::zeroed(); + winuser::GetWindowRect(self.window.0, &mut rect); + + let mut style = winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE); + let mut ex_style = winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE); + unjust_window_rect(&mut rect, style as _, ex_style as _); + + if decorations { + style = style | style_flags; + ex_style = ex_style | ex_style_flags; + } else { + style = style & !style_flags; + ex_style = ex_style & !ex_style_flags; } let window = self.window.clone(); + self.events_loop_proxy.execute_in_thread(move |_| { - let insert_after = if always_on_top { - winuser::HWND_TOPMOST - } else { - winuser::HWND_NOTOPMOST - }; - unsafe { - winuser::SetWindowPos( - window.0, - insert_after, - 0, - 0, - 0, - 0, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE, - ); - winuser::UpdateWindow(window.0); - } + winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style); + winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style); + winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _); + + winuser::SetWindowPos( + window.0, + ptr::null_mut(), + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + winuser::SWP_ASYNCWINDOWPOS + | winuser::SWP_NOZORDER + | winuser::SWP_NOACTIVATE + | winuser::SWP_FRAMECHANGED, + ); }); + } - window_state.attributes.always_on_top = always_on_top; + self.decorations.replace(decorations); + } + + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + if self.always_on_top.get() == always_on_top { + return; } + + let window = self.window.clone(); + self.events_loop_proxy.execute_in_thread(move |_| { + let insert_after = if always_on_top { + winuser::HWND_TOPMOST + } else { + winuser::HWND_NOTOPMOST + }; + unsafe { + winuser::SetWindowPos( + window.0, + insert_after, + 0, + 0, + 0, + 0, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE, + ); + winuser::UpdateWindow(window.0); + } + }); + + self.always_on_top.replace(always_on_top); } #[inline] @@ -752,7 +779,7 @@ impl Window { } #[inline] - pub fn set_ime_spot(&self, _x: i32, _y: i32) { + pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) { unimplemented!(); } } @@ -779,27 +806,26 @@ pub struct WindowWrapper(HWND, HDC); // https://github.com/retep998/winapi-rs/issues/396 unsafe impl Send for WindowWrapper {} -pub unsafe fn adjust_size( - (x, y): (u32, u32), style: DWORD, ex_style: DWORD, -) -> (LONG, LONG) { - let mut rect = RECT { left: 0, right: x as LONG, top: 0, bottom: y as LONG }; +pub unsafe fn adjust_size(physical_size: PhysicalSize, style: DWORD, ex_style: DWORD) -> (LONG, LONG) { + let (width, height): (u32, u32) = physical_size.into(); + let mut rect = RECT { left: 0, right: width as LONG, top: 0, bottom: height as LONG }; winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style); (rect.right - rect.left, rect.bottom - rect.top) } unsafe fn init( - mut window: WindowAttributes, + mut attributes: WindowAttributes, mut pl_attribs: PlatformSpecificWindowBuilderAttributes, inserter: events_loop::Inserter, events_loop_proxy: events_loop::EventsLoopProxy, ) -> Result { - let title = OsStr::new(&window.title) + let title = OsStr::new(&attributes.title) .encode_wide() .chain(Some(0).into_iter()) .collect::>(); let window_icon = { - let icon = window.window_icon + let icon = attributes.window_icon .take() .map(WinIcon::from_icon); if icon.is_some() { @@ -826,14 +852,19 @@ unsafe fn init( // registering the window class let class_name = register_window_class(&window_icon, &taskbar_icon); + let (width, height) = attributes.dimensions + .map(Into::into) + .unwrap_or((1024, 768)); // building a RECT object with coordinates let mut rect = RECT { - left: 0, right: window.dimensions.unwrap_or((1024, 768)).0 as LONG, - top: 0, bottom: window.dimensions.unwrap_or((1024, 768)).1 as LONG, + left: 0, + right: width as LONG, + top: 0, + bottom: height as LONG, }; // computing the style and extended style of the window - let (mut ex_style, style) = if !window.decorations { + let (mut ex_style, style) = if !attributes.decorations { (winuser::WS_EX_APPWINDOW, //winapi::WS_POPUP is incompatible with winapi::WS_CHILD if pl_attribs.parent.is_some() { @@ -848,7 +879,7 @@ unsafe fn init( winuser::WS_OVERLAPPEDWINDOW | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN) }; - if window.always_on_top { + if attributes.always_on_top { ex_style |= winuser::WS_EX_TOPMOST; } @@ -857,14 +888,15 @@ unsafe fn init( // creating the real window this time, by using the functions in `extra_functions` let real_window = { - let (width, height) = if window.dimensions.is_some() { - let min_dimensions = window.min_dimensions - .map(|d| adjust_size(d, style, ex_style)) + let (adjusted_width, adjusted_height) = if attributes.dimensions.is_some() { + let min_dimensions = attributes.min_dimensions + .map(|logical_size| PhysicalSize::from_logical(logical_size, 1.0)) + .map(|physical_size| adjust_size(physical_size, style, ex_style)) .unwrap_or((0, 0)); - let max_dimensions = window.max_dimensions - .map(|d| adjust_size(d, style, ex_style)) - .unwrap_or((raw::c_int::max_value(), raw::c_int::max_value())); - + let max_dimensions = attributes.max_dimensions + .map(|logical_size| PhysicalSize::from_logical(logical_size, 1.0)) + .map(|physical_size| adjust_size(physical_size, style, ex_style)) + .unwrap_or((c_int::max_value(), c_int::max_value())); ( Some((rect.right - rect.left).min(max_dimensions.0).max(min_dimensions.0)), Some((rect.bottom - rect.top).min(max_dimensions.1).max(min_dimensions.1)) @@ -873,13 +905,13 @@ unsafe fn init( (None, None) }; - let mut style = if !window.visible { + let mut style = if !attributes.visible { style } else { style | winuser::WS_VISIBLE }; - if !window.resizable { + if !attributes.resizable { style &= !winuser::WS_SIZEBOX; } @@ -892,10 +924,13 @@ unsafe fn init( title.as_ptr() as LPCWSTR, style | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN, winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT, - width.unwrap_or(winuser::CW_USEDEFAULT), height.unwrap_or(winuser::CW_USEDEFAULT), + adjusted_width.unwrap_or(winuser::CW_USEDEFAULT), + adjusted_height.unwrap_or(winuser::CW_USEDEFAULT), pl_attribs.parent.unwrap_or(ptr::null_mut()), - ptr::null_mut(), libloaderapi::GetModuleHandleW(ptr::null()), - ptr::null_mut()); + ptr::null_mut(), + libloaderapi::GetModuleHandleW(ptr::null()), + ptr::null_mut(), + ); if handle.is_null() { return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}", @@ -922,21 +957,42 @@ unsafe fn init( } } - let (transparent, maximized, fullscreen) = ( - window.transparent.clone(), window.maximized.clone(), window.fullscreen.clone() - ); + let dpi = get_window_dpi(real_window.0, real_window.1); + let dpi_factor = dpi_to_scale_factor(dpi); + if dpi != BASE_DPI { + let mut packed_dimensions = 0; + // MAKELPARAM isn't provided by winapi yet. + let ptr = &mut packed_dimensions as *mut LPARAM as *mut WORD; + *ptr.offset(0) = width as WORD; + *ptr.offset(1) = height as WORD; + winuser::PostMessageW( + real_window.0, + *INITIAL_DPI_MSG_ID, + dpi as WPARAM, + packed_dimensions, + ); + } - // Creating a mutex to track the current window state - let window_state = Arc::new(Mutex::new(events_loop::WindowState { - cursor: Cursor(winuser::IDC_ARROW), // use arrow by default - cursor_state: CursorState::Normal, - attributes: window, - mouse_in_window: false, - saved_window_info: None, - })); + let window_state = { + let max_size = attributes.max_dimensions + .map(|logical_size| PhysicalSize::from_logical(logical_size, dpi_factor)); + let min_size = attributes.min_dimensions + .map(|logical_size| PhysicalSize::from_logical(logical_size, dpi_factor)); + let mut window_state = events_loop::WindowState { + cursor: Cursor(winuser::IDC_ARROW), // use arrow by default + cursor_state: CursorState::Normal, + max_size, + min_size, + mouse_in_window: false, + saved_window_info: None, + dpi_factor, + }; + // Creating a mutex to track the current window state + Arc::new(Mutex::new(window_state)) + }; // making the window transparent - if transparent { + if attributes.transparent { let bb = dwmapi::DWM_BLURBEHIND { dwFlags: 0x1, // FIXME: DWM_BB_ENABLE; fEnable: 1, @@ -950,14 +1006,19 @@ unsafe fn init( let win = Window { window: real_window, window_state: window_state, + decorations: Cell::new(attributes.decorations), + maximized: Cell::new(attributes.maximized.clone()), + resizable: Cell::new(attributes.resizable.clone()), + fullscreen: RefCell::new(attributes.fullscreen.clone()), + always_on_top: Cell::new(attributes.always_on_top), window_icon: Cell::new(window_icon), taskbar_icon: Cell::new(taskbar_icon), events_loop_proxy, }; - win.set_maximized(maximized); - if let Some(_) = fullscreen { - win.set_fullscreen(fullscreen); + win.set_maximized(attributes.maximized); + if let Some(_) = attributes.fullscreen { + win.set_fullscreen(attributes.fullscreen); force_window_active(win.window.0); } diff --git a/src/window.rs b/src/window.rs index 814fdeba21..0fb2c4d246 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,16 +1,20 @@ use std::collections::vec_deque::IntoIter as VecDequeIter; -use CreationError; -use CursorState; -use EventsLoop; -use Icon; -use MouseCursor; -use Window; -use WindowBuilder; -use WindowId; - -use libc; -use platform; +use { + CreationError, + CursorState, + EventsLoop, + Icon, + LogicalPosition, + LogicalSize, + MouseCursor, + PhysicalPosition, + PhysicalSize, + platform, + Window, + WindowBuilder, + WindowId, +}; impl WindowBuilder { /// Initializes a new `WindowBuilder` with default values. @@ -23,29 +27,23 @@ impl WindowBuilder { } /// Requests the window to be of specific dimensions. - /// - /// Width and height are in pixels. #[inline] - pub fn with_dimensions(mut self, width: u32, height: u32) -> WindowBuilder { - self.window.dimensions = Some((width, height)); + pub fn with_dimensions(mut self, size: LogicalSize) -> WindowBuilder { + self.window.dimensions = Some(size); self } /// Sets a minimum dimension size for the window - /// - /// Width and height are in pixels. #[inline] - pub fn with_min_dimensions(mut self, width: u32, height: u32) -> WindowBuilder { - self.window.min_dimensions = Some((width, height)); + pub fn with_min_dimensions(mut self, min_size: LogicalSize) -> WindowBuilder { + self.window.min_dimensions = Some(min_size); self } /// Sets a maximum dimension size for the window - /// - /// Width and height are in pixels. #[inline] - pub fn with_max_dimensions(mut self, width: u32, height: u32) -> WindowBuilder { - self.window.max_dimensions = Some((width, height)); + pub fn with_max_dimensions(mut self, max_size: LogicalSize) -> WindowBuilder { + self.window.max_dimensions = Some(max_size); self } @@ -144,14 +142,15 @@ impl WindowBuilder { /// /// Error should be very rare and only occur in case of permission denied, incompatible system, /// out of memory, etc. + #[inline] pub fn build(mut self, events_loop: &EventsLoop) -> Result { self.window.dimensions = Some(self.window.dimensions.unwrap_or_else(|| { if let Some(ref monitor) = self.window.fullscreen { // resizing the window to the dimensions of the monitor when fullscreen - monitor.get_dimensions() + LogicalSize::from_physical(monitor.get_dimensions(), 1.0) } else { // default dimensions - (1024, 768) + (1024, 768).into() } })); @@ -219,7 +218,7 @@ impl Window { /// /// Returns `None` if the window no longer exists. #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { + pub fn get_position(&self) -> Option { self.window.get_position() } @@ -228,7 +227,7 @@ impl Window { /// /// The same conditions that apply to `get_position` apply to this method. #[inline] - pub fn get_inner_position(&self) -> Option<(i32, i32)> { + pub fn get_inner_position(&self) -> Option { self.window.get_inner_position() } @@ -238,61 +237,30 @@ impl Window { /// /// This is a no-op if the window has already been closed. #[inline] - pub fn set_position(&self, x: i32, y: i32) { - self.window.set_position(x, y) + pub fn set_position(&self, position: LogicalPosition) { + self.window.set_position(position) } - /// Returns the size in pixels of the client area of the window. + /// Returns the logical size of the window's client area. /// /// The client area is the content of the window, excluding the title bar and borders. - /// These are the dimensions that need to be supplied to `glViewport`. - /// - /// Returns `None` if the window no longer exists. - #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { - self.window.get_inner_size() - } - - /// Returns the size in points of the client area of the window. /// - /// The client area is the content of the window, excluding the title bar and borders. - /// To get the dimensions of the frame buffer when calling `glViewport`, multiply with hidpi factor. - /// - /// Returns `None` if the window no longer exists. - /// - /// DEPRECATED - #[inline] - #[deprecated] - pub fn get_inner_size_points(&self) -> Option<(u32, u32)> { - self.window.get_inner_size().map(|(x, y)| { - let hidpi = self.hidpi_factor(); - ((x as f32 / hidpi) as u32, (y as f32 / hidpi) as u32) - }) - } - - /// Returns the size in pixels of the client area of the window. - /// - /// The client area is the content of the window, excluding the title bar and borders. - /// These are the dimensions of the frame buffer, and the dimensions that you should use - /// when you call `glViewport`. + /// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be. /// /// Returns `None` if the window no longer exists. - /// - /// DEPRECATED #[inline] - #[deprecated] - pub fn get_inner_size_pixels(&self) -> Option<(u32, u32)> { + pub fn get_inner_size(&self) -> Option { self.window.get_inner_size() } - /// Returns the size in pixels of the window. + /// Returns the logical size of the entire window. /// - /// These dimensions include title bar and borders. If you don't want these, you should use - /// use `get_inner_size` instead. + /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), + /// use `get_inner_size` instead. /// /// Returns `None` if the window no longer exists. #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { + pub fn get_outer_size(&self) -> Option { self.window.get_outer_size() } @@ -302,23 +270,19 @@ impl Window { /// /// This is a no-op if the window has already been closed. #[inline] - pub fn set_inner_size(&self, x: u32, y: u32) { - self.window.set_inner_size(x, y) + pub fn set_inner_size(&self, size: LogicalSize) { + self.window.set_inner_size(size) } /// Sets a minimum dimension size for the window. - /// - /// Width and height are in pixels. #[inline] - pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + pub fn set_min_dimensions(&self, dimensions: Option) { self.window.set_min_dimensions(dimensions) } /// Sets a maximum dimension size for the window. - /// - /// Width and height are in pixels. #[inline] - pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + pub fn set_max_dimensions(&self, dimensions: Option) { self.window.set_max_dimensions(dimensions) } @@ -337,46 +301,30 @@ impl Window { self.window.set_resizable(resizable) } - /// DEPRECATED. Gets the native platform specific display for this window. - /// This is typically only required when integrating with - /// other libraries that need this information. - #[deprecated] - #[inline] - pub unsafe fn platform_display(&self) -> *mut libc::c_void { - self.window.platform_display() - } - - /// DEPRECATED. Gets the native platform specific window handle. This is - /// typically only required when integrating with other libraries - /// that need this information. - #[deprecated] + /// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa. + /// + /// See the [`dpi`](dpi/index.html) module for more information. + /// + /// ## Platform-specific + /// + /// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. + /// - **Android:** Always returns 1.0. #[inline] - pub unsafe fn platform_window(&self) -> *mut libc::c_void { - self.window.platform_window() + pub fn get_hidpi_factor(&self) -> f64 { + self.window.get_hidpi_factor() } /// Modifies the mouse cursor of the window. /// Has no effect on Android. + #[inline] pub fn set_cursor(&self, cursor: MouseCursor) { self.window.set_cursor(cursor); } - /// Returns the ratio between the backing framebuffer resolution and the - /// window size in screen pixels. This is typically one for a normal display - /// and two for a retina display. - /// - /// ## Platform-specific - /// On X11 the DPI factor can be overridden using the `WINIT_HIDPI_FACTOR` environment - /// variable. - #[inline] - pub fn hidpi_factor(&self) -> f32 { - self.window.hidpi_factor() - } - /// Changes the position of the cursor in window coordinates. #[inline] - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { - self.window.set_cursor_position(x, y) + pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ()> { + self.window.set_cursor_position(position) } /// Sets how winit handles the cursor. See the documentation of `CursorState` for details. @@ -426,11 +374,12 @@ impl Window { /// Sets location of IME candidate box in client area coordinates relative to the top left. #[inline] - pub fn set_ime_spot(&self, x: i32, y: i32) { - self.window.set_ime_spot(x, y) + pub fn set_ime_spot(&self, position: LogicalPosition) { + self.window.set_ime_spot(position) } /// Returns the monitor on which the window currently resides + #[inline] pub fn get_current_monitor(&self) -> MonitorId { self.window.get_current_monitor() } @@ -477,26 +426,29 @@ impl MonitorId { self.inner.get_name() } - /// Returns the number of pixels currently displayed on the monitor. + /// Returns the monitor's resolution. #[inline] - pub fn get_dimensions(&self) -> (u32, u32) { + pub fn get_dimensions(&self) -> PhysicalSize { self.inner.get_dimensions() } /// Returns the top-left corner position of the monitor relative to the larger full /// screen area. #[inline] - pub fn get_position(&self) -> (i32, i32) { + pub fn get_position(&self) -> PhysicalPosition { self.inner.get_position() } - /// Returns the ratio between the monitor's physical pixels and logical pixels. + /// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa. + /// + /// See the [`dpi`](dpi/index.html) module for more information. /// /// ## Platform-specific - /// On X11 the DPI factor can be overridden using the `WINIT_HIDPI_FACTOR` environment - /// variable. + /// + /// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. + /// - **Android:** Always returns 1.0. #[inline] - pub fn get_hidpi_factor(&self) -> f32 { + pub fn get_hidpi_factor(&self) -> f64 { self.inner.get_hidpi_factor() } }