mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28f0eb598d | ||
|
|
c1eb7f9629 | ||
|
|
2f8aa5c52a | ||
|
|
22dcc19898 | ||
|
|
e295104199 | ||
|
|
66fe69edd9 | ||
|
|
fd946feac4 | ||
|
|
8856b6ecb7 | ||
|
|
0ae78db6cb | ||
|
|
3e3bb8a8f1 | ||
|
|
e48262a797 | ||
|
|
d934f94704 | ||
|
|
1fe4a7a4ea | ||
|
|
9daa0738a9 | ||
|
|
ad7d4939a8 | ||
|
|
c4d07952cb | ||
|
|
dc302b0db4 | ||
|
|
a6d180cefb | ||
|
|
1ddceeb063 | ||
|
|
633d0deeae | ||
|
|
9e3844ddd9 | ||
|
|
4b618bd6a6 | ||
|
|
09c4ed0694 | ||
|
|
d15eb04f9e | ||
|
|
02ac7456e4 | ||
|
|
6b0875728c | ||
|
|
6a330a2894 |
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -39,7 +39,6 @@ jobs:
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { target: x86_64-apple-ios, os: macos-latest, }
|
||||
- { target: armv7-apple-ios, os: macos-latest, }
|
||||
- { target: aarch64-apple-ios, os: macos-latest, }
|
||||
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
|
||||
# doesn't currently work on Linux.
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@ Cargo.lock
|
||||
target/
|
||||
rls/
|
||||
.vscode/
|
||||
util/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,8 +1,26 @@
|
||||
# Unreleased
|
||||
|
||||
# 0.21.0 (2020-02-04)
|
||||
|
||||
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
|
||||
- On macOS, fix set_minimized(true) works only with decorations.
|
||||
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
|
||||
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
|
||||
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
|
||||
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
|
||||
- On macOS, fix `run_return` does not return unless it receives a message.
|
||||
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
|
||||
- On X11, fix deadlock on window state when handling certain window events.
|
||||
- `WindowBuilder` now implements `Default`.
|
||||
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
|
||||
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
|
||||
- On Web, add the ability to provide a custom canvas
|
||||
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
|
||||
|
||||
# 0.20.0 (2020-01-05)
|
||||
|
||||
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
|
||||
|
||||
- **Breaking**: Overhaul how Winit handles DPI:
|
||||
+ Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
|
||||
+ Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
|
||||
@@ -20,7 +38,7 @@
|
||||
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
|
||||
- On X11, fix `CursorEntered` event being generated for non-winit windows.
|
||||
- On macOS, fix crash when starting maximized without decorations.
|
||||
- On macOS, fix application not to terminate on `run_return`.
|
||||
- On macOS, fix application not terminating on `run_return`.
|
||||
- On Wayland, fix cursor icon updates on window borders when using CSD.
|
||||
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
|
||||
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.20.0"
|
||||
version = "0.21.0"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
@@ -29,7 +29,7 @@ bitflags = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.21"
|
||||
env_logger = "0.5"
|
||||
simple_logger = "1"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
||||
version = "0.2"
|
||||
@@ -41,7 +41,7 @@ objc = "0.2.3"
|
||||
cocoa = "0.19.1"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.3"
|
||||
dispatch = "0.1.4"
|
||||
dispatch = "0.2.0"
|
||||
objc = "0.2.6"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.20.0"
|
||||
winit = "0.21.0"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -46,12 +46,14 @@ fn main() {
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -11,6 +11,7 @@ fn main() {
|
||||
Timer,
|
||||
}
|
||||
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::monitor::{MonitorHandle, VideoMode};
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
|
||||
@@ -6,6 +6,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::WindowBuilder;
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
extern crate env_logger;
|
||||
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use winit::{
|
||||
@@ -14,7 +12,7 @@ fn main() {
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
|
||||
|
||||
env_logger::init();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
||||
for _ in 0..WINDOW_COUNT {
|
||||
|
||||
@@ -6,6 +6,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -6,6 +6,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut resizable = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use winit::event_loop::EventLoop;
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let monitor = event_loop.primary_monitor();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -8,6 +8,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -7,6 +7,8 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
|
||||
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
|
||||
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
|
||||
// since it seems to work well enough in most cases. Be careful about going too high, or
|
||||
|
||||
@@ -18,6 +18,7 @@ fn main() {
|
||||
};
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
simple_logger::init().unwrap();
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
@@ -27,6 +28,8 @@ fn main() {
|
||||
|
||||
while !quit {
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{:?}", event);
|
||||
@@ -42,7 +45,7 @@ fn main() {
|
||||
Event::MainEventsCleared => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ pub enum WindowEvent<'a> {
|
||||
/// (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: PhysicalPosition<i32>,
|
||||
position: PhysicalPosition<f64>,
|
||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
@@ -215,14 +215,10 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventLoopClosed<T>(pub T);
|
||||
|
||||
impl<T: fmt::Debug> fmt::Display for EventLoopClosed<T> {
|
||||
impl<T> fmt::Display for EventLoopClosed<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", error::Error::description(self))
|
||||
f.write_str("Tried to wake up a closed `EventLoop`")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {
|
||||
fn description(&self) -> &str {
|
||||
"Tried to wake up a closed `EventLoop`"
|
||||
}
|
||||
}
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
|
||||
|
||||
17
src/icon.rs
17
src/icon.rs
@@ -29,31 +29,26 @@ pub enum BadIcon {
|
||||
|
||||
impl fmt::Display for BadIcon {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let msg = match self {
|
||||
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
|
||||
match self {
|
||||
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
|
||||
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
byte_count,
|
||||
),
|
||||
&BadIcon::DimensionsVsPixelCount {
|
||||
BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => format!(
|
||||
} => write!(f,
|
||||
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
|
||||
width, height, pixel_count, width_x_height,
|
||||
),
|
||||
};
|
||||
write!(f, "{}", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn description(&self) -> &str {
|
||||
"A valid icon cannot be created from these arguments"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "serde")]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::EventLoopWindowTarget,
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
@@ -209,3 +210,17 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
self.inner.ns_screen().map(|s| s as *mut c_void)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
|
||||
pub trait EventLoopWindowTargetExtMacOS {
|
||||
/// Hide the entire application. In most applications this is typically triggered with Command-H.
|
||||
fn hide_application(&self);
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
fn hide_application(&self) {
|
||||
let cls = objc::runtime::Class::get("NSApplication").unwrap();
|
||||
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
|
||||
unsafe { msg_send![app, hide: 0] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::{os::raw, ptr, sync::Arc};
|
||||
|
||||
use smithay_client_toolkit::window::{ButtonState, Theme};
|
||||
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
|
||||
|
||||
use crate::{
|
||||
dpi::Size,
|
||||
@@ -23,74 +23,6 @@ pub use crate::platform_impl::x11;
|
||||
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
|
||||
/// Theme for wayland client side decorations
|
||||
///
|
||||
/// Colors must be in ARGB8888 format
|
||||
pub struct WaylandTheme {
|
||||
/// Primary color when the window is focused
|
||||
pub primary_active: [u8; 4],
|
||||
/// Primary color when the window is unfocused
|
||||
pub primary_inactive: [u8; 4],
|
||||
/// Secondary color when the window is focused
|
||||
pub secondary_active: [u8; 4],
|
||||
/// Secondary color when the window is unfocused
|
||||
pub secondary_inactive: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub close_button_hovered: [u8; 4],
|
||||
/// Close button color
|
||||
pub close_button: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub maximize_button_hovered: [u8; 4],
|
||||
/// Maximize button color
|
||||
pub maximize_button: [u8; 4],
|
||||
/// Minimize button color when hovered over
|
||||
pub minimize_button_hovered: [u8; 4],
|
||||
/// Minimize button color
|
||||
pub minimize_button: [u8; 4],
|
||||
}
|
||||
|
||||
struct WaylandThemeObject(WaylandTheme);
|
||||
|
||||
impl Theme for WaylandThemeObject {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.primary_active
|
||||
} else {
|
||||
self.0.primary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
// Used for division line
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.secondary_active
|
||||
} else {
|
||||
self.0.secondary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.close_button_hovered,
|
||||
_ => self.0.close_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.maximize_button_hovered,
|
||||
_ => self.0.maximize_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.minimize_button_hovered,
|
||||
_ => self.0.minimize_button,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
|
||||
pub trait EventLoopWindowTargetExtUnix {
|
||||
/// True if the `EventLoopWindowTarget` uses Wayland.
|
||||
@@ -275,7 +207,7 @@ pub trait WindowExtUnix {
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Sets the color theme of the client side window decorations on wayland
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme);
|
||||
fn set_wayland_theme<T: Theme>(&self, theme: T);
|
||||
|
||||
/// Check if the window is ready for drawing
|
||||
///
|
||||
@@ -353,9 +285,9 @@ impl WindowExtUnix for Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme) {
|
||||
fn set_wayland_theme<T: Theme>(&self, theme: T) {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -461,3 +393,96 @@ impl MonitorHandleExtUnix for MonitorHandle {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for implementing SCTK's theme trait.
|
||||
struct WaylandTheme<T: Theme>(T);
|
||||
|
||||
pub trait Theme: Send + 'static {
|
||||
/// Primary color of the scheme.
|
||||
fn primary_color(&self, window_active: bool) -> [u8; 4];
|
||||
|
||||
/// Secondary color of the scheme.
|
||||
fn secondary_color(&self, window_active: bool) -> [u8; 4];
|
||||
|
||||
/// Color for the close button.
|
||||
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the close button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the maximize button.
|
||||
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the maximize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the minimize button.
|
||||
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the minimize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.primary_color(active)
|
||||
}
|
||||
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.secondary_color(active)
|
||||
}
|
||||
|
||||
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.close_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.close_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.maximize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.maximize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.minimize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.minimize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ButtonState {
|
||||
/// Button is being hovered over by pointer.
|
||||
Hovered,
|
||||
/// Button is not being hovered over by pointer.
|
||||
Idle,
|
||||
/// Button is disabled.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl ButtonState {
|
||||
fn from_sctk(button_state: SCTKButtonState) -> Self {
|
||||
match button_state {
|
||||
SCTKButtonState::Hovered => Self::Hovered,
|
||||
SCTKButtonState::Idle => Self::Idle,
|
||||
SCTKButtonState::Disabled => Self::Disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
//! The web target does not automatically insert the canvas element object into the web page, to
|
||||
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
|
||||
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
|
||||
//! Window.
|
||||
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
|
||||
//! your own canvas.
|
||||
|
||||
use crate::window::WindowBuilder;
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
use stdweb::web::html_element::CanvasElement;
|
||||
@@ -20,3 +23,31 @@ use web_sys::HtmlCanvasElement;
|
||||
pub trait WindowExtWebSys {
|
||||
fn canvas(&self) -> HtmlCanvasElement;
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
pub trait WindowBuilderExtStdweb {
|
||||
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
impl WindowBuilderExtStdweb for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,10 +215,10 @@ fn setup_control_flow_observers() {
|
||||
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
||||
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
|
||||
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
|
||||
// chosen conservatively to guard against apple using different priorities for their redraw
|
||||
// observers in different OS's or on different devices. If it so happens that it's too
|
||||
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
|
||||
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
|
||||
@@ -7,10 +7,13 @@ use crate::event::{
|
||||
|
||||
use super::{
|
||||
event_loop::{CursorManager, EventsSink},
|
||||
make_wid,
|
||||
window::WindowStore,
|
||||
DeviceId,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::surface;
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_pointer::{self, Event as PtrEvent, WlPointer},
|
||||
wl_seat,
|
||||
@@ -36,6 +39,7 @@ pub fn implement_pointer(
|
||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
||||
) -> WlPointer {
|
||||
seat.get_pointer(|pointer| {
|
||||
// Currently focused winit surface
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
@@ -53,8 +57,10 @@ pub fn implement_pointer(
|
||||
..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
|
||||
if let Some(wid) = wid {
|
||||
mouse_focus = Some(wid);
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
mouse_focus = Some(surface);
|
||||
|
||||
// Reload cursor style only when we enter winit's surface. Calling
|
||||
// this function every time on `PtrEvent::Enter` could interfere with
|
||||
@@ -75,7 +81,8 @@ pub fn implement_pointer(
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position: (surface_x, surface_y).into(),
|
||||
position: (surface_x * scale_factor, surface_y * scale_factor)
|
||||
.into(),
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
@@ -101,13 +108,16 @@ pub fn implement_pointer(
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
let wid = make_wid(surface);
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position: (surface_x, surface_y).into(),
|
||||
position: (surface_x * scale_factor, surface_y * scale_factor)
|
||||
.into(),
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
@@ -115,7 +125,7 @@ pub fn implement_pointer(
|
||||
}
|
||||
}
|
||||
PtrEvent::Button { button, state, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
@@ -137,12 +147,13 @@ pub fn implement_pointer(
|
||||
button,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
make_wid(surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Axis { axis, value, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
if pointer.as_ref().version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
// old seat compatibility
|
||||
@@ -184,7 +195,8 @@ pub fn implement_pointer(
|
||||
PtrEvent::Frame => {
|
||||
let axis_buffer = axis_buffer.take();
|
||||
let axis_discrete_buffer = axis_discrete_buffer.take();
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
|
||||
@@ -481,9 +481,10 @@ impl WindowStore {
|
||||
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());
|
||||
let mut size = { *window.size.lock().unwrap() };
|
||||
f(WindowStoreForEach {
|
||||
newsize: window.newsize.take(),
|
||||
size: &mut *(window.size.lock().unwrap()),
|
||||
size: &mut size,
|
||||
prev_dpi: window.current_dpi,
|
||||
new_dpi: window.new_dpi,
|
||||
closed: window.closed,
|
||||
@@ -492,6 +493,7 @@ impl WindowStore {
|
||||
wid: make_wid(&window.surface),
|
||||
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
|
||||
});
|
||||
*window.size.lock().unwrap() = size;
|
||||
if let Some(dpi) = window.new_dpi.take() {
|
||||
window.current_dpi = dpi;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
|
||||
|
||||
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
|
||||
|
||||
use parking_lot::MutexGuard;
|
||||
|
||||
use super::{
|
||||
events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd,
|
||||
DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
|
||||
@@ -384,9 +386,12 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
|
||||
shared_state_lock.position = Some(outer);
|
||||
if moved {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Moved(outer.into()),
|
||||
// Temporarily unlock shared state to prevent deadlock
|
||||
MutexGuard::unlocked(&mut shared_state_lock, || {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Moved(outer.into()),
|
||||
});
|
||||
});
|
||||
}
|
||||
outer
|
||||
@@ -426,12 +431,15 @@ impl<T: 'static> EventProcessor<T> {
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let mut new_inner_size = PhysicalSize::new(new_width, new_height);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
},
|
||||
// Temporarily unlock shared state to prevent deadlock
|
||||
MutexGuard::unlocked(&mut shared_state_lock, || {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if new_inner_size != old_inner_size {
|
||||
@@ -461,6 +469,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
|
||||
if resized {
|
||||
// Drop the shared state lock to prevent deadlock
|
||||
drop(shared_state_lock);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(new_inner_size.into()),
|
||||
@@ -712,8 +723,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
|
||||
});
|
||||
if cursor_moved == Some(true) {
|
||||
let position =
|
||||
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
@@ -819,8 +829,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: CursorEntered { device_id },
|
||||
});
|
||||
|
||||
let position =
|
||||
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
|
||||
let position = PhysicalPosition::new(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
|
||||
@@ -888,8 +897,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.map(|device| device.attachment)
|
||||
.unwrap_or(2);
|
||||
|
||||
let position =
|
||||
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
|
||||
@@ -395,7 +395,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
let mut xev = MaybeUninit::uninit();
|
||||
|
||||
let wt = get_xtarget(&self.target);
|
||||
let mut pending_redraws = wt.pending_redraws.lock().unwrap();
|
||||
|
||||
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
||||
let mut xev = unsafe { xev.assume_init() };
|
||||
@@ -409,7 +408,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
super::WindowId::X(wid),
|
||||
)) = event
|
||||
{
|
||||
pending_redraws.insert(wid);
|
||||
wt.pending_redraws.lock().unwrap().insert(wid);
|
||||
} else {
|
||||
callback(event, window_target, control_flow);
|
||||
}
|
||||
@@ -490,21 +489,9 @@ impl<'a> Deref for DeviceInfo<'a> {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(ffi::Window);
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(c_int);
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window(Arc<UnownedWindow>);
|
||||
|
||||
impl Deref for Window {
|
||||
|
||||
@@ -43,62 +43,4 @@ impl XConnection {
|
||||
};
|
||||
self.send_event(target_window, event_mask, event)
|
||||
}
|
||||
|
||||
// Prepare yourself for the ultimate in unsafety!
|
||||
// You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you
|
||||
// to send more than one message worth of data.
|
||||
pub fn send_client_msg_multi<T: Formattable>(
|
||||
&self,
|
||||
window: c_ulong, // The window this is "about"; not necessarily this window
|
||||
target_window: c_ulong, // The window we're sending to
|
||||
message_type: ffi::Atom,
|
||||
event_mask: Option<c_long>,
|
||||
data: &[T],
|
||||
) -> Flusher<'_> {
|
||||
let format = T::FORMAT;
|
||||
let size_of_t = mem::size_of::<T>();
|
||||
debug_assert_eq!(size_of_t, format.get_actual_size());
|
||||
let mut event = ffi::XClientMessageEvent {
|
||||
type_: ffi::ClientMessage,
|
||||
display: self.display,
|
||||
window,
|
||||
message_type,
|
||||
format: format as c_int,
|
||||
data: ffi::ClientMessageData::new(),
|
||||
// These fields are ignored by `XSendEvent`
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
let t_per_payload = format.get_payload_size() / size_of_t;
|
||||
assert!(t_per_payload > 0);
|
||||
let payload_count = data.len() / t_per_payload;
|
||||
let payload_remainder = data.len() % t_per_payload;
|
||||
let payload_ptr = data.as_ptr() as *const ClientMsgPayload;
|
||||
|
||||
let mut payload_index = 0;
|
||||
while payload_index < payload_count {
|
||||
let payload = unsafe { payload_ptr.offset(payload_index as isize) };
|
||||
payload_index += 1;
|
||||
event.data = unsafe { mem::transmute(*payload) };
|
||||
self.send_event(target_window, event_mask, &event).queue();
|
||||
}
|
||||
|
||||
if payload_remainder > 0 {
|
||||
let mut payload: ClientMsgPayload = [0; 5];
|
||||
let t_payload = payload.as_mut_ptr() as *mut T;
|
||||
let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) };
|
||||
let invalid_t_payload = invalid_payload as *const T;
|
||||
let mut t_index = 0;
|
||||
while t_index < payload_remainder {
|
||||
let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) };
|
||||
unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() };
|
||||
t_index += 1;
|
||||
}
|
||||
event.data = unsafe { mem::transmute(payload) };
|
||||
self.send_event(target_window, event_mask, &event).queue();
|
||||
}
|
||||
|
||||
Flusher::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ impl Format {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_same_size_as<T>(&self) -> bool {
|
||||
mem::size_of::<T>() == self.get_actual_size()
|
||||
}
|
||||
|
||||
pub fn get_actual_size(&self) -> usize {
|
||||
match self {
|
||||
&Format::Char => mem::size_of::<c_char>(),
|
||||
@@ -32,15 +28,6 @@ impl Format {
|
||||
&Format::Long => mem::size_of::<c_long>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_payload_size(&self) -> usize {
|
||||
match self {
|
||||
// Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit).
|
||||
&Format::Char => mem::size_of::<c_char>() * 20,
|
||||
&Format::Short => mem::size_of::<c_short>() * 10,
|
||||
&Format::Long => mem::size_of::<c_long>() * 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::cmp;
|
||||
|
||||
use super::*;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
// Friendly neighborhood axis-aligned rectangle
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -89,16 +88,6 @@ 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)]
|
||||
@@ -135,20 +124,6 @@ impl FrameExtentsHeuristic {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_pos_to_outer_logical(
|
||||
&self,
|
||||
mut logical: LogicalPosition<f64>,
|
||||
factor: f64,
|
||||
) -> LogicalPosition<f64> {
|
||||
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(
|
||||
@@ -163,17 +138,6 @@ impl FrameExtentsHeuristic {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn inner_size_to_outer_logical(
|
||||
&self,
|
||||
mut logical: LogicalSize<f64>,
|
||||
factor: f64,
|
||||
) -> LogicalSize<f64> {
|
||||
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 {
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum StateOperation {
|
||||
Remove = 0, // _NET_WM_STATE_REMOVE
|
||||
Add = 1, // _NET_WM_STATE_ADD
|
||||
@@ -189,22 +190,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -216,14 +201,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -234,14 +211,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -252,14 +221,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -270,14 +231,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -23,18 +23,12 @@ pub use self::{
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::BitAnd,
|
||||
os::raw::*,
|
||||
ptr,
|
||||
};
|
||||
|
||||
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<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
let wrapped = Some(value);
|
||||
if *field != wrapped {
|
||||
@@ -45,13 +39,6 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
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,
|
||||
|
||||
@@ -26,6 +26,7 @@ impl GetPropertyError {
|
||||
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum PropMode {
|
||||
Replace = ffi::PropModeReplace as isize,
|
||||
Prepend = ffi::PropModePrepend as isize,
|
||||
|
||||
@@ -111,12 +111,7 @@ pub struct XError {
|
||||
pub minor_code: u8,
|
||||
}
|
||||
|
||||
impl Error for XError {
|
||||
#[inline]
|
||||
fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
}
|
||||
impl Error for XError {}
|
||||
|
||||
impl fmt::Display for XError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
@@ -144,17 +139,18 @@ impl From<ffi::OpenError> for XNotSupported {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for XNotSupported {
|
||||
#[inline]
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
impl XNotSupported {
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
|
||||
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for XNotSupported {
|
||||
#[inline]
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match *self {
|
||||
XNotSupported::LibraryOpenError(ref err) => Some(err),
|
||||
_ => None,
|
||||
|
||||
208
src/platform_impl/macos/activation_hack.rs
Normal file
208
src/platform_impl/macos/activation_hack.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
// Normally when you run or distribute a macOS app, it's bundled: it's in one
|
||||
// of those fun little folders that you have to right click "Show Package
|
||||
// Contents" on, and usually contains myriad delights including, but not
|
||||
// limited to, plists, icons, and of course, your beloved executable. However,
|
||||
// when you use `cargo run`, your app is unbundled - it's just a lonely, bare
|
||||
// executable.
|
||||
//
|
||||
// Apple isn't especially fond of unbundled apps, which is to say, they seem to
|
||||
// barely be supported. If you move the mouse while opening a winit window from
|
||||
// an unbundled app, the window will fail to activate and be in a grayed-out
|
||||
// uninteractable state. Switching to another app and back is the only way to
|
||||
// get the winit window into a normal state. None of this happens if the app is
|
||||
// bundled, i.e. when running via Xcode.
|
||||
//
|
||||
// To workaround this, we just switch focus to the Dock and then switch back to
|
||||
// our app. We only do this for unbundled apps, and only when they fail to
|
||||
// become active on their own.
|
||||
//
|
||||
// This solution was derived from this Godot PR:
|
||||
// https://github.com/godotengine/godot/pull/17187
|
||||
// (which appears to be based on https://stackoverflow.com/a/7602677)
|
||||
// The curious specialness of mouse motions is touched upon here:
|
||||
// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512
|
||||
//
|
||||
// We omit the 2nd step of the solution used in Godot, since it appears to have
|
||||
// no effect - I speculate that it's just technical debt picked up from the SO
|
||||
// answer; the API used is fairly exotic, and was historically used for very
|
||||
// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e.
|
||||
// in previous versions of SDL:
|
||||
// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322
|
||||
//
|
||||
// The `performSelector` delays in the Godot solution are used for sequencing,
|
||||
// since refocusing the app will fail if the call is made before it finishes
|
||||
// unfocusing. The delays used there are much smaller than the ones in the
|
||||
// original SO answer, presumably because they found the fastest delay that
|
||||
// works reliably through trial and error. Instead of using delays, we just
|
||||
// handle `applicationDidResignActive`; despite the app not activating reliably,
|
||||
// that still triggers when we switch focus to the Dock.
|
||||
//
|
||||
// The Godot solution doesn't appear to skip the hack when an unbundled app
|
||||
// activates normally. Checking for this is difficult, since if you call
|
||||
// `isActive` too early, it will always be `NO`. Even though we receive
|
||||
// `applicationDidResignActive` when switching focus to the Dock, we never
|
||||
// receive a preceding `applicationDidBecomeActive` if the app fails to
|
||||
// activate normally. I wasn't able to find a proper point in time to perform
|
||||
// the `isActive` check, so we instead check for the cause of the quirk: if
|
||||
// any mouse motion occurs prior to us receiving `applicationDidResignActive`,
|
||||
// we assume the app failed to become active.
|
||||
//
|
||||
// Fun fact: this issue is still present in GLFW
|
||||
// (https://github.com/glfw/glfw/issues/1515)
|
||||
//
|
||||
// A similar issue was found in SDL, but the resolution doesn't seem to work
|
||||
// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051
|
||||
|
||||
use super::util;
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSApplicationActivateIgnoringOtherApps},
|
||||
base::id,
|
||||
foundation::NSUInteger,
|
||||
};
|
||||
use objc::runtime::{Object, Sel, BOOL, NO, YES};
|
||||
use std::{
|
||||
os::raw::c_void,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
// Indicates that the hack has either completed or been skipped.
|
||||
activated: AtomicBool,
|
||||
// Indicates that the mouse has moved at some point in time.
|
||||
mouse_moved: AtomicBool,
|
||||
// Indicates that the hack is in progress, and that we should refocus when
|
||||
// the app resigns active.
|
||||
needs_refocus: AtomicBool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn name() -> &'static str {
|
||||
"activationHackState"
|
||||
}
|
||||
|
||||
pub fn new() -> *mut c_void {
|
||||
let this = Box::new(Self::default());
|
||||
Box::into_raw(this) as *mut c_void
|
||||
}
|
||||
|
||||
pub unsafe fn free(this: *mut Self) {
|
||||
Box::from_raw(this);
|
||||
}
|
||||
|
||||
pub unsafe fn get_ptr(obj: &Object) -> *mut Self {
|
||||
let this: *mut c_void = *(*obj).get_ivar(Self::name());
|
||||
assert!(!this.is_null(), "`activationHackState` pointer was null");
|
||||
this as *mut Self
|
||||
}
|
||||
|
||||
pub unsafe fn set_activated(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).activated.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
unsafe fn get_activated(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).activated.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub unsafe fn set_mouse_moved(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).mouse_moved.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
pub unsafe fn get_mouse_moved(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).mouse_moved.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub unsafe fn set_needs_refocus(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).needs_refocus.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
unsafe fn get_needs_refocus(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).needs_refocus.load(Ordering::Acquire)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the entry point for the hack - if the app is unbundled and a mouse
|
||||
// movement occurs before the app activates, it will trigger the hack. Because
|
||||
// mouse movements prior to activation are the cause of this quirk, they should
|
||||
// be a reliable way to determine if the hack needs to be performed.
|
||||
pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `activationHackMouseMoved`");
|
||||
unsafe {
|
||||
if !State::get_activated(this) {
|
||||
// We check if `CFBundleName` is undefined to determine if the
|
||||
// app is unbundled.
|
||||
if let None = util::app_name() {
|
||||
info!("App detected as unbundled");
|
||||
unfocus(this);
|
||||
} else {
|
||||
info!("App detected as bundled");
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("Completed `activationHackMouseMoved`");
|
||||
}
|
||||
|
||||
// Switch focus to the dock.
|
||||
unsafe fn unfocus(this: &Object) {
|
||||
// We only perform the hack if the app failed to activate, since otherwise,
|
||||
// there'd be a gross (but fast) flicker as it unfocused and then refocused.
|
||||
// However, we only enter this function if we detect mouse movement prior
|
||||
// to activation, so this should always be `NO`.
|
||||
//
|
||||
// Note that this check isn't necessarily reliable in detecting a violation
|
||||
// of the invariant above, since it's not guaranteed that activation will
|
||||
// resolve before this point. In other words, it can spuriously return `NO`.
|
||||
// This is also why the mouse motion approach was chosen, since it's not
|
||||
// obvious how to sequence this check - if someone knows how to, then that
|
||||
// would almost surely be a cleaner approach.
|
||||
let active: BOOL = msg_send![NSApp(), isActive];
|
||||
if active == YES {
|
||||
error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!");
|
||||
} else {
|
||||
info!("Performing unbundled app activation hack");
|
||||
let dock_bundle_id = util::ns_string_id_ref("com.apple.dock");
|
||||
let dock_array: id = msg_send![
|
||||
class!(NSRunningApplication),
|
||||
runningApplicationsWithBundleIdentifier: *dock_bundle_id
|
||||
];
|
||||
let dock_array_len: NSUInteger = msg_send![dock_array, count];
|
||||
if dock_array_len == 0 {
|
||||
error!("The Dock doesn't seem to be running, so switching focus to it is impossible");
|
||||
} else {
|
||||
State::set_needs_refocus(this, true);
|
||||
let dock: id = msg_send![dock_array, objectAtIndex: 0];
|
||||
// This will trigger `applicationDidResignActive`, which will in
|
||||
// turn call `refocus`.
|
||||
let status: BOOL = msg_send![
|
||||
dock,
|
||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
||||
];
|
||||
if status == NO {
|
||||
error!("Failed to switch focus to Dock");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch focus back to our app, causing the user to rejoice!
|
||||
pub unsafe fn refocus(this: &Object) {
|
||||
if State::get_needs_refocus(this) {
|
||||
State::set_needs_refocus(this, false);
|
||||
let app: id = msg_send![class!(NSRunningApplication), currentApplication];
|
||||
// Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The
|
||||
// nuanced difference isn't clear to me, but hey, I tried.
|
||||
let success: BOOL = msg_send![
|
||||
app,
|
||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
||||
];
|
||||
if success == NO {
|
||||
error!("Failed to refocus app");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,15 @@ use std::collections::VecDeque;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{self, NSEvent},
|
||||
base::id,
|
||||
base::{id, nil},
|
||||
};
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
event::{DeviceEvent, ElementState, Event},
|
||||
platform_impl::platform::{app_state::AppState, event::EventWrapper, util, DEVICE_ID},
|
||||
};
|
||||
use super::{activation_hack, app_state::AppState, event::EventWrapper, util, DEVICE_ID};
|
||||
use crate::event::{DeviceEvent, ElementState, Event};
|
||||
|
||||
pub struct AppClass(pub *const Class);
|
||||
unsafe impl Send for AppClass {}
|
||||
@@ -51,14 +49,14 @@ extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
|
||||
let key_window: id = msg_send![this, keyWindow];
|
||||
let _: () = msg_send![key_window, sendEvent: event];
|
||||
} else {
|
||||
maybe_dispatch_device_event(event);
|
||||
maybe_dispatch_device_event(this, event);
|
||||
let superclass = util::superclass(this);
|
||||
let _: () = msg_send![super(this, superclass), sendEvent: event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn maybe_dispatch_device_event(event: id) {
|
||||
unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
|
||||
let event_type = event.eventType();
|
||||
match event_type {
|
||||
appkit::NSMouseMoved
|
||||
@@ -100,6 +98,21 @@ unsafe fn maybe_dispatch_device_event(event: id) {
|
||||
}
|
||||
|
||||
AppState::queue_events(events);
|
||||
|
||||
// Notify the delegate when the first mouse move occurs. This is
|
||||
// used for the unbundled app activation hack, which needs to know
|
||||
// if any mouse motions occurred prior to the app activating.
|
||||
let delegate: id = msg_send![this, delegate];
|
||||
assert_ne!(delegate, nil);
|
||||
if !activation_hack::State::get_mouse_moved(&*delegate) {
|
||||
activation_hack::State::set_mouse_moved(&*delegate, true);
|
||||
let () = msg_send![
|
||||
delegate,
|
||||
performSelector: sel!(activationHackMouseMoved:)
|
||||
withObject: nil
|
||||
afterDelay: 0.0
|
||||
];
|
||||
}
|
||||
}
|
||||
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::{activation_hack, app_state::AppState};
|
||||
use cocoa::base::id;
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel, BOOL, YES},
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::app_state::AppState;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
pub struct AppDelegateClass(pub *const Class);
|
||||
unsafe impl Send for AppDelegateClass {}
|
||||
@@ -15,90 +15,67 @@ lazy_static! {
|
||||
let superclass = class!(NSResponder);
|
||||
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
|
||||
|
||||
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
|
||||
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
|
||||
decl.add_method(
|
||||
sel!(applicationDidFinishLaunching:),
|
||||
did_finish_launching as extern "C" fn(&Object, Sel, id) -> BOOL,
|
||||
did_finish_launching as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillResignActive:),
|
||||
will_resign_active as extern "C" fn(&Object, Sel, id),
|
||||
sel!(applicationDidResignActive:),
|
||||
did_resign_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_ivar::<*mut c_void>(activation_hack::State::name());
|
||||
decl.add_method(
|
||||
sel!(applicationWillEnterForeground:),
|
||||
will_enter_foreground as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidEnterBackground:),
|
||||
did_enter_background as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillTerminate:),
|
||||
will_terminate as extern "C" fn(&Object, Sel, id),
|
||||
sel!(activationHackMouseMoved:),
|
||||
activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL {
|
||||
trace!("Triggered `didFinishLaunching`");
|
||||
extern "C" fn new(class: &Class, _: Sel) -> id {
|
||||
unsafe {
|
||||
let this: id = msg_send![class, alloc];
|
||||
let this: id = msg_send![this, init];
|
||||
(*this).set_ivar(
|
||||
activation_hack::State::name(),
|
||||
activation_hack::State::new(),
|
||||
);
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn dealloc(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
activation_hack::State::free(activation_hack::State::get_ptr(this));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidFinishLaunching`");
|
||||
AppState::launched();
|
||||
trace!("Completed `didFinishLaunching`");
|
||||
YES
|
||||
trace!("Completed `applicationDidFinishLaunching`");
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `didBecomeActive`");
|
||||
/*unsafe {
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Resumed)
|
||||
}*/
|
||||
trace!("Completed `didBecomeActive`");
|
||||
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidBecomeActive`");
|
||||
unsafe {
|
||||
activation_hack::State::set_activated(this, true);
|
||||
}
|
||||
trace!("Completed `applicationDidBecomeActive`");
|
||||
}
|
||||
|
||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `willResignActive`");
|
||||
/*unsafe {
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended)
|
||||
}*/
|
||||
trace!("Completed `willResignActive`");
|
||||
}
|
||||
|
||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `willEnterForeground`");
|
||||
trace!("Completed `willEnterForeground`");
|
||||
}
|
||||
|
||||
extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `didEnterBackground`");
|
||||
trace!("Completed `didEnterBackground`");
|
||||
}
|
||||
|
||||
extern "C" fn will_terminate(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `willTerminate`");
|
||||
/*unsafe {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
let windows: id = msg_send![app, windows];
|
||||
let windows_enum: id = msg_send![windows, objectEnumerator];
|
||||
let mut events = Vec::new();
|
||||
loop {
|
||||
let window: id = msg_send![windows_enum, nextObject];
|
||||
if window == nil {
|
||||
break
|
||||
}
|
||||
let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)];
|
||||
if is_winit_window == YES {
|
||||
events.push(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
}
|
||||
}
|
||||
HANDLER.lock().unwrap().handle_nonuser_events(events);
|
||||
HANDLER.lock().unwrap().terminated();
|
||||
}*/
|
||||
trace!("Completed `willTerminate`");
|
||||
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidResignActive`");
|
||||
unsafe {
|
||||
activation_hack::refocus(this);
|
||||
}
|
||||
trace!("Completed `applicationDidResignActive`");
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ use std::{
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSWindow},
|
||||
base::nil,
|
||||
foundation::{NSAutoreleasePool, NSSize, NSString},
|
||||
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSPoint, NSSize},
|
||||
};
|
||||
|
||||
use objc::runtime::YES;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
@@ -29,7 +31,6 @@ use crate::{
|
||||
},
|
||||
window::WindowId,
|
||||
};
|
||||
use objc::runtime::Object;
|
||||
|
||||
lazy_static! {
|
||||
static ref HANDLER: Handler = Default::default();
|
||||
@@ -339,21 +340,23 @@ impl AppState {
|
||||
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
|
||||
let windows: *const Object = msg_send![NSApp(), windows];
|
||||
let window: *const Object = msg_send![windows, objectAtIndex:0];
|
||||
let windows: id = msg_send![NSApp(), windows];
|
||||
let window: id = msg_send![windows, objectAtIndex:0];
|
||||
assert_ne!(window, nil);
|
||||
|
||||
let title: *const Object = msg_send![window, title];
|
||||
assert_ne!(title, nil);
|
||||
let postfix = NSString::alloc(nil).init_str("*");
|
||||
let some_unique_title: *const Object =
|
||||
msg_send![title, stringByAppendingString: postfix];
|
||||
assert_ne!(some_unique_title, nil);
|
||||
|
||||
// To stop event loop immediately, we need to send some UI event here.
|
||||
let _: () = msg_send![window, setTitle: some_unique_title];
|
||||
// And restore it.
|
||||
let _: () = msg_send![window, setTitle: title];
|
||||
let dummy_event: id = msg_send![class!(NSEvent),
|
||||
otherEventWithType: NSApplicationDefined
|
||||
location: NSPoint::new(0.0, 0.0)
|
||||
modifierFlags: 0
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: 0
|
||||
data1: 0
|
||||
data2: 0
|
||||
];
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
|
||||
|
||||
pool.drain();
|
||||
};
|
||||
|
||||
@@ -287,6 +287,7 @@ pub unsafe fn modifier_event(
|
||||
|
||||
let scancode = get_scancode(ns_event);
|
||||
let virtual_keycode = scancode_to_keycode(scancode);
|
||||
#[allow(deprecated)]
|
||||
Some(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
mod activation_hack;
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::{collections::VecDeque, fmt};
|
||||
|
||||
use super::ffi;
|
||||
use super::{ffi, util};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::util::IdRef,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
foundation::{NSString, NSUInteger},
|
||||
foundation::NSUInteger,
|
||||
};
|
||||
use core_foundation::{
|
||||
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
|
||||
@@ -303,7 +302,7 @@ impl MonitorHandle {
|
||||
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
|
||||
let screens = NSScreen::screens(nil);
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
let key = util::ns_string_id_ref("NSScreenNumber");
|
||||
for i in 0..count {
|
||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
|
||||
@@ -122,7 +122,7 @@ extern "C" fn control_flow_begin_handler(
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
// without that, LoopDestroyed would get sent after EventsCleared
|
||||
// without that, LoopDestroyed would get sent after MainEventsCleared
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
use std::{
|
||||
os::raw::c_void,
|
||||
ops::Deref,
|
||||
sync::{Mutex, Weak},
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSPoint, NSSize, NSString},
|
||||
foundation::{NSPoint, NSSize, NSString},
|
||||
};
|
||||
use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f};
|
||||
use dispatch::Queue;
|
||||
use objc::rc::autoreleasepool;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
platform_impl::platform::{ffi, util::IdRef, window::SharedState},
|
||||
};
|
||||
|
||||
// Unsafe wrapper type that allows us to dispatch things that aren't Send.
|
||||
// This should *only* be used to dispatch to the main queue.
|
||||
// While it is indeed not guaranteed that these types can safely be sent to
|
||||
// other threads, we know that they're safe to use on the main thread.
|
||||
struct MainThreadSafe<T>(T);
|
||||
|
||||
unsafe impl<T> Send for MainThreadSafe<T> {}
|
||||
|
||||
impl<T> Deref for MainThreadSafe<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
ns_window.setStyleMask_(mask);
|
||||
// If we don't do this, key handling will break
|
||||
@@ -22,203 +38,55 @@ unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
ns_window.makeFirstResponder_(ns_view);
|
||||
}
|
||||
|
||||
struct SetStyleMaskData {
|
||||
ns_window: id,
|
||||
ns_view: id,
|
||||
mask: NSWindowStyleMask,
|
||||
}
|
||||
impl SetStyleMaskData {
|
||||
fn new_ptr(ns_window: id, ns_view: id, mask: NSWindowStyleMask) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetStyleMaskData {
|
||||
ns_window,
|
||||
ns_view,
|
||||
mask,
|
||||
}))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_style_mask_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetStyleMaskData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
set_style_mask(context.ns_window, context.ns_view, context.mask);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// Always use this function instead of trying to modify `styleMask` directly!
|
||||
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
|
||||
// Otherwise, this would vomit out errors about not being on the main thread
|
||||
// and fail to do anything.
|
||||
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_style_mask_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
Queue::main().exec_async(move || {
|
||||
set_style_mask(*ns_window, *ns_view, mask);
|
||||
});
|
||||
}
|
||||
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
|
||||
if msg_send![class!(NSThread), isMainThread] {
|
||||
set_style_mask_callback(context as *mut _);
|
||||
set_style_mask(ns_window, ns_view, mask);
|
||||
} else {
|
||||
dispatch_sync_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_style_mask_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
Queue::main().exec_sync(move || {
|
||||
set_style_mask(*ns_window, *ns_view, mask);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SetContentSizeData {
|
||||
ns_window: id,
|
||||
size: LogicalSize<f64>,
|
||||
}
|
||||
impl SetContentSizeData {
|
||||
fn new_ptr(ns_window: id, size: LogicalSize<f64>) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetContentSizeData { ns_window, size }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_content_size_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetContentSizeData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
NSWindow::setContentSize_(
|
||||
context.ns_window,
|
||||
NSSize::new(
|
||||
context.size.width as CGFloat,
|
||||
context.size.height as CGFloat,
|
||||
),
|
||||
);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
|
||||
// and just fails silently. Anyway, GCD to the rescue!
|
||||
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
|
||||
let context = SetContentSizeData::new_ptr(ns_window, size);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_content_size_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat));
|
||||
});
|
||||
}
|
||||
|
||||
struct SetFrameTopLeftPointData {
|
||||
ns_window: id,
|
||||
point: NSPoint,
|
||||
}
|
||||
impl SetFrameTopLeftPointData {
|
||||
fn new_ptr(ns_window: id, point: NSPoint) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetFrameTopLeftPointData { ns_window, point }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_frame_top_left_point_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetFrameTopLeftPointData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
NSWindow::setFrameTopLeftPoint_(context.ns_window, context.point);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
|
||||
// to log errors.
|
||||
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) {
|
||||
let context = SetFrameTopLeftPointData::new_ptr(ns_window, point);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_frame_top_left_point_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setFrameTopLeftPoint_(point);
|
||||
});
|
||||
}
|
||||
|
||||
struct SetLevelData {
|
||||
ns_window: id,
|
||||
level: ffi::NSWindowLevel,
|
||||
}
|
||||
impl SetLevelData {
|
||||
fn new_ptr(ns_window: id, level: ffi::NSWindowLevel) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetLevelData { ns_window, level }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_level_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetLevelData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
context.ns_window.setLevel_(context.level as _);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
|
||||
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
|
||||
let context = SetLevelData::new_ptr(ns_window, level);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_level_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setLevel_(level as _);
|
||||
});
|
||||
}
|
||||
|
||||
struct ToggleFullScreenData {
|
||||
ns_window: id,
|
||||
ns_view: id,
|
||||
not_fullscreen: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
}
|
||||
impl ToggleFullScreenData {
|
||||
fn new_ptr(
|
||||
ns_window: id,
|
||||
ns_view: id,
|
||||
not_fullscreen: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) -> *mut Self {
|
||||
Box::into_raw(Box::new(ToggleFullScreenData {
|
||||
ns_window,
|
||||
ns_view,
|
||||
not_fullscreen,
|
||||
shared_state,
|
||||
}))
|
||||
}
|
||||
}
|
||||
extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut ToggleFullScreenData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
|
||||
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
if context.not_fullscreen {
|
||||
let curr_mask = context.ns_window.styleMask();
|
||||
let required = NSWindowStyleMask::NSTitledWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask;
|
||||
if !curr_mask.contains(required) {
|
||||
set_style_mask(context.ns_window, context.ns_view, required);
|
||||
if let Some(shared_state) = context.shared_state.upgrade() {
|
||||
trace!("Locked shared state in `toggle_full_screen_callback`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
(*shared_state_lock).saved_style = Some(curr_mask);
|
||||
trace!("Unlocked shared state in `toggle_full_screen_callback`");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
context.ns_window.setLevel_(0);
|
||||
context.ns_window.toggleFullScreen_(nil);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `toggleFullScreen` is thread-safe, but our additional logic to account for
|
||||
// window styles isn't.
|
||||
pub unsafe fn toggle_full_screen_async(
|
||||
@@ -227,91 +95,42 @@ pub unsafe fn toggle_full_screen_async(
|
||||
not_fullscreen: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) {
|
||||
let context = ToggleFullScreenData::new_ptr(ns_window, ns_view, not_fullscreen, shared_state);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
toggle_full_screen_callback,
|
||||
);
|
||||
}
|
||||
|
||||
extern "C" fn restore_display_mode_callback(screen: *mut c_void) {
|
||||
unsafe {
|
||||
let screen = Box::from_raw(screen as *mut u32);
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess);
|
||||
}
|
||||
}
|
||||
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
Box::into_raw(Box::new(ns_screen)) as *mut _,
|
||||
restore_display_mode_callback,
|
||||
);
|
||||
}
|
||||
|
||||
struct SetMaximizedData {
|
||||
ns_window: id,
|
||||
is_zoomed: bool,
|
||||
maximized: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
}
|
||||
impl SetMaximizedData {
|
||||
fn new_ptr(
|
||||
ns_window: id,
|
||||
is_zoomed: bool,
|
||||
maximized: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetMaximizedData {
|
||||
ns_window,
|
||||
is_zoomed,
|
||||
maximized,
|
||||
shared_state,
|
||||
}))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_maximized_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetMaximizedData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
|
||||
if let Some(shared_state) = context.shared_state.upgrade() {
|
||||
trace!("Locked shared state in `set_maximized`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
|
||||
// Save the standard frame sized if it is not zoomed
|
||||
if !context.is_zoomed {
|
||||
shared_state_lock.standard_frame = Some(NSWindow::frame(context.ns_window));
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
let shared_state = MainThreadSafe(shared_state);
|
||||
Queue::main().exec_async(move || {
|
||||
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
if not_fullscreen {
|
||||
let curr_mask = ns_window.styleMask();
|
||||
let required =
|
||||
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
|
||||
if !curr_mask.contains(required) {
|
||||
set_style_mask(*ns_window, *ns_view, required);
|
||||
if let Some(shared_state) = shared_state.upgrade() {
|
||||
trace!("Locked shared state in `toggle_full_screen_callback`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
(*shared_state_lock).saved_style = Some(curr_mask);
|
||||
trace!("Unlocked shared state in `toggle_full_screen_callback`");
|
||||
}
|
||||
|
||||
shared_state_lock.maximized = context.maximized;
|
||||
|
||||
let curr_mask = context.ns_window.styleMask();
|
||||
if shared_state_lock.fullscreen.is_some() {
|
||||
// Handle it in window_did_exit_fullscreen
|
||||
return;
|
||||
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
|
||||
// Just use the native zoom if resizable
|
||||
context.ns_window.zoom_(nil);
|
||||
} else {
|
||||
// if it's not resizable, we set the frame directly
|
||||
let new_rect = if context.maximized {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
NSScreen::visibleFrame(screen)
|
||||
} else {
|
||||
shared_state_lock.saved_standard_frame()
|
||||
};
|
||||
context.ns_window.setFrame_display_(new_rect, 0);
|
||||
}
|
||||
|
||||
trace!("Unlocked shared state in `set_maximized`");
|
||||
}
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
ns_window.setLevel_(0);
|
||||
ns_window.toggleFullScreen_(nil);
|
||||
});
|
||||
}
|
||||
|
||||
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
||||
Queue::main().exec_async(move || {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
|
||||
});
|
||||
}
|
||||
|
||||
// `setMaximized` is not thread-safe
|
||||
pub unsafe fn set_maximized_async(
|
||||
ns_window: id,
|
||||
@@ -319,127 +138,79 @@ pub unsafe fn set_maximized_async(
|
||||
maximized: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) {
|
||||
let context = SetMaximizedData::new_ptr(ns_window, is_zoomed, maximized, shared_state);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_maximized_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let shared_state = MainThreadSafe(shared_state);
|
||||
Queue::main().exec_async(move || {
|
||||
if let Some(shared_state) = shared_state.upgrade() {
|
||||
trace!("Locked shared state in `set_maximized`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
|
||||
// Save the standard frame sized if it is not zoomed
|
||||
if !is_zoomed {
|
||||
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window));
|
||||
}
|
||||
|
||||
shared_state_lock.maximized = maximized;
|
||||
|
||||
let curr_mask = ns_window.styleMask();
|
||||
if shared_state_lock.fullscreen.is_some() {
|
||||
// Handle it in window_did_exit_fullscreen
|
||||
return;
|
||||
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
|
||||
// Just use the native zoom if resizable
|
||||
ns_window.zoom_(nil);
|
||||
} else {
|
||||
// if it's not resizable, we set the frame directly
|
||||
let new_rect = if maximized {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
NSScreen::visibleFrame(screen)
|
||||
} else {
|
||||
shared_state_lock.saved_standard_frame()
|
||||
};
|
||||
ns_window.setFrame_display_(new_rect, 0);
|
||||
}
|
||||
|
||||
trace!("Unlocked shared state in `set_maximized`");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
struct OrderOutData {
|
||||
ns_window: id,
|
||||
}
|
||||
impl OrderOutData {
|
||||
fn new_ptr(ns_window: id) -> *mut Self {
|
||||
Box::into_raw(Box::new(OrderOutData { ns_window }))
|
||||
}
|
||||
}
|
||||
extern "C" fn order_out_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut OrderOutData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
context.ns_window.orderOut_(nil);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
|
||||
// but with an odd delay.
|
||||
pub unsafe fn order_out_async(ns_window: id) {
|
||||
let context = OrderOutData::new_ptr(ns_window);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
order_out_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.orderOut_(nil);
|
||||
});
|
||||
}
|
||||
|
||||
struct MakeKeyAndOrderFrontData {
|
||||
ns_window: id,
|
||||
}
|
||||
impl MakeKeyAndOrderFrontData {
|
||||
fn new_ptr(ns_window: id) -> *mut Self {
|
||||
Box::into_raw(Box::new(MakeKeyAndOrderFrontData { ns_window }))
|
||||
}
|
||||
}
|
||||
extern "C" fn make_key_and_order_front_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut MakeKeyAndOrderFrontData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
context.ns_window.makeKeyAndOrderFront_(nil);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
|
||||
// actually works, but with an odd delay.
|
||||
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
|
||||
let context = MakeKeyAndOrderFrontData::new_ptr(ns_window);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
make_key_and_order_front_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.makeKeyAndOrderFront_(nil);
|
||||
});
|
||||
}
|
||||
|
||||
struct SetTitleData {
|
||||
ns_window: id,
|
||||
title: String,
|
||||
}
|
||||
impl SetTitleData {
|
||||
fn new_ptr(ns_window: id, title: String) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetTitleData { ns_window, title }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_title_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetTitleData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
let title = IdRef::new(NSString::alloc(nil).init_str(&context.title));
|
||||
context.ns_window.setTitle_(*title);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
|
||||
// window drag regions, which throws an exception when not done in the main
|
||||
// thread
|
||||
pub unsafe fn set_title_async(ns_window: id, title: String) {
|
||||
let context = SetTitleData::new_ptr(ns_window, title);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_title_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
|
||||
ns_window.setTitle_(*title);
|
||||
});
|
||||
}
|
||||
|
||||
struct CloseData {
|
||||
ns_window: id,
|
||||
}
|
||||
impl CloseData {
|
||||
fn new_ptr(ns_window: id) -> *mut Self {
|
||||
Box::into_raw(Box::new(CloseData { ns_window }))
|
||||
}
|
||||
}
|
||||
extern "C" fn close_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut CloseData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
context.ns_window.close();
|
||||
pool.drain();
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `close:` is thread-safe, but we want the event to be triggered from the main
|
||||
// thread. Though, it's a good idea to look into that more...
|
||||
pub unsafe fn close_async(ns_window: id) {
|
||||
let context = CloseData::new_ptr(ns_window);
|
||||
dispatch_async_f(dispatch_get_main_queue(), context as *mut _, close_callback);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
autoreleasepool(move || {
|
||||
ns_window.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::ops::{BitAnd, Deref};
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSWindowStyleMask},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSRect, NSUInteger},
|
||||
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
|
||||
};
|
||||
use core_graphics::display::CGDisplay;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL, YES};
|
||||
@@ -91,6 +91,22 @@ 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 ns_string_id_ref(s: &str) -> IdRef {
|
||||
IdRef::new(NSString::alloc(nil).init_str(s))
|
||||
}
|
||||
|
||||
pub unsafe fn app_name() -> Option<id> {
|
||||
let bundle: id = msg_send![class!(NSBundle), mainBundle];
|
||||
let dict: id = msg_send![bundle, infoDictionary];
|
||||
let key = ns_string_id_ref("CFBundleName");
|
||||
let app_name: id = msg_send![dict, objectForKey:*key];
|
||||
if app_name != nil {
|
||||
Some(app_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
|
||||
let superclass: id = msg_send![this, superclass];
|
||||
&*(superclass as *const _)
|
||||
|
||||
@@ -17,6 +17,7 @@ use objc::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalPosition,
|
||||
event::{
|
||||
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton,
|
||||
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
|
||||
@@ -59,6 +60,12 @@ struct ViewState {
|
||||
tracking_rect: Option<NSInteger>,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
fn get_scale_factor(&self) -> f64 {
|
||||
(unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
|
||||
let cursor_state = Default::default();
|
||||
let cursor_access = Arc::downgrade(&cursor_state);
|
||||
@@ -376,13 +383,8 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
|
||||
unsafe {
|
||||
trace!("Triggered `hasMarkedText`");
|
||||
let marked_text: id = *this.get_ivar("markedText");
|
||||
trace!("Completed `hasMarkedText`");
|
||||
(marked_text.length() > 0) as i8
|
||||
}
|
||||
extern "C" fn has_marked_text(_this: &Object, _sel: Sel) -> BOOL {
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn marked_range(this: &Object, _sel: Sel) -> NSRange {
|
||||
@@ -631,6 +633,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
|
||||
|
||||
let is_repeat = msg_send![event, isARepeat];
|
||||
|
||||
#[allow(deprecated)]
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -683,6 +686,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
|
||||
let scancode = get_scancode(event) as u32;
|
||||
let virtual_keycode = retrieve_keycode(event);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -797,6 +801,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||
|
||||
let event: id = msg_send![NSApp(), currentEvent];
|
||||
|
||||
#[allow(deprecated)]
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -882,12 +887,13 @@ fn mouse_motion(this: &Object, event: id) {
|
||||
|
||||
let x = view_point.x as f64;
|
||||
let y = view_rect.size.height as f64 - view_point.y as f64;
|
||||
let logical_position = LogicalPosition::new(x, y);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
position: logical_position.to_physical(state.get_scale_factor()),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
@@ -925,27 +931,8 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
|
||||
},
|
||||
};
|
||||
|
||||
let move_event = {
|
||||
let window_point = event.locationInWindow();
|
||||
let view_point: NSPoint = msg_send![this,
|
||||
convertPoint:window_point
|
||||
fromView:nil // convert from window coordinates
|
||||
];
|
||||
let view_rect: NSRect = msg_send![this, frame];
|
||||
let x = view_point.x as f64;
|
||||
let y = (view_rect.size.height - view_point.y) as f64;
|
||||
Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
AppState::queue_event(EventWrapper::StaticEvent(enter_event));
|
||||
AppState::queue_event(EventWrapper::StaticEvent(move_event));
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
trace!("Completed `mouseEntered`");
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ use cocoa::{
|
||||
NSWindow, NSWindowButton, NSWindowStyleMask,
|
||||
},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize},
|
||||
};
|
||||
use core_graphics::display::{CGDisplay, CGDisplayMode};
|
||||
use objc::{
|
||||
@@ -150,10 +150,14 @@ fn create_window(
|
||||
let mut masks = if !attrs.decorations && !screen.is_some() {
|
||||
// Resizable UnownedWindow without a titlebar or borders
|
||||
// if decorations is set to false, ignore pl_attrs
|
||||
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
} else if pl_attrs.titlebar_hidden {
|
||||
// if the titlebar is hidden, ignore other pl_attrs
|
||||
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
} else {
|
||||
// default case, resizable window with titlebar and titlebar buttons
|
||||
NSWindowStyleMask::NSClosableWindowMask
|
||||
@@ -178,7 +182,7 @@ fn create_window(
|
||||
NO,
|
||||
));
|
||||
let res = ns_window.non_nil().map(|ns_window| {
|
||||
let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title));
|
||||
let title = util::ns_string_id_ref(&attrs.title);
|
||||
ns_window.setReleasedWhenClosed_(NO);
|
||||
ns_window.setTitle_(*title);
|
||||
ns_window.setAcceptsMouseMovedEvents_(YES);
|
||||
@@ -946,7 +950,7 @@ impl UnownedWindow {
|
||||
unsafe {
|
||||
let screen: id = msg_send![*self.ns_window, screen];
|
||||
let desc = NSScreen::deviceDescription(screen);
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
let key = util::ns_string_id_ref("NSScreenNumber");
|
||||
let value = NSDictionary::valueForKey_(desc, *key);
|
||||
let display_id = msg_send![value, unsignedIntegerValue];
|
||||
RootMonitorHandle {
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::{
|
||||
cell::RefCell,
|
||||
clone::Clone,
|
||||
collections::{HashSet, VecDeque},
|
||||
iter,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
@@ -60,7 +61,7 @@ impl<T: 'static> Shared<T> {
|
||||
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
|
||||
) {
|
||||
self.0.runner.replace(Some(Runner::new(event_handler)));
|
||||
self.send_event(Event::NewEvents(StartCause::Init));
|
||||
self.init();
|
||||
|
||||
let close_instance = self.clone();
|
||||
backend::on_unload(move || close_instance.handle_unload());
|
||||
@@ -79,55 +80,98 @@ impl<T: 'static> Shared<T> {
|
||||
self.0.redraw_pending.borrow_mut().insert(id);
|
||||
}
|
||||
|
||||
// Add an event to the event loop runner
|
||||
pub fn init(&self) {
|
||||
let start_cause = Event::NewEvents(StartCause::Init);
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Run the polling logic for the Poll ControlFlow, which involves clearing the queue
|
||||
pub fn poll(&self) {
|
||||
let start_cause = Event::NewEvents(StartCause::Poll);
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Run the logic for waking from a WaitUntil, which involves clearing the queue
|
||||
// Generally there shouldn't be events built up when this is called
|
||||
pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) {
|
||||
let start_cause = Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume,
|
||||
});
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Add an event to the event loop runner, from the user or an event handler
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub fn send_event(&self, event: Event<'static, T>) {
|
||||
self.send_events(iter::once(event));
|
||||
}
|
||||
|
||||
// Add a series of events to the event loop runner
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub fn send_events(&self, events: impl Iterator<Item = Event<'static, T>>) {
|
||||
// If the event loop is closed, it should discard any new events
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if event handling is in process, and then release the borrow on the runner
|
||||
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
|
||||
Some(ref runner) if !runner.is_busy => {
|
||||
if let Event::NewEvents(cause) = event {
|
||||
(cause, true)
|
||||
} else {
|
||||
(
|
||||
match runner.state {
|
||||
State::Init => StartCause::Init,
|
||||
State::Poll { .. } => StartCause::Poll,
|
||||
State::Wait { start } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(end),
|
||||
},
|
||||
State::Exit => {
|
||||
return;
|
||||
}
|
||||
},
|
||||
false,
|
||||
)
|
||||
// If we can run the event processing right now, or need to queue this and wait for later
|
||||
let mut process_immediately = true;
|
||||
if let Some(ref runner) = &*self.0.runner.borrow() {
|
||||
// If we're currently polling, queue this and wait for the poll() method to be called
|
||||
if let State::Poll { .. } = runner.state {
|
||||
process_immediately = false;
|
||||
}
|
||||
// If the runner is busy, queue this and wait for it to process it later
|
||||
if runner.is_busy {
|
||||
process_immediately = false;
|
||||
}
|
||||
} else {
|
||||
// The runner still hasn't been attached: queue this event and wait for it to be
|
||||
process_immediately = false;
|
||||
}
|
||||
if !process_immediately {
|
||||
// Queue these events to look at later
|
||||
self.0.events.borrow_mut().extend(events);
|
||||
return;
|
||||
}
|
||||
// At this point, we know this is a fresh set of events
|
||||
// Now we determine why new events are incoming, and handle the events
|
||||
let start_cause = if let Some(runner) = &*self.0.runner.borrow() {
|
||||
match runner.state {
|
||||
State::Init => StartCause::Init,
|
||||
State::Poll { .. } => StartCause::Poll,
|
||||
State::Wait { start } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(end),
|
||||
},
|
||||
State::Exit => {
|
||||
// If we're in the exit state, don't do event processing
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Events are currently being handled, so queue this one and don't try to
|
||||
// double-process the event queue
|
||||
self.0.events.borrow_mut().push_back(event);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
unreachable!("The runner cannot process events when it is not attached");
|
||||
};
|
||||
// Take the start event, then the events provided to this function, and run an iteration of
|
||||
// the event loop
|
||||
let start_event = Event::NewEvents(start_cause);
|
||||
let events = iter::once(start_event).chain(events);
|
||||
self.run_until_cleared(events);
|
||||
}
|
||||
|
||||
// Given the set of new events, run the event loop until the main events and redraw events are
|
||||
// cleared
|
||||
//
|
||||
// This will also process any events that have been queued or that are queued during processing
|
||||
fn run_until_cleared(&self, events: impl Iterator<Item = Event<'static, T>>) {
|
||||
let mut control = self.current_control_flow();
|
||||
// Handle starting a new batch of events
|
||||
//
|
||||
// The user is informed via Event::NewEvents that there is a batch of events to process
|
||||
// However, there is only one of these per batch of events
|
||||
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
||||
if !event_is_start {
|
||||
for event in events {
|
||||
self.handle_event(event, &mut control);
|
||||
}
|
||||
self.handle_event(Event::MainEventsCleared, &mut control);
|
||||
@@ -196,10 +240,7 @@ impl<T: 'static> Shared<T> {
|
||||
root::ControlFlow::Poll => {
|
||||
let cloned = self.clone();
|
||||
State::Poll {
|
||||
timeout: backend::Timeout::new(
|
||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
||||
Duration::from_millis(0),
|
||||
),
|
||||
timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)),
|
||||
}
|
||||
}
|
||||
root::ControlFlow::Wait => State::Wait {
|
||||
@@ -220,7 +261,7 @@ impl<T: 'static> Shared<T> {
|
||||
start,
|
||||
end,
|
||||
timeout: backend::Timeout::new(
|
||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
||||
move || cloned.resume_time_reached(start, end),
|
||||
delay,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ impl<T> WindowTarget<T> {
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
|
||||
#[allow(deprecated)]
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -74,6 +75,7 @@ impl<T> WindowTarget<T> {
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
|
||||
#[allow(deprecated)]
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
// TODO: close events (port from old stdweb branch)
|
||||
// TODO: pointer locking (stdweb PR required)
|
||||
// TODO: fullscreen API (stdweb PR required)
|
||||
// Brief introduction to the internals of the web backend:
|
||||
// Currently, the web backend supports both wasm-bindgen and stdweb as methods of binding to the
|
||||
// environment. Because they are both supporting the same underlying APIs, the actual web bindings
|
||||
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
|
||||
//
|
||||
// When adding support for new events or interactions with the browser, first consult trusted
|
||||
// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers.
|
||||
// Once you have decided on the relevant web APIs, add support to both backends.
|
||||
//
|
||||
// The backend is used by the rest of the module to implement Winit's business logic, which forms
|
||||
// the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures
|
||||
// for winit's cross-platform structures. They are all relatively simple translations.
|
||||
//
|
||||
// The event_loop module handles listening for and processing events. 'Proxy' implements
|
||||
// EventLoopProxy and 'WindowTarget' implements EventLoopWindowTarget. WindowTarget also handles
|
||||
// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking
|
||||
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
|
||||
// compliant way.
|
||||
|
||||
mod device;
|
||||
mod error;
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::event;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::error::OsError as RootOE;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::OsError;
|
||||
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
@@ -43,12 +43,15 @@ impl Drop for Canvas {
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let canvas: CanvasElement = document()
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.try_into()
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
||||
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
||||
let canvas = match attr.canvas {
|
||||
Some(canvas) => canvas,
|
||||
None => document()
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.try_into()
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?,
|
||||
};
|
||||
|
||||
// A tabindex is needed in order to capture local keyboard events.
|
||||
// A "0" value means that the element should be focusable in
|
||||
@@ -207,7 +210,7 @@ impl Canvas {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
// todo
|
||||
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
|
||||
|
||||
@@ -72,3 +72,5 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub type RawCanvasType = CanvasElement;
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::event;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::error::OsError as RootOE;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::OsError;
|
||||
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
@@ -35,18 +35,23 @@ impl Drop for Canvas {
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let window =
|
||||
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
|
||||
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
||||
let canvas = match attr.canvas {
|
||||
Some(canvas) => canvas,
|
||||
None => {
|
||||
let window = web_sys::window()
|
||||
.ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
|
||||
|
||||
let document = window
|
||||
.document()
|
||||
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
|
||||
let document = window
|
||||
.document()
|
||||
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
|
||||
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.unchecked_into();
|
||||
document
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.unchecked_into()
|
||||
}
|
||||
};
|
||||
|
||||
// A tabindex is needed in order to capture local keyboard events.
|
||||
// A "0" value means that the element should be focusable in
|
||||
@@ -216,7 +221,7 @@ impl Canvas {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
|
||||
handler(
|
||||
|
||||
@@ -91,3 +91,5 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub type RawCanvasType = HtmlCanvasElement;
|
||||
|
||||
@@ -23,13 +23,13 @@ impl Window {
|
||||
pub fn new<T>(
|
||||
target: &EventLoopWindowTarget<T>,
|
||||
attr: WindowAttributes,
|
||||
_: PlatformSpecificBuilderAttributes,
|
||||
platform_attr: PlatformSpecificBuilderAttributes,
|
||||
) -> Result<Self, RootOE> {
|
||||
let runner = target.runner.clone();
|
||||
|
||||
let id = target.generate_id();
|
||||
|
||||
let mut canvas = backend::Canvas::create()?;
|
||||
let mut canvas = backend::Canvas::create(platform_attr)?;
|
||||
|
||||
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
|
||||
|
||||
@@ -281,5 +281,7 @@ impl Id {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct PlatformSpecificBuilderAttributes;
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PlatformSpecificBuilderAttributes {
|
||||
pub(crate) canvas: Option<backend::RawCanvasType>,
|
||||
}
|
||||
|
||||
@@ -100,13 +100,9 @@ pub(crate) struct SubclassInput<T: 'static> {
|
||||
}
|
||||
|
||||
impl<T> SubclassInput<T> {
|
||||
unsafe fn send_event(&self, event: Event<'static, T>) {
|
||||
unsafe fn send_event(&self, event: Event<'_, T>) {
|
||||
self.event_loop_runner.send_event(event);
|
||||
}
|
||||
|
||||
unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> {
|
||||
self.event_loop_runner.send_event_unbuffered(event)
|
||||
}
|
||||
}
|
||||
|
||||
struct ThreadMsgTargetSubclassInput<T: 'static> {
|
||||
@@ -116,7 +112,7 @@ struct ThreadMsgTargetSubclassInput<T: 'static> {
|
||||
}
|
||||
|
||||
impl<T> ThreadMsgTargetSubclassInput<T> {
|
||||
unsafe fn send_event(&self, event: Event<'static, T>) {
|
||||
unsafe fn send_event(&self, event: Event<'_, T>) {
|
||||
self.event_loop_runner.send_event(event);
|
||||
}
|
||||
}
|
||||
@@ -216,28 +212,57 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
unsafe {
|
||||
let mut msg = mem::zeroed();
|
||||
let mut msg_unprocessed = false;
|
||||
let mut unread_message_exists = false;
|
||||
|
||||
'main: loop {
|
||||
runner.new_events();
|
||||
loop {
|
||||
if !msg_unprocessed {
|
||||
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
|
||||
msg_unprocessed = false;
|
||||
}
|
||||
runner.events_cleared();
|
||||
if let Err(payload) = runner.take_panic_error() {
|
||||
runner.destroy_runner();
|
||||
panic::resume_unwind(payload);
|
||||
}
|
||||
|
||||
if !msg_unprocessed {
|
||||
runner.new_events();
|
||||
loop {
|
||||
if !unread_message_exists {
|
||||
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if msg.message == winuser::WM_PAINT {
|
||||
unread_message_exists = true;
|
||||
break;
|
||||
}
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
|
||||
unread_message_exists = false;
|
||||
}
|
||||
runner.main_events_cleared();
|
||||
loop {
|
||||
if !unread_message_exists {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
winuser::WM_PAINT,
|
||||
winuser::WM_PAINT,
|
||||
1,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
|
||||
unread_message_exists = false;
|
||||
}
|
||||
if runner.redraw_events_cleared().events_buffered() {
|
||||
if runner.control_flow() == ControlFlow::Exit {
|
||||
break 'main;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if !unread_message_exists {
|
||||
let control_flow = runner.control_flow();
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break 'main,
|
||||
@@ -245,7 +270,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
}
|
||||
msg_unprocessed = true;
|
||||
unread_message_exists = true;
|
||||
}
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
@@ -651,6 +676,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
}
|
||||
|
||||
winuser::WM_PAINT => {
|
||||
subclass_input.event_loop_runner.main_events_cleared();
|
||||
subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window))));
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
}
|
||||
@@ -829,8 +855,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
let x = windowsx::GET_X_LPARAM(lparam) as i32;
|
||||
let y = windowsx::GET_Y_LPARAM(lparam) as i32;
|
||||
let x = windowsx::GET_X_LPARAM(lparam) as f64;
|
||||
let y = windowsx::GET_Y_LPARAM(lparam) as f64;
|
||||
let position = PhysicalPosition::new(x, y);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
@@ -910,6 +936,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
} else {
|
||||
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -939,6 +966,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
winuser::WM_KEYUP | winuser::WM_SYSKEYUP => {
|
||||
use crate::event::ElementState::Released;
|
||||
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -1304,6 +1332,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC);
|
||||
let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode);
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -1334,6 +1363,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC);
|
||||
let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode);
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -1440,7 +1470,6 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
let style = winuser::GetWindowLongW(window, winuser::GWL_STYLE) as _;
|
||||
let style_ex = winuser::GetWindowLongW(window, winuser::GWL_EXSTYLE) as _;
|
||||
let b_menu = !winuser::GetMenu(window).is_null() as BOOL;
|
||||
|
||||
// New size as suggested by Windows.
|
||||
let suggested_rect = *(lparam as *const RECT);
|
||||
@@ -1454,14 +1483,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
// let margin_right: i32;
|
||||
// let margin_bottom: i32;
|
||||
{
|
||||
let mut adjusted_rect = suggested_rect;
|
||||
winuser::AdjustWindowRectExForDpi(
|
||||
&mut adjusted_rect,
|
||||
style,
|
||||
b_menu,
|
||||
style_ex,
|
||||
new_dpi_x,
|
||||
);
|
||||
let adjusted_rect =
|
||||
util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect)
|
||||
.unwrap_or(suggested_rect);
|
||||
margin_left = suggested_rect.left - adjusted_rect.left;
|
||||
margin_top = suggested_rect.top - adjusted_rect.top;
|
||||
// margin_right = adjusted_rect.right - suggested_rect.right;
|
||||
@@ -1497,7 +1521,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
false => old_physical_inner_size,
|
||||
};
|
||||
|
||||
let _ = subclass_input.send_event_unbuffered(Event::WindowEvent {
|
||||
let _ = subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: ScaleFactorChanged {
|
||||
scale_factor: new_dpi_factor,
|
||||
@@ -1526,13 +1550,13 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
bottom: suggested_ul.1 + new_physical_inner_size.height as LONG,
|
||||
};
|
||||
|
||||
winuser::AdjustWindowRectExForDpi(
|
||||
&mut conservative_rect,
|
||||
conservative_rect = util::adjust_window_rect_with_styles(
|
||||
window,
|
||||
style,
|
||||
b_menu,
|
||||
style_ex,
|
||||
new_dpi_x,
|
||||
);
|
||||
conservative_rect,
|
||||
)
|
||||
.unwrap_or(conservative_rect);
|
||||
|
||||
// If we're not dragging the window, offset the window so that the cursor's
|
||||
// relative horizontal position in the title bar is preserved.
|
||||
@@ -1697,44 +1721,49 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop();
|
||||
if in_modal_loop {
|
||||
let mut msg = mem::zeroed();
|
||||
loop {
|
||||
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
|
||||
break;
|
||||
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
|
||||
if msg.message != 0 && msg.message != winuser::WM_PAINT {
|
||||
queue_call_again();
|
||||
return 0;
|
||||
}
|
||||
// Clear all paint/timer messages from the queue before sending the events cleared message.
|
||||
match msg.message {
|
||||
// Flush the event queue of WM_PAINT messages.
|
||||
winuser::WM_PAINT | winuser::WM_TIMER => {
|
||||
// Remove the message from the message queue.
|
||||
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1);
|
||||
|
||||
if msg.hwnd != window {
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
subclass_input.event_loop_runner.main_events_cleared();
|
||||
loop {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
winuser::WM_PAINT,
|
||||
winuser::WM_PAINT,
|
||||
1,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
// If the message isn't one of those three, it may be handled by the modal
|
||||
// loop so we should return control flow to it.
|
||||
_ => {
|
||||
queue_call_again();
|
||||
return 0;
|
||||
|
||||
if msg.hwnd != window {
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we don't borrow until here because TODO SAFETY
|
||||
let runner = &subclass_input.event_loop_runner;
|
||||
runner.events_cleared();
|
||||
match runner.control_flow() {
|
||||
// Waiting is handled by the modal loop.
|
||||
ControlFlow::Exit | ControlFlow::Wait => runner.new_events(),
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
}
|
||||
ControlFlow::Poll => {
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
if runner.redraw_events_cleared().events_buffered() {
|
||||
queue_call_again();
|
||||
runner.new_events();
|
||||
} else {
|
||||
match runner.control_flow() {
|
||||
// Waiting is handled by the modal loop.
|
||||
ControlFlow::Exit | ControlFlow::Wait => runner.new_events(),
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
}
|
||||
ControlFlow::Poll => {
|
||||
runner.new_events();
|
||||
queue_call_again();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1882,6 +1911,7 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(KeyboardInput {
|
||||
|
||||
@@ -3,16 +3,17 @@ use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::R
|
||||
use winapi::{shared::windef::HWND, um::winuser};
|
||||
|
||||
use crate::{
|
||||
event::{Event, StartCause},
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
platform_impl::platform::event_loop::EventLoop,
|
||||
platform_impl::platform::{event_loop::EventLoop, util},
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
|
||||
pub(crate) struct ELRShared<T: 'static> {
|
||||
runner: RefCell<Option<EventLoopRunner<T>>>,
|
||||
buffer: RefCell<VecDeque<Event<'static, T>>>,
|
||||
buffer: RefCell<VecDeque<BufferedEvent<T>>>,
|
||||
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
|
||||
}
|
||||
struct EventLoopRunner<T: 'static> {
|
||||
@@ -26,6 +27,63 @@ struct EventLoopRunner<T: 'static> {
|
||||
}
|
||||
pub type PanicError = Box<dyn Any + Send + 'static>;
|
||||
|
||||
pub enum BufferedEvent<T: 'static> {
|
||||
Event(Event<'static, T>),
|
||||
ScaleFactorChanged(WindowId, f64, PhysicalSize<u32>),
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AreEventsBuffered {
|
||||
EventsBuffered,
|
||||
ReadyToSleep,
|
||||
}
|
||||
|
||||
impl AreEventsBuffered {
|
||||
pub fn events_buffered(&self) -> bool {
|
||||
match self {
|
||||
Self::EventsBuffered => true,
|
||||
Self::ReadyToSleep => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BufferedEvent<T> {
|
||||
pub fn from_event(event: Event<'_, T>) -> BufferedEvent<T> {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size,
|
||||
},
|
||||
window_id,
|
||||
} => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size),
|
||||
event => BufferedEvent::Event(event.to_static().unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) {
|
||||
match self {
|
||||
Self::Event(event) => dispatch(event),
|
||||
Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => {
|
||||
dispatch(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
},
|
||||
});
|
||||
util::set_inner_size_physical(
|
||||
(window_id.0).0,
|
||||
new_inner_size.width as _,
|
||||
new_inner_size.height as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ELRShared<T> {
|
||||
pub(crate) fn new() -> ELRShared<T> {
|
||||
ELRShared {
|
||||
@@ -45,9 +103,7 @@ impl<T> ELRShared<T> {
|
||||
loop {
|
||||
let event = self.buffer.borrow_mut().pop_front();
|
||||
match event {
|
||||
Some(e) => {
|
||||
runner.process_event(e);
|
||||
}
|
||||
Some(e) => e.dispatch_event(|e| runner.process_event(e)),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
@@ -63,35 +119,65 @@ impl<T> ELRShared<T> {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.new_events();
|
||||
loop {
|
||||
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(e) => e.dispatch_event(|e| runner.process_event(e)),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn send_event(&self, event: Event<'static, T>) {
|
||||
if let Err(event) = self.send_event_unbuffered(event) {
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation. Add
|
||||
// the event to a buffer to be processed later.
|
||||
self.buffer_event(event);
|
||||
pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) {
|
||||
let handling_redraw = self
|
||||
.runner
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|r| RunnerState::HandlingRedraw == r.runner_state)
|
||||
.unwrap_or(false);
|
||||
let mut send = None;
|
||||
if handling_redraw {
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
send = Some(event);
|
||||
} else {
|
||||
self.buffer_event(event);
|
||||
}
|
||||
} else {
|
||||
send = Some(event);
|
||||
}
|
||||
if let Some(event) = send {
|
||||
if let Err(event) = self.send_event_unbuffered(event) {
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation. Add
|
||||
// the event to a buffer to be processed later.
|
||||
self.buffer_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn send_event_unbuffered<'e>(
|
||||
&self,
|
||||
event: Event<'e, T>,
|
||||
) -> Result<(), Event<'e, T>> {
|
||||
unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> {
|
||||
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.process_event(event);
|
||||
|
||||
// Dispatch any events that were buffered during the call to `process_event`.
|
||||
loop {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
// of the loop's body and attempts to add events to the event buffer while in
|
||||
// `process_event` will fail.
|
||||
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(event) => runner.process_event(event),
|
||||
None => break,
|
||||
let handling_redraw = if let RunnerState::HandlingRedraw = runner.runner_state {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !handling_redraw {
|
||||
// Dispatch any events that were buffered during the call to `process_event`.
|
||||
loop {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
// of the loop's body and attempts to add events to the event buffer while in
|
||||
// `process_event` will fail.
|
||||
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(e) => e.dispatch_event(|e| runner.process_event(e)),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,10 +197,21 @@ impl<T> ELRShared<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn events_cleared(&self) {
|
||||
pub(crate) fn main_events_cleared(&self) {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.events_cleared();
|
||||
runner.main_events_cleared();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn redraw_events_cleared(&self) -> AreEventsBuffered {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.redraw_events_cleared();
|
||||
}
|
||||
match self.buffer.borrow().len() {
|
||||
0 => AreEventsBuffered::ReadyToSleep,
|
||||
_ => AreEventsBuffered::EventsBuffered,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,12 +249,15 @@ impl<T> ELRShared<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_event(&self, event: Event<'static, T>) {
|
||||
fn buffer_event(&self, event: Event<'_, T>) {
|
||||
match event {
|
||||
Event::RedrawRequested(window_id) => {
|
||||
self.redraw_buffer.borrow_mut().push_back(window_id)
|
||||
}
|
||||
_ => self.buffer.borrow_mut().push_back(event),
|
||||
_ => self
|
||||
.buffer
|
||||
.borrow_mut()
|
||||
.push_back(BufferedEvent::from_event(event)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +273,7 @@ enum RunnerState {
|
||||
/// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`.
|
||||
DeferredNewEvents(Instant),
|
||||
/// The event loop is handling the OS's events and sending them to the user's callback.
|
||||
/// `NewEvents` has been sent, and `EventsCleared` hasn't.
|
||||
/// `NewEvents` has been sent, and `MainEventsCleared` hasn't.
|
||||
HandlingEvents,
|
||||
HandlingRedraw,
|
||||
}
|
||||
@@ -308,7 +408,7 @@ impl<T> EventLoopRunner<T> {
|
||||
self.call_event_handler(Event::NewEvents(start_cause));
|
||||
}
|
||||
// This can be reached if the control flow is changed to poll during a `RedrawRequested`
|
||||
// that was sent after `EventsCleared`.
|
||||
// that was sent after `MainEventsCleared`.
|
||||
ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)),
|
||||
}
|
||||
}
|
||||
@@ -317,16 +417,20 @@ impl<T> EventLoopRunner<T> {
|
||||
(RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => {
|
||||
self.call_event_handler(event)
|
||||
}
|
||||
(_, Event::RedrawRequested(_)) => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
(RunnerState::New, Event::RedrawRequested(_))
|
||||
| (RunnerState::Idle(..), Event::RedrawRequested(_)) => {
|
||||
self.new_events();
|
||||
self.main_events_cleared();
|
||||
self.call_event_handler(event);
|
||||
}
|
||||
(_, Event::RedrawRequested(_)) => {
|
||||
panic!("redraw event in non-redraw phase");
|
||||
}
|
||||
(RunnerState::HandlingRedraw, _) => {
|
||||
warn!("Non-redraw event dispatched durning redraw phase");
|
||||
self.events_cleared();
|
||||
self.new_events();
|
||||
self.call_event_handler(event);
|
||||
panic!(
|
||||
"Non-redraw event dispatched durning redraw phase: {:?}",
|
||||
event.map_nonuser_event::<()>().ok()
|
||||
);
|
||||
}
|
||||
(_, _) => {
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
@@ -345,17 +449,64 @@ impl<T> EventLoopRunner<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn events_cleared(&mut self) {
|
||||
fn main_events_cleared(&mut self) {
|
||||
match self.runner_state {
|
||||
// If we were handling events, send the EventsCleared message.
|
||||
// If we were handling events, send the MainEventsCleared message.
|
||||
RunnerState::HandlingEvents => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
|
||||
RunnerState::HandlingRedraw => (),
|
||||
|
||||
// If we *weren't* handling events, we don't have to do anything.
|
||||
RunnerState::New | RunnerState::Idle(..) => (),
|
||||
|
||||
// Some control flows require a NewEvents call even if no events were received. This
|
||||
// branch handles those.
|
||||
RunnerState::DeferredNewEvents(wait_start) => {
|
||||
match self.control_flow {
|
||||
// If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared.
|
||||
ControlFlow::Poll => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Poll));
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
// If we had deferred a WaitUntil and the resume time has since been reached,
|
||||
// send the resume notification and MainEventsCleared event.
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
if Instant::now() >= resume_time {
|
||||
self.call_event_handler(Event::NewEvents(
|
||||
StartCause::ResumeTimeReached {
|
||||
start: wait_start,
|
||||
requested_resume: resume_time,
|
||||
},
|
||||
));
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
}
|
||||
// If we deferred a wait and no events were received, the user doesn't have to
|
||||
// get an event.
|
||||
ControlFlow::Wait | ControlFlow::Exit => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_events_cleared(&mut self) {
|
||||
match self.runner_state {
|
||||
// If we were handling events, send the MainEventsCleared message.
|
||||
RunnerState::HandlingEvents => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
self.flush_redraws();
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::Idle(Instant::now());
|
||||
}
|
||||
|
||||
RunnerState::HandlingRedraw => {
|
||||
self.flush_redraws();
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::Idle(Instant::now());
|
||||
}
|
||||
@@ -367,7 +518,7 @@ impl<T> EventLoopRunner<T> {
|
||||
// branch handles those.
|
||||
RunnerState::DeferredNewEvents(wait_start) => {
|
||||
match self.control_flow {
|
||||
// If we had deferred a Poll, send the Poll NewEvents and EventsCleared.
|
||||
// If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared.
|
||||
ControlFlow::Poll => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Poll));
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
@@ -375,7 +526,7 @@ impl<T> EventLoopRunner<T> {
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
}
|
||||
// If we had deferred a WaitUntil and the resume time has since been reached,
|
||||
// send the resume notification and EventsCleared event.
|
||||
// send the resume notification and MainEventsCleared event.
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
if Instant::now() >= resume_time {
|
||||
self.call_event_handler(Event::NewEvents(
|
||||
|
||||
@@ -88,6 +88,38 @@ pub fn adjust_size(hwnd: HWND, size: PhysicalSize<u32>) -> PhysicalSize<u32> {
|
||||
PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _)
|
||||
}
|
||||
|
||||
pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) {
|
||||
unsafe {
|
||||
let rect = adjust_window_rect(
|
||||
window,
|
||||
RECT {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: y as LONG,
|
||||
right: x as LONG,
|
||||
},
|
||||
)
|
||||
.expect("adjust_window_rect failed");
|
||||
|
||||
let outer_x = (rect.right - rect.left).abs() as _;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as _;
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
outer_x,
|
||||
outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOREPOSITION
|
||||
| winuser::SWP_NOMOVE
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::UpdateWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
|
||||
unsafe {
|
||||
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
|
||||
|
||||
@@ -24,7 +24,7 @@ use winapi::{
|
||||
oleidl::LPDROPTARGET,
|
||||
shobjidl_core::{CLSID_TaskbarList, ITaskbarList2},
|
||||
wingdi::{CreateRectRgn, DeleteObject},
|
||||
winnt::{LONG, LPCWSTR},
|
||||
winnt::LPCWSTR,
|
||||
winuser,
|
||||
},
|
||||
};
|
||||
@@ -215,38 +215,6 @@ impl Window {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
|
||||
unsafe {
|
||||
let rect = util::adjust_window_rect(
|
||||
self.window.0,
|
||||
RECT {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: y as LONG,
|
||||
right: x as LONG,
|
||||
},
|
||||
)
|
||||
.expect("adjust_window_rect failed");
|
||||
|
||||
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::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let dpi_factor = self.scale_factor();
|
||||
@@ -260,7 +228,7 @@ impl Window {
|
||||
});
|
||||
});
|
||||
|
||||
self.set_inner_size_physical(width, height);
|
||||
util::set_inner_size_physical(self.window.0, width, height);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct WindowState {
|
||||
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
|
||||
/// times in `EventsCleared`.
|
||||
/// times in `MainEventsCleared`.
|
||||
pub queued_out_of_band_redraw: bool,
|
||||
pub is_dark_mode: bool,
|
||||
pub high_surrogate: Option<u16>,
|
||||
|
||||
@@ -26,12 +26,14 @@ pub use crate::icon::*;
|
||||
/// let window = Window::new(&event_loop).unwrap();
|
||||
///
|
||||
/// event_loop.run(move |event, _, control_flow| {
|
||||
/// *control_flow = ControlFlow::Wait;
|
||||
///
|
||||
/// match event {
|
||||
/// Event::WindowEvent {
|
||||
/// event: WindowEvent::CloseRequested,
|
||||
/// ..
|
||||
/// } => *control_flow = ControlFlow::Exit,
|
||||
/// _ => *control_flow = ControlFlow::Wait,
|
||||
/// _ => (),
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
@@ -78,7 +80,7 @@ impl WindowId {
|
||||
}
|
||||
|
||||
/// Object that allows you to build windows.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct WindowBuilder {
|
||||
/// The attributes to use to create the window.
|
||||
pub window: WindowAttributes,
|
||||
@@ -185,10 +187,7 @@ impl WindowBuilder {
|
||||
/// Initializes a new `WindowBuilder` with default values.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
WindowBuilder {
|
||||
window: Default::default(),
|
||||
platform_specific: Default::default(),
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Requests the window to be of specific dimensions.
|
||||
@@ -393,10 +392,10 @@ impl Window {
|
||||
/// This is the **strongly encouraged** method of redrawing windows, as it can integrate with
|
||||
/// OS-requested redraws (e.g. when a window gets resized).
|
||||
///
|
||||
/// This function can cause `RedrawRequested` events to be emitted after `Event::EventsCleared`
|
||||
/// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared`
|
||||
/// but before `Event::NewEvents` if called in the following circumstances:
|
||||
/// * While processing `EventsCleared`.
|
||||
/// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any
|
||||
/// * While processing `MainEventsCleared`.
|
||||
/// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any
|
||||
/// directly subsequent `RedrawRequested` event.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
|
||||
Reference in New Issue
Block a user