Skip to content

Commit

Permalink
monitor: refactor MonitorHandle to store dyn object
Browse files Browse the repository at this point in the history
This also alters `VideoMode` to be a regular object and not reference
the `MonitorHandle`, since it's a static data.

Given that `VideoMode` set may change during runtime keeping the
reference as a some sort of validity may not be idea and propagating
errors when changing video mode could be more reliable.
  • Loading branch information
kchibisov committed Nov 14, 2024
1 parent 59b1eb5 commit 84c9ae6
Show file tree
Hide file tree
Showing 45 changed files with 802 additions and 1,170 deletions.
5 changes: 3 additions & 2 deletions examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
use winit::monitor::Fullscreen;
#[cfg(macos_platform)]
use winit::platform::macos::{
ApplicationHandlerExtMacOS, OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS,
Expand All @@ -34,8 +35,8 @@ use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttr
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
Theme, Window, WindowAttributes, WindowId,
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Icon, ResizeDirection, Theme, Window,
WindowAttributes, WindowId,
};

#[path = "util/tracing.rs"]
Expand Down
2 changes: 2 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ changelog entry.
- In the same spirit rename `DeviceEvent::MouseMotion` to `PointerMotion`.
- Remove `Force::Calibrated::altitude_angle`.
- On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
- `VideoModeHandle` to `VideoMode` which stores plain data.
- `Fullscreen::Exclusive` to take `(MonitorHandle, VideoMode)`.

### Removed

Expand Down
199 changes: 91 additions & 108 deletions src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,14 @@
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
use std::borrow::Cow;
use std::fmt;
use std::num::{NonZeroU16, NonZeroU32};
use std::ops::Deref;
use std::sync::Arc;

use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl;

/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
}

impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f)
}
}

impl PartialOrd for VideoModeHandle {
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for VideoModeHandle {
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
self.monitor().cmp(&other.monitor()).then(
self.size()
.cmp(&other.size())
.then(
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
)
}
}

impl VideoModeHandle {
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::surface_size()`]: crate::window::Window::surface_size
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.video_mode.size()
}

/// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not.
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.video_mode.bit_depth()
}

/// Returns the refresh rate of this video mode in mHz.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.video_mode.refresh_rate_millihertz()
}

/// Returns the monitor that this video mode is valid for. Each monitor has
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.video_mode.monitor() }
}
}

impl std::fmt::Display for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} {}{}",
self.size().width,
self.size().height,
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
)
}
}
use crate::utils::AsAny;

/// Handle to a monitor.
///
Expand All @@ -112,21 +36,33 @@ impl std::fmt::Display for VideoModeHandle {
/// to check.
///
/// [`Window`]: crate::window::Window
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle,
#[derive(Debug, Clone)]
pub struct MonitorHandle(pub(crate) Arc<dyn MonitorHandleProvider>);

impl Deref for MonitorHandle {
type Target = dyn MonitorHandleProvider;

fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}

impl std::fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.0.as_ref().eq(other.0.as_ref())
}
}

impl MonitorHandle {
impl Eq for MonitorHandle {}

/// Provider of the [`MonitorHandle`].
pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
/// Native platform identifier of this monitor.
fn native_id(&self) -> u64;

/// Returns a human-readable name of the monitor.
///
/// Returns `None` if the monitor doesn't exist anymore.
/// Returns `None` if the monitor doesn't exist anymore or the name couldn't be obtained.
///
/// ## Platform-specific
///
Expand All @@ -136,10 +72,7 @@ impl MonitorHandle {
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline]
pub fn name(&self) -> Option<String> {
self.inner.name()
}
fn name(&self) -> Option<Cow<'_, str>>;

/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
Expand All @@ -152,10 +85,7 @@ impl MonitorHandle {
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
self.inner.position()
}
fn position(&self) -> Option<PhysicalPosition<i32>>;

/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`].
Expand All @@ -176,20 +106,73 @@ impl MonitorHandle {
///
#[rustfmt::skip]
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()
}
fn scale_factor(&self) -> f64;

/// Returns the currently active video mode of this monitor.
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
}
fn current_video_mode(&self) -> Option<VideoMode>;

/// Returns all fullscreen video modes supported by this monitor.
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>>;
}

impl PartialEq for dyn MonitorHandleProvider + '_ {
fn eq(&self, other: &Self) -> bool {
self.native_id() == other.native_id()
}
}

impl Eq for dyn MonitorHandleProvider + '_ {}

/// Describes a fullscreen video mode of a monitor.
///
/// Can be acquired with [`MonitorHandleProvider::video_modes`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
pub(crate) bit_depth: Option<NonZeroU16>,
pub(crate) refresh_rate_millihertz: Option<NonZeroU32>,
}

impl VideoMode {
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::surface_size()`]: crate::window::Window::surface_size
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}

/// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not.
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}

/// Returns the refresh rate of this video mode in mHz.
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
}

impl fmt::Display for VideoMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}x{} {}{}",
self.size.width,
self.size.height,
self.refresh_rate_millihertz.map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
self.bit_depth.map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
)
}
}

/// Fullscreen modes.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Fullscreen {
Exclusive(MonitorHandle, VideoMode),

/// Providing `None` to `Borderless` will fullscreen on the current monitor.
Borderless(Option<MonitorHandle>),
}
21 changes: 13 additions & 8 deletions src/platform/ios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,20 @@ use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::monitor::{MonitorHandle, VideoMode};
use crate::platform_impl::MonitorHandle as IosMonitorHandle;
use crate::window::{Window, WindowAttributes};

/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::scale_factor()`].
/// this to [`MonitorHandleProvider::scale_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
fn set_scale_factor(&self, scale_factor: f64);

/// Sets the valid orientations for the [`Window`].
Expand Down Expand Up @@ -289,10 +291,11 @@ pub trait WindowAttributesExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::scale_factor()`].
/// this to [`MonitorHandleProvider::scale_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
fn with_scale_factor(self, scale_factor: f64) -> Self;

/// Sets the valid orientations for the [`Window`].
Expand Down Expand Up @@ -384,23 +387,25 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void;

/// Returns the preferred [`VideoModeHandle`] for this monitor.
/// Returns the preferred [`VideoMode`] for this monitor.
///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoModeHandle;
fn preferred_video_mode(&self) -> VideoMode;
}

impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
let monitor = self.as_any().downcast_ref::<IosMonitorHandle>().unwrap();
objc2::rc::Retained::as_ptr(monitor.ui_screen(mtm)) as *mut c_void
}

#[inline]
fn preferred_video_mode(&self) -> VideoModeHandle {
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
fn preferred_video_mode(&self) -> VideoMode {
let monitor = self.as_any().downcast_ref::<IosMonitorHandle>().unwrap();
monitor.preferred_video_mode()
}
}

Expand Down
11 changes: 3 additions & 8 deletions src/platform/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ use serde::{Deserialize, Serialize};
use crate::application::ApplicationHandler;
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::platform_impl::MonitorHandle as MacOsMonitorHandle;
use crate::window::{Window, WindowAttributes, WindowId};

/// Additional methods on [`Window`] that are specific to MacOS.
Expand Down Expand Up @@ -488,22 +489,16 @@ impl EventLoopBuilderExtMacOS for EventLoopBuilder {

/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
/// Returns a pointer to the NSScreen representing this monitor.
fn ns_screen(&self) -> Option<*mut c_void>;
}

impl MonitorHandleExtMacOS for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}

fn ns_screen(&self) -> Option<*mut c_void> {
let monitor = self.as_any().downcast_ref::<MacOsMonitorHandle>().unwrap();
// SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
}
}

Expand Down
Loading

0 comments on commit 84c9ae6

Please sign in to comment.