Compare commits

..

27 Commits

Author SHA1 Message Date
Freya Gentz
28f0eb598d Release 0.21.0 (#1440)
* Update CHANGELOG.md

* Update README.md

* Update Cargo.toml

* Update Cargo.toml

* Update README.md

* Update CHANGELOG.md
2020-02-04 19:07:31 -07:00
David Craven
c1eb7f9629 Fix deadlock wayland. (#1438) 2020-02-04 16:46:19 -07:00
Murarth
2f8aa5c52a Remove armv7-apple-ios target from CI (#1433) 2020-02-03 17:42:52 -07:00
hatoo
22dcc19898 Fix set_minimized(true) works only with decorations on macOS (#1411)
* On macOS, Fix set_minimized(true) works only with decorations

* Add CHANGELOG
2020-01-31 18:07:36 +03:00
Christian Duerr
e295104199 Remove Wayland theme intermediates (#1209)
* Remove Wayland theme intermediates

This removes the intermediate struct for passing a Wayland theme to
allow the user direct implementation of the trait.

By passing the trait directly, it is possible for downstream users to
have more freedom with customization without relying on winit to offer
these options as fields.

It should also make maintenance easier, since winit already doesn't
implement all the functions which are offered by the smithay client
toolkit.

* Reimplement SCTK's Theme and ButtonState

* Fix style issues

* Remove public signature

* Format code

* Add change log entry

Co-authored-by: Murarth <murarth@gmail.com>
2020-01-26 19:56:54 -07:00
Murarth
66fe69edd9 Fix warnings on macos (#1419) 2020-01-26 13:55:27 -07:00
Ryan G
fd946feac4 Web backend refactor and documentation (#1415)
The current implementation of the event loop runner has some significant
problems. It can't handle multiple events being emitted at once (for
example, when a keyboard event causes a key input, a text input, and a
modifier change.) It's also relatively easy to introduce bugs for the
different possible control flow states.

The new model separates intentionally emitting a NewEvents (poll
completed, wait completed, init) and emitting a normal event, as well as
providing a method for emitting multiple events in a single call.
2020-01-25 19:04:03 -05:00
Freya Gentz
8856b6ecb7 Remove unused code in X11 backend. (#1416)
Signed-off-by: Freya Gentz <zegentzy@protonmail.com>
2020-01-23 12:42:15 -07:00
Osspial
0ae78db6cb Fix building on Windows 7 and 8 (#1398)
* Fix building on Windows 7 and 8

* Format
2020-01-21 12:43:36 -07:00
David Yamnitsky
3e3bb8a8f1 add hide_application on macos (#1364)
Co-authored-by: Osspial <osspial@gmail.com>
Co-authored-by: Bogaevsky <vbogaevsky@gmail.com>
2020-01-19 16:38:52 -07:00
Steven Sheldon
e48262a797 Simplify code by switching to higher-level dispatch APIs (#1409)
* Switch to higher-level dispatch APIs

* Inline all the functions

* Switch to autoreleasepool from objc
2020-01-19 11:47:55 -07:00
Diggory Hardy
d934f94704 Fix: deadlock when requesting redraw on X11 (#1408) 2020-01-18 10:49:02 -07:00
Ryan G
1fe4a7a4ea Add the ability to pass a prebuilt canvas (#1394)
This allows Winit to take control of existing canvas elements in the
DOM, which is useful for web applications with other content in the
page.
2020-01-15 21:20:14 -05:00
hatoo
9daa0738a9 on MacOS, Fix not sending ReceivedCharacter event for some key combination (#1347)
* MacOS FIX #1267

* Add CHANGELOG

* Remove unnecessary trace!
2020-01-15 00:52:18 +03:00
Philippe Renon
ad7d4939a8 doc: change remaining EventsCleared references to MainEventsCleared (#1390) 2020-01-13 12:15:44 -07:00
Ryan G
c4d07952cb Remove TODOs from the web backen (#1395) 2020-01-13 12:14:25 -07:00
Héctor Ramón
dc302b0db4 Return physical position in CursorMoved on macOS (#1378) 2020-01-12 20:50:34 +03:00
Kirill Chibisov
a6d180cefb On Wayland, fix coordinates in mouse events when scale factor isn't 1 (#1385)
* On Wayland, fix coordinates in mouse events when scale factor isn't 1

* Refactor mouse_focus to be a surface instead of WindowId
2020-01-11 01:45:52 -07:00
Francesca Plebani
1ddceeb063 macOS: Unbundled window activation hack (#1318)
* `ns_string_id_ref` convenience fn

* Unbundled app activation hack

* Greatly improved solution

* Shortened names

* Remove cruft I left here a year ago

* Doc improvements

* Use `AtomicBool` for `activationHackFlag`

* Add CHANGELOG entry

* Doc improvements

* Fix `did_finish_launching` return type + delay more

* More reliable activation checking

* Fix merge goof

* ...fix other merge goof

Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Bogaevsky <vbogaevsky@gmail.com>
2020-01-10 16:02:42 -08:00
hatoo
633d0deeae Fix run_return does not return on macOS unless it receives a message (#1380)
* On MacOS, fix `run_return` not exit immediately

* Add CHANGELOG
2020-01-10 18:25:55 +03:00
Murarth
9e3844ddd9 Fix warnings on all platforms (#1383)
* Fix warnings on all platforms

* Also fixed a trait impl bound that I noticed along the way
2020-01-09 22:29:31 -07:00
Benjamin Saunders
4b618bd6a6 Don't discard high-precision cursor position data (#1375)
* Don't discard high-precision cursor position data

Most platforms (X11, wayland, macos, stdweb, ...) provide physical
positions in f64 units, which can contain meaningful fractional
data. For example, this can be empirically observed on modern X11
using a typical laptop touchpad. This is useful for e.g. content
creation tools, where cursor motion might map to brush strokes on a
canvas with higher-than-screen resolution, or positioning of an object
in a vector space.

* Update CHANGELOG.md

Co-Authored-By: Murarth <murarth@gmail.com>

Co-authored-by: Murarth <murarth@gmail.com>
2020-01-09 21:19:50 -07:00
Osspial
09c4ed0694 Remove util from gitignore 2020-01-09 12:24:57 -05:00
Murarth
d15eb04f9e Make docs set control_flow in a more realistic way (#1376) 2020-01-07 22:55:18 -05:00
Bastian Kauschke
02ac7456e4 impl Default for WindowBuilder (#1373) 2020-01-07 14:33:56 -05:00
Murarth
6b0875728c X11: Fix deadlock on window state with certain window events (#1369) 2020-01-06 20:54:22 -07:00
Osspial
6a330a2894 On Windows, fix bug where RedrawRequested would only get emitted every other iteration of the event loop (#1366)
* Fix bug causing RedrawRequested events to only get emitted every other iteration of the event loop.

* Initialize simple_logger in examples.

This PR's primary bug was discovered because a friend of mine reported
that winit was emitting concerning log messages, which I'd never seen
since none of the examples print out the log messages. This addresses
that, to hopefully reduce the chance of bugs going unnoticed in the
future.

* Add changelog entry

* Format
2020-01-06 15:28:58 -05:00
69 changed files with 1208 additions and 1037 deletions

View File

@@ -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
View File

@@ -2,7 +2,6 @@ Cargo.lock
target/
rls/
.vscode/
util/
*~
*.wasm
*.ts

View File

@@ -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".

View File

@@ -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]

View File

@@ -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,
_ => (),
}
});
}

View File

@@ -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();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -11,6 +11,7 @@ fn main() {
Timer,
}
simple_logger::init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event();
let _window = WindowBuilder::new()

View File

@@ -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: ");

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -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();

View File

@@ -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()

View File

@@ -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();

View File

@@ -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 {

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut windows = HashMap::new();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -6,6 +6,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let mut resizable = false;

View File

@@ -7,6 +7,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -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();

View File

@@ -5,6 +5,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -8,6 +8,7 @@ use winit::{
};
fn main() {
simple_logger::init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()

View File

@@ -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

View File

@@ -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,
_ => (),
}
});

View File

@@ -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,
},

View File

@@ -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> {}

View File

@@ -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)
}
}

View File

@@ -123,6 +123,7 @@
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
#[cfg(feature = "serde")]

View File

@@ -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] }
}
}

View File

@@ -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,
}
}
}

View File

@@ -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
}
}

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View 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");
}
}
}

View File

@@ -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);

View File

@@ -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`");
}

View File

@@ -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();
};

View File

@@ -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 {

View File

@@ -1,5 +1,6 @@
#![cfg(target_os = "macos")]
mod activation_hack;
mod app;
mod app_delegate;
mod app_state;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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();
});
});
}

View File

@@ -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 _)

View File

@@ -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`");
}

View File

@@ -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 {

View File

@@ -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,
),
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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| {

View File

@@ -72,3 +72,5 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
None => false,
}
}
pub type RawCanvasType = CanvasElement;

View File

@@ -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(

View File

@@ -91,3 +91,5 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
None => false,
}
}
pub type RawCanvasType = HtmlCanvasElement;

View File

@@ -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>,
}

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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);

View File

@@ -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]

View File

@@ -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>,

View File

@@ -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