Merge master into stdweb-eventloop-2

Update the internal APIs to match the new API changes
This commit is contained in:
Ryan Goldstein 2019-06-01 13:21:50 -07:00
commit f2b6ef2edd
86 changed files with 10095 additions and 7707 deletions

View file

@ -2,3 +2,4 @@
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/tomaka/winit/blob/master/FEATURES.md), if new features were added or implemented

View file

@ -1,4 +1,5 @@
# Unreleased
- Changes below are considered **breaking**.
- Change all occurrences of `EventsLoop` to `EventLoop`.
- Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules.
@ -36,13 +37,36 @@
- `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit.
- Rename `MonitorId` to `MonitorHandle`.
- Removed `serde` implementations from `ControlFlow`.
- Rename several functions to improve both internal consistency and compliance with Rust API guidelines.
- Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now.
# Version 0.19.1 (2019-04-08)
- On Wayland, added a `get_wayland_display` function to `EventsLoopExt`.
- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus.
- On macOS, fix command key event left and right reverse.
- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend.
- On Windows, fix icon not showing up in corner of window.
- On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior.
# Version 0.19.0 (2019-03-06)
- On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available.
- On macOS, fix keycodes being incorrect when using a non-US keyboard layout.
- On Wayland, fix `with_title()` not setting the windows title
- On Wayland, add `set_wayland_theme()` to control client decoration color theme
- Added serde serialization to `os::unix::XWindowType`.
- **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency.
- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent
- On Windows, fix malformed function pointer typecast that could invoke undefined behavior.
- Refactored Windows state/flag-setting code.
- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on.
- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area.
- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled.
- On Windows, ignore the AltGr key when populating the `ModifersState` type.
- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes
- On macOS, the numpad's subtract key has been added to the `Subtract` mapping
- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes
# Version 0.18.1 (2018-12-30)

View file

@ -1,28 +1,10 @@
# Winit Contributing Guidelines
## Scope
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
consider whether or not it's directly related to window creation or input handling. If it isn't, it
may be worth creating a separate crate that extends Winit's API to add that functionality.
Winit aims to provide a generic platform abstracting the main graphic platforms (Windows, macOS, X11,
Wayland, Android, iOS and the web platform via Emscripten).
Most platforms expose capabilities that cannot be meaningfully transposed to the others. Winit does not
aim to support every single functionality of every platform, but rather to abstract the set of
capabilities that is common to all platforms. In this context, APIs exposed in winit can be split into
different "support levels":
- Tier 1: features which are in the main scope of winit. They are part of the common API of winit, and
are taken care of by the maintainers. Any part of these features that is not working correctly is
considered a bug in winit.
- Tier 2: some platform-specific features can be sufficiently fundamental to the platform that winit can
integrate support for them in the platform-specific part of the API. These features are not considered
directly handled by the maintainers of winit. If you have a strong incentive to have such a feature
integrated in winit, consider implementing it and proposing yourself to maintain it in the future.
- Tier 3: these features are not directly exposed by winit, but rather can be implemented using the
raw handles to the underlying platform that winit exposes. If your feature of interest is rather
niche, this is probably where it belongs.
The exact list of supported Tier 1 features is tracked in this issue:
[#252](https://github.com/tomaka/winit/issues/252).
## Reporting an issue
@ -44,6 +26,9 @@ When making a code contribution to winit, before opening your pull request, plea
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
- if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including
@ -56,15 +41,14 @@ backends of winit. As such, depending on your platform of interest, your contact
This table summarizes who can be contacted in which case, with the following legend:
- `M`: is a main maintainer for this platform
- `R`: can review code for this platform
- `T`: has the ability of testing the platform
- `M` - Maintainer: is a main maintainer for this platform
- `C` - Collaborator: can review code and address issues on this platform
- `T` - Tester: has the ability of testing the platform
- ` `: knows nothing of this platform
| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| @francesca64 | R | M | M | | M | R | |
| @mitchmindtree | T | | T | T | | | |
| @Osspial | M | | T | T | T | | T |
| @vberger | | | T | M | | | |
| @mtak- | | T | | | T | M | |
| Platform | Windows | macOS | X11 | Wayland | Android | iOS | Emscripten |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| @mitchmindtree | T | | T | T | | | |
| @Osspial | M | | T | T | T | | T |
| @vberger | | | T | M | | | |
| @mtak- | | T | | | T | M | |

View file

@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.18.1"
version = "0.19.1"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
keywords = ["windowing"]
@ -21,6 +21,7 @@ serde = { version = "1", optional = true, features = ["serde_derive"] }
[dev-dependencies]
image = "0.21"
env_logger = "0.5"
[target.'cfg(target_os = "android")'.dependencies.android_glue]
version = "0.2"
@ -29,13 +30,13 @@ version = "0.2"
objc = "0.2.3"
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.3"
cocoa = "0.18.4"
core-foundation = "0.6"
core-graphics = "0.17.3"
dispatch = "0.1.4"
objc = "0.2.3"
[target.'cfg(target_os = "windows")'.dependencies]
backtrace = "0.3"
bitflags = "1"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
@ -67,12 +68,11 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "
calloop = "0.4.2"
smithay-client-toolkit = "0.6"
x11-dl = "2.18.3"
parking_lot = "0.7"
percent-encoding = "1.0"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))'.dependencies.parking_lot]
version = "0.7"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
version = "0.8"
[target.'cfg(target_arch = "wasm32")'.dependencies.stdweb]
version = "0.4.13"
version = "0.4.17"
optional = true

209
FEATURES.md Normal file
View file

@ -0,0 +1,209 @@
# Winit Scope
Winit aims to expose an interface that abstracts over window creation and input handling, and can
be used to create both games and applications. It supports the main graphical platforms:
- Desktop
- Windows
- macOS
- Unix
- via X11
- via Wayland
- Mobile
- iOS
- Android
- Web
- via Emscripten
- via WASM
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features
available everywhere. In this context, APIs exposed in winit can be split into different "support tiers":
- **Core:** Features that are essential to providing a well-formed abstraction over each platform's
windowing and input APIs.
- **Platform:** Platform-specific features that can't be meaningfully exposed through a common API and
cannot be implemented outside of Winit without exposing a significant amount of Winit's internals
or interfering with Winit's abstractions.
- **Usability:** Features that are not strictly essential to Winit's functionality, but provide meaningful
usability improvements and cannot be reasonably implemented in an external crate. These are
generally optional and exposed through Cargo features.
Core features are taken care of by the core Winit maintainers. Platform features are not.
When a platform feature is submitted, the submitter is considered the expert in the
feature and may be asked to support the feature should it break in the future.
Winit ***does not*** directly expose functionality for drawing inside windows or creating native
menus, but ***does*** commit to providing APIs that higher-level crates can use to implement that
functionality.
## `1.0` and stability
When all core features are implemented to the satisfaction of the Winit maintainers, Winit 1.0 will
be released and the library will enter maintenance mode. For the most part, new core features will not
be added past this point. New platform features may be accepted and exposed through point releases.
### Tier upgrades
Some platform features could in theory be exposed across multiple platforms, but have not gone
through the implementation work necessary to function on all platforms. When one of these features
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions gets deprecated and become permanently
exposed through the core, cross-platform API.
# Features
## Extending this section
If your PR makes notable changes to Winit's features, please update this section as follows:
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
## Core
### Windowing
- **Window initialization**: Winit allows the creation of a window
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
be deactivated
- **Window decorations toggle**: Decorations can be turned on or off after window creation
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
when they are. The application can precisely control its window size if desired.
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
size to specific values.
- **Window transparency**: Winit allows the creation of windows with a transparent background.
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
get drawn above their owner.
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Touch events**: Single-touch events.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the taskbar icon
* Setting the parent window
* `WS_EX_NOREDIRECTIONBITMAP` support
### macOS
* Window activation policy
* Window movable by background
* Transparent titlebar
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
### Unix
* Window urgency
* X11 Window Class
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
## Compatibility Matrix
Legend:
- ✔️: Works as intended
- ▢: Mostly works but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** |
|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ |
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A** |
|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ |
|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|Multitouch |❓ |❌ |✔️ |✔️ |❓ |❌ |❌ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❌ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❌ |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Event Loop 2.0 ([#459]) |✔️ |❌ |❌ |✔️ |❌ |❌ |❌ |
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
[#165]: https://github.com/tomaka/winit/issues/165
[#219]: https://github.com/tomaka/winit/issues/219
[#242]: https://github.com/tomaka/winit/issues/242
[#306]: https://github.com/tomaka/winit/issues/306
[#315]: https://github.com/tomaka/winit/issues/315
[#319]: https://github.com/tomaka/winit/issues/319
[#33]: https://github.com/tomaka/winit/issues/33
[#459]: https://github.com/tomaka/winit/issues/459
[#5]: https://github.com/tomaka/winit/issues/5
[#63]: https://github.com/tomaka/winit/issues/63
[#720]: https://github.com/tomaka/winit/issues/720
[#721]: https://github.com/tomaka/winit/issues/721
[#750]: https://github.com/tomaka/winit/issues/750
[#804]: https://github.com/tomaka/winit/issues/804
[#812]: https://github.com/tomaka/winit/issues/812

11
HALL_OF_CHAMPIONS.md Normal file
View file

@ -0,0 +1,11 @@
# Hall of Champions
The Winit maintainers would like to recognize the following former Winit
contributors, without whom Winit would not exist in its current form. We thank
them deeply for their time and efforts, and wish them best of luck in their
future endeavors:
* @tomaka: For creating the Winit project and guiding it through its early
years of existence.
* @francesca64: For taking over the responsibility of maintaining almost every
Winit backend, and standardizing HiDPI support across all of them

View file

@ -2,16 +2,24 @@
[![](http://meritbadge.herokuapp.com/winit)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Build Status](https://travis-ci.org/tomaka/winit.svg?branch=master)](https://travis-ci.org/tomaka/winit)
[![Build status](https://ci.appveyor.com/api/projects/status/5h87hj0g4q2xe3j9/branch/master?svg=true)](https://ci.appveyor.com/project/tomaka/winit/branch/master)
[![Build Status](https://travis-ci.org/rust-windowing/winit.svg?branch=master)](https://travis-ci.org/rust-windowing/winit)
[![Build status](https://ci.appveyor.com/api/projects/status/hr89but4x1n3dphq/branch/master?svg=true)](https://ci.appveyor.com/project/Osspial/winit/branch/master)
```toml
[dependencies]
winit = "0.18.1"
winit = "0.19.1"
```
## [Documentation](https://docs.rs/winit)
## Contact Us
Join us in any of these:
[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Usage
Winit is a window creation and management library. It can create windows and lets you handle

View file

@ -1,6 +1,6 @@
extern crate winit;
use winit::window::{WindowBuilder, MouseCursor};
use winit::window::{WindowBuilder, CursorIcon};
use winit::event::{Event, WindowEvent, ElementState, KeyboardInput};
use winit::event_loop::{EventLoop, ControlFlow};
@ -16,7 +16,7 @@ fn main() {
match event {
Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor(CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
@ -32,17 +32,17 @@ fn main() {
});
}
const CURSORS: &[MouseCursor] = &[
MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand,
MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text,
MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress,
MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::Cell,
MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy,
MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing,
MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut,
MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize,
MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize,
MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize,
MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize,
MouseCursor::ColResize, MouseCursor::RowResize
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default, CursorIcon::Crosshair, CursorIcon::Hand,
CursorIcon::Arrow, CursorIcon::Move, CursorIcon::Text,
CursorIcon::Wait, CursorIcon::Help, CursorIcon::Progress,
CursorIcon::NotAllowed, CursorIcon::ContextMenu, CursorIcon::Cell,
CursorIcon::VerticalText, CursorIcon::Alias, CursorIcon::Copy,
CursorIcon::NoDrop, CursorIcon::Grab, CursorIcon::Grabbing,
CursorIcon::AllScroll, CursorIcon::ZoomIn, CursorIcon::ZoomOut,
CursorIcon::EResize, CursorIcon::NResize, CursorIcon::NeResize,
CursorIcon::NwResize, CursorIcon::SResize, CursorIcon::SeResize,
CursorIcon::SwResize, CursorIcon::WResize, CursorIcon::EwResize,
CursorIcon::NsResize, CursorIcon::NeswResize, CursorIcon::NwseResize,
CursorIcon::ColResize, CursorIcon::RowResize
];

View file

@ -29,8 +29,8 @@ fn main() {
use winit::event::VirtualKeyCode::*;
match key {
Escape => *control_flow = ControlFlow::Exit,
G => window.grab_cursor(!modifiers.shift).unwrap(),
H => window.hide_cursor(!modifiers.shift),
G => window.set_cursor_grab(!modifiers.shift).unwrap(),
H => window.set_cursor_visible(modifiers.shift),
_ => (),
}
}

View file

@ -70,12 +70,11 @@ fn main() {
#[cfg(target_os = "macos")]
{
if macos_use_simple_fullscreen {
use winit::os::macos::WindowExt;
if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) {
use winit::platform::macos::WindowExtMacOS;
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
is_fullscreen = !is_fullscreen;
}
return ControlFlow::Continue;
return;
}
}
@ -83,7 +82,16 @@ fn main() {
if !is_fullscreen {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(window.get_current_monitor()));
window.set_fullscreen(Some(window.current_monitor()));
}
}
(VirtualKeyCode::S, ElementState::Pressed) => {
println!("window.fullscreen {:?}", window.fullscreen());
#[cfg(target_os = "macos")]
{
use winit::platform::macos::WindowExtMacOS;
println!("window.simple_fullscreen {:?}", WindowExtMacOS::simple_fullscreen(&window));
}
}
(VirtualKeyCode::M, ElementState::Pressed) => {
@ -105,8 +113,8 @@ fn main() {
// Enumerate monitors and prompt user to choose one
fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
for (num, monitor) in event_loop.get_available_monitors().enumerate() {
println!("Monitor #{}: {:?}", num, monitor.get_name());
for (num, monitor) in event_loop.available_monitors().enumerate() {
println!("Monitor #{}: {:?}", num, monitor.name());
}
print!("Please write the number of the monitor to use: ");
@ -115,9 +123,9 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
let mut num = String::new();
io::stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = event_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
let monitor = event_loop.available_monitors().nth(num).expect("Please enter a valid ID");
println!("Using {:?}", monitor.get_name());
println!("Using {:?}", monitor.name());
monitor
}

View file

@ -12,8 +12,8 @@ fn main() {
.build(&event_loop)
.unwrap();
window.set_min_dimensions(Some(LogicalSize::new(400.0, 200.0)));
window.set_max_dimensions(Some(LogicalSize::new(800.0, 400.0)));
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);

View file

@ -5,5 +5,5 @@ use winit::window::WindowBuilder;
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
println!("{:#?}\nPrimary: {:#?}", window.get_available_monitors(), window.get_primary_monitor());
println!("{:#?}\nPrimary: {:#?}", window.available_monitors(), window.primary_monitor());
}

115
examples/multithreaded.rs Normal file
View file

@ -0,0 +1,115 @@
extern crate env_logger;
extern crate winit;
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop}, window::{CursorIcon, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
const WINDOW_SIZE: (u32, u32) = (600, 400);
fn main() {
env_logger::init();
let event_loop = EventLoop::new();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
let window = WindowBuilder::new()
.with_inner_size(WINDOW_SIZE.into())
.build(&event_loop)
.unwrap();
let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx);
thread::spawn(move || {
while let Ok(event) = rx.recv() {
match event {
WindowEvent::KeyboardInput { input: KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
}, .. } => {
window.set_title(&format!("{:?}", key));
let state = !modifiers.shift;
use self::VirtualKeyCode::*;
match key {
A => window.set_always_on_top(state),
C => window.set_cursor_icon(match state {
true => CursorIcon::Progress,
false => CursorIcon::Default,
}),
D => window.set_decorations(!state),
F => window.set_fullscreen(match state {
true => Some(window.current_monitor()),
false => None,
}),
G => window.set_cursor_grab(state).unwrap(),
H => window.set_cursor_visible(!state),
I => {
println!("Info:");
println!("-> outer_position : {:?}", window.outer_position());
println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size());
},
L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE.into()),
false => None,
}),
M => window.set_maximized(state),
P => window.set_outer_position({
let mut position = window.outer_position().unwrap();
let sign = if state { 1.0 } else { -1.0 };
position.x += 10.0 * sign;
position.y += 10.0 * sign;
position
}),
Q => window.request_redraw(),
R => window.set_resizable(state),
S => window.set_inner_size(match state {
true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100),
false => WINDOW_SIZE,
}.into()),
W => window.set_cursor_position((
WINDOW_SIZE.0 as i32 / 2,
WINDOW_SIZE.1 as i32 / 2,
).into()).unwrap(),
Z => {
window.set_visible(false);
thread::sleep(Duration::from_secs(1));
window.set_visible(true);
},
_ => (),
}
},
_ => (),
}
}
});
}
event_loop.run(move |event, _event_loop, control_flow| {
*control_flow = match !window_senders.is_empty() {
true => ControlFlow::Wait,
false => ControlFlow::Exit,
};
match event {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested
| WindowEvent::Destroyed
| WindowEvent::KeyboardInput { input: KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Escape),
.. }, .. } => {
window_senders.remove(&window_id);
},
_ => if let Some(tx) = window_senders.get(&window_id) {
tx.send(event).unwrap();
},
}
}
_ => (),
}
})
}

View file

@ -10,7 +10,7 @@ fn main() {
let window = WindowBuilder::new()
.with_title("Hit space to toggle resizability.")
.with_dimensions((400, 200).into())
.with_inner_size((400, 200).into())
.with_resizable(resizable)
.build(&event_loop)
.unwrap();

View file

@ -21,6 +21,7 @@ fn main() {
Event::NewEvents(StartCause::ResumeTimeReached{..}) => {
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0));
println!("\nTimer\n");
_window.set_inner_size(winit::dpi::LogicalSize::new(300.0, 300.0));
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,

View file

@ -33,10 +33,10 @@
//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor,
//! or because the user changed the configuration of their screen.
//! - You can also retrieve the DPI factor of a monitor by calling
//! [`MonitorHandle::get_hidpi_factor`](../monitor/struct.MonitorHandle.html#method.get_hidpi_factor), or the
//! [`MonitorHandle::hidpi_factor`](../monitor/struct.MonitorHandle.html#method.hidpi_factor), or the
//! current DPI factor applied to a window by calling
//! [`Window::get_hidpi_factor`](../window/struct.Window.html#method.get_hidpi_factor), which is roughly equivalent
//! to `window.get_current_monitor().get_hidpi_factor()`.
//! [`Window::hidpi_factor`](../window/struct.Window.html#method.hidpi_factor), which is roughly equivalent
//! to `window.current_monitor().hidpi_factor()`.
//!
//! Depending on the platform, the window's actual DPI factor may only be known after
//! the event loop has started and your window has been drawn once. To properly handle these cases,

86
src/error.rs Normal file
View file

@ -0,0 +1,86 @@
use std::fmt;
use std::error;
use platform_impl;
/// An error whose cause it outside Winit's control.
#[derive(Debug)]
pub enum ExternalError {
/// The operation is not supported by the backend.
NotSupported(NotSupportedError),
/// The OS cannot perform the operation.
Os(OsError),
}
/// The error type for when the requested operation is not supported by the backend.
#[derive(Clone)]
pub struct NotSupportedError {
_marker: (),
}
/// The error type for when the OS cannot perform the requested operation.
#[derive(Debug)]
pub struct OsError {
line: u32,
file: &'static str,
error: platform_impl::OsError,
}
impl NotSupportedError {
#[inline]
#[allow(dead_code)]
pub(crate) fn new() -> NotSupportedError {
NotSupportedError {
_marker: ()
}
}
}
impl OsError {
#[allow(dead_code)]
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
OsError {
line,
file,
error,
}
}
}
#[allow(unused_macros)]
macro_rules! os_error {
($error:expr) => {{
crate::error::OsError::new(line!(), file!(), $error)
}}
}
impl fmt::Display for OsError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
formatter.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
}
}
impl fmt::Display for ExternalError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
ExternalError::NotSupported(e) => e.fmt(formatter),
ExternalError::Os(e) => e.fmt(formatter),
}
}
}
impl fmt::Debug for NotSupportedError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
formatter.debug_struct("NotSupportedError").finish()
}
}
impl fmt::Display for NotSupportedError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
formatter.pad("the requested operation is not supported by Winit")
}
}
impl error::Error for OsError {}
impl error::Error for ExternalError {}
impl error::Error for NotSupportedError {}

View file

@ -94,6 +94,10 @@ impl Default for ControlFlow {
impl EventLoop<()> {
/// Builds a new event loop with a `()` as the user event type.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn new() -> EventLoop<()> {
EventLoop::<()>::new_user_event()
}
@ -106,6 +110,10 @@ impl<T> EventLoop<T> {
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will
/// fallback on x11. If this variable is set with any other value, winit will panic.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn new_user_event() -> EventLoop<T> {
EventLoop {
event_loop: platform_impl::EventLoop::new(),
@ -113,21 +121,6 @@ impl<T> EventLoop<T> {
}
}
/// Returns the list of all the monitors available on the system.
///
// Note: should be replaced with `-> impl Iterator` once stable.
#[inline]
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
let data = self.event_loop.get_available_monitors();
AvailableMonitorsIter{ data: data.into_iter() }
}
/// Returns the primary monitor of the system.
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.event_loop.get_primary_monitor() }
}
/// Hijacks the calling thread and initializes the `winit` event loop with the provided
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
@ -145,13 +138,27 @@ impl<T> EventLoop<T> {
self.event_loop.run(event_handler)
}
/// Creates an `EventLoopProxy` that can be used to wake up the `EventLoop` from another
/// thread.
/// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(),
}
}
/// Returns the list of all the monitors available on the system.
///
// Note: should be replaced with `-> impl Iterator` once stable.
#[inline]
pub fn available_monitors(&self) -> AvailableMonitorsIter {
let data = self.event_loop.available_monitors();
AvailableMonitorsIter{ data: data.into_iter() }
}
/// Returns the primary monitor of the system.
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.event_loop.primary_monitor() }
}
}
impl<T> Deref for EventLoop<T> {

View file

@ -87,8 +87,6 @@ extern crate serde;
#[cfg(target_os = "windows")]
extern crate winapi;
#[cfg(target_os = "windows")]
extern crate backtrace;
#[macro_use]
#[cfg(target_os = "windows")]
extern crate bitflags;
@ -98,6 +96,8 @@ extern crate objc;
#[cfg(target_os = "macos")]
extern crate cocoa;
#[cfg(target_os = "macos")]
extern crate dispatch;
#[cfg(target_os = "macos")]
extern crate core_foundation;
#[cfg(target_os = "macos")]
extern crate core_graphics;
@ -116,6 +116,8 @@ extern crate stdweb;
extern crate calloop;
pub mod dpi;
#[macro_use]
pub mod error;
pub mod event;
pub mod event_loop;
mod icon;

View file

@ -3,13 +3,13 @@
//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_id]
//! type. This is retreived from an [`AvailableMonitorsIter`][monitor_iter], which can be acquired
//! with:
//! - [`EventLoop::get_available_monitors`][loop_get]
//! - [`Window::get_available_monitors`][window_get].
//! - [`EventLoop::available_monitors`][loop_get]
//! - [`Window::available_monitors`][window_get].
//!
//! [monitor_id]: ./struct.MonitorHandle.html
//! [monitor_iter]: ./struct.AvailableMonitorsIter.html
//! [loop_get]: ../event_loop/struct.EventLoop.html#method.get_available_monitors
//! [window_get]: ../window/struct.Window.html#method.get_available_monitors
//! [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors
//! [window_get]: ../window/struct.Window.html#method.available_monitors
use std::collections::vec_deque::IntoIter as VecDequeIter;
use platform_impl;
@ -18,11 +18,11 @@ use dpi::{PhysicalPosition, PhysicalSize};
/// An iterator over all available monitors.
///
/// Can be acquired with:
/// - [`EventLoop::get_available_monitors`][loop_get]
/// - [`Window::get_available_monitors`][window_get].
/// - [`EventLoop::available_monitors`][loop_get]
/// - [`Window::available_monitors`][window_get].
///
/// [loop_get]: ../event_loop/struct.EventLoop.html#method.get_available_monitors
/// [window_get]: ../window/struct.Window.html#method.get_available_monitors
/// [loop_get]: ../event_loop/struct.EventLoop.html#method.available_monitors
/// [window_get]: ../window/struct.Window.html#method.available_monitors
// Implementation note: we retrieve the list once, then serve each element by one by one.
// This may change in the future.
#[derive(Debug)]
@ -59,21 +59,21 @@ impl MonitorHandle {
///
/// Returns `None` if the monitor doesn't exist anymore.
#[inline]
pub fn get_name(&self) -> Option<String> {
self.inner.get_name()
pub fn name(&self) -> Option<String> {
self.inner.name()
}
/// Returns the monitor's resolution.
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
self.inner.get_dimensions()
pub fn dimensions(&self) -> PhysicalSize {
self.inner.dimensions()
}
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
self.inner.get_position()
pub fn position(&self) -> PhysicalPosition {
self.inner.position()
}
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
@ -85,7 +85,7 @@ impl MonitorHandle {
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.inner.get_hidpi_factor()
pub fn hidpi_factor(&self) -> f64 {
self.inner.hidpi_factor()
}
}

View file

@ -19,13 +19,13 @@ impl EventLoopExtAndroid for EventLoop {
/// Additional methods on `Window` that are specific to Android.
pub trait WindowExtAndroid {
fn get_native_window(&self) -> *const c_void;
fn native_window(&self) -> *const c_void;
}
impl WindowExtAndroid for Window {
#[inline]
fn get_native_window(&self) -> *const c_void {
self.window.get_native_window()
fn native_window(&self) -> *const c_void {
self.window.native_window()
}
}

View file

@ -2,30 +2,74 @@
use std::os::raw::c_void;
use {MonitorHandle, Window, WindowBuilder};
use event_loop::EventLoop;
use monitor::MonitorHandle;
use window::{Window, WindowBuilder};
/// Additional methods on `EventLoop` that are specific to iOS.
pub trait EventLoopExtIOS {
/// Returns the idiom (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom;
}
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
fn idiom(&self) -> Idiom {
self.event_loop.idiom()
}
}
/// Additional methods on `Window` that are specific to iOS.
pub trait WindowExtIOS {
/// Returns a pointer to the `UIWindow` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_uiwindow(&self) -> *mut c_void;
fn ui_window(&self) -> *mut c_void;
/// Returns a pointer to the `UIViewController` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn ui_view_controller(&self) -> *mut c_void;
/// Returns a pointer to the `UIView` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_uiview(&self) -> *mut c_void;
fn ui_view(&self) -> *mut c_void;
/// Sets the HiDpi factor used by this window.
///
/// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`.
fn set_hidpi_factor(&self, hidpi_factor: f64);
/// Sets the valid orientations for screens showing this `Window`.
///
/// On iPhones and iPods upside down portrait is never enabled.
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
}
impl WindowExtIOS for Window {
#[inline]
fn get_uiwindow(&self) -> *mut c_void {
self.window.get_uiwindow() as _
fn ui_window(&self) -> *mut c_void {
self.window.ui_window() as _
}
#[inline]
fn get_uiview(&self) -> *mut c_void {
self.window.get_uiview() as _
fn ui_view_controller(&self) -> *mut c_void {
self.window.ui_view_controller() as _
}
#[inline]
fn ui_view(&self) -> *mut c_void {
self.window.ui_view() as _
}
#[inline]
fn set_hidpi_factor(&self, hidpi_factor: f64) {
self.window.set_hidpi_factor(hidpi_factor)
}
#[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations)
}
}
@ -35,6 +79,15 @@ pub trait WindowBuilderExtIOS {
///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the `contentScaleFactor` of the underlying `UIWindow` to `hidpi_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to `MonitorHandle::hidpi_factor()`.
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the `Window`.
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
}
impl WindowBuilderExtIOS for WindowBuilder {
@ -43,17 +96,66 @@ impl WindowBuilderExtIOS for WindowBuilder {
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
self
}
#[inline]
fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder {
self.platform_specific.hidpi_factor = Some(hidpi_factor);
self
}
#[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
self.platform_specific.valid_orientations = valid_orientations;
self
}
}
/// Additional methods on `MonitorHandle` that are specific to iOS.
pub trait MonitorHandleExtIOS {
/// Returns a pointer to the `UIScreen` that is used by this monitor.
fn get_uiscreen(&self) -> *mut c_void;
fn ui_screen(&self) -> *mut c_void;
}
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn get_uiscreen(&self) -> *mut c_void {
self.inner.get_uiscreen() as _
fn ui_screen(&self) -> *mut c_void {
self.inner.ui_screen() as _
}
}
/// Valid orientations for a particular `Window`.
#[derive(Clone, Copy, Debug)]
pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone
LandscapeAndPortrait,
Landscape,
/// Excludes `PortraitUpsideDown` on iphone
Portrait,
}
impl Default for ValidOrientations {
#[inline]
fn default() -> ValidOrientations {
ValidOrientations::LandscapeAndPortrait
}
}
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Idiom {
Unspecified,
/// iPhone and iPod touch.
Phone,
/// iPad.
Pad,
/// tvOS and Apple TV.
TV,
CarPlay,
}

View file

@ -1,19 +1,22 @@
#![cfg(target_os = "macos")]
use std::os::raw::c_void;
use {LogicalSize, MonitorHandle, Window, WindowBuilder};
use crate::dpi::LogicalSize;
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowBuilder};
/// Additional methods on `Window` that are specific to MacOS.
pub trait WindowExtMacOS {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_nswindow(&self) -> *mut c_void;
fn nswindow(&self) -> *mut c_void;
/// Returns a pointer to the cocoa `NSView` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_nsview(&self) -> *mut c_void;
fn nsview(&self) -> *mut c_void;
/// Request user attention, causing the application's dock icon to bounce.
/// Note that this has no effect if the application is already focused.
@ -23,6 +26,9 @@ pub trait WindowExtMacOS {
/// - `true`: the dock icon will bounce until the application is focused.
fn request_user_attention(&self, is_critical: bool);
/// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool;
/// Toggles a fullscreen mode that doesn't require a new macOS space.
/// Returns a boolean indicating whether the transition was successful (this
/// won't work if the window was already in the native fullscreen).
@ -35,13 +41,13 @@ pub trait WindowExtMacOS {
impl WindowExtMacOS for Window {
#[inline]
fn get_nswindow(&self) -> *mut c_void {
self.window.get_nswindow()
fn nswindow(&self) -> *mut c_void {
self.window.nswindow()
}
#[inline]
fn get_nsview(&self) -> *mut c_void {
self.window.get_nsview()
fn nsview(&self) -> *mut c_void {
self.window.nsview()
}
#[inline]
@ -49,6 +55,11 @@ impl WindowExtMacOS for Window {
self.window.request_user_attention(is_critical)
}
#[inline]
fn simple_fullscreen(&self) -> bool {
self.window.simple_fullscreen()
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen)
@ -156,16 +167,16 @@ pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
/// Returns a pointer to the NSScreen representing this monitor.
fn get_nsscreen(&self) -> Option<*mut c_void>;
fn nsscreen(&self) -> Option<*mut c_void>;
}
impl MonitorHandleExtMacOS for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.get_native_identifier()
self.inner.native_identifier()
}
fn get_nsscreen(&self) -> Option<*mut c_void> {
self.inner.get_nsscreen().map(|s| s as *mut c_void)
fn nsscreen(&self) -> Option<*mut c_void> {
self.inner.nsscreen().map(|s| s as *mut c_void)
}
}

View file

@ -1,149 +0,0 @@
use cocoa::{
appkit::NSImage, base::{id, nil, YES},
foundation::{NSDictionary, NSPoint, NSString},
};
use objc::runtime::Sel;
use super::IntoOption;
use MouseCursor;
pub enum Cursor {
Native(&'static str),
Undocumented(&'static str),
WebKit(&'static str),
}
impl From<MouseCursor> for Cursor {
fn from(cursor: MouseCursor) -> Self {
match cursor {
MouseCursor::Arrow | MouseCursor::Default => Cursor::Native("arrowCursor"),
MouseCursor::Hand => Cursor::Native("pointingHandCursor"),
MouseCursor::Grabbing | MouseCursor::Grab => Cursor::Native("closedHandCursor"),
MouseCursor::Text => Cursor::Native("IBeamCursor"),
MouseCursor::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
MouseCursor::Copy => Cursor::Native("dragCopyCursor"),
MouseCursor::Alias => Cursor::Native("dragLinkCursor"),
MouseCursor::NotAllowed | MouseCursor::NoDrop => Cursor::Native("operationNotAllowedCursor"),
MouseCursor::ContextMenu => Cursor::Native("contextualMenuCursor"),
MouseCursor::Crosshair => Cursor::Native("crosshairCursor"),
MouseCursor::EResize => Cursor::Native("resizeRightCursor"),
MouseCursor::NResize => Cursor::Native("resizeUpCursor"),
MouseCursor::WResize => Cursor::Native("resizeLeftCursor"),
MouseCursor::SResize => Cursor::Native("resizeDownCursor"),
MouseCursor::EwResize | MouseCursor::ColResize => Cursor::Native("resizeLeftRightCursor"),
MouseCursor::NsResize | MouseCursor::RowResize => Cursor::Native("resizeUpDownCursor"),
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
MouseCursor::Help => Cursor::Undocumented("_helpCursor"),
MouseCursor::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
MouseCursor::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
MouseCursor::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
MouseCursor::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
MouseCursor::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
MouseCursor::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
MouseCursor::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
MouseCursor::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),
// While these are available, the former just loads a white arrow,
// and the latter loads an ugly deflated beachball!
// MouseCursor::Move => Cursor::Undocumented("_moveCursor"),
// MouseCursor::Wait => Cursor::Undocumented("_waitCursor"),
// An even more undocumented cursor...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
MouseCursor::Wait | MouseCursor::Progress => Cursor::Undocumented("busyButClickableCursor"),
// For the rest, we can just snatch the cursors from WebKit...
// They fit the style of the native cursors, and will seem
// completely standard to macOS users.
// https://stackoverflow.com/a/21786835/5435443
MouseCursor::Move | MouseCursor::AllScroll => Cursor::WebKit("move"),
MouseCursor::Cell => Cursor::WebKit("cell"),
}
}
}
impl Default for Cursor {
fn default() -> Self {
Cursor::Native("arrowCursor")
}
}
impl Cursor {
pub unsafe fn load(&self) -> id {
match self {
Cursor::Native(cursor_name) => {
let sel = Sel::register(cursor_name);
msg_send![class!(NSCursor), performSelector:sel]
},
Cursor::Undocumented(cursor_name) => {
let class = class!(NSCursor);
let sel = Sel::register(cursor_name);
let sel = if msg_send![class, respondsToSelector:sel] {
sel
} else {
warn!("Cursor `{}` appears to be invalid", cursor_name);
sel!(arrowCursor)
};
msg_send![class, performSelector:sel]
},
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name)
.unwrap_or_else(|message| {
warn!("{}", message);
Self::default().load()
}),
}
}
}
// Note that loading `busybutclickable` with this code won't animate the frames;
// instead you'll just get them all in a column.
unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result<id, String> {
static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors";
let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT);
let cursor_name = NSString::alloc(nil).init_str(cursor_name_str);
let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf");
let cursor_plist = NSString::alloc(nil).init_str("info.plist");
let key_x = NSString::alloc(nil).init_str("hotx");
let key_y = NSString::alloc(nil).init_str("hoty");
let cursor_path: id = msg_send![cursor_root,
stringByAppendingPathComponent:cursor_name
];
let pdf_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_pdf
];
let info_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_plist
];
let image = NSImage::alloc(nil)
.initByReferencingFile_(pdf_path)
// This will probably never be `None`, since images are loaded lazily...
.into_option()
// because of that, we need to check for validity.
.filter(|image| image.isValid() == YES)
.ok_or_else(||
format!("Failed to read image for `{}` cursor", cursor_name_str)
)?;
let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path)
.into_option()
.ok_or_else(||
format!("Failed to read info for `{}` cursor", cursor_name_str)
)?;
let x = info.valueForKey_(key_x);
let y = info.valueForKey_(key_y);
let point = NSPoint::new(
msg_send![x, doubleValue],
msg_send![y, doubleValue],
);
let cursor: id = msg_send![class!(NSCursor), alloc];
let cursor: id = msg_send![cursor, initWithImage:image hotSpot:point];
cursor
.into_option()
.ok_or_else(||
format!("Failed to initialize `{}` cursor", cursor_name_str)
)
}

View file

@ -1,63 +0,0 @@
mod cursor;
mod into_option;
pub use self::{cursor::Cursor, into_option::IntoOption};
use cocoa::appkit::NSWindowStyleMask;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSRect, NSUInteger};
use core_graphics::display::CGDisplay;
use objc::runtime::{Class, Object};
use platform::platform::ffi;
use platform::platform::window::IdRef;
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
location: ffi::NSNotFound as NSUInteger,
length: 0,
};
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
use cocoa::appkit::NSWindow;
window.setStyleMask_(mask);
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
use cocoa::appkit::NSWindow;
let current_style_mask = window.styleMask();
if on {
window.setStyleMask_(current_style_mask | mask);
} else {
window.setStyleMask_(current_style_mask & (!mask));
}
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
let superclass: id = msg_send![this, superclass];
&*(superclass as *const _)
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
let input_context: id = msg_send![input_context, initWithClient:view];
IdRef::new(input_context)
}
#[allow(dead_code)]
pub unsafe fn open_emoji_picker() {
let app: id = msg_send![class!(NSApplication), sharedApplication];
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
}

View file

@ -15,15 +15,15 @@ use platform_impl::{
EventLoop as LinuxEventLoop,
Window as LinuxWindow,
};
//use platform_impl::x11::XConnection;
//use platform_impl::x11::ffi::XVisualInfo;
//
use platform_impl::x11::XConnection;
use platform_impl::x11::ffi::XVisualInfo;
// TODO: stupid hack so that glutin can do its work
//#[doc(hidden)]
//pub use platform_impl::x11;
//
//pub use platform_impl::XNotSupported;
//pub use platform_impl::x11::util::WindowType as XWindowType;
#[doc(hidden)]
pub use platform_impl::x11;
pub use platform_impl::XNotSupported;
pub use platform_impl::x11::util::WindowType as XWindowType;
/// Theme for wayland client side decorations
///
@ -96,8 +96,8 @@ impl Theme for WaylandThemeObject {
/// Additional methods on `EventLoop` that are specific to Unix.
pub trait EventLoopExtUnix {
/// Builds a new `EventLoops` that is forced to use X11.
//fn new_x11() -> Result<Self, XNotSupported>
// where Self: Sized;
fn new_x11() -> Result<Self, XNotSupported>
where Self: Sized;
/// Builds a new `EventLoop` that is forced to use Wayland.
fn new_wayland() -> Self
@ -109,20 +109,27 @@ pub trait EventLoopExtUnix {
/// True if the `EventLoop` uses X11.
fn is_x11(&self) -> bool;
//#[doc(hidden)]
//fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this `EventLoop`.
///
/// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `EventLoop` is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopExtUnix for EventLoop<T> {
//#[inline]
//fn new_x11() -> Result<Self, XNotSupported> {
// LinuxEventLoop::new_x11().map(|ev|
// EventLoop {
// event_loop: ev,
// _marker: ::std::marker::PhantomData,
// }
// )
//}
#[inline]
fn new_x11() -> Result<Self, XNotSupported> {
LinuxEventLoop::new_x11().map(|ev|
EventLoop {
event_loop: ev,
_marker: ::std::marker::PhantomData,
}
)
}
#[inline]
fn new_wayland() -> Self {
@ -145,11 +152,22 @@ impl<T> EventLoopExtUnix for EventLoop<T> {
!self.event_loop.is_wayland()
}
//#[inline]
//#[doc(hidden)]
//fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
// self.event_loop.x_connection().cloned()
//}
#[inline]
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.event_loop {
LinuxEventLoop::X(ref e) => Some(e.x_connection().clone()),
_ => None
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.event_loop {
LinuxEventLoop::Wayland(ref e) => Some(e.display().get_display_ptr() as *mut _),
_ => None
}
}
}
/// Additional methods on `Window` that are specific to Unix.
@ -157,19 +175,19 @@ pub trait WindowExtUnix {
/// Returns the ID of the `Window` xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
fn get_xlib_window(&self) -> Option<raw::c_ulong>;
fn xlib_window(&self) -> Option<raw::c_ulong>;
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_xlib_display(&self) -> Option<*mut raw::c_void>;
fn xlib_display(&self) -> Option<*mut raw::c_void>;
fn get_xlib_screen_id(&self) -> Option<raw::c_int>;
fn xlib_screen_id(&self) -> Option<raw::c_int>;
//#[doc(hidden)]
//fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
fn set_urgent(&self, is_urgent: bool);
@ -179,21 +197,21 @@ pub trait WindowExtUnix {
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_xcb_connection(&self) -> Option<*mut raw::c_void>;
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_wayland_surface(&self) -> Option<*mut raw::c_void>;
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the glutin `Window` is destroyed.
fn get_wayland_display(&self) -> Option<*mut raw::c_void>;
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);
@ -210,65 +228,65 @@ pub trait WindowExtUnix {
impl WindowExtUnix for Window {
#[inline]
fn get_xlib_window(&self) -> Option<raw::c_ulong> {
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
//LinuxWindow::X(ref w) => Some(w.get_xlib_window()),
LinuxWindow::X(ref w) => Some(w.xlib_window()),
_ => None
}
}
#[inline]
fn get_xlib_display(&self) -> Option<*mut raw::c_void> {
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
//LinuxWindow::X(ref w) => Some(w.get_xlib_display()),
LinuxWindow::X(ref w) => Some(w.xlib_display()),
_ => None
}
}
#[inline]
fn get_xlib_screen_id(&self) -> Option<raw::c_int> {
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
//LinuxWindow::X(ref w) => Some(w.get_xlib_screen_id()),
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
_ => None
}
}
//#[inline]
//#[doc(hidden)]
//fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>> {
// match self.window {
// //LinuxWindow::X(ref w) => Some(w.get_xlib_xconnection()),
// _ => None
// }
//}
#[inline]
#[doc(hidden)]
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
_ => None
}
}
#[inline]
fn get_xcb_connection(&self) -> Option<*mut raw::c_void> {
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
//LinuxWindow::X(ref w) => Some(w.get_xcb_connection()),
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
_ => None
}
}
#[inline]
fn set_urgent(&self, is_urgent: bool) {
//if let LinuxWindow::X(ref w) = self.window {
// w.set_urgent(is_urgent);
//}
if let LinuxWindow::X(ref w) = self.window {
w.set_urgent(is_urgent);
}
}
#[inline]
fn get_wayland_surface(&self) -> Option<*mut raw::c_void> {
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.get_surface().as_ref().c_ptr() as *mut _),
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
_ => None
}
}
#[inline]
fn get_wayland_display(&self) -> Option<*mut raw::c_void> {
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.get_display().as_ref().c_ptr() as *mut _),
LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _),
_ => None
}
}
@ -297,7 +315,7 @@ pub trait WindowBuilderExtUnix {
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder;
/// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
//fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder;
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder;
/// Build window with resize increment hint. Only implemented on X11.
@ -316,9 +334,9 @@ pub trait WindowBuilderExtUnix {
impl WindowBuilderExtUnix for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> WindowBuilder {
//self.platform_specific.visual_infos = Some(
// unsafe { ptr::read(visual_infos as *const XVisualInfo) }
//);
self.platform_specific.visual_infos = Some(
unsafe { ptr::read(visual_infos as *const XVisualInfo) }
);
self
}
@ -340,11 +358,11 @@ impl WindowBuilderExtUnix for WindowBuilder {
self
}
//#[inline]
//fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder {
// self.platform_specific.x11_window_type = x11_window_type;
// self
//}
#[inline]
fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder {
self.platform_specific.x11_window_type = x11_window_type;
self
}
#[inline]
fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder {
@ -380,6 +398,6 @@ pub trait MonitorHandleExtUnix {
impl MonitorHandleExtUnix for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.get_native_identifier()
self.inner.native_identifier()
}
}

View file

@ -33,7 +33,7 @@ pub trait WindowExtWindows {
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
fn get_hwnd(&self) -> *mut libc::c_void;
fn hwnd(&self) -> *mut libc::c_void;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
@ -41,7 +41,7 @@ pub trait WindowExtWindows {
impl WindowExtWindows for Window {
#[inline]
fn get_hwnd(&self) -> *mut libc::c_void {
fn hwnd(&self) -> *mut libc::c_void {
self.window.hwnd() as *mut _
}
@ -95,12 +95,12 @@ pub trait MonitorHandleExtWindows {
impl MonitorHandleExtWindows for MonitorHandle {
#[inline]
fn native_id(&self) -> String {
self.inner.get_native_identifier()
self.inner.native_identifier()
}
#[inline]
fn hmonitor(&self) -> *mut c_void {
self.inner.get_hmonitor() as *mut _
self.inner.hmonitor() as *mut _
}
}
@ -109,12 +109,12 @@ pub trait DeviceIdExtWindows {
/// Returns an identifier that persistently refers to this specific device.
///
/// Will return `None` if the device is no longer available.
fn get_persistent_identifier(&self) -> Option<String>;
fn persistent_identifier(&self) -> Option<String>;
}
impl DeviceIdExtWindows for DeviceId {
#[inline]
fn get_persistent_identifier(&self) -> Option<String> {
self.0.get_persistent_identifier()
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
}

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@ use {
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
CursorIcon,
PhysicalPosition,
PhysicalSize,
WindowAttributes,
@ -23,9 +23,12 @@ use {
WindowId as RootWindowId,
};
use CreationError::OsError;
use error::{ExternalError, NotSupportedError};
use events::{Touch, TouchPhase};
use window::MonitorHandle as RootMonitorHandle;
pub type OsError = std::io::Error;
pub struct EventLoop {
event_rx: Receiver<android_glue::Event>,
suspend_callback: RefCell<Option<Box<Fn(bool) -> ()>>>,
@ -45,14 +48,14 @@ impl EventLoop {
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
@ -62,7 +65,7 @@ impl EventLoop {
while let Ok(event) = self.event_rx.try_recv() {
let e = match event{
android_glue::Event::EventMotion(motion) => {
let dpi_factor = MonitorHandle.get_hidpi_factor();
let dpi_factor = MonitorHandle.hidpi_factor();
let location = LogicalPosition::from_physical(
(motion.x as f64, motion.y as f64),
dpi_factor,
@ -99,12 +102,12 @@ impl EventLoop {
android_glue::Event::WindowResized |
android_glue::Event::ConfigChanged => {
// Activity Orientation changed or resized.
let native_window = unsafe { android_glue::get_native_window() };
let native_window = unsafe { android_glue::native_window() };
if native_window.is_null() {
None
} else {
let dpi_factor = MonitorHandle.get_hidpi_factor();
let physical_size = MonitorHandle.get_dimensions();
let dpi_factor = MonitorHandle.hidpi_factor();
let physical_size = MonitorHandle.dimensions();
let size = LogicalSize::from_physical(physical_size, dpi_factor);
Some(Event::WindowEvent {
window_id: RootWindowId(WindowId),
@ -203,10 +206,10 @@ impl fmt::Debug for MonitorHandle {
}
let monitor_id_proxy = MonitorHandle {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
name: self.name(),
dimensions: self.dimensions(),
position: self.outer_position(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@ -215,14 +218,14 @@ impl fmt::Debug for MonitorHandle {
impl MonitorHandle {
#[inline]
pub fn get_name(&self) -> Option<String> {
pub fn name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
unsafe {
let window = android_glue::get_native_window();
let window = android_glue::native_window();
(
ffi::ANativeWindow_getWidth(window) as f64,
ffi::ANativeWindow_getHeight(window) as f64,
@ -231,13 +234,13 @@ impl MonitorHandle {
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
pub fn outer_position(&self) -> PhysicalPosition {
// Android assumes single screen
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
1.0
}
}
@ -252,12 +255,12 @@ impl Window {
_: PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError>
{
let native_window = unsafe { android_glue::get_native_window() };
let native_window = unsafe { android_glue::native_window() };
if native_window.is_null() {
return Err(OsError(format!("Android's native window is null")));
}
android_glue::set_multitouch(win_attribs.multitouch);
android_glue::set_multitouch(true);
Ok(Window {
native_window: native_window as *const _,
@ -265,7 +268,7 @@ impl Window {
}
#[inline]
pub fn get_native_window(&self) -> *const c_void {
pub fn native_window(&self) -> *const c_void {
self.native_window
}
@ -285,29 +288,29 @@ impl Window {
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
pub fn outer_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
pub fn inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _position: LogicalPosition) {
pub fn set_outer_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
@ -317,19 +320,19 @@ impl Window {
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
pub fn inner_size(&self) -> Option<LogicalSize> {
if self.native_window.is_null() {
None
} else {
let dpi_factor = self.get_hidpi_factor();
let physical_size = self.get_current_monitor().get_dimensions();
let dpi_factor = self.hidpi_factor();
let physical_size = self.current_monitor().dimensions();
Some(LogicalSize::from_physical(physical_size, dpi_factor))
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
pub fn outer_size(&self) -> Option<LogicalSize> {
self.inner_size()
}
#[inline]
@ -338,18 +341,18 @@ impl Window {
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.get_current_monitor().get_hidpi_factor()
pub fn hidpi_factor(&self) -> f64 {
self.current_monitor().hidpi_factor()
}
#[inline]
pub fn set_cursor(&self, _: MouseCursor) {
pub fn set_cursor_icon(&self, _: CursorIcon) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on Android.".to_owned())
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
@ -358,8 +361,8 @@ impl Window {
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on Android.".to_owned())
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
@ -368,6 +371,13 @@ impl Window {
// Android has single screen maximized apps so nothing to do
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
// N/A
// Android has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
// N/A
@ -390,24 +400,24 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, _spot: LogicalPosition) {
pub fn set_ime_position(&self, _spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorHandle {
pub fn current_monitor(&self) -> RootMonitorHandle {
RootMonitorHandle { inner: MonitorHandle }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}

View file

@ -10,11 +10,12 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, Arc};
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use error::{ExternalError, NotSupportedError};
use window::MonitorHandle as RootMonitorHandle;
const DOCUMENT_NAME: &'static str = "#document\0";
fn get_hidpi_factor() -> f64 {
fn hidpi_factor() -> f64 {
unsafe { ffi::emscripten_get_device_pixel_ratio() as f64 }
}
@ -24,6 +25,8 @@ pub struct PlatformSpecificWindowBuilderAttributes;
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
pub type OsError = std::io::Error;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
@ -41,23 +44,23 @@ pub struct MonitorHandle;
impl MonitorHandle {
#[inline]
pub fn get_name(&self) -> Option<String> {
pub fn name(&self) -> Option<String> {
Some("Canvas".to_owned())
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
pub fn outer_position(&self) -> PhysicalPosition {
unimplemented!()
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
get_hidpi_factor()
pub fn hidpi_factor(&self) -> f64 {
hidpi_factor()
}
}
@ -113,14 +116,14 @@ impl EventLoop {
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut list = VecDeque::with_capacity(1);
list.push_back(MonitorHandle);
list
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
@ -163,7 +166,7 @@ impl WindowId {
pub struct Window2 {
cursor_grabbed: Mutex<bool>,
cursor_hidden: Mutex<bool>,
cursor_visible: Mutex<bool>,
is_fullscreen: bool,
events: Box<Mutex<VecDeque<::Event>>>,
}
@ -208,7 +211,7 @@ extern "C" fn mouse_callback(
match event_type {
ffi::EMSCRIPTEN_EVENT_MOUSEMOVE => {
let dpi_factor = get_hidpi_factor();
let dpi_factor = hidpi_factor();
let position = LogicalPosition::from_physical(
((*event).canvasX as f64, (*event).canvasY as f64),
dpi_factor,
@ -328,7 +331,7 @@ extern fn touch_callback(
for touch in 0..(*event).numTouches as usize {
let touch = (*event).touches[touch];
if touch.isChanged == ffi::EM_TRUE {
let dpi_factor = get_hidpi_factor();
let dpi_factor = hidpi_factor();
let location = LogicalPosition::from_physical(
(touch.canvasX as f64, touch.canvasY as f64),
dpi_factor,
@ -387,8 +390,8 @@ impl Window {
}
let w = Window2 {
cursor_grabbed: Default::default(),
cursor_hidden: Default::default(),
cursor_grabbed: Mutex::new(false),
cursor_visible: Mutex::new(true),
events: Default::default(),
is_fullscreen: attribs.fullscreen.is_some(),
};
@ -427,7 +430,7 @@ impl Window {
em_try(ffi::emscripten_set_fullscreenchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(fullscreen_callback)))
.map_err(|e| ::CreationError::OsError(e))?;
}
} else if let Some(size) = attribs.dimensions {
} else if let Some(size) = attribs.inner_size {
window.set_inner_size(size);
}
@ -445,21 +448,21 @@ impl Window {
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
pub fn outer_position(&self) -> Option<LogicalPosition> {
Some((0, 0).into())
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
pub fn inner_position(&self) -> Option<LogicalPosition> {
Some((0, 0).into())
}
#[inline]
pub fn set_position(&self, _: LogicalPosition) {
pub fn set_outer_position(&self, _: LogicalPosition) {
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
pub fn inner_size(&self) -> Option<LogicalSize> {
unsafe {
let mut width = 0;
let mut height = 0;
@ -470,7 +473,7 @@ impl Window {
{
None
} else {
let dpi_factor = self.get_hidpi_factor();
let dpi_factor = self.hidpi_factor();
let logical = LogicalSize::from_physical((width as u32, height as u32), dpi_factor);
Some(logical)
}
@ -478,14 +481,14 @@ impl Window {
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
pub fn outer_size(&self) -> Option<LogicalSize> {
self.inner_size()
}
#[inline]
pub fn set_inner_size(&self, size: LogicalSize) {
unsafe {
let dpi_factor = self.get_hidpi_factor();
let dpi_factor = self.hidpi_factor();
let physical = PhysicalSize::from_logical(size, dpi_factor);
let (width, height): (u32, u32) = physical.into();
ffi::emscripten_set_element_css_size(
@ -497,12 +500,12 @@ impl Window {
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
@ -522,12 +525,12 @@ impl Window {
}
#[inline]
pub fn set_cursor(&self, _cursor: ::MouseCursor) {
pub fn set_cursor_icon(&self, _cursor: ::CursorIcon) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
let mut grabbed_lock = self.window.cursor_grabbed.lock().unwrap();
if grab == *grabbed_lock { return Ok(()); }
unsafe {
@ -554,24 +557,24 @@ impl Window {
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let mut hidden_lock = self.window.cursor_hidden.lock().unwrap();
if hide == *hidden_lock { return; }
if hide {
unsafe { ffi::emscripten_hide_mouse() };
} else {
pub fn set_cursor_visible(&self, visible: bool) {
let mut visible_lock = self.window.cursor_visible.lock().unwrap();
if visible == *visible_lock { return; }
if visible {
show_mouse();
} else {
unsafe { ffi::emscripten_hide_mouse() };
}
*hidden_lock = hide;
*visible_lock = visible;
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
get_hidpi_factor()
pub fn hidpi_factor(&self) -> f64 {
hidpi_factor()
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
Err("Setting cursor position is not possible on Emscripten.".to_owned())
}
@ -580,6 +583,11 @@ impl Window {
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn fullscreen(&self) -> Option<::MonitorHandle> {
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<::MonitorHandle>) {
// iOS has single screen maximized apps so nothing to do
@ -601,24 +609,24 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
pub fn set_ime_position(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorHandle {
pub fn current_monitor(&self) -> RootMonitorHandle {
RootMonitorHandle { inner: MonitorHandle }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut list = VecDeque::with_capacity(1);
list.push_back(MonitorHandle);
list
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
}
@ -634,7 +642,7 @@ impl Drop for Window {
unsafe {
// Return back to normal cursor state
self.hide_cursor(false);
self.grab_cursor(false);
self.set_cursor_grab(false);
// Exit fullscreen if on
if self.window.is_fullscreen {

View file

@ -0,0 +1,580 @@
use std::{mem, ptr};
use std::cell::{RefCell, RefMut};
use std::mem::ManuallyDrop;
use std::os::raw::c_void;
use std::time::Instant;
use event::{Event, StartCause};
use event_loop::ControlFlow;
use platform_impl::platform::event_loop::{EventHandler, Never};
use platform_impl::platform::ffi::{
id,
CFAbsoluteTimeGetCurrent,
CFRelease,
CFRunLoopAddTimer,
CFRunLoopGetMain,
CFRunLoopRef,
CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate,
CFRunLoopTimerRef,
CFRunLoopTimerSetNextFireDate,
kCFRunLoopCommonModes,
NSUInteger,
};
macro_rules! bug {
($msg:expr) => {
panic!("winit iOS bug, file an issue: {}", $msg)
};
}
// this is the state machine for the app lifecycle
#[derive(Debug)]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<id>,
queued_events: Vec<Event<Never>>,
},
Launching {
queued_windows: Vec<id>,
queued_events: Vec<Event<Never>>,
queued_event_handler: Box<EventHandler>,
},
ProcessingEvents {
event_handler: Box<EventHandler>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<Event<Never>>,
},
Waiting {
waiting_event_handler: Box<EventHandler>,
start: Instant,
},
PollFinished {
waiting_event_handler: Box<EventHandler>,
},
Terminated,
}
impl Drop for AppStateImpl {
fn drop(&mut self) {
match self {
&mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } |
&mut AppStateImpl::Launching { ref mut queued_windows, .. } => unsafe {
for &mut window in queued_windows {
let () = msg_send![window, release];
}
}
_ => {}
}
}
}
pub struct AppState {
app_state: AppStateImpl,
control_flow: ControlFlow,
waker: EventLoopWaker,
}
impl AppState {
// requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
if cfg!(debug_assertions) {
assert_main_thread!("bug in winit: `AppState::get_mut()` can only be called on the main thread");
}
let mut guard = APP_STATE.borrow_mut();
if guard.is_none() {
#[inline(never)]
#[cold]
unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(CFRunLoopGetMain());
**guard = Some(AppState {
app_state: AppStateImpl::NotLaunched {
queued_windows: Vec::new(),
queued_events: Vec::new(),
},
control_flow: ControlFlow::default(),
waker,
});
}
init_guard(&mut guard)
}
RefMut::map(guard, |state| {
state.as_mut().unwrap()
})
}
// requires main thread and window is a UIWindow
// retains window
pub unsafe fn set_key_window(window: id) {
let mut this = AppState::get_mut();
match &mut this.app_state {
&mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => {
queued_windows.push(window);
msg_send![window, retain];
return;
}
&mut AppStateImpl::ProcessingEvents { .. } => {},
&mut AppStateImpl::InUserCallback { .. } => {},
&mut AppStateImpl::Terminated => panic!("Attempt to create a `Window` \
after the app has terminated"),
app_state => unreachable!("unexpected state: {:#?}", app_state), // all other cases should be impossible
}
drop(this);
msg_send![window, makeKeyAndVisible]
}
// requires main thread
pub unsafe fn will_launch(queued_event_handler: Box<EventHandler>) {
let mut this = AppState::get_mut();
let (queued_windows, queued_events) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
ref mut queued_events,
} => {
let windows = ptr::read(queued_windows);
let events = ptr::read(queued_events);
(windows, events)
}
_ => panic!("winit iOS expected the app to be in a `NotLaunched` \
state, but was not - please file an issue"),
};
ptr::write(&mut this.app_state, AppStateImpl::Launching {
queued_windows,
queued_events,
queued_event_handler,
});
}
// requires main thread
pub unsafe fn did_finish_launching() {
let mut this = AppState::get_mut();
let windows = match &mut this.app_state {
&mut AppStateImpl::Launching {
ref mut queued_windows,
..
} => mem::replace(queued_windows, Vec::new()),
_ => panic!(
"winit iOS expected the app to be in a `Launching` \
state, but was not - please file an issue"
),
};
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance.
// ```
let screen: id = msg_send![window, screen];
let () = msg_send![screen, retain];
let () = msg_send![window, setScreen:0 as id];
let () = msg_send![window, setScreen:screen];
let () = msg_send![screen, release];
let controller: id = msg_send![window, rootViewController];
let () = msg_send![window, setRootViewController:ptr::null::<()>()];
let () = msg_send![window, setRootViewController:controller];
let () = msg_send![window, makeKeyAndVisible];
}
let () = msg_send![window, release];
}
let mut this = AppState::get_mut();
let (windows, events, event_handler) = match &mut this.app_state {
&mut AppStateImpl::Launching {
ref mut queued_windows,
ref mut queued_events,
ref mut queued_event_handler,
} => {
let windows = ptr::read(queued_windows);
let events = ptr::read(queued_events);
let event_handler = ptr::read(queued_event_handler);
(windows, events, event_handler)
}
_ => panic!("winit iOS expected the app to be in a `Launching` \
state, but was not - please file an issue"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Poll,
});
drop(this);
let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events);
AppState::handle_nonuser_events(events);
// the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching
for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
let () = msg_send![window, makeKeyAndVisible];
}
let () = msg_send![window, release];
}
}
// requires main thread
// AppState::did_finish_launching handles the special transition `Init`
pub unsafe fn handle_wakeup_transition() {
let mut this = AppState::get_mut();
let event = match this.control_flow {
ControlFlow::Poll => {
let event_handler = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } |
&mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::PollFinished {
ref mut waiting_event_handler,
} => ptr::read(waiting_event_handler),
_ => bug!("`EventHandler` unexpectedly started polling"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Poll,
});
Event::NewEvents(StartCause::Poll)
}
ControlFlow::Wait => {
let (event_handler, start) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } |
&mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::Waiting {
ref mut waiting_event_handler,
ref mut start,
} => (ptr::read(waiting_event_handler), *start),
_ => bug!("`EventHandler` unexpectedly woke up"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Wait,
});
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
})
}
ControlFlow::WaitUntil(requested_resume) => {
let (event_handler, start) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } |
&mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::Waiting {
ref mut waiting_event_handler,
ref mut start,
} => (ptr::read(waiting_event_handler), *start),
_ => bug!("`EventHandler` unexpectedly woke up"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::WaitUntil(requested_resume),
});
if Instant::now() >= requested_resume {
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
})
} else {
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
})
}
}
ControlFlow::Exit => bug!("unexpected controlflow `Exit`"),
};
drop(this);
AppState::handle_nonuser_event(event)
}
// requires main thread
pub unsafe fn handle_nonuser_event(event: Event<Never>) {
AppState::handle_nonuser_events(std::iter::once(event))
}
// requires main thread
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = Event<Never>>>(events: I) {
let mut this = AppState::get_mut();
let mut control_flow = this.control_flow;
let (mut event_handler, active_control_flow) = match &mut this.app_state {
&mut AppStateImpl::Launching {
ref mut queued_events,
..
}
| &mut AppStateImpl::NotLaunched {
ref mut queued_events,
..
}
| &mut AppStateImpl::InUserCallback {
ref mut queued_events,
..
} => {
queued_events.extend(events);
return
}
&mut AppStateImpl::ProcessingEvents {
ref mut event_handler,
ref mut active_control_flow,
} => (ptr::read(event_handler), *active_control_flow),
&mut AppStateImpl::PollFinished { .. }
| &mut AppStateImpl::Waiting { .. }
| &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"),
};
ptr::write(&mut this.app_state, AppStateImpl::InUserCallback {
queued_events: Vec::new(),
});
drop(this);
for event in events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
loop {
let mut this = AppState::get_mut();
let queued_events = match &mut this.app_state {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
} => mem::replace(queued_events, Vec::new()),
_ => bug!("unexpected `AppStateImpl`"),
};
if queued_events.is_empty() {
this.app_state = AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow,
};
this.control_flow = control_flow;
break
}
drop(this);
for event in queued_events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
}
}
// requires main thread
pub unsafe fn handle_user_events() {
let mut this = AppState::get_mut();
let mut control_flow = this.control_flow;
let (mut event_handler, active_control_flow) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::ProcessingEvents {
ref mut event_handler,
ref mut active_control_flow,
} => (ptr::read(event_handler), *active_control_flow),
&mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::PollFinished { .. }
| &mut AppStateImpl::Waiting { .. }
| &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"),
};
ptr::write(&mut this.app_state, AppStateImpl::InUserCallback {
queued_events: Vec::new(),
});
drop(this);
event_handler.handle_user_events(&mut control_flow);
loop {
let mut this = AppState::get_mut();
let queued_events = match &mut this.app_state {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
} => mem::replace(queued_events, Vec::new()),
_ => bug!("unexpected `AppStateImpl`"),
};
if queued_events.is_empty() {
this.app_state = AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow,
};
this.control_flow = control_flow;
break
}
drop(this);
for event in queued_events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
event_handler.handle_user_events(&mut control_flow);
}
}
// requires main thread
pub unsafe fn handle_events_cleared() {
let mut this = AppState::get_mut();
match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::ProcessingEvents { .. } => {}
_ => unreachable!(),
};
drop(this);
AppState::handle_user_events();
AppState::handle_nonuser_event(Event::EventsCleared);
let mut this = AppState::get_mut();
let (event_handler, old) = match &mut this.app_state {
&mut AppStateImpl::ProcessingEvents {
ref mut event_handler,
ref mut active_control_flow,
} => (ManuallyDrop::new(ptr::read(event_handler)), *active_control_flow),
_ => unreachable!(),
};
let new = this.control_flow;
match (old, new) {
(ControlFlow::Poll, ControlFlow::Poll) => {
ptr::write(
&mut this.app_state,
AppStateImpl::PollFinished {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
},
)
},
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
)
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
)
}
(_, ControlFlow::Wait) => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
);
this.waker.stop()
},
(_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
);
this.waker.start_at(new_instant)
},
(_, ControlFlow::Poll) => {
ptr::write(
&mut this.app_state,
AppStateImpl::PollFinished {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
},
);
this.waker.start()
},
(_, ControlFlow::Exit) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
this.control_flow = old
}
}
}
pub fn terminated() {
let mut this = unsafe { AppState::get_mut() };
let mut old = mem::replace(&mut this.app_state, AppStateImpl::Terminated);
let mut control_flow = this.control_flow;
if let AppStateImpl::ProcessingEvents { ref mut event_handler, .. } = old {
drop(this);
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
} else {
bug!("`LoopDestroyed` happened while not processing events")
}
}
}
struct EventLoopWaker {
timer: CFRunLoopTimerRef,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl EventLoopWaker {
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
extern fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// create a timer with a 1microsec interval (1ns does not work) to mimic polling.
// it is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediatley in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
std::f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
EventLoopWaker { timer }
}
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {
let now = Instant::now();
if now >= instant {
self.start();
} else {
unsafe {
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs =
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
}
}
}

View file

@ -0,0 +1,334 @@
use std::{mem, ptr};
use std::collections::VecDeque;
use std::ffi::c_void;
use std::fmt::{self, Debug, Formatter};
use std::marker::PhantomData;
use std::sync::mpsc::{self, Sender, Receiver};
use event::Event;
use event_loop::{
ControlFlow,
EventLoopWindowTarget as RootEventLoopWindowTarget,
EventLoopClosed,
};
use platform::ios::Idiom;
use platform_impl::platform::app_state::AppState;
use platform_impl::platform::ffi::{
id,
nil,
CFIndex,
CFRelease,
CFRunLoopActivity,
CFRunLoopAddObserver,
CFRunLoopAddSource,
CFRunLoopGetMain,
CFRunLoopObserverCreate,
CFRunLoopObserverRef,
CFRunLoopSourceContext,
CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate,
CFRunLoopSourceRef,
CFRunLoopSourceSignal,
CFRunLoopWakeUp,
kCFRunLoopCommonModes,
kCFRunLoopDefaultMode,
kCFRunLoopEntry,
kCFRunLoopBeforeWaiting,
kCFRunLoopAfterWaiting,
kCFRunLoopExit,
NSOperatingSystemVersion,
NSString,
UIApplicationMain,
UIUserInterfaceIdiom,
};
use platform_impl::platform::monitor;
use platform_impl::platform::MonitorHandle;
use platform_impl::platform::view;
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
capabilities: Capabilities,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
}
pub struct EventLoop<T: 'static> {
window_target: RootEventLoopWindowTarget<T>,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> {
static mut SINGLETON_INIT: bool = false;
unsafe {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
assert!(!SINGLETON_INIT, "Only one `EventLoop` is supported on iOS. \
`EventLoopProxy` might be helpful");
SINGLETON_INIT = true;
view::create_delegate_class();
}
let (sender_to_clone, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
msg_send![process_info, operatingSystemVersion]
};
let capabilities = version.into();
EventLoop {
window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget {
receiver,
sender_to_clone,
capabilities,
},
_marker: PhantomData,
}
}
}
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow)
{
unsafe {
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
assert_eq!(application, ptr::null_mut(), "\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS");
AppState::will_launch(Box::new(EventLoopHandler {
f: event_handler,
event_loop: self.window_target,
}));
UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate"));
unreachable!()
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread
unsafe {
monitor::uiscreens()
}
}
pub fn primary_monitor(&self) -> MonitorHandle {
// guaranteed to be on main thread
unsafe {
monitor::main_uiscreen()
}
}
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target
}
}
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
// guaranteed to be on main thread
unsafe {
self::get_idiom()
}
}
}
pub struct EventLoopProxy<T> {
sender: Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl<T> Send for EventLoopProxy<T> {}
unsafe impl<T> Sync for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
}
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
}
}
}
impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wakeup the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
// we want all the members of context to be zero/null, except one
let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = event_loop_proxy_handler;
let source = CFRunLoopSourceCreate(
ptr::null_mut(),
CFIndex::max_value() - 1,
&mut context,
);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy {
sender,
source,
}
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}
fn setup_control_flow_observers() {
unsafe {
// begin is queued with the highest priority to ensure it is processed before other observers
extern fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(),
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopDestroyed will get sent after EventsCleared
extern fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::min_value(),
control_flow_begin_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::max_value(),
control_flow_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
}
}
#[derive(Debug)]
pub enum Never {}
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
struct EventLoopHandler<F, T: 'static> {
f: F,
event_loop: RootEventLoopWindowTarget<T>,
}
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.debug_struct("EventLoopHandler")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,
control_flow,
);
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
for event in self.event_loop.p.receiver.try_iter() {
(self.f)(
Event::UserEvent(event),
&self.event_loop,
control_flow,
);
}
}
}
// must be called on main thread
pub unsafe fn get_idiom() -> Idiom {
let device: id = msg_send![class!(UIDevice), currentDevice];
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
raw_idiom.into()
}
pub struct Capabilities {
pub supports_safe_area: bool,
}
impl From<NSOperatingSystemVersion> for Capabilities {
fn from(os_version: NSOperatingSystemVersion) -> Capabilities {
assert!(os_version.major >= 8, "`winit` current requires iOS version 8 or greater");
let supports_safe_area = os_version.major >= 11;
Capabilities { supports_safe_area }
}
}

View file

@ -1,24 +1,33 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::ffi::CString;
use std::ops::BitOr;
use std::os::raw::*;
use objc::{Encode, Encoding};
use objc::runtime::Object;
use platform::ios::{Idiom, ValidOrientations};
pub type id = *mut Object;
pub const nil: id = 0 as id;
pub type CFStringRef = *const c_void;
pub type CFTimeInterval = f64;
pub type Boolean = u32;
pub const kCFRunLoopRunHandledSource: i32 = 4;
#[cfg(target_pointer_width = "32")]
pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
pub type NSInteger = isize;
pub type NSUInteger = usize;
#[repr(C)]
#[derive(Clone, Debug)]
pub struct NSOperatingSystemVersion {
pub major: NSInteger,
pub minor: NSInteger,
pub patch: NSInteger,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGPoint {
@ -26,13 +35,6 @@ pub struct CGPoint {
pub y: CGFloat,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGSize {
@ -40,13 +42,134 @@ pub struct CGSize {
pub height: CGFloat,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
unsafe impl Encode for CGRect {
fn encode() -> Encoding {
unsafe {
if cfg!(target_pointer_width = "32") {
Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}")
} else if cfg!(target_pointer_width = "64") {
Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}")
} else {
unimplemented!()
}
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger);
unsafe impl Encode for UIUserInterfaceIdiom {
fn encode() -> Encoding { NSInteger::encode() }
}
impl UIUserInterfaceIdiom {
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
}
impl From<Idiom> for UIUserInterfaceIdiom {
fn from(idiom: Idiom) -> UIUserInterfaceIdiom {
match idiom {
Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified,
Idiom::Phone => UIUserInterfaceIdiom::Phone,
Idiom::Pad => UIUserInterfaceIdiom::Pad,
Idiom::TV => UIUserInterfaceIdiom::TV,
Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay,
}
}
}
impl Into<Idiom> for UIUserInterfaceIdiom {
fn into(self) -> Idiom {
match self {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => unreachable!(),
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct UIInterfaceOrientationMask(NSUInteger);
unsafe impl Encode for UIInterfaceOrientationMask {
fn encode() -> Encoding { NSUInteger::encode() }
}
impl UIInterfaceOrientationMask {
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
pub const Landscape: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
pub const AllButUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
pub const All: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
}
impl BitOr for UIInterfaceOrientationMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
UIInterfaceOrientationMask(self.0 | rhs.0)
}
}
impl UIInterfaceOrientationMask {
pub fn from_valid_orientations_idiom(
valid_orientations: ValidOrientations,
idiom: Idiom,
) -> UIInterfaceOrientationMask {
match (valid_orientations, idiom) {
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => UIInterfaceOrientationMask::AllButUpsideDown,
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
(ValidOrientations::Portrait, _) => UIInterfaceOrientationMask::Portrait | UIInterfaceOrientationMask::PortraitUpsideDown,
}
}
}
#[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "GlKit", kind = "framework")]
extern {
pub static kCFRunLoopDefaultMode: CFStringRef;
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode;
// int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
@ -54,31 +177,115 @@ extern {
delegateClassName: id,
) -> c_int;
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );
pub fn CFRunLoopRunInMode(
mode: CFStringRef,
seconds: CFTimeInterval,
returnAfterSourceHandled: Boolean,
) -> i32;
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef,
activities: CFOptionFlags,
repeats: Boolean,
order: CFIndex,
callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) -> CFRunLoopObserverRef;
pub fn CFRunLoopAddObserver(
rl: CFRunLoopRef,
observer: CFRunLoopObserverRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerCreate(
allocator: CFAllocatorRef,
fireDate: CFAbsoluteTime,
interval: CFTimeInterval,
flags: CFOptionFlags,
order: CFIndex,
callout: CFRunLoopTimerCallBack,
context: *mut CFRunLoopTimerContext,
) -> CFRunLoopTimerRef;
pub fn CFRunLoopAddTimer(
rl: CFRunLoopRef,
timer: CFRunLoopTimerRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerSetNextFireDate(
timer: CFRunLoopTimerRef,
fireDate: CFAbsoluteTime,
);
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
pub fn CFRunLoopSourceCreate(
allocator: CFAllocatorRef,
order: CFIndex,
context: *mut CFRunLoopSourceContext,
) -> CFRunLoopSourceRef;
pub fn CFRunLoopAddSource(
rl: CFRunLoopRef,
source: CFRunLoopSourceRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
pub fn CFRelease(cftype: *const c_void);
}
extern {
pub fn setjmp(env: *mut c_void) -> c_int;
pub fn longjmp(env: *mut c_void, val: c_int) -> !;
pub type Boolean = u8;
pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {}
pub type CFRunLoopRef = *mut CFRunLoop;
pub type CFRunLoopMode = CFStringRef;
pub enum CFRunLoopObserver {}
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
pub enum CFRunLoopTimer {}
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
pub enum CFRunLoopSource {}
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
pub enum CFString {}
pub type CFStringRef = *const CFString;
pub type CFHashCode = c_ulong;
pub type CFIndex = c_long;
pub type CFOptionFlags = c_ulong;
pub type CFRunLoopActivity = CFOptionFlags;
pub type CFAbsoluteTime = CFTimeInterval;
pub type CFTimeInterval = f64;
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
pub type CFRunLoopObserverCallBack = extern "C" fn(
observer: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
info: *mut c_void,
);
pub type CFRunLoopTimerCallBack = extern "C" fn(
timer: CFRunLoopTimerRef,
info: *mut c_void,
);
pub enum CFRunLoopObserverContext {}
pub enum CFRunLoopTimerContext {}
#[repr(C)]
pub struct CFRunLoopSourceContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub perform: extern "C" fn(*mut c_void),
}
// values taken from "setjmp.h" header in xcode iPhoneOS/iPhoneSimulator SDK
#[cfg(any(target_arch = "x86_64"))]
pub const JBLEN: usize = (9 * 2) + 3 + 16;
#[cfg(any(target_arch = "x86"))]
pub const JBLEN: usize = 18;
#[cfg(target_arch = "arm")]
pub const JBLEN: usize = 10 + 16 + 2;
#[cfg(target_arch = "aarch64")]
pub const JBLEN: usize = (14 + 8 + 2) * 2;
pub type JmpBuf = [c_int; JBLEN];
pub trait NSString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSString), alloc]

View file

@ -47,656 +47,68 @@
//!
//! This is how those event are represented in winit:
//!
//! - applicationDidBecomeActive is Focused(true)
//! - applicationWillResignActive is Focused(false)
//! - applicationDidEnterBackground is Suspended(true)
//! - applicationWillEnterForeground is Suspended(false)
//! - applicationWillTerminate is Destroyed
//! - applicationDidBecomeActive is Suspended(false)
//! - applicationWillResignActive is Suspended(true)
//! - applicationWillTerminate is LoopDestroyed
//!
//! Keep in mind that after Destroyed event is received every attempt to draw with
//! Keep in mind that after LoopDestroyed event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
#![cfg(target_os = "ios")]
use std::{fmt, mem, ptr};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::Arc;
use objc::declare::ClassDecl;
use objc::runtime::{BOOL, Class, Object, Sel, YES};
use {
CreationError,
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
WindowAttributes,
WindowEvent,
WindowId as RootEventId,
};
use events::{Touch, TouchPhase};
use window::MonitorHandle as RootMonitorHandle;
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
if !msg_send![class!(NSThread), isMainThread] {
panic!($($t)*);
}
};
}
mod app_state;
mod event_loop;
mod ffi;
use self::ffi::{
CFTimeInterval,
CFRunLoopRunInMode,
CGFloat,
CGPoint,
CGRect,
id,
JBLEN,
JmpBuf,
kCFRunLoopDefaultMode,
kCFRunLoopRunHandledSource,
longjmp,
nil,
NSString,
setjmp,
UIApplicationMain,
};
mod monitor;
mod view;
mod window;
static mut JMPBUF: Option<Box<JmpBuf>> = None;
use std::fmt;
pub struct Window {
_events_queue: Arc<RefCell<VecDeque<Event>>>,
delegate_state: Box<DelegateState>,
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
#[derive(Debug)]
struct DelegateState {
window: id,
controller: id,
view: id,
size: LogicalSize,
scale: f64,
}
impl DelegateState {
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
DelegateState {
window,
controller,
view,
size,
scale,
}
}
}
impl Drop for DelegateState {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.window, release];
let _: () = msg_send![self.controller, release];
let _: () = msg_send![self.view, release];
}
}
}
#[derive(Clone)]
pub struct MonitorHandle;
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorHandle {
#[inline]
pub fn get_uiscreen(&self) -> id {
let class = class!(UIScreen);
unsafe { msg_send![class, mainScreen] }
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] };
(bounds.size.width as f64, bounds.size.height as f64).into()
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
// iOS assumes single screen
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] };
scale as f64
}
}
pub struct EventLoop {
events_queue: Arc<RefCell<VecDeque<Event>>>,
}
#[derive(Clone)]
pub struct EventLoopProxy;
impl EventLoop {
pub fn new() -> EventLoop {
unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("`EventLoop` can only be created on the main thread on iOS");
}
}
EventLoop { events_queue: Default::default() }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(::Event)
{
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
unsafe {
// jump hack, so we won't quit on willTerminate event before processing it
assert!(JMPBUF.is_some(), "`EventLoop::poll_events` must be called after window creation on iOS");
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event);
return;
}
}
}
unsafe {
// run runloop
let seconds: CFTimeInterval = 0.000002;
while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {}
}
if let Some(event) = self.events_queue.borrow_mut().pop_front() {
callback(event)
}
}
pub fn run_forever<F>(&mut self, mut callback: F)
where F: FnMut(::Event) -> ::ControlFlow,
{
// Yeah that's a very bad implementation.
loop {
let mut control_flow = ::ControlFlow::Continue;
self.poll_events(|e| {
if let ::ControlFlow::Break = callback(e) {
control_flow = ::ControlFlow::Break;
}
});
if let ::ControlFlow::Break = control_flow {
break;
}
::std::thread::sleep(::std::time::Duration::from_millis(5));
}
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy
}
}
impl EventLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> {
unimplemented!()
}
}
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use self::monitor::MonitorHandle;
pub use self::window::{
PlatformSpecificWindowBuilderAttributes,
Window,
WindowId,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
pub struct DeviceId {
uiscreen: ffi::id,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
DeviceId {
uiscreen: std::ptr::null_mut(),
}
}
}
// TODO: AFAIK transparency is enabled by default on iOS,
// so to be consistent with other platforms we have to change that.
impl Window {
pub fn new(
ev: &EventLoop,
_attributes: WindowAttributes,
pl_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
unsafe {
debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
assert!(mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(), "Only one `Window` is supported on iOS");
unsafe impl Send for DeviceId {}
unsafe impl Sync for DeviceId {}
#[derive(Debug)]
pub enum OsError {}
impl fmt::Display for OsError {
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
match self {
_ => unreachable!()
}
unsafe {
if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
let app_class = class!(UIApplication);
let app: id = msg_send![app_class, sharedApplication];
let delegate: id = msg_send![app, delegate];
let state: *mut c_void = *(&*delegate).get_ivar("winitState");
let mut delegate_state = Box::from_raw(state as *mut DelegateState);
let events_queue = &*ev.events_queue;
(&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
// easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
let rect: CGRect = msg_send![MonitorHandle.get_uiscreen(), bounds];
let uiview_class = class!(UIView);
let root_view_class = pl_attributes.root_view_class;
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
delegate_state.view = msg_send![root_view_class, alloc];
assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
return Ok(Window {
_events_queue: ev.events_queue.clone(),
delegate_state,
});
}
}
create_delegate_class();
start_app();
panic!("Couldn't create `UIApplication`!")
}
#[inline]
pub fn get_uiwindow(&self) -> id {
self.delegate_state.window
}
#[inline]
pub fn get_uiview(&self) -> id {
self.delegate_state.view
}
#[inline]
pub fn set_title(&self, _title: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.delegate_state.size)
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on iOS.".to_owned())
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.delegate_state.scale
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on iOS.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorHandle {
RootMonitorHandle { inner: MonitorHandle }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
}
fn create_delegate_class() {
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
let screen_class = class!(UIScreen);
let window_class = class!(UIWindow);
let controller_class = class!(UIViewController);
unsafe {
let main_screen: id = msg_send![screen_class, mainScreen];
let bounds: CGRect = msg_send![main_screen, bounds];
let scale: CGFloat = msg_send![main_screen, nativeScale];
let window: id = msg_send![window_class, alloc];
let window: id = msg_send![window, initWithFrame:bounds.clone()];
let size = (bounds.size.width as f64, bounds.size.height as f64).into();
let view_controller: id = msg_send![controller_class, alloc];
let view_controller: id = msg_send![view_controller, init];
let _: () = msg_send![window, setRootViewController:view_controller];
let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64));
let state_ptr: *mut DelegateState = mem::transmute(state);
this.set_ivar("winitState", state_ptr as *mut c_void);
// The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here.
// This makes it easier to configure the specific `UIView` type.
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
}
YES
}
extern fn post_launch(_: &Object, _: Sel, _: id) {
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
}
extern fn did_become_active(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(true),
});
}
}
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(false),
});
}
}
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(false));
}
}
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(true));
}
}
extern fn will_terminate(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
// push event to the front to garantee that we'll process it
// immidiatly after jump
events_queue.borrow_mut().push_front(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Destroyed,
});
longjmp(mem::transmute_copy(&mut JMPBUF), 1);
}
}
extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
let touches_enum: id = msg_send![touches, objectEnumerator];
loop {
let touch: id = msg_send![touches_enum, nextObject];
if touch == nil {
break
}
let location: CGPoint = msg_send![touch, locationInView:nil];
let touch_id = touch as u64;
let phase: i32 = msg_send![touch, phase];
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Touch(Touch {
device_id: DEVICE_ID,
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
phase: match phase {
0 => TouchPhase::Started,
1 => TouchPhase::Moved,
// 2 is UITouchPhaseStationary and is not expected here
3 => TouchPhase::Ended,
4 => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase)
}
}),
});
}
}
}
let ui_responder = class!(UIResponder);
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
unsafe {
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL);
decl.add_method(sel!(applicationDidBecomeActive:),
did_become_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillResignActive:),
will_resign_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillEnterForeground:),
will_enter_foreground as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationDidEnterBackground:),
did_enter_background as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillTerminate:),
will_terminate as extern fn(&Object, Sel, id));
decl.add_method(sel!(touchesBegan:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesMoved:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesEnded:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesCancelled:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(postLaunch:),
post_launch as extern fn(&Object, Sel, id));
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<*mut c_void>("eventsQueue");
decl.register();
}
}
#[inline]
fn start_app() {
unsafe {
UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate"));
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

View file

@ -0,0 +1,171 @@
use std::{
collections::VecDeque,
fmt,
ops::{Deref, DerefMut},
};
use dpi::{PhysicalPosition, PhysicalSize};
use platform_impl::platform::ffi::{
id,
nil,
CGFloat,
CGRect,
NSUInteger,
};
pub struct Inner {
uiscreen: id,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.uiscreen, release];
}
}
}
pub struct MonitorHandle {
inner: Inner,
}
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
unsafe {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
}
&self.inner
}
}
impl DerefMut for MonitorHandle {
fn deref_mut(&mut self) -> &mut Inner {
unsafe {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
}
&mut self.inner
}
}
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Clone for MonitorHandle {
fn clone(&self) -> MonitorHandle {
MonitorHandle::retained_new(self.uiscreen)
}
}
impl Drop for MonitorHandle {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.name(),
dimensions: self.dimensions(),
position: self.position(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorHandle {
pub fn retained_new(uiscreen: id) -> MonitorHandle {
unsafe {
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
let () = msg_send![uiscreen, retain];
}
MonitorHandle { inner: Inner { uiscreen } }
}
}
impl Inner {
pub fn name(&self) -> Option<String> {
unsafe {
if self.uiscreen == main_uiscreen().uiscreen {
Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen().uiscreen {
Some("Mirrored".to_string())
} else {
uiscreens()
.iter()
.position(|rhs| rhs.uiscreen == self.uiscreen)
.map(|idx| idx.to_string())
}
}
}
pub fn dimensions(&self) -> PhysicalSize {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
(bounds.size.width as f64, bounds.size.height as f64).into()
}
}
pub fn position(&self) -> PhysicalPosition {
unsafe {
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
}
pub fn hidpi_factor(&self) -> f64 {
unsafe {
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
scale as f64
}
}
}
// MonitorHandleExtIOS
impl Inner {
pub fn ui_screen(&self) -> id {
self.uiscreen
}
}
// requires being run on main thread
pub unsafe fn main_uiscreen() -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
unsafe fn mirrored_uiscreen() -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
let screens: id = msg_send![class!(UIScreen), screens];
let count: NSUInteger = msg_send![screens, count];
let mut result = VecDeque::with_capacity(count as _);
let screens_enum: id = msg_send![screens, objectEnumerator];
loop {
let screen: id = msg_send![screens_enum, nextObject];
if screen == nil {
break result
}
result.push_back(MonitorHandle::retained_new(screen));
}
}

View file

@ -0,0 +1,393 @@
use std::collections::HashMap;
use objc::declare::ClassDecl;
use objc::runtime::{BOOL, Class, NO, Object, Sel, YES};
use event::{
DeviceId as RootDeviceId,
Event,
Touch,
TouchPhase,
WindowEvent
};
use platform::ios::MonitorHandleExtIOS;
use window::{WindowAttributes, WindowId as RootWindowId};
use platform_impl::platform::app_state::AppState;
use platform_impl::platform::DeviceId;
use platform_impl::platform::event_loop;
use platform_impl::platform::ffi::{
id,
nil,
CGFloat,
CGPoint,
CGRect,
UIInterfaceOrientationMask,
UITouchPhase,
};
use platform_impl::platform::window::{PlatformSpecificWindowBuilderAttributes};
// requires main thread
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
static mut CLASSES: Option<HashMap<*const Class, &'static Class>> = None;
static mut ID: usize = 0;
if CLASSES.is_none() {
CLASSES = Some(HashMap::default());
}
let classes = CLASSES.as_mut().unwrap();
classes.entry(root_view_class).or_insert_with(move || {
let uiview_class = class!(UIView);
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
assert_eq!(is_uiview, YES, "`root_view_class` must inherit from `UIView`");
extern fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
unsafe {
let window: id = msg_send![object, window];
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), drawRect: rect];
}
}
extern fn layout_subviews(object: &Object, _: Sel) {
unsafe {
let window: id = msg_send![object, window];
let bounds: CGRect = msg_send![window, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
};
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
});
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), layoutSubviews];
}
}
let mut decl = ClassDecl::new(&format!("WinitUIView{}", ID), root_view_class)
.expect("Failed to declare class `WinitUIView`");
ID += 1;
decl.add_method(sel!(drawRect:),
draw_rect as extern fn(&Object, Sel, CGRect));
decl.add_method(sel!(layoutSubviews),
layout_subviews as extern fn(&Object, Sel));
decl.register()
})
}
// requires main thread
unsafe fn get_view_controller_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiviewcontroller_class = class!(UIViewController);
extern fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) {
unsafe {
object.set_ivar::<BOOL>("_prefers_status_bar_hidden", hidden);
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
}
extern fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL {
unsafe {
*object.get_ivar::<BOOL>("_prefers_status_bar_hidden")
}
}
extern fn set_supported_orientations(object: &mut Object, _: Sel, orientations: UIInterfaceOrientationMask) {
unsafe {
object.set_ivar::<UIInterfaceOrientationMask>("_supported_orientations", orientations);
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
}
extern fn supported_orientations(object: &Object, _: Sel) -> UIInterfaceOrientationMask {
unsafe {
*object.get_ivar::<UIInterfaceOrientationMask>("_supported_orientations")
}
}
extern fn should_autorotate(_: &Object, _: Sel) -> BOOL {
YES
}
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
.expect("Failed to declare class `WinitUIViewController`");
decl.add_ivar::<BOOL>("_prefers_status_bar_hidden");
decl.add_ivar::<UIInterfaceOrientationMask>("_supported_orientations");
decl.add_method(sel!(setPrefersStatusBarHidden:),
set_prefers_status_bar_hidden as extern fn(&mut Object, Sel, BOOL));
decl.add_method(sel!(prefersStatusBarHidden),
prefers_status_bar_hidden as extern fn(&Object, Sel) -> BOOL);
decl.add_method(sel!(setSupportedInterfaceOrientations:),
set_supported_orientations as extern fn(&mut Object, Sel, UIInterfaceOrientationMask));
decl.add_method(sel!(supportedInterfaceOrientations),
supported_orientations as extern fn(&Object, Sel) -> UIInterfaceOrientationMask);
decl.add_method(sel!(shouldAutorotate),
should_autorotate as extern fn(&Object, Sel) -> BOOL);
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
unsafe fn get_window_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiwindow_class = class!(UIWindow);
extern fn become_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern fn resign_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
extern fn handle_touches(object: &Object, _: Sel, touches: id, _:id) {
unsafe {
let uiscreen = msg_send![object, screen];
let touches_enum: id = msg_send![touches, objectEnumerator];
let mut touch_events = Vec::new();
loop {
let touch: id = msg_send![touches_enum, nextObject];
if touch == nil {
break
}
let location: CGPoint = msg_send![touch, locationInView:nil];
let touch_id = touch as u64;
let phase: UITouchPhase = msg_send![touch, phase];
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
phase,
}),
});
}
AppState::handle_nonuser_events(touch_events);
}
}
extern fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) {
unsafe {
let () = msg_send![super(object, class!(UIWindow)), setContentScaleFactor:hidpi_factor];
let view_controller: id = msg_send![object, rootViewController];
let view: id = msg_send![view_controller, view];
let () = msg_send![view, setContentScaleFactor:hidpi_factor];
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![object, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
};
AppState::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
}).chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Resized(size),
}))
);
}
}
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
.expect("Failed to declare class `WinitUIWindow`");
decl.add_method(sel!(becomeKeyWindow),
become_key_window as extern fn(&Object, Sel));
decl.add_method(sel!(resignKeyWindow),
resign_key_window as extern fn(&Object, Sel));
decl.add_method(sel!(touchesBegan:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesMoved:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesEnded:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesCancelled:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(setContentScaleFactor:),
set_content_scale_factor as extern fn(&mut Object, Sel, CGFloat));
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
pub unsafe fn create_view(
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> id {
let class = get_view_class(platform_attributes.root_view_class);
let view: id = msg_send![class, alloc];
assert!(!view.is_null(), "Failed to create `UIView` instance");
let view: id = msg_send![view, initWithFrame:frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
let () = msg_send![view, setMultipleTouchEnabled:YES];
view
}
// requires main thread
pub unsafe fn create_view_controller(
window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: id,
) -> id {
let class = get_view_controller_class();
let view_controller: id = msg_send![class, alloc];
assert!(!view_controller.is_null(), "Failed to create `UIViewController` instance");
let view_controller: id = msg_send![view_controller, init];
assert!(!view_controller.is_null(), "Failed to initialize `UIViewController` instance");
let status_bar_hidden = if window_attributes.decorations {
NO
} else {
YES
};
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
platform_attributes.valid_orientations,
idiom,
);
let () = msg_send![view_controller, setPrefersStatusBarHidden:status_bar_hidden];
let () = msg_send![view_controller, setSupportedInterfaceOrientations:supported_orientations];
let () = msg_send![view_controller, setView:view];
view_controller
}
// requires main thread
pub unsafe fn create_window(
window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
view_controller: id,
) -> id {
let class = get_window_class();
let window: id = msg_send![class, alloc];
assert!(!window.is_null(), "Failed to create `UIWindow` instance");
let window: id = msg_send![window, initWithFrame:frame];
assert!(!window.is_null(), "Failed to initialize `UIWindow` instance");
let () = msg_send![window, setRootViewController:view_controller];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![window, setContentScaleFactor:hidpi_factor as CGFloat];
}
if let &Some(ref monitor) = &window_attributes.fullscreen {
let () = msg_send![window, setScreen:monitor.ui_screen()];
}
window
}
pub fn create_delegate_class() {
extern fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
unsafe {
AppState::did_finish_launching();
}
YES
}
extern fn did_become_active(_: &Object, _: Sel, _: id) {
unsafe {
AppState::handle_nonuser_event(Event::Suspended(false))
}
}
extern fn will_resign_active(_: &Object, _: Sel, _: id) {
unsafe {
AppState::handle_nonuser_event(Event::Suspended(true))
}
}
extern fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
extern fn did_enter_background(_: &Object, _: Sel, _: id) {}
extern fn will_terminate(_: &Object, _: Sel, _: id) {
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,
});
}
}
AppState::handle_nonuser_events(events);
AppState::terminated();
}
}
let ui_responder = class!(UIResponder);
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
unsafe {
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL);
decl.add_method(sel!(applicationDidBecomeActive:),
did_become_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillResignActive:),
will_resign_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillEnterForeground:),
will_enter_foreground as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationDidEnterBackground:),
did_enter_background as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillTerminate:),
will_terminate as extern fn(&Object, Sel, id));
decl.register();
}
}

View file

@ -0,0 +1,487 @@
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use objc::runtime::{Class, NO, Object, YES};
use dpi::{self, LogicalPosition, LogicalSize};
use error::{ExternalError, NotSupportedError, OsError as RootOsError};
use icon::Icon;
use monitor::MonitorHandle as RootMonitorHandle;
use platform::ios::{MonitorHandleExtIOS, ValidOrientations};
use window::{
CursorIcon,
WindowAttributes,
};
use platform_impl::{
platform::{
app_state::AppState,
event_loop,
ffi::{
id,
CGFloat,
CGPoint,
CGRect,
CGSize,
UIEdgeInsets,
UIInterfaceOrientationMask,
},
monitor,
view,
EventLoopWindowTarget,
MonitorHandle
},
};
pub struct Inner {
pub window: id,
pub view_controller: id,
pub view: id,
supports_safe_area: bool,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.view, release];
let () = msg_send![self.view_controller, release];
let () = msg_send![self.window, release];
}
}
}
impl Inner {
pub fn set_title(&self, _title: &str) {
debug!("`Window::set_title` is ignored on iOS")
}
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe {
let () = msg_send![self.window, setHidden:NO];
},
false => unsafe {
let () = msg_send![self.window, setHidden:YES];
},
}
}
pub fn request_redraw(&self) {
unsafe {
let () = msg_send![self.view, setNeedsDisplay];
}
}
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
unsafe {
let safe_area = self.safe_area_screen_space();
Ok(LogicalPosition {
x: safe_area.origin.x,
y: safe_area.origin.y,
})
}
}
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
unsafe {
let screen_frame = self.screen_frame();
Ok(LogicalPosition {
x: screen_frame.origin.x,
y: screen_frame.origin.y,
})
}
}
pub fn set_outer_position(&self, position: LogicalPosition) {
unsafe {
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
x: position.x as _,
y: position.y as _,
},
size: screen_frame.size,
};
let bounds = self.from_screen_space(new_screen_frame);
let () = msg_send![self.window, setBounds:bounds];
}
}
pub fn inner_size(&self) -> LogicalSize {
unsafe {
let safe_area = self.safe_area_screen_space();
LogicalSize {
width: safe_area.size.width,
height: safe_area.size.height,
}
}
}
pub fn outer_size(&self) -> LogicalSize {
unsafe {
let screen_frame = self.screen_frame();
LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
}
}
}
pub fn set_inner_size(&self, _size: LogicalSize) {
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
}
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_min_inner_size` is ignored on iOS")
}
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_max_inner_size` is ignored on iOS")
}
pub fn set_resizable(&self, _resizable: bool) {
warn!("`Window::set_resizable` is ignored on iOS")
}
pub fn hidpi_factor(&self) -> f64 {
unsafe {
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
hidpi as _
}
}
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
debug!("`Window::set_cursor_icon` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_visible(&self, _visible: bool) {
debug!("`Window::set_cursor_visible` is ignored on iOS")
}
pub fn set_maximized(&self, _maximized: bool) {
warn!("`Window::set_maximized` is ignored on iOS")
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
unsafe {
match monitor {
Some(monitor) => {
let uiscreen = monitor.ui_screen() as id;
let current: id = msg_send![self.window, screen];
let bounds: CGRect = msg_send![uiscreen, bounds];
// this is pretty slow on iOS, so avoid doing it if we can
if uiscreen != current {
let () = msg_send![self.window, setScreen:uiscreen];
}
let () = msg_send![self.window, setFrame:bounds];
}
None => warn!("`Window::set_fullscreen(None)` ignored on iOS"),
}
}
}
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
unsafe {
let monitor = self.current_monitor();
let uiscreen = monitor.inner.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(monitor)
} else {
None
}
}
}
pub fn set_decorations(&self, decorations: bool) {
unsafe {
let status_bar_hidden = if decorations { NO } else { YES };
let () = msg_send![self.view_controller, setPrefersStatusBarHidden:status_bar_hidden];
}
}
pub fn set_always_on_top(&self, _always_on_top: bool) {
warn!("`Window::set_always_on_top` is ignored on iOS")
}
pub fn set_window_icon(&self, _icon: Option<Icon>) {
warn!("`Window::set_window_icon` is ignored on iOS")
}
pub fn set_ime_position(&self, _position: LogicalPosition) {
warn!("`Window::set_ime_position` is ignored on iOS")
}
pub fn current_monitor(&self) -> RootMonitorHandle {
unsafe {
let uiscreen: id = msg_send![self.window, screen];
RootMonitorHandle { inner: MonitorHandle::retained_new(uiscreen) }
}
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
unsafe {
monitor::uiscreens()
}
}
pub fn primary_monitor(&self) -> MonitorHandle {
unsafe {
monitor::main_uiscreen()
}
}
pub fn id(&self) -> WindowId {
self.window.into()
}
}
pub struct Window {
pub inner: Inner,
}
impl Drop for Window {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
}
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Deref for Window {
type Target = Inner;
fn deref(&self) -> &Inner {
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
&self.inner
}
}
impl DerefMut for Window {
fn deref_mut(&mut self) -> &mut Inner {
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
&mut self.inner
}
}
impl Window {
pub fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
if let Some(_) = window_attributes.min_inner_size {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
}
if let Some(_) = window_attributes.max_inner_size {
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
}
if window_attributes.always_on_top {
warn!("`WindowAttributes::always_on_top` is unsupported on iOS");
}
// TODO: transparency, visible
unsafe {
let screen = window_attributes.fullscreen
.as_ref()
.map(|screen| screen.ui_screen() as _)
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen());
let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size {
Some(dim) => CGRect {
origin: screen_bounds.origin,
size: CGSize { width: dim.width, height: dim.height },
},
None => screen_bounds,
};
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
let view_controller = view::create_view_controller(&window_attributes, &platform_attributes, view);
let window = view::create_window(&window_attributes, &platform_attributes, frame, view_controller);
let supports_safe_area = event_loop.capabilities().supports_safe_area;
let result = Window {
inner: Inner {
window,
view_controller,
view,
supports_safe_area,
},
};
AppState::set_key_window(window);
Ok(result)
}
}
}
// WindowExtIOS
impl Inner {
pub fn ui_window(&self) -> id { self.window }
pub fn ui_view_controller(&self) -> id { self.view_controller }
pub fn ui_view(&self) -> id { self.view }
pub fn set_hidpi_factor(&self, hidpi_factor: f64) {
unsafe {
assert!(dpi::validate_hidpi_factor(hidpi_factor), "`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor");
let hidpi_factor = hidpi_factor as CGFloat;
let () = msg_send![self.view, setContentScaleFactor:hidpi_factor];
}
}
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
unsafe {
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(valid_orientations, idiom);
msg_send![self.view_controller, setSupportedInterfaceOrientations:supported_orientations];
}
}
}
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
self.to_screen_space(msg_send![self.window, bounds])
}
// requires main thread
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space]
} else {
rect
}
}
// requires main thread
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space]
} else {
rect
}
}
// requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds: CGRect = msg_send![self.window, bounds];
if self.supports_safe_area {
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
let safe_bounds = CGRect {
origin: CGPoint {
x: bounds.origin.x + safe_area.left,
y: bounds.origin.y + safe_area.top,
},
size: CGSize {
width: bounds.size.width - safe_area.left - safe_area.right,
height: bounds.size.height - safe_area.top - safe_area.bottom,
},
};
self.to_screen_space(safe_bounds)
} else {
let screen_frame = self.to_screen_space(bounds);
let status_bar_frame: CGRect = {
let app: id = msg_send![class!(UIApplication), sharedApplication];
assert!(!app.is_null(), "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS");
msg_send![app, statusBarFrame]
};
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
(screen_frame.origin.y, screen_frame.size.height)
} else {
let y = status_bar_frame.size.height;
let height = screen_frame.size.height - (status_bar_frame.size.height - screen_frame.origin.y);
(y, height)
};
CGRect {
origin: CGPoint {
x: screen_frame.origin.x,
y,
},
size: CGSize {
width: screen_frame.size.width,
height,
}
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId {
window: id,
}
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId {
window: std::ptr::null_mut(),
}
}
}
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl From<&Object> for WindowId {
fn from(window: &Object) -> WindowId {
WindowId { window: window as *const _ as _ }
}
}
impl From<&mut Object> for WindowId {
fn from(window: &mut Object) -> WindowId {
WindowId { window: window as _ }
}
}
impl From<id> for WindowId {
fn from(window: id) -> WindowId {
WindowId { window }
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
pub hidpi_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> PlatformSpecificWindowBuilderAttributes {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
hidpi_factor: None,
valid_orientations: Default::default(),
}
}
}

View file

@ -6,7 +6,7 @@ use std::os::raw::{c_void, c_char, c_int};
pub const RTLD_LAZY: c_int = 0x001;
pub const RTLD_NOW: c_int = 0x002;
#[link="dl"]
#[link(name ="dl")]
extern {
pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
pub fn dlerror() -> *mut c_char;

View file

@ -1,7 +1,7 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
use std::collections::VecDeque;
use std::{env, mem};
use std::{env, mem, fmt};
use std::ffi::CStr;
use std::os::raw::*;
use std::sync::Arc;
@ -11,16 +11,18 @@ use sctk::reexports::client::ConnectError;
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use icon::Icon;
use error::{ExternalError, NotSupportedError, OsError as RootOsError};
use event::Event;
use event_loop::{EventLoopClosed, ControlFlow, EventLoopWindowTarget as RootELW};
use monitor::MonitorHandle as RootMonitorHandle;
use window::{WindowAttributes, CreationError, MouseCursor};
//use self::x11::{XConnection, XError};
//use self::x11::ffi::XVisualInfo;
//pub use self::x11::XNotSupported;
use window::{WindowAttributes, CursorIcon};
use self::x11::{XConnection, XError};
use self::x11::ffi::XVisualInfo;
pub use self::x11::XNotSupported;
mod dlopen;
pub mod wayland;
//pub mod x11;
pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
///
@ -33,31 +35,46 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {
//pub visual_infos: Option<XVisualInfo>,
pub visual_infos: Option<XVisualInfo>,
pub screen_id: Option<i32>,
pub resize_increments: Option<(u32, u32)>,
pub base_size: Option<(u32, u32)>,
pub class: Option<(String, String)>,
pub override_redirect: bool,
//pub x11_window_type: x11::util::WindowType,
pub x11_window_type: x11::util::WindowType,
pub gtk_theme_variant: Option<String>,
pub app_id: Option<String>
}
//lazy_static!(
// pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = {
// Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))
// };
//);
lazy_static!(
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> = {
Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))
};
);
#[derive(Debug, Clone)]
pub enum OsError {
XError(XError),
XMisc(&'static str),
}
impl fmt::Display for OsError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
OsError::XError(e) => formatter.pad(&e.description),
OsError::XMisc(e) => formatter.pad(e),
}
}
}
pub enum Window {
//X(x11::Window),
X(x11::Window),
Wayland(wayland::Window),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WindowId {
//X(x11::WindowId),
X(x11::WindowId),
Wayland(wayland::WindowId),
}
@ -69,7 +86,7 @@ impl WindowId {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
//X(x11::DeviceId),
X(x11::DeviceId),
Wayland(wayland::DeviceId),
}
@ -81,48 +98,48 @@ impl DeviceId {
#[derive(Debug, Clone)]
pub enum MonitorHandle {
//X(x11::MonitorHandle),
X(x11::MonitorHandle),
Wayland(wayland::MonitorHandle),
}
impl MonitorHandle {
#[inline]
pub fn get_name(&self) -> Option<String> {
pub fn name(&self) -> Option<String> {
match self {
//&MonitorHandle::X(ref m) => m.get_name(),
&MonitorHandle::Wayland(ref m) => m.get_name(),
&MonitorHandle::X(ref m) => m.name(),
&MonitorHandle::Wayland(ref m) => m.name(),
}
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
pub fn native_identifier(&self) -> u32 {
match self {
//&MonitorHandle::X(ref m) => m.get_native_identifier(),
&MonitorHandle::Wayland(ref m) => m.get_native_identifier(),
&MonitorHandle::X(ref m) => m.native_identifier(),
&MonitorHandle::Wayland(ref m) => m.native_identifier(),
}
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
match self {
//&MonitorHandle::X(ref m) => m.get_dimensions(),
&MonitorHandle::Wayland(ref m) => m.get_dimensions(),
&MonitorHandle::X(ref m) => m.dimensions(),
&MonitorHandle::Wayland(ref m) => m.dimensions(),
}
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
pub fn position(&self) -> PhysicalPosition {
match self {
//&MonitorHandle::X(ref m) => m.get_position(),
&MonitorHandle::Wayland(ref m) => m.get_position(),
&MonitorHandle::X(ref m) => m.position(),
&MonitorHandle::Wayland(ref m) => m.position(),
}
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
match self {
//&MonitorHandle::X(ref m) => m.get_hidpi_factor(),
&MonitorHandle::Wayland(ref m) => m.get_hidpi_factor() as f64,
&MonitorHandle::X(ref m) => m.hidpi_factor(),
&MonitorHandle::Wayland(ref m) => m.hidpi_factor() as f64,
}
}
}
@ -133,21 +150,21 @@ impl Window {
window_target: &EventLoopWindowTarget<T>,
attribs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, CreationError> {
) -> Result<Self, RootOsError> {
match *window_target {
EventLoopWindowTarget::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
},
//EventLoop::X(ref event_loop) => {
// x11::Window::new(event_loop, attribs, pl_attribs).map(Window::X)
//},
EventLoopWindowTarget::X(ref window_target) => {
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
},
}
}
#[inline]
pub fn id(&self) -> WindowId {
match self {
//&Window::X(ref w) => WindowId::X(w.id()),
&Window::X(ref w) => WindowId::X(w.id()),
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
}
}
@ -155,135 +172,127 @@ impl Window {
#[inline]
pub fn set_title(&self, title: &str) {
match self {
//&Window::X(ref w) => w.set_title(title),
&Window::X(ref w) => w.set_title(title),
&Window::Wayland(ref w) => w.set_title(title),
}
}
#[inline]
pub fn show(&self) {
pub fn set_visible(&self, visible: bool) {
match self {
//&Window::X(ref w) => w.show(),
&Window::Wayland(ref w) => w.show(),
&Window::X(ref w) => w.set_visible(visible),
&Window::Wayland(ref w) => w.set_visible(visible),
}
}
#[inline]
pub fn hide(&self) {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
match self {
//&Window::X(ref w) => w.hide(),
&Window::Wayland(ref w) => w.hide(),
&Window::X(ref w) => w.outer_position(),
&Window::Wayland(ref w) => w.outer_position(),
}
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
match self {
//&Window::X(ref w) => w.get_position(),
&Window::Wayland(ref w) => w.get_position(),
&Window::X(ref m) => m.inner_position(),
&Window::Wayland(ref m) => m.inner_position(),
}
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
pub fn set_outer_position(&self, position: LogicalPosition) {
match self {
//&Window::X(ref m) => m.get_inner_position(),
&Window::Wayland(ref m) => m.get_inner_position(),
&Window::X(ref w) => w.set_outer_position(position),
&Window::Wayland(ref w) => w.set_outer_position(position),
}
}
#[inline]
pub fn set_position(&self, position: LogicalPosition) {
pub fn inner_size(&self) -> LogicalSize {
match self {
//&Window::X(ref w) => w.set_position(position),
&Window::Wayland(ref w) => w.set_position(position),
&Window::X(ref w) => w.inner_size(),
&Window::Wayland(ref w) => w.inner_size(),
}
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
pub fn outer_size(&self) -> LogicalSize {
match self {
//&Window::X(ref w) => w.get_inner_size(),
&Window::Wayland(ref w) => w.get_inner_size(),
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
match self {
//&Window::X(ref w) => w.get_outer_size(),
&Window::Wayland(ref w) => w.get_outer_size(),
&Window::X(ref w) => w.outer_size(),
&Window::Wayland(ref w) => w.outer_size(),
}
}
#[inline]
pub fn set_inner_size(&self, size: LogicalSize) {
match self {
//&Window::X(ref w) => w.set_inner_size(size),
&Window::X(ref w) => w.set_inner_size(size),
&Window::Wayland(ref w) => w.set_inner_size(size),
}
}
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
match self {
//&Window::X(ref w) => w.set_min_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_min_dimensions(dimensions),
&Window::X(ref w) => w.set_min_inner_size(dimensions),
&Window::Wayland(ref w) => w.set_min_inner_size(dimensions),
}
}
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
match self {
//&Window::X(ref w) => w.set_max_dimensions(dimensions),
&Window::Wayland(ref w) => w.set_max_dimensions(dimensions),
&Window::X(ref w) => w.set_max_inner_size(dimensions),
&Window::Wayland(ref w) => w.set_max_inner_size(dimensions),
}
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
match self {
//&Window::X(ref w) => w.set_resizable(resizable),
&Window::X(ref w) => w.set_resizable(resizable),
&Window::Wayland(ref w) => w.set_resizable(resizable),
}
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
match self {
//&Window::X(ref w) => w.set_cursor(cursor),
&Window::Wayland(ref w) => w.set_cursor(cursor)
&Window::X(ref w) => w.set_cursor_icon(cursor),
&Window::Wayland(ref w) => w.set_cursor_icon(cursor)
}
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
match self {
//&Window::X(ref window) => window.grab_cursor(grab),
&Window::Wayland(ref window) => window.grab_cursor(grab),
&Window::X(ref window) => window.set_cursor_grab(grab),
&Window::Wayland(ref window) => window.set_cursor_grab(grab),
}
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
pub fn set_cursor_visible(&self, visible: bool) {
match self {
//&Window::X(ref window) => window.hide_cursor(hide),
&Window::Wayland(ref window) => window.hide_cursor(hide),
&Window::X(ref window) => window.set_cursor_visible(visible),
&Window::Wayland(ref window) => window.set_cursor_visible(visible),
}
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
match self {
//&Window::X(ref w) => w.get_hidpi_factor(),
&Window::X(ref w) => w.hidpi_factor(),
&Window::Wayland(ref w) => w.hidpi_factor() as f64,
}
}
#[inline]
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> {
match self {
//&Window::X(ref w) => w.set_cursor_position(position),
&Window::X(ref w) => w.set_cursor_position(position),
&Window::Wayland(ref w) => w.set_cursor_position(position),
}
}
@ -291,15 +300,24 @@ impl Window {
#[inline]
pub fn set_maximized(&self, maximized: bool) {
match self {
//&Window::X(ref w) => w.set_maximized(maximized),
&Window::X(ref w) => w.set_maximized(maximized),
&Window::Wayland(ref w) => w.set_maximized(maximized),
}
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
match self {
&Window::X(ref w) => w.fullscreen(),
&Window::Wayland(ref w) => w.fullscreen()
.map(|monitor_id| RootMonitorHandle { inner: MonitorHandle::Wayland(monitor_id) })
}
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
match self {
//&Window::X(ref w) => w.set_fullscreen(monitor),
&Window::X(ref w) => w.set_fullscreen(monitor),
&Window::Wayland(ref w) => w.set_fullscreen(monitor)
}
}
@ -307,7 +325,7 @@ impl Window {
#[inline]
pub fn set_decorations(&self, decorations: bool) {
match self {
//&Window::X(ref w) => w.set_decorations(decorations),
&Window::X(ref w) => w.set_decorations(decorations),
&Window::Wayland(ref w) => w.set_decorations(decorations)
}
}
@ -315,7 +333,7 @@ impl Window {
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
match self {
//&Window::X(ref w) => w.set_always_on_top(always_on_top),
&Window::X(ref w) => w.set_always_on_top(always_on_top),
&Window::Wayland(_) => (),
}
}
@ -323,15 +341,15 @@ impl Window {
#[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
match self {
//&Window::X(ref w) => w.set_window_icon(window_icon),
&Window::X(ref w) => w.set_window_icon(window_icon),
&Window::Wayland(_) => (),
}
}
#[inline]
pub fn set_ime_spot(&self, position: LogicalPosition) {
pub fn set_ime_position(&self, position: LogicalPosition) {
match self {
//&Window::X(ref w) => w.set_ime_spot(position),
&Window::X(ref w) => w.set_ime_position(position),
&Window::Wayland(_) => (),
}
}
@ -339,27 +357,27 @@ impl Window {
#[inline]
pub fn request_redraw(&self) {
match self {
//&Window::X(ref w) => w.request_redraw(),
&Window::X(ref w) => w.request_redraw(),
&Window::Wayland(ref w) => w.request_redraw(),
}
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorHandle {
pub fn current_monitor(&self) -> RootMonitorHandle {
match self {
//&Window::X(ref window) => RootMonitorHandle { inner: MonitorHandle::X(window.get_current_monitor()) },
&Window::Wayland(ref window) => RootMonitorHandle { inner: MonitorHandle::Wayland(window.get_current_monitor()) },
&Window::X(ref window) => RootMonitorHandle { inner: MonitorHandle::X(window.current_monitor()) },
&Window::Wayland(ref window) => RootMonitorHandle { inner: MonitorHandle::Wayland(window.current_monitor()) },
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
//&Window::X(ref window) => window.get_available_monitors()
// .into_iter()
// .map(MonitorHandle::X)
// .collect(),
&Window::Wayland(ref window) => window.get_available_monitors()
&Window::X(ref window) => window.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
&Window::Wayland(ref window) => window.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
@ -367,15 +385,15 @@ impl Window {
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
match self {
//&Window::X(ref window) => MonitorHandle::X(window.get_primary_monitor()),
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.get_primary_monitor()),
&Window::X(ref window) => MonitorHandle::X(window.primary_monitor()),
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
}
}
}
/*
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
@ -405,16 +423,16 @@ unsafe extern "C" fn x_error_callback(
// Fun fact: this return value is completely ignored.
0
}
*/
pub enum EventLoop<T: 'static> {
Wayland(wayland::EventLoop<T>),
//X(x11::EventLoop)
X(x11::EventLoop<T>)
}
#[derive(Clone)]
pub enum EventLoopProxy<T: 'static> {
//X(x11::EventLoopProxy),
X(x11::EventLoopProxy<T>),
Wayland(wayland::EventLoopProxy<T>),
}
@ -460,46 +478,45 @@ impl<T:'static> EventLoop<T> {
.map(EventLoop::Wayland)
}
pub fn new_x11() -> Result<EventLoop<T>, () /*XNotSupported*/> {
//X11_BACKEND
// .lock()
// .as_ref()
// .map(Arc::clone)
// .map(x11::EventLoop::new)
// .map(EventLoop::X)
// .map_err(|err| err.clone())
unimplemented!()
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
X11_BACKEND
.lock()
.as_ref()
.map(Arc::clone)
.map(x11::EventLoop::new)
.map(EventLoop::X)
.map_err(|err| err.clone())
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match *self {
EventLoop::Wayland(ref evlp) => evlp
.get_available_monitors()
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
//EventLoop::X(ref evlp) => evlp
// .x_connection()
// .get_available_monitors()
// .into_iter()
// .map(MonitorHandle::X)
// .collect(),
EventLoop::X(ref evlp) => evlp
.x_connection()
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
}
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
match *self {
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.get_primary_monitor()),
//EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().get_primary_monitor()),
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()),
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
match *self {
EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()),
//EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()),
EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()),
}
}
@ -508,7 +525,7 @@ impl<T:'static> EventLoop<T> {
{
match *self {
EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback),
//EventLoop::X(ref mut evlp) => evlp.run_return(callback)
EventLoop::X(ref mut evlp) => evlp.run_return(callback)
}
}
@ -517,7 +534,7 @@ impl<T:'static> EventLoop<T> {
{
match self {
EventLoop::Wayland(evlp) => evlp.run(callback),
//EventLoop::X(ref mut evlp) => evlp.run(callback)
EventLoop::X(evlp) => evlp.run(callback)
}
}
@ -525,36 +542,44 @@ impl<T:'static> EventLoop<T> {
pub fn is_wayland(&self) -> bool {
match *self {
EventLoop::Wayland(_) => true,
//EventLoop::X(_) => false,
EventLoop::X(_) => false,
}
}
pub fn window_target(&self) -> &::event_loop::EventLoopWindowTarget<T> {
match *self {
EventLoop::Wayland(ref evl) => evl.window_target(),
//EventLoop::X(ref evl) => evl.window_target()
EventLoop::X(ref evl) => evl.window_target()
}
}
//#[inline]
//pub fn x_connection(&self) -> Option<&Arc<XConnection>> {
// match *self {
// EventLoop::Wayland(_) => None,
// EventLoop::X(ref ev) => Some(ev.x_connection()),
// }
//}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
match *self {
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
//EventLoopProxy::X(ref proxy) => proxy.wakeup(),
EventLoopProxy::X(ref proxy) => proxy.send_event(event),
}
}
}
pub enum EventLoopWindowTarget<T> {
Wayland(wayland::EventLoopWindowTarget<T>),
//X(x11::EventLoopWIndowTarget<T>)
X(x11::EventLoopWindowTarget<T>)
}
fn sticky_exit_callback<T, F>(
evt: Event<T>, target: &RootELW<T>, control_flow: &mut ControlFlow, callback: &mut F
) where F: FnMut(Event<T>, &RootELW<T>, &mut ControlFlow)
{
// make ControlFlow::Exit sticky by providing a dummy
// control flow reference if it is already Exit.
let mut dummy = ControlFlow::Exit;
let cf = if *control_flow == ControlFlow::Exit {
&mut dummy
} else {
control_flow
};
// user callback
callback(evt, target, cf)
}

View file

@ -8,6 +8,7 @@ use std::time::Instant;
use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW};
use event::ModifiersState;
use dpi::{PhysicalPosition, PhysicalSize};
use platform_impl::platform::sticky_exit_callback;
use super::window::WindowStore;
use super::WindowId;
@ -204,23 +205,39 @@ impl<T: 'static> EventLoop<T> {
// empty buffer of events
{
let mut guard = sink.lock().unwrap();
guard.empty_with(|evt| callback(evt, &self.window_target, &mut control_flow));
guard.empty_with(|evt| {
sticky_exit_callback(evt, &self.window_target, &mut control_flow, &mut callback);
});
}
// empty user events
{
let mut guard = user_events.borrow_mut();
for evt in guard.drain(..) {
callback(::event::Event::UserEvent(evt), &self.window_target, &mut control_flow);
sticky_exit_callback(
::event::Event::UserEvent(evt),
&self.window_target,
&mut control_flow,
&mut callback
);
}
}
callback(::event::Event::EventsCleared, &self.window_target, &mut control_flow);
// fo a second run of post-dispatch-triggers, to handle user-generated "request-redraw"
// do a second run of post-dispatch-triggers, to handle user-generated "request-redraw"
// in response of resize & friends
self.post_dispatch_triggers();
{
let mut guard = sink.lock().unwrap();
guard.empty_with(|evt| callback(evt, &self.window_target, &mut control_flow));
guard.empty_with(|evt| {
sticky_exit_callback(evt, &self.window_target, &mut control_flow, &mut callback);
});
}
// send Events cleared
{
sticky_exit_callback(
::event::Event::EventsCleared,
&self.window_target,
&mut control_flow,
&mut callback
);
}
// send pending events to the server
@ -249,7 +266,11 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::WaitUntil(deadline) => {
let start = Instant::now();
// compute the blocking duration
let duration = deadline.duration_since(::std::cmp::max(deadline, start));
let duration = if deadline > start {
deadline - start
} else {
::std::time::Duration::from_millis(0)
};
self.inner_loop.dispatch(Some(duration), &mut ()).unwrap();
control_flow = ControlFlow::default();
let now = Instant::now();
@ -279,12 +300,16 @@ impl<T: 'static> EventLoop<T> {
callback(::event::Event::LoopDestroyed, &self.window_target, &mut control_flow);
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor(&self.outputs)
pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor(&self.outputs)
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors(&self.outputs)
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
available_monitors(&self.outputs)
}
pub fn display(&self) -> &Display {
&*self.display
}
pub fn window_target(&self) -> &RootELW<T> {
@ -511,11 +536,11 @@ impl fmt::Debug for MonitorHandle {
}
let monitor_id_proxy = MonitorHandle {
name: self.get_name(),
native_identifier: self.get_native_identifier(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
name: self.name(),
native_identifier: self.native_identifier(),
dimensions: self.dimensions(),
position: self.position(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@ -523,18 +548,18 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub fn get_name(&self) -> Option<String> {
pub fn name(&self) -> Option<String> {
self.mgr.with_info(&self.proxy, |_, info| {
format!("{} ({})", info.model, info.make)
})
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
pub fn native_identifier(&self) -> u32 {
self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0)
}
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
match self.mgr.with_info(&self.proxy, |_, info| {
info.modes
.iter()
@ -546,7 +571,7 @@ impl MonitorHandle {
}.into()
}
pub fn get_position(&self) -> PhysicalPosition {
pub fn position(&self) -> PhysicalPosition {
self.mgr
.with_info(&self.proxy, |_, info| info.location)
.unwrap_or((0, 0))
@ -554,14 +579,14 @@ impl MonitorHandle {
}
#[inline]
pub fn get_hidpi_factor(&self) -> i32 {
pub fn hidpi_factor(&self) -> i32 {
self.mgr
.with_info(&self.proxy, |_, info| info.scale_factor)
.unwrap_or(1)
}
}
pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorHandle {
pub fn primary_monitor(outputs: &OutputMgr) -> MonitorHandle {
outputs.with_all(|list| {
if let Some(&(_, ref proxy, _)) = list.first() {
MonitorHandle {
@ -574,7 +599,7 @@ pub fn get_primary_monitor(outputs: &OutputMgr) -> MonitorHandle {
})
}
pub fn get_available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorHandle> {
pub fn available_monitors(outputs: &OutputMgr) -> VecDeque<MonitorHandle> {
outputs.with_all(|list| {
list.iter()
.map(|&(_, ref proxy, _)| MonitorHandle {

View file

@ -295,6 +295,13 @@ fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add),
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract),
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide),
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
// => Some(VirtualKeyCode::OEM102),
// => Some(VirtualKeyCode::Period),
// => Some(VirtualKeyCode::Playpause),

View file

@ -2,18 +2,19 @@ use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use dpi::{LogicalPosition, LogicalSize};
use error::{ExternalError, NotSupportedError, OsError as RootOsError};
use platform_impl::{MonitorHandle as PlatformMonitorHandle, PlatformSpecificWindowBuilderAttributes as PlAttributes};
use monitor::MonitorHandle as RootMonitorHandle;
use window::{CreationError, WindowAttributes, MouseCursor};
use window::{WindowAttributes, CursorIcon};
use sctk::surface::{get_dpi_factor, get_outputs};
use sctk::window::{ConceptFrame, Event as WEvent, Window as SWindow, Theme};
use sctk::window::{ConceptFrame, Event as WEvent, State as WState, Window as SWindow, Theme};
use sctk::reexports::client::Display;
use sctk::reexports::client::protocol::{wl_seat, wl_surface};
use sctk::output::OutputMgr;
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
use platform_impl::platform::wayland::event_loop::{get_available_monitors, get_primary_monitor};
use platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
pub struct Window {
surface: wl_surface::WlSurface,
@ -23,14 +24,16 @@ pub struct Window {
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
display: Arc<Display>,
need_frame_refresh: Arc<Mutex<bool>>,
need_refresh: Arc<Mutex<bool>>
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
}
impl Window {
pub fn new<T>(evlp: &EventLoopWindowTarget<T>, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result<Window, CreationError> {
let (width, height) = attributes.dimensions.map(Into::into).unwrap_or((800, 600));
pub fn new<T>(evlp: &EventLoopWindowTarget<T>, attributes: WindowAttributes, pl_attribs: PlAttributes) -> Result<Window, RootOsError> {
let (width, height) = attributes.inner_size.map(Into::into).unwrap_or((800, 600));
// Create the window
let size = Arc::new(Mutex::new((width, height)));
let fullscreen = Arc::new(Mutex::new(false));
let window_store = evlp.store.clone();
let surface = evlp.env.create_surface(move |dpi, surface| {
@ -45,12 +48,15 @@ impl Window {
surface.clone(),
(width, height),
move |event| match event {
WEvent::Configure { new_size, .. } => {
WEvent::Configure { new_size, states } => {
let mut store = window_store.lock().unwrap();
let is_fullscreen = states.contains(&WState::Fullscreen);
for window in &mut store.windows {
if window.surface.as_ref().equals(&my_surface.as_ref()) {
window.newsize = new_size;
*(window.need_refresh.lock().unwrap()) = true;
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
*(window.need_frame_refresh.lock().unwrap()) = true;
return;
}
@ -104,8 +110,8 @@ impl Window {
frame.set_title(attributes.title);
// min-max dimensions
frame.set_min_size(attributes.min_dimensions.map(Into::into));
frame.set_max_size(attributes.max_dimensions.map(Into::into));
frame.set_min_size(attributes.min_inner_size.map(Into::into));
frame.set_max_size(attributes.max_inner_size.map(Into::into));
let kill_switch = Arc::new(Mutex::new(false));
let need_frame_refresh = Arc::new(Mutex::new(true));
@ -117,6 +123,7 @@ impl Window {
newsize: None,
size: size.clone(),
need_refresh: need_refresh.clone(),
fullscreen: fullscreen.clone(),
need_frame_refresh: need_frame_refresh.clone(),
surface: surface.clone(),
kill_switch: kill_switch.clone(),
@ -135,6 +142,7 @@ impl Window {
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
need_frame_refresh,
need_refresh,
fullscreen,
})
}
@ -147,35 +155,27 @@ impl Window {
self.frame.lock().unwrap().set_title(title.into());
}
#[inline]
pub fn show(&self) {
pub fn set_visible(&self, _visible: bool) {
// TODO
}
#[inline]
pub fn hide(&self) {
// TODO
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// Not possible with wayland
None
}
#[inline]
pub fn set_position(&self, _pos: LogicalPosition) {
pub fn set_outer_position(&self, _pos: LogicalPosition) {
// Not possible with wayland
}
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.size.lock().unwrap().clone().into())
pub fn inner_size(&self) -> LogicalSize {
self.size.lock().unwrap().clone().into()
}
pub fn request_redraw(&self) {
@ -183,10 +183,10 @@ impl Window {
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
pub fn outer_size(&self) -> LogicalSize {
let (w, h) = self.size.lock().unwrap().clone();
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
Some((w, h).into())
(w, h).into()
}
#[inline]
@ -198,12 +198,12 @@ impl Window {
}
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
self.frame.lock().unwrap().set_min_size(dimensions.map(Into::into));
}
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
self.frame.lock().unwrap().set_max_size(dimensions.map(Into::into));
}
@ -230,6 +230,14 @@ impl Window {
}
}
pub fn fullscreen(&self) -> Option<MonitorHandle> {
if *(self.fullscreen.lock().unwrap()) {
Some(self.current_monitor())
} else {
None
}
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
if let Some(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
@ -250,34 +258,34 @@ impl Window {
}
#[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) {
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
// TODO
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
pub fn set_cursor_visible(&self, _visible: bool) {
// TODO: This isn't possible on Wayland yet
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not yet possible on Wayland.".to_owned())
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), String> {
Err("Setting the cursor position is not yet possible on Wayland.".to_owned())
pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn get_display(&self) -> &Display {
pub fn display(&self) -> &Display {
&*self.display
}
pub fn get_surface(&self) -> &wl_surface::WlSurface {
pub fn surface(&self) -> &wl_surface::WlSurface {
&self.surface
}
pub fn get_current_monitor(&self) -> MonitorHandle {
pub fn current_monitor(&self) -> MonitorHandle {
let output = get_outputs(&self.surface).last().unwrap().clone();
MonitorHandle {
proxy: output,
@ -285,12 +293,12 @@ impl Window {
}
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors(&self.outputs)
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
available_monitors(&self.outputs)
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor(&self.outputs)
pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor(&self.outputs)
}
}
@ -310,6 +318,7 @@ struct InternalWindow {
newsize: Option<(u32, u32)>,
size: Arc<Mutex<(u32, u32)>>,
need_refresh: Arc<Mutex<bool>>,
fullscreen: Arc<Mutex<bool>>,
need_frame_refresh: Arc<Mutex<bool>>,
closed: bool,
kill_switch: Arc<Mutex<bool>>,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,3 +6,4 @@ pub use x11_dl::xinput2::*;
pub use x11_dl::xlib_xcb::*;
pub use x11_dl::error::OpenError;
pub use x11_dl::xrandr::*;
pub use x11_dl::xrender::*;

View file

@ -12,8 +12,9 @@ use super::{ffi, util, XConnection, XError};
use self::inner::{close_im, ImeInner};
use self::input_method::PotentialInputMethods;
use self::context::{ImeContextCreationError, ImeContext};
use self::context::ImeContext;
use self::callbacks::*;
pub use self::context::ImeContextCreationError;
pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>;
pub type ImeSender = Sender<(ffi::Window, i16, i16)>;

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ use std::os::raw::*;
use parking_lot::Mutex;
use {PhysicalPosition, PhysicalSize};
use dpi::{PhysicalPosition, PhysicalSize};
use super::{util, XConnection, XError};
use super::ffi::{
RRCrtcChangeNotifyMask,
@ -67,7 +67,7 @@ impl MonitorHandle {
primary: bool,
) -> Option<Self> {
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? };
let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) };
let (dimensions, position) = unsafe { (repr.dimensions(), repr.position()) };
let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle {
id,
@ -80,32 +80,32 @@ impl MonitorHandle {
})
}
pub fn get_name(&self) -> Option<String> {
pub fn name(&self) -> Option<String> {
Some(self.name.clone())
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
pub fn native_identifier(&self) -> u32 {
self.id as u32
}
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
pub fn get_position(&self) -> PhysicalPosition {
pub fn position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}
impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
let monitors = self.get_available_monitors();
let monitors = self.available_monitors();
let default = monitors
.get(0)
.expect("[winit] Failed to find any monitors using XRandR.");
@ -131,9 +131,14 @@ impl XConnection {
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
unsafe {
let root = (self.xlib.XDefaultRootWindow)(self.display);
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
// Upon failure, `resources` will be null.
let resources = (self.xrandr.XRRGetScreenResources)(self.display, root);
let resources = if version_is_at_least(1, 3) {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
// Upon failure, `resources` will be null.
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
if resources.is_null() {
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
}
@ -201,7 +206,7 @@ impl XConnection {
}
}
pub fn get_available_monitors(&self) -> Vec<MonitorHandle> {
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
let mut monitors_lock = MONITORS.lock();
(*monitors_lock)
.as_ref()
@ -217,8 +222,8 @@ impl XConnection {
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
self.get_available_monitors()
pub fn primary_monitor(&self) -> MonitorHandle {
self.available_monitors()
.into_iter()
.find(|monitor| monitor.primary)
.expect("[winit] Failed to find any monitors using XRandR.")

View file

@ -1,7 +1,7 @@
use std::cmp;
use super::*;
use {LogicalPosition, LogicalSize};
use dpi::{LogicalPosition, LogicalSize};
// Friendly neighborhood axis-aligned rectangle
#[derive(Debug, Clone, PartialEq, Eq)]
@ -152,7 +152,7 @@ impl FrameExtentsHeuristic {
}
impl XConnection {
// This is adequate for get_inner_position
// This is adequate for inner_position
pub fn translate_coords(&self, window: ffi::Window, root: ffi::Window) -> Result<TranslatedCoords, XError> {
let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() };
unsafe {
@ -171,7 +171,7 @@ impl XConnection {
self.check_errors().map(|_| translated_coords)
}
// This is adequate for get_inner_size
// This is adequate for inner_size
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
let mut geometry: Geometry = unsafe { mem::uninitialized() };
let _status = unsafe {

View file

@ -1,4 +1,4 @@
use {Icon, Pixel, PIXEL_SIZE};
use window::{Icon, Pixel, PIXEL_SIZE};
use super::*;
impl Pixel {

View file

@ -1,7 +1,7 @@
use std::str;
use super::*;
use events::ModifiersState;
use event::ModifiersState;
pub const VIRTUAL_CORE_POINTER: c_int = 2;
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;

View file

@ -1,7 +1,7 @@
use std::{env, slice};
use std::str::FromStr;
use validate_hidpi_factor;
use dpi::validate_hidpi_factor;
use super::*;
pub fn calc_dpi_factor(
@ -51,14 +51,14 @@ impl MonitorRepr {
}
}
pub unsafe fn get_dimensions(&self) -> (u32, u32) {
pub unsafe fn dimensions(&self) -> (u32, u32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
}
}
pub unsafe fn get_position(&self) -> (i32, i32) {
pub unsafe fn position(&self) -> (i32, i32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
@ -79,6 +79,24 @@ impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
}
impl XConnection {
// Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
(self.xlib.XrmInitialize)();
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
if resource_manager_str == ptr::null_mut() {
return None;
}
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
let name : &str = "Xft.dpi:\t";
for pair in res.split("\n") {
if pair.starts_with(&name) {
let res = &pair[name.len()..];
return f64::from_str(&res).ok();
}
}
}
None
}
pub unsafe fn get_output_info(
&self,
resources: *mut ffi::XRRScreenResources,
@ -101,10 +119,15 @@ impl XConnection {
(*output_info).nameLen as usize,
);
let name = String::from_utf8_lossy(name_slice).into();
let hidpi_factor = calc_dpi_factor(
repr.get_dimensions(),
((*output_info).mm_width as u64, (*output_info).mm_height as u64),
);
let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() {
dpi / 96.
} else {
calc_dpi_factor(
repr.dimensions(),
((*output_info).mm_width as u64, (*output_info).mm_height as u64),
)
};
(self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, hidpi_factor))
}

View file

@ -1,5 +1,3 @@
use std;
use super::*;
pub type Cardinal = c_long;

View file

@ -1,4 +1,5 @@
use std::{cmp, env, mem};
use std::collections::HashSet;
use std::ffi::CString;
use std::os::raw::*;
use std::path::Path;
@ -7,15 +8,16 @@ use std::sync::Arc;
use libc;
use parking_lot::Mutex;
use {Icon, MouseCursor, WindowAttributes};
use CreationError::{self, OsError};
use error::{ExternalError, NotSupportedError, OsError as RootOsError};
use window::{Icon, CursorIcon, WindowAttributes};
use dpi::{LogicalPosition, LogicalSize};
use platform_impl::MonitorHandle as PlatformMonitorHandle;
use platform_impl::PlatformSpecificWindowBuilderAttributes;
use platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
use platform_impl::x11::ime::ImeContextCreationError;
use platform_impl::x11::MonitorHandle as X11MonitorHandle;
use window::MonitorHandle as RootMonitorHandle;
use monitor::MonitorHandle as RootMonitorHandle;
use super::{ffi, util, ImeSender, XConnection, XError, WindowId, EventLoop};
use super::{ffi, util, ImeSender, XConnection, XError, WindowId, EventLoopWindowTarget};
unsafe extern "C" fn visibility_predicate(
_display: *mut ffi::Display,
@ -37,11 +39,12 @@ pub struct SharedState {
pub guessed_dpi: Option<f64>,
pub last_monitor: Option<X11MonitorHandle>,
pub dpi_adjusted: Option<(f64, f64)>,
pub fullscreen: Option<RootMonitorHandle>,
// Used to restore position after exiting fullscreen.
pub restore_position: Option<(i32, i32)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_dimensions: Option<LogicalSize>,
pub max_dimensions: Option<LogicalSize>,
pub min_inner_size: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>,
}
impl SharedState {
@ -60,28 +63,28 @@ pub struct UnownedWindow {
xwindow: ffi::Window, // never changes
root: ffi::Window, // never changes
screen_id: i32, // never changes
cursor: Mutex<MouseCursor>,
cursor: Mutex<CursorIcon>,
cursor_grabbed: Mutex<bool>,
cursor_hidden: Mutex<bool>,
cursor_visible: Mutex<bool>,
ime_sender: Mutex<ImeSender>,
pub multitouch: bool, // never changes
pub shared_state: Mutex<SharedState>,
pending_redraws: Arc<::std::sync::Mutex<HashSet<WindowId>>>,
}
impl UnownedWindow {
pub fn new(
event_loop: &EventLoop,
pub fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
window_attrs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<UnownedWindow, CreationError> {
) -> Result<UnownedWindow, RootOsError> {
let xconn = &event_loop.xconn;
let root = event_loop.root;
let monitors = xconn.get_available_monitors();
let monitors = xconn.available_monitors();
let dpi_factor = if !monitors.is_empty() {
let mut dpi_factor = Some(monitors[0].get_hidpi_factor());
let mut dpi_factor = Some(monitors[0].hidpi_factor());
for monitor in &monitors {
if Some(monitor.get_hidpi_factor()) != dpi_factor {
if Some(monitor.hidpi_factor()) != dpi_factor {
dpi_factor = None;
}
}
@ -93,7 +96,7 @@ impl UnownedWindow {
let mut dpi_factor = None;
for monitor in &monitors {
if monitor.rect.contains_point(x, y) {
dpi_factor = Some(monitor.get_hidpi_factor());
dpi_factor = Some(monitor.hidpi_factor());
break;
}
}
@ -102,31 +105,31 @@ impl UnownedWindow {
.unwrap_or(1.0)
})
} else {
return Err(OsError(format!("No monitors were detected.")));
return Err(os_error!(OsError::XMisc("No monitors were detected.")));
};
info!("Guessed window DPI factor: {}", dpi_factor);
let max_dimensions: Option<(u32, u32)> = window_attrs.max_dimensions.map(|size| {
let max_inner_size: Option<(u32, u32)> = window_attrs.max_inner_size.map(|size| {
size.to_physical(dpi_factor).into()
});
let min_dimensions: Option<(u32, u32)> = window_attrs.min_dimensions.map(|size| {
let min_inner_size: Option<(u32, u32)> = window_attrs.min_inner_size.map(|size| {
size.to_physical(dpi_factor).into()
});
let dimensions = {
// x11 only applies constraints when the window is actively resized
// by the user, so we have to manually apply the initial constraints
let mut dimensions: (u32, u32) = window_attrs.dimensions
let mut dimensions: (u32, u32) = window_attrs.inner_size
.or_else(|| Some((800, 600).into()))
.map(|size| size.to_physical(dpi_factor))
.map(Into::into)
.unwrap();
if let Some(max) = max_dimensions {
if let Some(max) = max_inner_size {
dimensions.0 = cmp::min(dimensions.0, max.0);
dimensions.1 = cmp::min(dimensions.1, max.1);
}
if let Some(min) = min_dimensions {
if let Some(min) = min_inner_size {
dimensions.0 = cmp::max(dimensions.0, min.0);
dimensions.1 = cmp::max(dimensions.1, min.1);
}
@ -183,6 +186,14 @@ impl UnownedWindow {
None => ffi::CopyFromParent,
},
ffi::InputOutput as c_uint,
// TODO: If window wants transparency and `visual_infos` is None,
// we need to find our own visual which has an `alphaMask` which
// is > 0, like we do in glutin.
//
// It is non obvious which masks, if any, we should pass to
// `XGetVisualInfo`. winit doesn't recieve any info about what
// properties the user wants. Users should consider choosing the
// visual themselves as glutin does.
match pl_attribs.visual_infos {
Some(vi) => vi.visual,
None => ffi::CopyFromParent as *mut ffi::Visual,
@ -198,11 +209,11 @@ impl UnownedWindow {
root,
screen_id,
cursor: Default::default(),
cursor_grabbed: Default::default(),
cursor_hidden: Default::default(),
cursor_grabbed: Mutex::new(false),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
multitouch: window_attrs.multitouch,
shared_state: SharedState::new(dpi_factor),
pending_redraws: event_loop.pending_redraws.clone(),
};
// Title must be set before mapping. Some tiling window managers (i.e. i3) use the window
@ -278,27 +289,27 @@ impl UnownedWindow {
// set size hints
{
let mut min_dimensions = window_attrs.min_dimensions
let mut min_inner_size = window_attrs.min_inner_size
.map(|size| size.to_physical(dpi_factor));
let mut max_dimensions = window_attrs.max_dimensions
let mut max_inner_size = window_attrs.max_inner_size
.map(|size| size.to_physical(dpi_factor));
if !window_attrs.resizable {
if util::wm_name_is_one_of(&["Xfwm4"]) {
warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
} else {
max_dimensions = Some(dimensions.into());
min_dimensions = Some(dimensions.into());
max_inner_size = Some(dimensions.into());
min_inner_size = Some(dimensions.into());
let mut shared_state_lock = window.shared_state.lock();
shared_state_lock.min_dimensions = window_attrs.min_dimensions;
shared_state_lock.max_dimensions = window_attrs.max_dimensions;
shared_state_lock.min_inner_size = window_attrs.min_inner_size;
shared_state_lock.max_inner_size = window_attrs.max_inner_size;
}
}
let mut normal_hints = util::NormalHints::new(xconn);
normal_hints.set_size(Some(dimensions));
normal_hints.set_min_size(min_dimensions.map(Into::into));
normal_hints.set_max_size(max_dimensions.map(Into::into));
normal_hints.set_min_size(min_inner_size.map(Into::into));
normal_hints.set_max_size(max_inner_size.map(Into::into));
normal_hints.set_resize_increments(pl_attribs.resize_increments);
normal_hints.set_base_size(pl_attribs.base_size);
xconn.set_normal_hints(window.xwindow, normal_hints).queue();
@ -335,13 +346,13 @@ impl UnownedWindow {
&mut supported_ptr,
);
if supported_ptr == ffi::False {
return Err(OsError(format!("`XkbSetDetectableAutoRepeat` failed")));
return Err(os_error!(OsError::XMisc("`XkbSetDetectableAutoRepeat` failed")));
}
}
// Select XInput2 events
let mask = {
let mut mask = ffi::XI_MotionMask
let mask = ffi::XI_MotionMask
| ffi::XI_ButtonPressMask
| ffi::XI_ButtonReleaseMask
//| ffi::XI_KeyPressMask
@ -349,12 +360,10 @@ impl UnownedWindow {
| ffi::XI_EnterMask
| ffi::XI_LeaveMask
| ffi::XI_FocusInMask
| ffi::XI_FocusOutMask;
if window_attrs.multitouch {
mask |= ffi::XI_TouchBeginMask
| ffi::XI_TouchUpdateMask
| ffi::XI_TouchEndMask;
}
| ffi::XI_FocusOutMask
| ffi::XI_TouchBeginMask
| ffi::XI_TouchUpdateMask
| ffi::XI_TouchEndMask;
mask
};
xconn.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask).queue();
@ -364,7 +373,11 @@ impl UnownedWindow {
.borrow_mut()
.create_context(window.xwindow);
if let Err(err) = result {
return Err(OsError(format!("Failed to create input context: {:?}", err)));
let e = match err {
ImeContextCreationError::XError(err) => OsError::XError(err),
ImeContextCreationError::Null => OsError::XMisc("IME Context creation failed"),
};
return Err(os_error!(e));
}
}
@ -403,18 +416,16 @@ impl UnownedWindow {
// We never want to give the user a broken window, since by then, it's too late to handle.
xconn.sync_with_server()
.map(|_| window)
.map_err(|x_err| OsError(
format!("X server returned error while building window: {:?}", x_err)
))
.map_err(|x_err| os_error!(OsError::XError(x_err)))
}
fn logicalize_coords(&self, (x, y): (i32, i32)) -> LogicalPosition {
let dpi = self.get_hidpi_factor();
let dpi = self.hidpi_factor();
LogicalPosition::from_physical((x, y), dpi)
}
fn logicalize_size(&self, (width, height): (u32, u32)) -> LogicalSize {
let dpi = self.get_hidpi_factor();
let dpi = self.hidpi_factor();
LogicalSize::from_physical((width, height), dpi)
}
@ -523,9 +534,9 @@ impl UnownedWindow {
flusher
},
Some(RootMonitorHandle { inner: PlatformMonitorHandle::X(monitor) }) => {
let window_position = self.get_position_physical();
self.shared_state.lock().restore_position = window_position;
let monitor_origin: (i32, i32) = monitor.get_position().into();
let window_position = self.outer_position_physical();
self.shared_state.lock().restore_position = Some(window_position);
let monitor_origin: (i32, i32) = monitor.position().into();
self.set_position_inner(monitor_origin.0, monitor_origin.1).queue();
self.set_fullscreen_hint(true)
}
@ -533,25 +544,29 @@ impl UnownedWindow {
}
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
self.shared_state.lock().fullscreen.clone()
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
self.shared_state.lock().fullscreen = monitor.clone();
self.set_fullscreen_inner(monitor)
.flush()
.expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents();
}
fn get_rect(&self) -> Option<util::AaRect> {
fn get_rect(&self) -> util::AaRect {
// TODO: This might round-trip more times than needed.
if let (Some(position), Some(size)) = (self.get_position_physical(), self.get_outer_size_physical()) {
Some(util::AaRect::new(position, size))
} else {
None
}
let position = self.outer_position_physical();
let size = self.outer_size_physical();
util::AaRect::new(position, size)
}
#[inline]
pub fn get_current_monitor(&self) -> X11MonitorHandle {
pub fn current_monitor(&self) -> X11MonitorHandle {
let monitor = self.shared_state
.lock()
.last_monitor
@ -559,18 +574,18 @@ impl UnownedWindow {
.cloned();
monitor
.unwrap_or_else(|| {
let monitor = self.xconn.get_monitor_for_window(self.get_rect()).to_owned();
let monitor = self.xconn.get_monitor_for_window(Some(self.get_rect())).to_owned();
self.shared_state.lock().last_monitor = Some(monitor.clone());
monitor
})
}
pub fn get_available_monitors(&self) -> Vec<X11MonitorHandle> {
self.xconn.get_available_monitors()
pub fn available_monitors(&self) -> Vec<X11MonitorHandle> {
self.xconn.available_monitors()
}
pub fn get_primary_monitor(&self) -> X11MonitorHandle {
self.xconn.get_primary_monitor()
pub fn primary_monitor(&self) -> X11MonitorHandle {
self.xconn.primary_monitor()
}
fn set_maximized_inner(&self, maximized: bool) -> util::Flusher {
@ -684,20 +699,18 @@ impl UnownedWindow {
}
#[inline]
pub fn show(&self) {
unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
self.xconn.flush_requests()
.expect("Failed to call XMapRaised");
}
}
#[inline]
pub fn hide(&self) {
unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
self.xconn.flush_requests()
.expect("Failed to call XUnmapWindow");
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
self.xconn.flush_requests()
.expect("Failed to call XMapRaised");
},
false => unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
self.xconn.flush_requests()
.expect("Failed to call XUnmapWindow");
}
}
}
@ -710,39 +723,40 @@ impl UnownedWindow {
(*self.shared_state.lock()).frame_extents.take();
}
pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> {
pub(crate) fn outer_position_physical(&self) -> (i32, i32) {
let extents = (*self.shared_state.lock()).frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_position_physical()
.map(|(x, y)| extents.inner_pos_to_outer(x, y))
let (x, y) = self.inner_position_physical();
extents.inner_pos_to_outer(x, y)
} else {
self.update_cached_frame_extents();
self.get_position_physical()
self.outer_position_physical()
}
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let extents = (*self.shared_state.lock()).frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_position()
.map(|logical| extents.inner_pos_to_outer_logical(logical, self.get_hidpi_factor()))
let logical = self.inner_position().unwrap();
Ok(extents.inner_pos_to_outer_logical(logical, self.hidpi_factor()))
} else {
self.update_cached_frame_extents();
self.get_position()
self.outer_position()
}
}
pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> {
pub(crate) fn inner_position_physical(&self) -> (i32, i32) {
// This should be okay to unwrap since the only error XTranslateCoordinates can return
// is BadWindow, and if the window handle is bad we have bigger problems.
self.xconn.translate_coords(self.xwindow, self.root)
.ok()
.map(|coords| (coords.x_rel_root, coords.y_rel_root))
.unwrap()
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
self.get_inner_position_physical()
.map(|coords| self.logicalize_coords(coords))
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Ok(self.logicalize_coords(self.inner_position_physical()))
}
pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher {
@ -776,43 +790,44 @@ impl UnownedWindow {
}
#[inline]
pub fn set_position(&self, logical_position: LogicalPosition) {
let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into();
pub fn set_outer_position(&self, logical_position: LogicalPosition) {
let (x, y) = logical_position.to_physical(self.hidpi_factor()).into();
self.set_position_physical(x, y);
}
pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> {
pub(crate) fn inner_size_physical(&self) -> (u32, u32) {
// This should be okay to unwrap since the only error XGetGeometry can return
// is BadWindow, and if the window handle is bad we have bigger problems.
self.xconn.get_geometry(self.xwindow)
.ok()
.map(|geo| (geo.width, geo.height))
.unwrap()
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
self.get_inner_size_physical()
.map(|size| self.logicalize_size(size))
pub fn inner_size(&self) -> LogicalSize {
self.logicalize_size(self.inner_size_physical())
}
pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> {
pub(crate) fn outer_size_physical(&self) -> (u32, u32) {
let extents = self.shared_state.lock().frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_size_physical()
.map(|(w, h)| extents.inner_size_to_outer(w, h))
let (w, h) = self.inner_size_physical();
extents.inner_size_to_outer(w, h)
} else {
self.update_cached_frame_extents();
self.get_outer_size_physical()
self.outer_size_physical()
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
pub fn outer_size(&self) -> LogicalSize {
let extents = self.shared_state.lock().frame_extents.clone();
if let Some(extents) = extents {
self.get_inner_size()
.map(|logical| extents.inner_size_to_outer_logical(logical, self.get_hidpi_factor()))
let logical = self.inner_size();
extents.inner_size_to_outer_logical(logical, self.hidpi_factor())
} else {
self.update_cached_frame_extents();
self.get_outer_size()
self.outer_size()
}
}
@ -830,7 +845,7 @@ impl UnownedWindow {
#[inline]
pub fn set_inner_size(&self, logical_size: LogicalSize) {
let dpi_factor = self.get_hidpi_factor();
let dpi_factor = self.hidpi_factor();
let (width, height) = logical_size.to_physical(dpi_factor).into();
self.set_inner_size_physical(width, height);
}
@ -843,32 +858,32 @@ impl UnownedWindow {
self.xconn.set_normal_hints(self.xwindow, normal_hints).flush()
}
pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) {
self.update_normal_hints(|normal_hints| normal_hints.set_min_size(dimensions))
.expect("Failed to call `XSetWMNormalHints`");
}
#[inline]
pub fn set_min_dimensions(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().min_dimensions = logical_dimensions;
pub fn set_min_inner_size(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().min_inner_size = logical_dimensions;
let physical_dimensions = logical_dimensions.map(|logical_dimensions| {
logical_dimensions.to_physical(self.get_hidpi_factor()).into()
logical_dimensions.to_physical(self.hidpi_factor()).into()
});
self.set_min_dimensions_physical(physical_dimensions);
self.set_min_inner_size_physical(physical_dimensions);
}
pub(crate) fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
pub(crate) fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) {
self.update_normal_hints(|normal_hints| normal_hints.set_max_size(dimensions))
.expect("Failed to call `XSetWMNormalHints`");
}
#[inline]
pub fn set_max_dimensions(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().max_dimensions = logical_dimensions;
pub fn set_max_inner_size(&self, logical_dimensions: Option<LogicalSize>) {
self.shared_state.lock().max_inner_size = logical_dimensions;
let physical_dimensions = logical_dimensions.map(|logical_dimensions| {
logical_dimensions.to_physical(self.get_hidpi_factor()).into()
logical_dimensions.to_physical(self.hidpi_factor()).into()
});
self.set_max_dimensions_physical(physical_dimensions);
self.set_max_inner_size_physical(physical_dimensions);
}
pub(crate) fn adjust_for_dpi(
@ -918,47 +933,47 @@ impl UnownedWindow {
let (logical_min, logical_max) = if resizable {
let shared_state_lock = self.shared_state.lock();
(shared_state_lock.min_dimensions, shared_state_lock.max_dimensions)
(shared_state_lock.min_inner_size, shared_state_lock.max_inner_size)
} else {
let window_size = self.get_inner_size();
let window_size = Some(self.inner_size());
(window_size.clone(), window_size)
};
let dpi_factor = self.get_hidpi_factor();
let min_dimensions = logical_min
let dpi_factor = self.hidpi_factor();
let min_inner_size = logical_min
.map(|logical_size| logical_size.to_physical(dpi_factor))
.map(Into::into);
let max_dimensions = logical_max
let max_inner_size = logical_max
.map(|logical_size| logical_size.to_physical(dpi_factor))
.map(Into::into);
self.update_normal_hints(|normal_hints| {
normal_hints.set_min_size(min_dimensions);
normal_hints.set_max_size(max_dimensions);
normal_hints.set_min_size(min_inner_size);
normal_hints.set_max_size(max_inner_size);
}).expect("Failed to call `XSetWMNormalHints`");
}
#[inline]
pub fn get_xlib_display(&self) -> *mut c_void {
pub fn xlib_display(&self) -> *mut c_void {
self.xconn.display as _
}
#[inline]
pub fn get_xlib_screen_id(&self) -> c_int {
pub fn xlib_screen_id(&self) -> c_int {
self.screen_id
}
#[inline]
pub fn get_xlib_xconnection(&self) -> Arc<XConnection> {
pub fn xlib_xconnection(&self) -> Arc<XConnection> {
Arc::clone(&self.xconn)
}
#[inline]
pub fn get_xlib_window(&self) -> c_ulong {
pub fn xlib_window(&self) -> c_ulong {
self.xwindow
}
#[inline]
pub fn get_xcb_connection(&self) -> *mut c_void {
pub fn xcb_connection(&self) -> *mut c_void {
unsafe {
(self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _
}
@ -983,7 +998,7 @@ impl UnownedWindow {
0
}
fn get_cursor(&self, cursor: MouseCursor) -> ffi::Cursor {
fn get_cursor(&self, cursor: CursorIcon) -> ffi::Cursor {
let load = |name: &[u8]| {
self.load_cursor(name)
};
@ -997,48 +1012,48 @@ impl UnownedWindow {
//
// Try the better looking (or more suiting) names first.
match cursor {
MouseCursor::Alias => load(b"link\0"),
MouseCursor::Arrow => load(b"arrow\0"),
MouseCursor::Cell => load(b"plus\0"),
MouseCursor::Copy => load(b"copy\0"),
MouseCursor::Crosshair => load(b"crosshair\0"),
MouseCursor::Default => load(b"left_ptr\0"),
MouseCursor::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
MouseCursor::Help => load(b"question_arrow\0"),
MouseCursor::Move => load(b"move\0"),
MouseCursor::Grab => loadn(&[b"openhand\0", b"grab\0"]),
MouseCursor::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
MouseCursor::Progress => load(b"left_ptr_watch\0"),
MouseCursor::AllScroll => load(b"all-scroll\0"),
MouseCursor::ContextMenu => load(b"context-menu\0"),
CursorIcon::Alias => load(b"link\0"),
CursorIcon::Arrow => load(b"arrow\0"),
CursorIcon::Cell => load(b"plus\0"),
CursorIcon::Copy => load(b"copy\0"),
CursorIcon::Crosshair => load(b"crosshair\0"),
CursorIcon::Default => load(b"left_ptr\0"),
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
CursorIcon::Help => load(b"question_arrow\0"),
CursorIcon::Move => load(b"move\0"),
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
CursorIcon::Progress => load(b"left_ptr_watch\0"),
CursorIcon::AllScroll => load(b"all-scroll\0"),
CursorIcon::ContextMenu => load(b"context-menu\0"),
MouseCursor::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
MouseCursor::NotAllowed => load(b"crossed_circle\0"),
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
// Resize cursors
MouseCursor::EResize => load(b"right_side\0"),
MouseCursor::NResize => load(b"top_side\0"),
MouseCursor::NeResize => load(b"top_right_corner\0"),
MouseCursor::NwResize => load(b"top_left_corner\0"),
MouseCursor::SResize => load(b"bottom_side\0"),
MouseCursor::SeResize => load(b"bottom_right_corner\0"),
MouseCursor::SwResize => load(b"bottom_left_corner\0"),
MouseCursor::WResize => load(b"left_side\0"),
MouseCursor::EwResize => load(b"h_double_arrow\0"),
MouseCursor::NsResize => load(b"v_double_arrow\0"),
MouseCursor::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
MouseCursor::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
MouseCursor::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
MouseCursor::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
CursorIcon::EResize => load(b"right_side\0"),
CursorIcon::NResize => load(b"top_side\0"),
CursorIcon::NeResize => load(b"top_right_corner\0"),
CursorIcon::NwResize => load(b"top_left_corner\0"),
CursorIcon::SResize => load(b"bottom_side\0"),
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
CursorIcon::WResize => load(b"left_side\0"),
CursorIcon::EwResize => load(b"h_double_arrow\0"),
CursorIcon::NsResize => load(b"v_double_arrow\0"),
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]),
MouseCursor::VerticalText => load(b"vertical-text\0"),
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
CursorIcon::VerticalText => load(b"vertical-text\0"),
MouseCursor::Wait => load(b"watch\0"),
CursorIcon::Wait => load(b"watch\0"),
MouseCursor::ZoomIn => load(b"zoom-in\0"),
MouseCursor::ZoomOut => load(b"zoom-out\0"),
CursorIcon::ZoomIn => load(b"zoom-in\0"),
CursorIcon::ZoomOut => load(b"zoom-out\0"),
}
}
@ -1053,9 +1068,9 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
*self.cursor.lock() = cursor;
if !*self.cursor_hidden.lock() {
if *self.cursor_visible.lock() {
self.update_cursor(self.get_cursor(cursor));
}
}
@ -1099,7 +1114,7 @@ impl UnownedWindow {
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed.lock();
if grab == *grabbed_lock { return Ok(()); }
unsafe {
@ -1143,10 +1158,10 @@ impl UnownedWindow {
ffi::GrabNotViewable => Err("Cursor could not be grabbed: grab location not viewable"),
ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"),
_ => unreachable!(),
}.map_err(|err| err.to_owned())
}.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err))))
} else {
self.xconn.flush_requests()
.map_err(|err| format!("Failed to call `XUngrabPointer`: {:?}", err))
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
};
if result.is_ok() {
*grabbed_lock = grab;
@ -1155,25 +1170,25 @@ impl UnownedWindow {
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let mut hidden_lock = self.cursor_hidden.lock();
if hide == *hidden_lock {return; }
let cursor = if hide {
self.create_empty_cursor().expect("Failed to create empty cursor")
} else {
pub fn set_cursor_visible(&self, visible: bool) {
let mut visible_lock = self.cursor_visible.lock();
if visible == *visible_lock {return; }
let cursor = if visible {
self.get_cursor(*self.cursor.lock())
} else {
self.create_empty_cursor().expect("Failed to create empty cursor")
};
*hidden_lock = hide;
drop(hidden_lock);
*visible_lock = visible;
drop(visible_lock);
self.update_cursor(cursor);
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.get_current_monitor().hidpi_factor
pub fn hidpi_factor(&self) -> f64 {
self.current_monitor().hidpi_factor
}
pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> {
pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> {
unsafe {
(self.xconn.xlib.XWarpPointer)(
self.xconn.display,
@ -1186,28 +1201,33 @@ impl UnownedWindow {
x,
y,
);
self.xconn.flush_requests().map_err(|e| format!("`XWarpPointer` failed: {:?}", e))
self.xconn.flush_requests().map_err(|e| ExternalError::Os(os_error!(OsError::XError(e))))
}
}
#[inline]
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> {
let (x, y) = logical_position.to_physical(self.get_hidpi_factor()).into();
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), ExternalError> {
let (x, y) = logical_position.to_physical(self.hidpi_factor()).into();
self.set_cursor_position_physical(x, y)
}
pub(crate) fn set_ime_spot_physical(&self, x: i32, y: i32) {
pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
let _ = self.ime_sender
.lock()
.send((self.xwindow, x as i16, y as i16));
}
#[inline]
pub fn set_ime_spot(&self, logical_spot: LogicalPosition) {
let (x, y) = logical_spot.to_physical(self.get_hidpi_factor()).into();
self.set_ime_spot_physical(x, y);
pub fn set_ime_position(&self, logical_spot: LogicalPosition) {
let (x, y) = logical_spot.to_physical(self.hidpi_factor()).into();
self.set_ime_position_physical(x, y);
}
#[inline]
pub fn id(&self) -> WindowId { WindowId(self.xwindow) }
#[inline]
pub fn request_redraw(&self) {
self.pending_redraws.lock().unwrap().insert(WindowId(self.xwindow));
}
}

View file

@ -1,6 +1,7 @@
use std::ptr;
use std::fmt;
use std::error::Error;
use std::os::raw::c_int;
use libc;
use parking_lot::Mutex;
@ -17,7 +18,9 @@ pub struct XConnection {
pub xcursor: ffi::Xcursor,
pub xinput2: ffi::XInput2,
pub xlib_xcb: ffi::Xlib_xcb,
pub xrender: ffi::Xrender,
pub display: *mut ffi::Display,
pub x11_fd: c_int,
pub latest_error: Mutex<Option<XError>>,
}
@ -35,6 +38,7 @@ impl XConnection {
let xrandr_1_5 = ffi::Xrandr::open().ok();
let xinput2 = ffi::XInput2::open()?;
let xlib_xcb = ffi::Xlib_xcb::open()?;
let xrender = ffi::Xrender::open()?;
unsafe { (xlib.XInitThreads)() };
unsafe { (xlib.XSetErrorHandler)(error_handler) };
@ -48,6 +52,11 @@ impl XConnection {
display
};
// Get X11 socket file descriptor
let fd = unsafe {
(xlib.XConnectionNumber)(display)
};
Ok(XConnection {
xlib,
xrandr,
@ -55,7 +64,9 @@ impl XConnection {
xcursor,
xinput2,
xlib_xcb,
xrender,
display,
x11_fd: fd,
latest_error: Mutex::new(None),
})
}

View file

@ -0,0 +1,88 @@
use std::collections::VecDeque;
use cocoa::{appkit::{self, NSEvent}, base::id};
use objc::{declare::ClassDecl, runtime::{Class, Object, Sel}};
use event::{DeviceEvent, Event};
use platform_impl::platform::{app_state::AppState, DEVICE_ID, util};
pub struct AppClass(pub *const Class);
unsafe impl Send for AppClass {}
unsafe impl Sync for AppClass {}
lazy_static! {
pub static ref APP_CLASS: AppClass = unsafe {
let superclass = class!(NSApplication);
let mut decl = ClassDecl::new("WinitApp", superclass).unwrap();
decl.add_method(
sel!(sendEvent:),
send_event as extern fn(&Object, Sel, id),
);
AppClass(decl.register())
};
}
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
extern fn send_event(this: &Object, _sel: Sel, event: id) {
unsafe {
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = event.eventType();
let modifier_flags = event.modifierFlags();
if event_type == appkit::NSKeyUp && util::has_flag(
modifier_flags,
appkit::NSEventModifierFlags::NSCommandKeyMask,
) {
let key_window: id = msg_send![this, keyWindow];
let _: () = msg_send![key_window, sendEvent:event];
} else {
maybe_dispatch_device_event(event);
let superclass = util::superclass(this);
let _: () = msg_send![super(this, superclass), sendEvent:event];
}
}
}
unsafe fn maybe_dispatch_device_event(event: id) {
let event_type = event.eventType();
match event_type {
appkit::NSMouseMoved |
appkit::NSLeftMouseDragged |
appkit::NSOtherMouseDragged |
appkit::NSRightMouseDragged => {
let mut events = VecDeque::with_capacity(3);
let delta_x = event.deltaX() as f64;
let delta_y = event.deltaY() as f64;
if delta_x != 0.0 {
events.push_back(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::Motion { axis: 0, value: delta_x },
});
}
if delta_y != 0.0 {
events.push_back(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::Motion { axis: 1, value: delta_y },
});
}
if delta_x != 0.0 || delta_y != 0.0 {
events.push_back(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::MouseMotion { delta: (delta_x, delta_y) },
});
}
AppState::queue_events(events);
},
_ => (),
}
}

View file

@ -0,0 +1,101 @@
use cocoa::base::id;
use objc::{runtime::{Class, Object, Sel, BOOL, YES}, declare::ClassDecl};
use platform_impl::platform::app_state::AppState;
pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
unsafe impl Sync for AppDelegateClass {}
lazy_static! {
pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
let superclass = class!(NSResponder);
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(applicationDidBecomeActive:),
did_become_active as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillResignActive:),
will_resign_active as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillEnterForeground:),
will_enter_foreground as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidEnterBackground:),
did_enter_background as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationWillTerminate:),
will_terminate as extern fn(&Object, Sel, id),
);
AppDelegateClass(decl.register())
};
}
extern fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL {
trace!("Triggered `didFinishLaunching`");
AppState::launched();
trace!("Completed `didFinishLaunching`");
YES
}
extern fn did_become_active(_: &Object, _: Sel, _: id) {
trace!("Triggered `didBecomeActive`");
/*unsafe {
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(false))
}*/
trace!("Completed `didBecomeActive`");
}
extern fn will_resign_active(_: &Object, _: Sel, _: id) {
trace!("Triggered `willResignActive`");
/*unsafe {
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(true))
}*/
trace!("Completed `willResignActive`");
}
extern fn will_enter_foreground(_: &Object, _: Sel, _: id) {
trace!("Triggered `willEnterForeground`");
trace!("Completed `willEnterForeground`");
}
extern fn did_enter_background(_: &Object, _: Sel, _: id) {
trace!("Triggered `didEnterBackground`");
trace!("Completed `didEnterBackground`");
}
extern 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`");
}

View file

@ -0,0 +1,310 @@
use std::{
collections::VecDeque, fmt::{self, Debug, Formatter},
hint::unreachable_unchecked, mem,
sync::{atomic::{AtomicBool, Ordering}, Mutex, MutexGuard}, time::Instant,
};
use cocoa::{appkit::NSApp, base::nil};
use {
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
window::WindowId,
};
use platform_impl::platform::{observer::EventLoopWaker, util::Never};
lazy_static! {
static ref HANDLER: Handler = Default::default();
}
impl Event<Never> {
fn userify<T: 'static>(self) -> Event<T> {
self.map_nonuser_event()
// `Never` can't be constructed, so the `UserEvent` variant can't
// be present here.
.unwrap_or_else(|_| unsafe { unreachable_unchecked() })
}
}
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
struct EventLoopHandler<F, T: 'static> {
callback: F,
will_exit: bool,
window_target: RootWindowTarget<T>,
}
impl<F, T> Debug for EventLoopHandler<F, T> {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.debug_struct("EventLoopHandler")
.field("window_target", &self.window_target)
.finish()
}
}
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.callback)(
event.userify(),
&self.window_target,
control_flow,
);
self.will_exit |= *control_flow == ControlFlow::Exit;
if self.will_exit {
*control_flow = ControlFlow::Exit;
}
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
let mut will_exit = self.will_exit;
for event in self.window_target.p.receiver.try_iter() {
(self.callback)(
Event::UserEvent(event),
&self.window_target,
control_flow,
);
will_exit |= *control_flow == ControlFlow::Exit;
if will_exit {
*control_flow = ControlFlow::Exit;
}
}
self.will_exit = will_exit;
}
}
#[derive(Default)]
struct Handler {
ready: AtomicBool,
in_callback: AtomicBool,
control_flow: Mutex<ControlFlow>,
control_flow_prev: Mutex<ControlFlow>,
start_time: Mutex<Option<Instant>>,
callback: Mutex<Option<Box<dyn EventHandler>>>,
pending_events: Mutex<VecDeque<Event<Never>>>,
deferred_events: Mutex<VecDeque<Event<Never>>>,
pending_redraw: Mutex<Vec<WindowId>>,
waker: Mutex<EventLoopWaker>,
}
unsafe impl Send for Handler {}
unsafe impl Sync for Handler {}
impl Handler {
fn events<'a>(&'a self) -> MutexGuard<'a, VecDeque<Event<Never>>> {
self.pending_events.lock().unwrap()
}
fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque<Event<Never>>> {
self.deferred_events.lock().unwrap()
}
fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
self.pending_redraw.lock().unwrap()
}
fn waker<'a>(&'a self) -> MutexGuard<'a, EventLoopWaker> {
self.waker.lock().unwrap()
}
fn is_ready(&self) -> bool {
self.ready.load(Ordering::Acquire)
}
fn set_ready(&self) {
self.ready.store(true, Ordering::Release);
}
fn should_exit(&self) -> bool {
*self.control_flow.lock().unwrap() == ControlFlow::Exit
}
fn get_control_flow_and_update_prev(&self) -> ControlFlow {
let control_flow = self.control_flow.lock().unwrap();
*self.control_flow_prev.lock().unwrap() = *control_flow;
*control_flow
}
fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) {
let old = *self.control_flow_prev.lock().unwrap();
let new = *self.control_flow.lock().unwrap();
(old, new)
}
fn get_start_time(&self) -> Option<Instant> {
*self.start_time.lock().unwrap()
}
fn update_start_time(&self) {
*self.start_time.lock().unwrap() = Some(Instant::now());
}
fn take_events(&self) -> VecDeque<Event<Never>> {
mem::replace(&mut *self.events(), Default::default())
}
fn take_deferred(&self) -> VecDeque<Event<Never>> {
mem::replace(&mut *self.deferred(), Default::default())
}
fn should_redraw(&self) -> Vec<WindowId> {
mem::replace(&mut *self.redraw(), Default::default())
}
fn get_in_callback(&self) -> bool {
self.in_callback.load(Ordering::Acquire)
}
fn set_in_callback(&self, in_callback: bool) {
self.in_callback.store(in_callback, Ordering::Release);
}
fn handle_nonuser_event(&self, event: Event<Never>) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
callback.handle_nonuser_event(
event,
&mut *self.control_flow.lock().unwrap(),
);
}
}
fn handle_user_events(&self) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
callback.handle_user_events(
&mut *self.control_flow.lock().unwrap(),
);
}
}
}
pub enum AppState {}
impl AppState {
pub fn set_callback<F, T>(callback: F, window_target: RootWindowTarget<T>)
where
F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback,
will_exit: false,
window_target,
}));
}
pub fn exit() {
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(Event::LoopDestroyed);
HANDLER.set_in_callback(false);
}
pub fn launched() {
HANDLER.set_ready();
HANDLER.waker().start();
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init));
HANDLER.set_in_callback(false);
}
pub fn wakeup() {
if !HANDLER.is_ready() { return }
let start = HANDLER.get_start_time().unwrap();
let cause = match HANDLER.get_control_flow_and_update_prev() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
requested_resume: None,
},
ControlFlow::WaitUntil(requested_resume) => {
if Instant::now() >= requested_resume {
StartCause::ResumeTimeReached {
start,
requested_resume,
}
} else {
StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
}
}
},
ControlFlow::Exit => StartCause::Poll,//panic!("unexpected `ControlFlow::Exit`"),
};
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(Event::NewEvents(cause));
HANDLER.set_in_callback(false);
}
// This is called from multiple threads at present
pub fn queue_redraw(window_id: WindowId) {
let mut pending_redraw = HANDLER.redraw();
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
}
pub fn queue_event(event: Event<Never>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Event queued from different thread: {:#?}", event);
}
HANDLER.events().push_back(event);
}
pub fn queue_events(mut events: VecDeque<Event<Never>>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Events queued from different thread: {:#?}", events);
}
HANDLER.events().append(&mut events);
}
pub fn send_event_immediately(event: Event<Never>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Event sent from different thread: {:#?}", event);
}
HANDLER.deferred().push_back(event);
if !HANDLER.get_in_callback() {
HANDLER.set_in_callback(true);
for event in HANDLER.take_deferred() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.set_in_callback(false);
}
}
pub fn cleared() {
if !HANDLER.is_ready() { return }
if !HANDLER.get_in_callback() {
HANDLER.set_in_callback(true);
HANDLER.handle_user_events();
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
}
for window_id in HANDLER.should_redraw() {
HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
});
}
HANDLER.handle_nonuser_event(Event::EventsCleared);
HANDLER.set_in_callback(false);
}
if HANDLER.should_exit() {
let _: () = unsafe { msg_send![NSApp(), stop:nil] };
return
}
HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() {
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(),
(old, new) if old == new => (),
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),
(_, ControlFlow::Poll) => HANDLER.waker().start(),
}
}
}

View file

@ -0,0 +1,273 @@
use std::os::raw::c_ushort;
use cocoa::{appkit::{NSEvent, NSEventModifierFlags}, base::id};
use event::{
ElementState, KeyboardInput,
ModifiersState, VirtualKeyCode, WindowEvent,
};
use platform_impl::platform::DEVICE_ID;
pub fn char_to_keycode(c: char) -> Option<VirtualKeyCode> {
// We only translate keys that are affected by keyboard layout.
//
// Note that since keys are translated in a somewhat "dumb" way (reading character)
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
// letter to be received, and so we receive the wrong key.
//
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
Some(match c {
'a' | 'A' => VirtualKeyCode::A,
'b' | 'B' => VirtualKeyCode::B,
'c' | 'C' => VirtualKeyCode::C,
'd' | 'D' => VirtualKeyCode::D,
'e' | 'E' => VirtualKeyCode::E,
'f' | 'F' => VirtualKeyCode::F,
'g' | 'G' => VirtualKeyCode::G,
'h' | 'H' => VirtualKeyCode::H,
'i' | 'I' => VirtualKeyCode::I,
'j' | 'J' => VirtualKeyCode::J,
'k' | 'K' => VirtualKeyCode::K,
'l' | 'L' => VirtualKeyCode::L,
'm' | 'M' => VirtualKeyCode::M,
'n' | 'N' => VirtualKeyCode::N,
'o' | 'O' => VirtualKeyCode::O,
'p' | 'P' => VirtualKeyCode::P,
'q' | 'Q' => VirtualKeyCode::Q,
'r' | 'R' => VirtualKeyCode::R,
's' | 'S' => VirtualKeyCode::S,
't' | 'T' => VirtualKeyCode::T,
'u' | 'U' => VirtualKeyCode::U,
'v' | 'V' => VirtualKeyCode::V,
'w' | 'W' => VirtualKeyCode::W,
'x' | 'X' => VirtualKeyCode::X,
'y' | 'Y' => VirtualKeyCode::Y,
'z' | 'Z' => VirtualKeyCode::Z,
'1' | '!' => VirtualKeyCode::Key1,
'2' | '@' => VirtualKeyCode::Key2,
'3' | '#' => VirtualKeyCode::Key3,
'4' | '$' => VirtualKeyCode::Key4,
'5' | '%' => VirtualKeyCode::Key5,
'6' | '^' => VirtualKeyCode::Key6,
'7' | '&' => VirtualKeyCode::Key7,
'8' | '*' => VirtualKeyCode::Key8,
'9' | '(' => VirtualKeyCode::Key9,
'0' | ')' => VirtualKeyCode::Key0,
'=' | '+' => VirtualKeyCode::Equals,
'-' | '_' => VirtualKeyCode::Minus,
']' | '}' => VirtualKeyCode::RBracket,
'[' | '{' => VirtualKeyCode::LBracket,
'\''| '"' => VirtualKeyCode::Apostrophe,
';' | ':' => VirtualKeyCode::Semicolon,
'\\'| '|' => VirtualKeyCode::Backslash,
',' | '<' => VirtualKeyCode::Comma,
'/' | '?' => VirtualKeyCode::Slash,
'.' | '>' => VirtualKeyCode::Period,
'`' | '~' => VirtualKeyCode::Grave,
_ => return None,
})
}
pub fn scancode_to_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
Some(match scancode {
0x00 => VirtualKeyCode::A,
0x01 => VirtualKeyCode::S,
0x02 => VirtualKeyCode::D,
0x03 => VirtualKeyCode::F,
0x04 => VirtualKeyCode::H,
0x05 => VirtualKeyCode::G,
0x06 => VirtualKeyCode::Z,
0x07 => VirtualKeyCode::X,
0x08 => VirtualKeyCode::C,
0x09 => VirtualKeyCode::V,
//0x0a => World 1,
0x0b => VirtualKeyCode::B,
0x0c => VirtualKeyCode::Q,
0x0d => VirtualKeyCode::W,
0x0e => VirtualKeyCode::E,
0x0f => VirtualKeyCode::R,
0x10 => VirtualKeyCode::Y,
0x11 => VirtualKeyCode::T,
0x12 => VirtualKeyCode::Key1,
0x13 => VirtualKeyCode::Key2,
0x14 => VirtualKeyCode::Key3,
0x15 => VirtualKeyCode::Key4,
0x16 => VirtualKeyCode::Key6,
0x17 => VirtualKeyCode::Key5,
0x18 => VirtualKeyCode::Equals,
0x19 => VirtualKeyCode::Key9,
0x1a => VirtualKeyCode::Key7,
0x1b => VirtualKeyCode::Minus,
0x1c => VirtualKeyCode::Key8,
0x1d => VirtualKeyCode::Key0,
0x1e => VirtualKeyCode::RBracket,
0x1f => VirtualKeyCode::O,
0x20 => VirtualKeyCode::U,
0x21 => VirtualKeyCode::LBracket,
0x22 => VirtualKeyCode::I,
0x23 => VirtualKeyCode::P,
0x24 => VirtualKeyCode::Return,
0x25 => VirtualKeyCode::L,
0x26 => VirtualKeyCode::J,
0x27 => VirtualKeyCode::Apostrophe,
0x28 => VirtualKeyCode::K,
0x29 => VirtualKeyCode::Semicolon,
0x2a => VirtualKeyCode::Backslash,
0x2b => VirtualKeyCode::Comma,
0x2c => VirtualKeyCode::Slash,
0x2d => VirtualKeyCode::N,
0x2e => VirtualKeyCode::M,
0x2f => VirtualKeyCode::Period,
0x30 => VirtualKeyCode::Tab,
0x31 => VirtualKeyCode::Space,
0x32 => VirtualKeyCode::Grave,
0x33 => VirtualKeyCode::Back,
//0x34 => unkown,
0x35 => VirtualKeyCode::Escape,
0x36 => VirtualKeyCode::RWin,
0x37 => VirtualKeyCode::LWin,
0x38 => VirtualKeyCode::LShift,
//0x39 => Caps lock,
0x3a => VirtualKeyCode::LAlt,
0x3b => VirtualKeyCode::LControl,
0x3c => VirtualKeyCode::RShift,
0x3d => VirtualKeyCode::RAlt,
0x3e => VirtualKeyCode::RControl,
//0x3f => Fn key,
0x40 => VirtualKeyCode::F17,
0x41 => VirtualKeyCode::Decimal,
//0x42 -> unkown,
0x43 => VirtualKeyCode::Multiply,
//0x44 => unkown,
0x45 => VirtualKeyCode::Add,
//0x46 => unkown,
0x47 => VirtualKeyCode::Numlock,
//0x48 => KeypadClear,
0x49 => VirtualKeyCode::VolumeUp,
0x4a => VirtualKeyCode::VolumeDown,
0x4b => VirtualKeyCode::Divide,
0x4c => VirtualKeyCode::NumpadEnter,
//0x4d => unkown,
0x4e => VirtualKeyCode::Subtract,
0x4f => VirtualKeyCode::F18,
0x50 => VirtualKeyCode::F19,
0x51 => VirtualKeyCode::NumpadEquals,
0x52 => VirtualKeyCode::Numpad0,
0x53 => VirtualKeyCode::Numpad1,
0x54 => VirtualKeyCode::Numpad2,
0x55 => VirtualKeyCode::Numpad3,
0x56 => VirtualKeyCode::Numpad4,
0x57 => VirtualKeyCode::Numpad5,
0x58 => VirtualKeyCode::Numpad6,
0x59 => VirtualKeyCode::Numpad7,
0x5a => VirtualKeyCode::F20,
0x5b => VirtualKeyCode::Numpad8,
0x5c => VirtualKeyCode::Numpad9,
0x5d => VirtualKeyCode::Yen,
//0x5e => JIS Ro,
//0x5f => unkown,
0x60 => VirtualKeyCode::F5,
0x61 => VirtualKeyCode::F6,
0x62 => VirtualKeyCode::F7,
0x63 => VirtualKeyCode::F3,
0x64 => VirtualKeyCode::F8,
0x65 => VirtualKeyCode::F9,
//0x66 => JIS Eisuu (macOS),
0x67 => VirtualKeyCode::F11,
//0x68 => JIS Kanna (macOS),
0x69 => VirtualKeyCode::F13,
0x6a => VirtualKeyCode::F16,
0x6b => VirtualKeyCode::F14,
//0x6c => unkown,
0x6d => VirtualKeyCode::F10,
//0x6e => unkown,
0x6f => VirtualKeyCode::F12,
//0x70 => unkown,
0x71 => VirtualKeyCode::F15,
0x72 => VirtualKeyCode::Insert,
0x73 => VirtualKeyCode::Home,
0x74 => VirtualKeyCode::PageUp,
0x75 => VirtualKeyCode::Delete,
0x76 => VirtualKeyCode::F4,
0x77 => VirtualKeyCode::End,
0x78 => VirtualKeyCode::F2,
0x79 => VirtualKeyCode::PageDown,
0x7a => VirtualKeyCode::F1,
0x7b => VirtualKeyCode::Left,
0x7c => VirtualKeyCode::Right,
0x7d => VirtualKeyCode::Down,
0x7e => VirtualKeyCode::Up,
//0x7f => unkown,
0xa => VirtualKeyCode::Caret,
_ => return None,
})
}
// While F1-F20 have scancodes we can match on, we have to check against UTF-16
// constants for the rest.
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ
pub fn check_function_keys(string: &String) -> Option<VirtualKeyCode> {
if let Some(ch) = string.encode_utf16().next() {
return Some(match ch {
0xf718 => VirtualKeyCode::F21,
0xf719 => VirtualKeyCode::F22,
0xf71a => VirtualKeyCode::F23,
0xf71b => VirtualKeyCode::F24,
_ => return None,
})
}
None
}
pub fn event_mods(event: id) -> ModifiersState {
let flags = unsafe {
NSEvent::modifierFlags(event)
};
ModifiersState {
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
}
}
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
// and there is no easy way to navtively retrieve the layout-dependent character.
// In winit, we use keycode to refer to the key's character, and so this function aligns
// AppKit's terminology with ours.
unsafe {
msg_send![event, keyCode]
}
}
pub unsafe fn modifier_event(
ns_event: id,
keymask: NSEventModifierFlags,
was_key_pressed: bool,
) -> Option<WindowEvent> {
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
let state = if was_key_pressed {
ElementState::Released
} else {
ElementState::Pressed
};
let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state,
scancode: scancode as _,
virtual_keycode,
modifiers: event_mods(ns_event),
},
})
} else {
None
}
}

View file

@ -1,742 +1,144 @@
use {ControlFlow, EventLoopClosed};
use cocoa::{self, appkit, foundation};
use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow};
use events::{self, ElementState, Event, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use super::window::Window2;
use std;
use std::os::raw::*;
use super::DeviceId;
use std::{
collections::VecDeque, mem, os::raw::c_void, process, ptr, sync::mpsc, marker::PhantomData
};
pub struct EventLoop {
modifiers: Modifiers,
pub shared: Arc<Shared>,
use cocoa::{appkit::NSApp, base::{id, nil}, foundation::NSAutoreleasePool};
use {
event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
};
use platform_impl::platform::{
app::APP_CLASS, app_delegate::APP_DELEGATE_CLASS,
app_state::AppState, monitor::{self, MonitorHandle},
observer::*, util::IdRef,
};
pub struct EventLoopWindowTarget<T: 'static> {
pub sender: mpsc::Sender<T>, // this is only here to be cloned elsewhere
pub receiver: mpsc::Receiver<T>,
}
// State shared between the `EventLoop` and its registered windows.
pub struct Shared {
pub windows: Mutex<Vec<Weak<Window2>>>,
pub pending_events: Mutex<VecDeque<Event>>,
// The user event callback given via either of the `poll_events` or `run_forever` methods.
//
// We store the user's callback here so that it may be accessed by each of the window delegate
// callbacks (e.g. resize, close, etc) for the duration of a call to either of the
// `poll_events` or `run_forever` methods.
//
// This is *only* `Some` for the duration of a call to either of these methods and will be
// `None` otherwise.
user_callback: UserCallback,
impl<T> Default for EventLoopWindowTarget<T> {
fn default() -> Self {
let (sender, receiver) = mpsc::channel();
EventLoopWindowTarget { sender, receiver }
}
}
pub struct EventLoop<T: 'static> {
window_target: RootWindowTarget<T>,
_delegate: IdRef,
}
impl<T> EventLoop<T> {
pub fn new() -> Self {
let delegate = unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("On macOS, `EventLoop` must be created on the main thread!");
}
// This must be done before `NSApp()` (equivalent to sending
// `sharedApplication`) is called anywhere else, or we'll end up
// with the wrong `NSApplication` class and the wrong thread could
// be marked as main.
let app: id = msg_send![APP_CLASS.0, sharedApplication];
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]);
let pool = NSAutoreleasePool::new(nil);
let _: () = msg_send![app, setDelegate:*delegate];
let _: () = msg_send![pool, drain];
delegate
};
setup_control_flow_observers();
EventLoop {
window_target: RootWindowTarget {
p: Default::default(),
_marker: PhantomData,
},
_delegate: delegate,
}
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::available_monitors()
}
#[inline]
pub fn primary_monitor(&self) -> MonitorHandle {
monitor::primary_monitor()
}
pub fn window_target(&self) -> &RootWindowTarget<T> {
&self.window_target
}
pub fn run<F>(self, callback: F) -> !
where F: 'static + FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unsafe {
let _pool = NSAutoreleasePool::new(nil);
let app = NSApp();
assert_ne!(app, nil);
AppState::set_callback(callback, self.window_target);
let _: () = msg_send![app, run];
AppState::exit();
process::exit(0)
}
}
pub fn run_return<F>(&mut self, _callback: F)
where F: FnMut(Event<T>, &RootWindowTarget<T>, &mut ControlFlow),
{
unimplemented!();
}
pub fn create_proxy(&self) -> Proxy<T> {
Proxy::new(self.window_target.p.sender.clone())
}
}
#[derive(Clone)]
pub struct Proxy {}
struct Modifiers {
shift_pressed: bool,
ctrl_pressed: bool,
win_pressed: bool,
alt_pressed: bool,
pub struct Proxy<T> {
sender: mpsc::Sender<T>,
source: CFRunLoopSourceRef,
}
// Wrapping the user callback in a type allows us to:
//
// - ensure the callback pointer is never accidentally cloned
// - ensure that only the `EventLoop` can `store` and `drop` the callback pointer
// - Share access to the user callback with the NSWindow callbacks.
pub struct UserCallback {
mutex: Mutex<Option<*mut FnMut(Event)>>,
}
unsafe impl<T> Send for Proxy<T> {}
unsafe impl<T> Sync for Proxy<T> {}
impl Shared {
pub fn new() -> Self {
Shared {
windows: Mutex::new(Vec::new()),
pending_events: Mutex::new(VecDeque::new()),
user_callback: UserCallback { mutex: Mutex::new(None) },
}
}
fn call_user_callback_with_pending_events(&self) {
loop {
let event = match self.pending_events.lock().unwrap().pop_front() {
Some(event) => event,
None => return,
};
unsafe {
self.user_callback.call_with_event(event);
}
}
}
// Calls the user callback if one exists.
//
// Otherwise, stores the event in the `pending_events` queue.
//
// This is necessary for the case when `WindowDelegate` callbacks are triggered during a call
// to the user's callback.
pub fn call_user_callback_with_event_or_store_in_pending(&self, event: Event) {
if self.user_callback.mutex.lock().unwrap().is_some() {
unsafe {
self.user_callback.call_with_event(event);
}
} else {
self.pending_events.lock().unwrap().push_back(event);
}
}
// Removes the window with the given `Id` from the `windows` list.
//
// This is called in response to `windowWillClose`.
pub fn find_and_remove_window(&self, id: super::window::Id) {
if let Ok(mut windows) = self.windows.lock() {
windows.retain(|w| match w.upgrade() {
Some(w) => w.id() != id,
None => false,
});
}
}
}
impl Modifiers {
pub fn new() -> Self {
Modifiers {
shift_pressed: false,
ctrl_pressed: false,
win_pressed: false,
alt_pressed: false,
}
}
}
impl UserCallback {
// Here we store user's `callback` behind the mutex so that they may be safely shared between
// each of the window delegates.
//
// In order to make sure that the pointer is always valid, we must manually guarantee that it
// is dropped before the callback itself is dropped. Thus, this should *only* be called at the
// beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the
// callback at the end of their scope using the `drop` method.
fn store<F>(&self, callback: &mut F)
where F: FnMut(Event)
{
let trait_object = callback as &mut FnMut(Event);
let trait_object_ptr = trait_object as *const FnMut(Event) as *mut FnMut(Event);
*self.mutex.lock().unwrap() = Some(trait_object_ptr);
}
// Emits the given event via the user-given callback.
//
// This is unsafe as it requires dereferencing the pointer to the user-given callback. We
// guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given
// callback.
//
// Note that the callback may not always be `Some`. This is because some `NSWindowDelegate`
// callbacks can be triggered by means other than `NSApp().sendEvent`. For example, if a window
// is destroyed or created during a call to the user's callback, the `WindowDelegate` methods
// may be called with `windowShouldClose` or `windowDidResignKey`.
unsafe fn call_with_event(&self, event: Event) {
let callback = match self.mutex.lock().unwrap().take() {
Some(callback) => callback,
None => return,
};
(*callback)(event);
*self.mutex.lock().unwrap() = Some(callback);
}
// Used to drop the user callback pointer at the end of the `poll_events` and `run_forever`
// methods. This is done to enforce our guarantee that the top callback will never live longer
// than the call to either `poll_events` or `run_forever` to which it was given.
fn drop(&self) {
self.mutex.lock().unwrap().take();
}
}
impl EventLoop {
pub fn new() -> Self {
// Mark this thread as the main thread of the Cocoa event system.
//
// This must be done before any worker threads get a chance to call it
// (e.g., via `EventLoopProxy::wakeup()`), causing a wrong thread to be
// marked as the main thread.
unsafe { appkit::NSApp(); }
EventLoop {
shared: Arc::new(Shared::new()),
modifiers: Modifiers::new(),
}
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(Event),
{
impl<T> Proxy<T> {
fn new(sender: mpsc::Sender<T>) -> Self {
unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
// just wakeup the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = event_loop_proxy_handler;
let source = CFRunLoopSourceCreate(
ptr::null_mut(),
CFIndex::max_value() - 1,
&mut context,
);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
Proxy { sender, source }
}
self.shared.user_callback.store(&mut callback);
// Loop as long as we have pending events to return.
loop {
unsafe {
// First, yield all pending events.
self.shared.call_user_callback_with_pending_events();
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
// Poll for the next event, returning `nil` if there are none.
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
foundation::NSDate::distantPast(cocoa::base::nil),
foundation::NSDefaultRunLoopMode,
cocoa::base::YES);
let event = self.ns_event_to_event(ns_event);
let _: () = msg_send![pool, release];
match event {
// Call the user's callback.
Some(event) => self.shared.user_callback.call_with_event(event),
None => break,
}
}
}
self.shared.user_callback.drop();
}
pub fn run_forever<F>(&mut self, mut callback: F)
where F: FnMut(Event) -> ControlFlow
{
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("Events can only be polled from the main thread on macOS");
}
}
// Track whether or not control flow has changed.
let control_flow = std::cell::Cell::new(ControlFlow::Continue);
let mut callback = |event| {
if let ControlFlow::Break = callback(event) {
control_flow.set(ControlFlow::Break);
}
};
self.shared.user_callback.store(&mut callback);
loop {
unsafe {
// First, yield all pending events.
self.shared.call_user_callback_with_pending_events();
if let ControlFlow::Break = control_flow.get() {
break;
}
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
// Wait for the next event. Note that this function blocks during resize.
let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_(
NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(),
foundation::NSDate::distantFuture(cocoa::base::nil),
foundation::NSDefaultRunLoopMode,
cocoa::base::YES);
let maybe_event = self.ns_event_to_event(ns_event);
// Release the pool before calling the top callback in case the user calls either
// `run_forever` or `poll_events` within the callback.
let _: () = msg_send![pool, release];
if let Some(event) = maybe_event {
self.shared.user_callback.call_with_event(event);
if let ControlFlow::Break = control_flow.get() {
break;
}
}
}
}
self.shared.user_callback.drop();
}
// Convert some given `NSEvent` into a winit `Event`.
unsafe fn ns_event_to_event(&mut self, ns_event: cocoa::base::id) -> Option<Event> {
if ns_event == cocoa::base::nil {
return None;
}
// FIXME: Despite not being documented anywhere, an `NSEvent` is produced when a user opens
// Spotlight while the NSApplication is in focus. This `NSEvent` produces a `NSEventType`
// with value `21`. This causes a SEGFAULT as soon as we try to match on the `NSEventType`
// enum as there is no variant associated with the value. Thus, we return early if this
// sneaky event occurs. If someone does find some documentation on this, please fix this by
// adding an appropriate variant to the `NSEventType` enum in the cocoa-rs crate.
if ns_event.eventType() as u64 == 21 {
return None;
}
let event_type = ns_event.eventType();
let ns_window = ns_event.window();
let window_id = super::window::get_window_id(ns_window);
// FIXME: Document this. Why do we do this? Seems like it passes on events to window/app.
// If we don't do this, window does not become main for some reason.
appkit::NSApp().sendEvent_(ns_event);
let windows = self.shared.windows.lock().unwrap();
let maybe_window = windows.iter()
.filter_map(Weak::upgrade)
.find(|window| window_id == window.id());
let into_event = |window_event| Event::WindowEvent {
window_id: ::WindowId(window_id),
event: window_event,
};
// Returns `Some` window if one of our windows is the key window.
let maybe_key_window = || windows.iter()
.filter_map(Weak::upgrade)
.find(|window| {
let is_key_window: cocoa::base::BOOL = msg_send![*window.window, isKeyWindow];
is_key_window == cocoa::base::YES
});
match event_type {
// https://github.com/glfw/glfw/blob/50eccd298a2bbc272b4977bd162d3e4b55f15394/src/cocoa_window.m#L881
appkit::NSKeyUp => {
if let Some(key_window) = maybe_key_window() {
if event_mods(ns_event).logo {
let _: () = msg_send![*key_window.window, sendEvent:ns_event];
}
}
None
},
// similar to above, but for `<Cmd-.>`, the keyDown is suppressed instead of the
// KeyUp, and the above trick does not appear to work.
appkit::NSKeyDown => {
let modifiers = event_mods(ns_event);
let keycode = NSEvent::keyCode(ns_event);
if modifiers.logo && keycode == 47 {
modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false)
.map(into_event)
} else {
None
}
},
appkit::NSFlagsChanged => {
let mut events = std::collections::VecDeque::new();
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSShiftKeyMask,
self.modifiers.shift_pressed,
) {
self.modifiers.shift_pressed = !self.modifiers.shift_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSControlKeyMask,
self.modifiers.ctrl_pressed,
) {
self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSCommandKeyMask,
self.modifiers.win_pressed,
) {
self.modifiers.win_pressed = !self.modifiers.win_pressed;
events.push_back(into_event(window_event));
}
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSAlternateKeyMask,
self.modifiers.alt_pressed,
) {
self.modifiers.alt_pressed = !self.modifiers.alt_pressed;
events.push_back(into_event(window_event));
}
let event = events.pop_front();
self.shared.pending_events
.lock()
.unwrap()
.extend(events.into_iter());
event
},
appkit::NSMouseEntered => {
let window = match maybe_window.or_else(maybe_key_window) {
Some(window) => window,
None => return None,
};
let window_point = ns_event.locationInWindow();
let view_point = if ns_window == cocoa::base::nil {
let ns_size = foundation::NSSize::new(0.0, 0.0);
let ns_rect = foundation::NSRect::new(window_point, ns_size);
let window_rect = window.window.convertRectFromScreen_(ns_rect);
window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil)
} else {
window.view.convertPoint_fromView_(window_point, cocoa::base::nil)
};
let view_rect = NSView::frame(*window.view);
let x = view_point.x as f64;
let y = (view_rect.size.height - view_point.y) as f64;
let window_event = WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(ns_event),
};
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
self.shared.pending_events.lock().unwrap().push_back(event);
Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID }))
},
appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) },
appkit::NSMouseMoved |
appkit::NSLeftMouseDragged |
appkit::NSOtherMouseDragged |
appkit::NSRightMouseDragged => {
// If the mouse movement was on one of our windows, use it.
// Otherwise, if one of our windows is the key window (receiving input), use it.
// Otherwise, return `None`.
match maybe_window.or_else(maybe_key_window) {
Some(_window) => (),
None => return None,
}
let mut events = std::collections::VecDeque::with_capacity(3);
let delta_x = ns_event.deltaX() as f64;
if delta_x != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
let delta_y = ns_event.deltaY() as f64;
if delta_y != 0.0 {
let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
if delta_x != 0.0 || delta_y != 0.0 {
let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) };
let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event };
events.push_back(event);
}
let event = events.pop_front();
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
event
},
appkit::NSScrollWheel => {
// If none of the windows received the scroll, return `None`.
if maybe_window.is_none() {
return None;
}
use events::MouseScrollDelta::{LineDelta, PixelDelta};
let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
// TODO: This is probably wrong
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
};
let phase = match ns_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
_ => TouchPhase::Moved,
};
self.shared.pending_events.lock().unwrap().push_back(Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel {
delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES {
PixelDelta((
ns_event.scrollingDeltaX() as f64,
ns_event.scrollingDeltaY() as f64,
).into())
} else {
LineDelta(
ns_event.scrollingDeltaX() as f32,
ns_event.scrollingDeltaY() as f32,
)
},
}
});
let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase, modifiers: event_mods(ns_event) };
Some(into_event(window_event))
},
appkit::NSEventTypePressure => {
let pressure = ns_event.pressure();
let stage = ns_event.stage();
let window_event = WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: pressure, stage: stage };
Some(into_event(window_event))
},
appkit::NSApplicationDefined => match ns_event.subtype() {
appkit::NSEventSubtype::NSApplicationActivatedEventType => {
Some(Event::Awakened)
},
_ => None,
},
_ => None,
}
}
pub fn create_proxy(&self) -> Proxy {
Proxy {}
}
}
impl Proxy {
pub fn wakeup(&self) -> Result<(), EventLoopClosed> {
// Awaken the event loop by triggering `NSApplicationActivatedEventType`.
unsafe {
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
let event =
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
cocoa::base::nil,
appkit::NSApplicationDefined,
foundation::NSPoint::new(0.0, 0.0),
appkit::NSEventModifierFlags::empty(),
0.0,
0,
cocoa::base::nil,
appkit::NSEventSubtype::NSApplicationActivatedEventType,
0,
0);
appkit::NSApp().postEvent_atStart_(event, cocoa::base::NO);
foundation::NSAutoreleasePool::drain(pool);
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}
pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
Some(match code {
0x00 => events::VirtualKeyCode::A,
0x01 => events::VirtualKeyCode::S,
0x02 => events::VirtualKeyCode::D,
0x03 => events::VirtualKeyCode::F,
0x04 => events::VirtualKeyCode::H,
0x05 => events::VirtualKeyCode::G,
0x06 => events::VirtualKeyCode::Z,
0x07 => events::VirtualKeyCode::X,
0x08 => events::VirtualKeyCode::C,
0x09 => events::VirtualKeyCode::V,
//0x0a => World 1,
0x0b => events::VirtualKeyCode::B,
0x0c => events::VirtualKeyCode::Q,
0x0d => events::VirtualKeyCode::W,
0x0e => events::VirtualKeyCode::E,
0x0f => events::VirtualKeyCode::R,
0x10 => events::VirtualKeyCode::Y,
0x11 => events::VirtualKeyCode::T,
0x12 => events::VirtualKeyCode::Key1,
0x13 => events::VirtualKeyCode::Key2,
0x14 => events::VirtualKeyCode::Key3,
0x15 => events::VirtualKeyCode::Key4,
0x16 => events::VirtualKeyCode::Key6,
0x17 => events::VirtualKeyCode::Key5,
0x18 => events::VirtualKeyCode::Equals,
0x19 => events::VirtualKeyCode::Key9,
0x1a => events::VirtualKeyCode::Key7,
0x1b => events::VirtualKeyCode::Minus,
0x1c => events::VirtualKeyCode::Key8,
0x1d => events::VirtualKeyCode::Key0,
0x1e => events::VirtualKeyCode::RBracket,
0x1f => events::VirtualKeyCode::O,
0x20 => events::VirtualKeyCode::U,
0x21 => events::VirtualKeyCode::LBracket,
0x22 => events::VirtualKeyCode::I,
0x23 => events::VirtualKeyCode::P,
0x24 => events::VirtualKeyCode::Return,
0x25 => events::VirtualKeyCode::L,
0x26 => events::VirtualKeyCode::J,
0x27 => events::VirtualKeyCode::Apostrophe,
0x28 => events::VirtualKeyCode::K,
0x29 => events::VirtualKeyCode::Semicolon,
0x2a => events::VirtualKeyCode::Backslash,
0x2b => events::VirtualKeyCode::Comma,
0x2c => events::VirtualKeyCode::Slash,
0x2d => events::VirtualKeyCode::N,
0x2e => events::VirtualKeyCode::M,
0x2f => events::VirtualKeyCode::Period,
0x30 => events::VirtualKeyCode::Tab,
0x31 => events::VirtualKeyCode::Space,
0x32 => events::VirtualKeyCode::Grave,
0x33 => events::VirtualKeyCode::Back,
//0x34 => unkown,
0x35 => events::VirtualKeyCode::Escape,
0x36 => events::VirtualKeyCode::LWin,
0x37 => events::VirtualKeyCode::RWin,
0x38 => events::VirtualKeyCode::LShift,
//0x39 => Caps lock,
0x3a => events::VirtualKeyCode::LAlt,
0x3b => events::VirtualKeyCode::LControl,
0x3c => events::VirtualKeyCode::RShift,
0x3d => events::VirtualKeyCode::RAlt,
0x3e => events::VirtualKeyCode::RControl,
//0x3f => Fn key,
0x40 => events::VirtualKeyCode::F17,
0x41 => events::VirtualKeyCode::Decimal,
//0x42 -> unkown,
0x43 => events::VirtualKeyCode::Multiply,
//0x44 => unkown,
0x45 => events::VirtualKeyCode::Add,
//0x46 => unkown,
0x47 => events::VirtualKeyCode::Numlock,
//0x48 => KeypadClear,
0x49 => events::VirtualKeyCode::VolumeUp,
0x4a => events::VirtualKeyCode::VolumeDown,
0x4b => events::VirtualKeyCode::Divide,
0x4c => events::VirtualKeyCode::NumpadEnter,
//0x4d => unkown,
0x4e => events::VirtualKeyCode::Subtract,
0x4f => events::VirtualKeyCode::F18,
0x50 => events::VirtualKeyCode::F19,
0x51 => events::VirtualKeyCode::NumpadEquals,
0x52 => events::VirtualKeyCode::Numpad0,
0x53 => events::VirtualKeyCode::Numpad1,
0x54 => events::VirtualKeyCode::Numpad2,
0x55 => events::VirtualKeyCode::Numpad3,
0x56 => events::VirtualKeyCode::Numpad4,
0x57 => events::VirtualKeyCode::Numpad5,
0x58 => events::VirtualKeyCode::Numpad6,
0x59 => events::VirtualKeyCode::Numpad7,
0x5a => events::VirtualKeyCode::F20,
0x5b => events::VirtualKeyCode::Numpad8,
0x5c => events::VirtualKeyCode::Numpad9,
0x5d => events::VirtualKeyCode::Yen,
//0x5e => JIS Ro,
//0x5f => unkown,
0x60 => events::VirtualKeyCode::F5,
0x61 => events::VirtualKeyCode::F6,
0x62 => events::VirtualKeyCode::F7,
0x63 => events::VirtualKeyCode::F3,
0x64 => events::VirtualKeyCode::F8,
0x65 => events::VirtualKeyCode::F9,
//0x66 => JIS Eisuu (macOS),
0x67 => events::VirtualKeyCode::F11,
//0x68 => JIS Kana (macOS),
0x69 => events::VirtualKeyCode::F13,
0x6a => events::VirtualKeyCode::F16,
0x6b => events::VirtualKeyCode::F14,
//0x6c => unkown,
0x6d => events::VirtualKeyCode::F10,
//0x6e => unkown,
0x6f => events::VirtualKeyCode::F12,
//0x70 => unkown,
0x71 => events::VirtualKeyCode::F15,
0x72 => events::VirtualKeyCode::Insert,
0x73 => events::VirtualKeyCode::Home,
0x74 => events::VirtualKeyCode::PageUp,
0x75 => events::VirtualKeyCode::Delete,
0x76 => events::VirtualKeyCode::F4,
0x77 => events::VirtualKeyCode::End,
0x78 => events::VirtualKeyCode::F2,
0x79 => events::VirtualKeyCode::PageDown,
0x7a => events::VirtualKeyCode::F1,
0x7b => events::VirtualKeyCode::Left,
0x7c => events::VirtualKeyCode::Right,
0x7d => events::VirtualKeyCode::Down,
0x7e => events::VirtualKeyCode::Up,
//0x7f => unkown,
0xa => events::VirtualKeyCode::Caret,
_ => return None,
})
}
pub fn check_additional_virtual_key_codes(
s: &Option<String>
) -> Option<events::VirtualKeyCode> {
if let &Some(ref s) = s {
if let Some(ch) = s.encode_utf16().next() {
return Some(match ch {
0xf718 => events::VirtualKeyCode::F21,
0xf719 => events::VirtualKeyCode::F22,
0xf71a => events::VirtualKeyCode::F23,
0xf71b => events::VirtualKeyCode::F24,
_ => return None,
})
}
}
None
}
pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
let flags = unsafe {
NSEvent::modifierFlags(event)
};
ModifiersState {
shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
}
}
unsafe fn modifier_event(
ns_event: cocoa::base::id,
keymask: NSEventModifierFlags,
was_key_pressed: bool,
) -> Option<WindowEvent> {
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
let state = if was_key_pressed {
ElementState::Released
} else {
ElementState::Pressed
};
let keycode = NSEvent::keyCode(ns_event);
let scancode = keycode as u32;
let virtual_keycode = to_virtual_key_code(keycode);
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state,
scancode,
virtual_keycode,
modifiers: event_mods(ns_event),
},
})
} else {
None
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

View file

@ -95,6 +95,7 @@ pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
pub const kCGCursorWindowLevelKey: NSInteger = 19;
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
#[derive(Debug, Clone, Copy)]
pub enum NSWindowLevel {
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,

View file

@ -1,9 +1,31 @@
#![cfg(target_os = "macos")]
pub use self::event_loop::{EventLoop, Proxy as EventLoopProxy};
pub use self::monitor::MonitorHandle;
pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window2};
use std::sync::Arc;
mod app;
mod app_delegate;
mod app_state;
mod event;
mod event_loop;
mod ffi;
mod monitor;
mod observer;
mod util;
mod view;
mod window;
mod window_delegate;
use std::{fmt, ops::Deref, sync::Arc};
use {
event::DeviceId as RootDeviceId, window::WindowAttributes,
error::OsError as RootOsError,
};
pub use self::{
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
monitor::MonitorHandle,
window::{
Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow,
},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
@ -14,38 +36,48 @@ impl DeviceId {
}
}
use {CreationError};
// Constant device ID; to be removed when if backend is updated to report real device IDs.
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
pub struct Window {
pub window: Arc<Window2>,
window: Arc<UnownedWindow>,
// We keep this around so that it doesn't get dropped until the window does.
_delegate: util::IdRef,
}
impl ::std::ops::Deref for Window {
type Target = Window2;
#[derive(Debug)]
pub enum OsError {
CGError(core_graphics::base::CGError),
CreationError(&'static str)
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Deref for Window {
type Target = UnownedWindow;
#[inline]
fn deref(&self) -> &Window2 {
fn deref(&self) -> &Self::Target {
&*self.window
}
}
impl Window {
pub fn new(event_loop: &EventLoop,
attributes: ::WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError>
{
let weak_shared = Arc::downgrade(&event_loop.shared);
let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs)));
let weak_window = Arc::downgrade(&window);
event_loop.shared.windows.lock().unwrap().push(weak_window);
Ok(Window { window: window })
pub fn new<T: 'static>(
_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
let (window, _delegate) = UnownedWindow::new(attributes, pl_attribs)?;
Ok(Window { window, _delegate })
}
}
mod event_loop;
mod ffi;
mod monitor;
mod util;
mod view;
mod window;
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
OsError::CGError(e) => f.pad(&format!("CGError {}", e)),
OsError::CreationError(e) => f.pad(e),
}
}
}

View file

@ -1,23 +1,19 @@
use std::collections::VecDeque;
use std::fmt;
use std::{collections::VecDeque, fmt};
use cocoa::appkit::NSScreen;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSString, NSUInteger};
use cocoa::{appkit::NSScreen, base::{id, nil}, foundation::{NSString, NSUInteger}};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use {PhysicalPosition, PhysicalSize};
use super::EventLoop;
use super::window::{IdRef, Window2};
use dpi::{PhysicalPosition, PhysicalSize};
use platform_impl::platform::util::IdRef;
#[derive(Clone, PartialEq)]
pub struct MonitorHandle(CGDirectDisplayID);
fn get_available_monitors() -> VecDeque<MonitorHandle> {
pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
for d in displays {
monitors.push_back(MonitorHandle(d));
for display in displays {
monitors.push_back(MonitorHandle(display));
}
monitors
} else {
@ -25,42 +21,13 @@ fn get_available_monitors() -> VecDeque<MonitorHandle> {
}
}
pub fn get_primary_monitor() -> MonitorHandle {
let id = MonitorHandle(CGDisplay::main().id);
id
}
impl EventLoop {
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor()
}
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorHandle {
let id = MonitorHandle(id);
id
}
}
impl Window2 {
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor()
}
pub fn primary_monitor() -> MonitorHandle {
MonitorHandle(CGDisplay::main().id)
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO: Do this using the proper fmt API
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
@ -71,11 +38,11 @@ impl fmt::Debug for MonitorHandle {
}
let monitor_id_proxy = MonitorHandle {
name: self.get_name(),
native_identifier: self.get_native_identifier(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
name: self.name(),
native_identifier: self.native_identifier(),
dimensions: self.dimensions(),
position: self.position(),
hidpi_factor: self.hidpi_factor(),
};
monitor_id_proxy.fmt(f)
@ -83,48 +50,52 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub fn get_name(&self) -> Option<String> {
pub fn new(id: CGDirectDisplayID) -> Self {
MonitorHandle(id)
}
pub fn name(&self) -> Option<String> {
let MonitorHandle(display_id) = *self;
let screen_num = CGDisplay::new(display_id).model_number();
Some(format!("Monitor #{}", screen_num))
}
#[inline]
pub fn get_native_identifier(&self) -> u32 {
pub fn native_identifier(&self) -> u32 {
self.0
}
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
let MonitorHandle(display_id) = *self;
let display = CGDisplay::new(display_id);
let height = display.pixels_high();
let width = display.pixels_wide();
PhysicalSize::from_logical(
(width as f64, height as f64),
self.get_hidpi_factor(),
self.hidpi_factor(),
)
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
let bounds = unsafe { CGDisplayBounds(self.get_native_identifier()) };
pub fn position(&self) -> PhysicalPosition {
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
PhysicalPosition::from_logical(
(bounds.origin.x as f64, bounds.origin.y as f64),
self.get_hidpi_factor(),
self.hidpi_factor(),
)
}
pub fn get_hidpi_factor(&self) -> f64 {
let screen = match self.get_nsscreen() {
pub fn hidpi_factor(&self) -> f64 {
let screen = match self.nsscreen() {
Some(screen) => screen,
None => return 1.0, // default to 1.0 when we can't find the screen
};
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
}
pub(crate) fn get_nsscreen(&self) -> Option<id> {
pub(crate) fn nsscreen(&self) -> Option<id> {
unsafe {
let native_id = self.get_native_identifier();
let native_id = self.native_identifier();
let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count];
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));

View file

@ -0,0 +1,259 @@
use std::{self, ptr, os::raw::*, time::Instant};
use platform_impl::platform::app_state::AppState;
#[link(name = "CoreFoundation", kind = "framework")]
extern {
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode;
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef,
activities: CFOptionFlags,
repeats: Boolean,
order: CFIndex,
callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) -> CFRunLoopObserverRef;
pub fn CFRunLoopAddObserver(
rl: CFRunLoopRef,
observer: CFRunLoopObserverRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerCreate(
allocator: CFAllocatorRef,
fireDate: CFAbsoluteTime,
interval: CFTimeInterval,
flags: CFOptionFlags,
order: CFIndex,
callout: CFRunLoopTimerCallBack,
context: *mut CFRunLoopTimerContext,
) -> CFRunLoopTimerRef;
pub fn CFRunLoopAddTimer(
rl: CFRunLoopRef,
timer: CFRunLoopTimerRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerSetNextFireDate(
timer: CFRunLoopTimerRef,
fireDate: CFAbsoluteTime,
);
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
pub fn CFRunLoopSourceCreate(
allocator: CFAllocatorRef,
order: CFIndex,
context: *mut CFRunLoopSourceContext,
) -> CFRunLoopSourceRef;
pub fn CFRunLoopAddSource(
rl: CFRunLoopRef,
source: CFRunLoopSourceRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
pub fn CFRelease(cftype: *const c_void);
}
pub type Boolean = u8;
const FALSE: Boolean = 0;
const TRUE: Boolean = 1;
pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {}
pub type CFRunLoopRef = *mut CFRunLoop;
pub type CFRunLoopMode = CFStringRef;
pub enum CFRunLoopObserver {}
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
pub enum CFRunLoopTimer {}
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
pub enum CFRunLoopSource {}
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
pub enum CFString {}
pub type CFStringRef = *const CFString;
pub type CFHashCode = c_ulong;
pub type CFIndex = c_long;
pub type CFOptionFlags = c_ulong;
pub type CFRunLoopActivity = CFOptionFlags;
pub type CFAbsoluteTime = CFTimeInterval;
pub type CFTimeInterval = f64;
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
pub type CFRunLoopObserverCallBack = extern "C" fn(
observer: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
info: *mut c_void,
);
pub type CFRunLoopTimerCallBack = extern "C" fn(
timer: CFRunLoopTimerRef,
info: *mut c_void
);
pub enum CFRunLoopObserverContext {}
pub enum CFRunLoopTimerContext {}
#[repr(C)]
pub struct CFRunLoopSourceContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub perform: extern "C" fn(*mut c_void),
}
// begin is queued with the highest priority to ensure it is processed before other observers
extern fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => {
//trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::wakeup();
//trace!("Completed `CFRunLoopAfterWaiting`");
},
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopDestroyed would get sent after EventsCleared
extern fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => {
//trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::cleared();
//trace!("Completed `CFRunLoopBeforeWaiting`");
},
kCFRunLoopExit => (),//unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
struct RunLoop(CFRunLoopRef);
impl RunLoop {
unsafe fn get() -> Self {
RunLoop(CFRunLoopGetMain())
}
unsafe fn add_observer(
&self,
flags: CFOptionFlags,
priority: CFIndex,
handler: CFRunLoopObserverCallBack,
) {
let observer = CFRunLoopObserverCreate(
ptr::null_mut(),
flags,
TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode);
}
}
pub fn setup_control_flow_observers() {
unsafe {
let run_loop = RunLoop::get();
run_loop.add_observer(
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
CFIndex::min_value(),
control_flow_begin_handler,
);
run_loop.add_observer(
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::max_value(),
control_flow_end_handler,
);
}
}
pub struct EventLoopWaker {
timer: CFRunLoopTimerRef,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl Default for EventLoopWaker {
fn default() -> EventLoopWaker {
extern fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// create a timer with a 1µs interval (1ns does not work) to mimic polling.
// it is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediatley in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
std::f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
EventLoopWaker { timer }
}
}
}
impl EventLoopWaker {
pub fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
}
pub fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
}
pub fn start_at(&mut self, instant: Instant) {
let now = Instant::now();
if now >= instant {
self.start();
} else {
unsafe {
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
}
}
}

View file

@ -1,38 +0,0 @@
use cocoa::appkit::NSWindowStyleMask;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSRect, NSUInteger};
use core_graphics::display::CGDisplay;
use platform_impl::platform::ffi;
use platform_impl::platform::window::IdRef;
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
location: ffi::NSNotFound as NSUInteger,
length: 0,
};
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
use cocoa::appkit::NSWindow;
window.setStyleMask_(mask);
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
let input_context: id = msg_send![input_context, initWithClient:view];
IdRef::new(input_context)
}
#[allow(dead_code)]
pub unsafe fn open_emoji_picker() {
let app: id = msg_send![class!(NSApplication), sharedApplication];
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
}

View file

@ -0,0 +1,327 @@
use std::{os::raw::c_void, sync::{Mutex, Weak}};
use cocoa::{
appkit::{CGFloat, NSWindow, NSWindowStyleMask},
base::{id, nil},
foundation::{NSAutoreleasePool, NSPoint, NSSize},
};
use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f};
use dpi::LogicalSize;
use platform_impl::platform::{ffi, window::SharedState};
unsafe fn set_style_mask(nswindow: id, nsview: id, mask: NSWindowStyleMask) {
nswindow.setStyleMask_(mask);
// If we don't do this, key handling will break
// (at least until the window is clicked again/etc.)
nswindow.makeFirstResponder_(nsview);
}
struct SetStyleMaskData {
nswindow: id,
nsview: id,
mask: NSWindowStyleMask,
}
impl SetStyleMaskData {
fn new_ptr(
nswindow: id,
nsview: id,
mask: NSWindowStyleMask,
) -> *mut Self {
Box::into_raw(Box::new(SetStyleMaskData { nswindow, nsview, mask }))
}
}
extern 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.nswindow, context.nsview, 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(nswindow: id, nsview: id, mask: NSWindowStyleMask) {
let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
}
pub unsafe fn set_style_mask_sync(nswindow: id, nsview: id, mask: NSWindowStyleMask) {
let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask);
dispatch_sync_f(
dispatch_get_main_queue(),
context as *mut _,
set_style_mask_callback,
);
}
struct SetContentSizeData {
nswindow: id,
size: LogicalSize,
}
impl SetContentSizeData {
fn new_ptr(
nswindow: id,
size: LogicalSize,
) -> *mut Self {
Box::into_raw(Box::new(SetContentSizeData { nswindow, size }))
}
}
extern fn set_content_size_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetContentSizeData;
{
let context = &*context_ptr;
NSWindow::setContentSize_(
context.nswindow,
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(nswindow: id, size: LogicalSize) {
let context = SetContentSizeData::new_ptr(nswindow, size);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_content_size_callback,
);
}
struct SetFrameTopLeftPointData {
nswindow: id,
point: NSPoint,
}
impl SetFrameTopLeftPointData {
fn new_ptr(
nswindow: id,
point: NSPoint,
) -> *mut Self {
Box::into_raw(Box::new(SetFrameTopLeftPointData { nswindow, point }))
}
}
extern 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.nswindow, 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(nswindow: id, point: NSPoint) {
let context = SetFrameTopLeftPointData::new_ptr(nswindow, point);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_frame_top_left_point_callback,
);
}
struct SetLevelData {
nswindow: id,
level: ffi::NSWindowLevel,
}
impl SetLevelData {
fn new_ptr(
nswindow: id,
level: ffi::NSWindowLevel,
) -> *mut Self {
Box::into_raw(Box::new(SetLevelData { nswindow, level }))
}
}
extern fn set_level_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut SetLevelData;
{
let context = &*context_ptr;
context.nswindow.setLevel_(context.level as _);
}
Box::from_raw(context_ptr);
}
}
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
pub unsafe fn set_level_async(nswindow: id, level: ffi::NSWindowLevel) {
let context = SetLevelData::new_ptr(nswindow, level);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
set_level_callback,
);
}
struct ToggleFullScreenData {
nswindow: id,
nsview: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
}
impl ToggleFullScreenData {
fn new_ptr(
nswindow: id,
nsview: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) -> *mut Self {
Box::into_raw(Box::new(ToggleFullScreenData {
nswindow,
nsview,
not_fullscreen,
shared_state,
}))
}
}
extern 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.nswindow.styleMask();
let required = NSWindowStyleMask::NSTitledWindowMask
| NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
set_style_mask(context.nswindow, context.nsview, 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`");
}
}
}
context.nswindow.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(
nswindow: id,
nsview: id,
not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>,
) {
let context = ToggleFullScreenData::new_ptr(
nswindow,
nsview,
not_fullscreen,
shared_state,
);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
toggle_full_screen_callback,
);
}
struct OrderOutData {
nswindow: id,
}
impl OrderOutData {
fn new_ptr(nswindow: id) -> *mut Self {
Box::into_raw(Box::new(OrderOutData { nswindow }))
}
}
extern fn order_out_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut OrderOutData;
{
let context = &*context_ptr;
context.nswindow.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(nswindow: id) {
let context = OrderOutData::new_ptr(nswindow);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
order_out_callback,
);
}
struct MakeKeyAndOrderFrontData {
nswindow: id,
}
impl MakeKeyAndOrderFrontData {
fn new_ptr(nswindow: id) -> *mut Self {
Box::into_raw(Box::new(MakeKeyAndOrderFrontData { nswindow }))
}
}
extern fn make_key_and_order_front_callback(context: *mut c_void) {
unsafe {
let context_ptr = context as *mut MakeKeyAndOrderFrontData;
{
let context = &*context_ptr;
context.nswindow.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(nswindow: id) {
let context = MakeKeyAndOrderFrontData::new_ptr(nswindow);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
make_key_and_order_front_callback,
);
}
struct CloseData {
nswindow: id,
}
impl CloseData {
fn new_ptr(nswindow: id) -> *mut Self {
Box::into_raw(Box::new(CloseData { nswindow }))
}
}
extern 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.nswindow.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(nswindow: id) {
let context = CloseData::new_ptr(nswindow);
dispatch_async_f(
dispatch_get_main_queue(),
context as *mut _,
close_callback,
);
}

View file

@ -0,0 +1,133 @@
use cocoa::{
appkit::NSImage, base::{id, nil},
foundation::{NSDictionary, NSPoint, NSString},
};
use objc::runtime::Sel;
use window::CursorIcon;
pub enum Cursor {
Native(&'static str),
Undocumented(&'static str),
WebKit(&'static str),
}
impl From<CursorIcon> for Cursor {
fn from(cursor: CursorIcon) -> Self {
match cursor {
CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"),
CursorIcon::Hand => Cursor::Native("pointingHandCursor"),
CursorIcon::Grabbing | CursorIcon::Grab => Cursor::Native("closedHandCursor"),
CursorIcon::Text => Cursor::Native("IBeamCursor"),
CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
CursorIcon::Copy => Cursor::Native("dragCopyCursor"),
CursorIcon::Alias => Cursor::Native("dragLinkCursor"),
CursorIcon::NotAllowed | CursorIcon::NoDrop => Cursor::Native("operationNotAllowedCursor"),
CursorIcon::ContextMenu => Cursor::Native("contextualMenuCursor"),
CursorIcon::Crosshair => Cursor::Native("crosshairCursor"),
CursorIcon::EResize => Cursor::Native("resizeRightCursor"),
CursorIcon::NResize => Cursor::Native("resizeUpCursor"),
CursorIcon::WResize => Cursor::Native("resizeLeftCursor"),
CursorIcon::SResize => Cursor::Native("resizeDownCursor"),
CursorIcon::EwResize | CursorIcon::ColResize => Cursor::Native("resizeLeftRightCursor"),
CursorIcon::NsResize | CursorIcon::RowResize => Cursor::Native("resizeUpDownCursor"),
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
CursorIcon::Help => Cursor::Undocumented("_helpCursor"),
CursorIcon::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
CursorIcon::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
CursorIcon::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
CursorIcon::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
CursorIcon::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
CursorIcon::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
CursorIcon::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
CursorIcon::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),
// While these are available, the former just loads a white arrow,
// and the latter loads an ugly deflated beachball!
// CursorIcon::Move => Cursor::Undocumented("_moveCursor"),
// CursorIcon::Wait => Cursor::Undocumented("_waitCursor"),
// An even more undocumented cursor...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
CursorIcon::Wait | CursorIcon::Progress => Cursor::Undocumented("busyButClickableCursor"),
// For the rest, we can just snatch the cursors from WebKit...
// They fit the style of the native cursors, and will seem
// completely standard to macOS users.
// https://stackoverflow.com/a/21786835/5435443
CursorIcon::Move | CursorIcon::AllScroll => Cursor::WebKit("move"),
CursorIcon::Cell => Cursor::WebKit("cell"),
}
}
}
impl Default for Cursor {
fn default() -> Self {
Cursor::Native("arrowCursor")
}
}
impl Cursor {
pub unsafe fn load(&self) -> id {
match self {
Cursor::Native(cursor_name) => {
let sel = Sel::register(cursor_name);
msg_send![class!(NSCursor), performSelector:sel]
},
Cursor::Undocumented(cursor_name) => {
let class = class!(NSCursor);
let sel = Sel::register(cursor_name);
let sel = if msg_send![class, respondsToSelector:sel] {
sel
} else {
warn!("Cursor `{}` appears to be invalid", cursor_name);
sel!(arrowCursor)
};
msg_send![class, performSelector:sel]
},
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name),
}
}
}
// Note that loading `busybutclickable` with this code won't animate the frames;
// instead you'll just get them all in a column.
pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id {
static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors";
let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT);
let cursor_name = NSString::alloc(nil).init_str(cursor_name);
let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf");
let cursor_plist = NSString::alloc(nil).init_str("info.plist");
let key_x = NSString::alloc(nil).init_str("hotx");
let key_y = NSString::alloc(nil).init_str("hoty");
let cursor_path: id = msg_send![cursor_root,
stringByAppendingPathComponent:cursor_name
];
let pdf_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_pdf
];
let info_path: id = msg_send![cursor_path,
stringByAppendingPathComponent:cursor_plist
];
let image = NSImage::alloc(nil).initByReferencingFile_(pdf_path);
let info = NSDictionary::dictionaryWithContentsOfFile_(
nil,
info_path,
);
let x = info.valueForKey_(key_x);
let y = info.valueForKey_(key_y);
let point = NSPoint::new(
msg_send![x, doubleValue],
msg_send![y, doubleValue],
);
let cursor: id = msg_send![class!(NSCursor), alloc];
msg_send![cursor,
initWithImage:image
hotSpot:point
]
}

View file

@ -0,0 +1,123 @@
mod async;
mod cursor;
pub use self::{async::*, cursor::*};
use std::ops::Deref;
use std::ops::BitAnd;
use cocoa::{
appkit::{NSApp, NSWindowStyleMask},
base::{id, nil},
foundation::{NSAutoreleasePool, NSRect, NSUInteger},
};
use core_graphics::display::CGDisplay;
use objc::runtime::{BOOL, Class, Object, Sel, YES};
use platform_impl::platform::ffi;
// Replace with `!` once stable
#[derive(Debug)]
pub enum Never {}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where T:
Copy + PartialEq + BitAnd<T, Output = T>
{
bitset & flag == flag
}
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
location: ffi::NSNotFound as NSUInteger,
length: 0,
};
pub struct IdRef(id);
impl IdRef {
pub fn new(inner: id) -> IdRef {
IdRef(inner)
}
#[allow(dead_code)]
pub fn retain(inner: id) -> IdRef {
if inner != nil {
let () = unsafe { msg_send![inner, retain] };
}
IdRef(inner)
}
pub fn non_nil(self) -> Option<IdRef> {
if self.0 == nil { None } else { Some(self) }
}
}
impl Drop for IdRef {
fn drop(&mut self) {
if self.0 != nil {
unsafe {
let pool = NSAutoreleasePool::new(nil);
let () = msg_send![self.0, release];
pool.drain();
};
}
}
}
impl Deref for IdRef {
type Target = id;
fn deref<'a>(&'a self) -> &'a id {
&self.0
}
}
impl Clone for IdRef {
fn clone(&self) -> IdRef {
if self.0 != nil {
let _: id = unsafe { msg_send![self.0, retain] };
}
IdRef(self.0)
}
}
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
}
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
let superclass: id = msg_send![this, superclass];
&*(superclass as *const _)
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
let input_context: id = msg_send![input_context, initWithClient:view];
IdRef::new(input_context)
}
#[allow(dead_code)]
pub unsafe fn open_emoji_picker() {
let () = msg_send![NSApp(), orderFrontCharacterPalette:nil];
}
pub extern fn yes(_: &Object, _: Sel) -> BOOL {
YES
}
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
use cocoa::appkit::NSWindow;
let current_style_mask = window.styleMask();
if on {
window.setStyleMask_(current_style_mask | mask);
} else {
window.setStyleMask_(current_style_mask & (!mask));
}
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}

View file

@ -1,65 +1,74 @@
// This is a pretty close port of the implementation in GLFW:
// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m
use std::{
boxed::Box, collections::VecDeque, os::raw::*, slice, str,
sync::{Arc, Mutex, Weak},
};
use std::{slice, str};
use std::boxed::Box;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::{Arc, Mutex, Weak};
use cocoa::{
appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow},
base::{id, nil}, foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger},
};
use objc::{declare::ClassDecl, runtime::{BOOL, Class, NO, Object, Protocol, Sel, YES}};
use cocoa::base::{id, nil};
use cocoa::appkit::{NSEvent, NSView, NSWindow};
use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES};
use {
event::{
DeviceEvent, ElementState, Event, KeyboardInput, MouseButton,
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
},
window::WindowId,
};
use platform_impl::platform::{
app_state::AppState, DEVICE_ID,
event::{check_function_keys, event_mods, modifier_event, char_to_keycode, get_scancode, scancode_to_keycode},
util::{self, IdRef}, ffi::*, window::get_window_id,
};
use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId};
use platform_impl::platform::event_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code, check_additional_virtual_key_codes};
use platform_impl::platform::util;
use platform_impl::platform::ffi::*;
use platform_impl::platform::window::{get_window_id, IdRef};
#[derive(Default)]
struct Modifiers {
shift_pressed: bool,
ctrl_pressed: bool,
win_pressed: bool,
alt_pressed: bool,
}
struct ViewState {
window: id,
shared: Weak<Shared>,
cursor: Arc<Mutex<util::Cursor>>,
nswindow: id,
pub cursor: Arc<Mutex<util::Cursor>>,
ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>,
is_key_down: bool,
modifiers: Modifiers,
}
pub fn new_view(window: id, shared: Weak<Shared>) -> (IdRef, Weak<Mutex<util::Cursor>>) {
pub fn new_view(nswindow: id) -> (IdRef, Weak<Mutex<util::Cursor>>) {
let cursor = Default::default();
let cursor_access = Arc::downgrade(&cursor);
let state = ViewState {
window,
shared,
nswindow,
cursor,
ime_spot: None,
raw_characters: None,
is_key_down: false,
modifiers: Default::default(),
};
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let view: id = msg_send![VIEW_CLASS.0, alloc];
(IdRef::new(msg_send![view, initWithWinit:state_ptr]), cursor_access)
let nsview: id = msg_send![VIEW_CLASS.0, alloc];
(IdRef::new(msg_send![nsview, initWithWinit:state_ptr]), cursor_access)
}
}
pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) {
unsafe {
let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
);
let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
state.ime_spot = Some((base_x + x, base_y - y));
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
pub unsafe fn set_ime_position(nsview: id, input_context: id, x: f64, y: f64) {
let state_ptr: *mut c_void = *(*nsview).get_mut_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let content_rect = NSWindow::contentRectForFrameRect_(
state.nswindow,
NSWindow::frame(state.nswindow),
);
let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
state.ime_spot = Some((base_x + x, base_y - y));
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
struct ViewClass(*const Class);
@ -70,38 +79,61 @@ lazy_static! {
static ref VIEW_CLASS: ViewClass = unsafe {
let superclass = class!(NSView);
let mut decl = ClassDecl::new("WinitView", superclass).unwrap();
decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel));
decl.add_method(
sel!(dealloc),
dealloc as extern fn(&Object, Sel),
);
decl.add_method(
sel!(initWithWinit:),
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
);
decl.add_method(
sel!(viewDidMoveToWindow),
view_did_move_to_window as extern fn(&Object, Sel),
);
decl.add_method(
sel!(drawRect:),
draw_rect as extern fn(&Object, Sel, NSRect),
draw_rect as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(acceptsFirstResponder),
accepts_first_responder as extern fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(touchBar),
touch_bar as extern fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(resetCursorRects),
reset_cursor_rects as extern fn(&Object, Sel),
);
decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL);
decl.add_method(
sel!(hasMarkedText),
has_marked_text as extern fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(markedRange),
marked_range as extern fn(&Object, Sel) -> NSRange,
);
decl.add_method(sel!(selectedRange), selected_range as extern fn(&Object, Sel) -> NSRange);
decl.add_method(
sel!(selectedRange),
selected_range as extern fn(&Object, Sel) -> NSRange,
);
decl.add_method(
sel!(setMarkedText:selectedRange:replacementRange:),
set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange),
);
decl.add_method(sel!(unmarkText), unmark_text as extern fn(&Object, Sel));
decl.add_method(
sel!(unmarkText),
unmark_text as extern fn(&Object, Sel),
);
decl.add_method(
sel!(validAttributesForMarkedText),
valid_attributes_for_marked_text as extern fn(&Object, Sel) -> id,
);
decl.add_method(
sel!(attributedSubstringForProposedRange:actualRange:),
attributed_substring_for_proposed_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> id,
attributed_substring_for_proposed_range as extern fn(&Object, Sel, NSRange, *mut c_void) -> id,
);
decl.add_method(
sel!(insertText:replacementRange:),
@ -113,28 +145,96 @@ lazy_static! {
);
decl.add_method(
sel!(firstRectForCharacterRange:actualRange:),
first_rect_for_character_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect,
first_rect_for_character_range as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect,
);
decl.add_method(
sel!(doCommandBySelector:),
do_command_by_selector as extern fn(&Object, Sel, Sel),
);
decl.add_method(sel!(keyDown:), key_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(keyUp:), key_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertBackTab:), insert_back_tab as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseDown:), mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseUp:), mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseDown:), right_mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseUp:), right_mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseDown:), other_mouse_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseUp:), other_mouse_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseMoved:), mouse_moved as extern fn(&Object, Sel, id));
decl.add_method(sel!(mouseDragged:), mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(rightMouseDragged:), right_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id));
decl.add_method(sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL);
decl.add_method(
sel!(keyDown:),
key_down as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(keyUp:),
key_up as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(flagsChanged:),
flags_changed as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(insertTab:),
insert_tab as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(insertBackTab:),
insert_back_tab as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseDown:),
mouse_down as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseUp:),
mouse_up as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(rightMouseDown:),
right_mouse_down as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(rightMouseUp:),
right_mouse_up as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(otherMouseDown:),
other_mouse_down as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(otherMouseUp:),
other_mouse_up as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseMoved:),
mouse_moved as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseDragged:),
mouse_dragged as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(rightMouseDragged:),
right_mouse_dragged as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(otherMouseDragged:),
other_mouse_dragged as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseEntered:),
mouse_entered as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseExited:),
mouse_exited as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(scrollWheel:),
scroll_wheel as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(pressureChangeWithEvent:),
pressure_change_with_event as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(_wantsKeyDownForEvent:),
wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(cancelOperation:),
cancel_operation as extern fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
@ -166,27 +266,43 @@ extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
}
}
extern fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
extern fn view_did_move_to_window(this: &Object, _sel: Sel) {
trace!("Triggered `viewDidMoveToWindow`");
unsafe {
let rect: NSRect = msg_send![this, visibleRect];
let _: () = msg_send![this,
addTrackingRect:rect
owner:this
userData:nil
assumeInside:NO
];
}
trace!("Completed `viewDidMoveToWindow`");
}
extern fn draw_rect(this: &Object, _sel: Sel, rect: id) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
if let Some(shared) = state.shared.upgrade() {
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::Refresh,
};
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
AppState::queue_redraw(WindowId(get_window_id(state.nswindow)));
let superclass = util::superclass(this);
let () = msg_send![super(this, superclass), drawRect:rect];
}
}
extern fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL {
YES
}
// This is necessary to prevent a beefy terminal error on MacBook Pros:
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
// TODO: Add an API extension for using `NSTouchBar`
extern fn touch_bar(_this: &Object, _sel: Sel) -> BOOL {
NO
}
extern fn reset_cursor_rects(this: &Object, _sel: Sel) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
@ -201,19 +317,22 @@ extern fn reset_cursor_rects(this: &Object, _sel: Sel) {
}
}
extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
//println!("hasMarkedText");
unsafe {
trace!("Triggered `hasMarkedText`");
let marked_text: id = *this.get_ivar("markedText");
trace!("Completed `hasMarkedText`");
(marked_text.length() > 0) as i8
}
}
extern fn marked_range(this: &Object, _sel: Sel) -> NSRange {
//println!("markedRange");
unsafe {
trace!("Triggered `markedRange`");
let marked_text: id = *this.get_ivar("markedText");
let length = marked_text.length();
trace!("Completed `markedRange`");
if length > 0 {
NSRange::new(0, length - 1)
} else {
@ -223,7 +342,8 @@ extern fn marked_range(this: &Object, _sel: Sel) -> NSRange {
}
extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange {
//println!("selectedRange");
trace!("Triggered `selectedRange`");
trace!("Completed `selectedRange`");
util::EMPTY_RANGE
}
@ -234,7 +354,7 @@ extern fn set_marked_text(
_selected_range: NSRange,
_replacement_range: NSRange,
) {
//println!("setMarkedText");
trace!("Triggered `setMarkedText`");
unsafe {
let marked_text_ref: &mut id = this.get_mut_ivar("markedText");
let _: () = msg_send![(*marked_text_ref), release];
@ -247,10 +367,11 @@ extern fn set_marked_text(
};
*marked_text_ref = marked_text;
}
trace!("Completed `setMarkedText`");
}
extern fn unmark_text(this: &Object, _sel: Sel) {
//println!("unmarkText");
trace!("Triggered `unmarkText`");
unsafe {
let marked_text: id = *this.get_ivar("markedText");
let mutable_string = marked_text.mutableString();
@ -258,10 +379,12 @@ extern fn unmark_text(this: &Object, _sel: Sel) {
let input_context: id = msg_send![this, inputContext];
let _: () = msg_send![input_context, discardMarkedText];
}
trace!("Completed `unmarkText`");
}
extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id {
//println!("validAttributesForMarkedText");
trace!("Triggered `validAttributesForMarkedText`");
trace!("Completed `validAttributesForMarkedText`");
unsafe { msg_send![class!(NSArray), array] }
}
@ -271,12 +394,14 @@ extern fn attributed_substring_for_proposed_range(
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> id {
//println!("attributedSubstringForProposedRange");
trace!("Triggered `attributedSubstringForProposedRange`");
trace!("Completed `attributedSubstringForProposedRange`");
nil
}
extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger {
//println!("characterIndexForPoint");
trace!("Triggered `characterIndexForPoint`");
trace!("Completed `characterIndexForPoint`");
0
}
@ -286,20 +411,20 @@ extern fn first_rect_for_character_range(
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> NSRect {
//println!("firstRectForCharacterRange");
unsafe {
trace!("Triggered `firstRectForCharacterRange`");
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let (x, y) = state.ime_spot.unwrap_or_else(|| {
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
state.nswindow,
NSWindow::frame(state.nswindow),
);
let x = content_rect.origin.x;
let y = util::bottom_left_to_top_left(content_rect);
(x, y)
});
trace!("Completed `firstRectForCharacterRange`");
NSRect::new(
NSPoint::new(x as _, y as _),
NSSize::new(0.0, 0.0),
@ -308,7 +433,7 @@ extern fn first_rect_for_character_range(
}
extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) {
//println!("insertText");
trace!("Triggered `insertText`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
@ -330,46 +455,36 @@ extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range:
state.is_key_down = true;
// We don't need this now, but it's here if that changes.
//let event: id = msg_send![class!(NSApp), currentEvent];
//let event: id = msg_send![NSApp(), currentEvent];
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::ReceivedCharacter(character),
});
}
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
}
AppState::queue_events(events);
}
trace!("Completed `insertText`");
}
extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
//println!("doCommandBySelector");
trace!("Triggered `doCommandBySelector`");
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character
// happens, i.e. newlines, tabs, and Ctrl+C.
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let shared = if let Some(shared) = state.shared.upgrade() {
shared
} else {
return;
};
let mut events = VecDeque::with_capacity(1);
if command == sel!(insertNewline:) {
// The `else` condition would emit the same character, but I'm keeping this here both...
// 1) as a reminder for how `doCommandBySelector` works
// 2) to make our use of carriage return explicit
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::ReceivedCharacter('\r'),
});
} else {
@ -377,50 +492,80 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
if let Some(raw_characters) = raw_characters {
for character in raw_characters.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::ReceivedCharacter(character),
});
}
}
};
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
AppState::queue_events(events);
}
trace!("Completed `doCommandBySelector`");
}
fn get_characters(event: id) -> Option<String> {
fn get_characters(event: id, ignore_modifiers: bool) -> String {
unsafe {
let characters: id = msg_send![event, characters];
let characters: id = if ignore_modifiers {
msg_send![event, charactersIgnoringModifiers]
} else {
msg_send![event, characters]
};
assert_ne!(characters, nil);
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
Some(string.to_owned())
string.to_owned()
}
}
// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: id) -> Option<VirtualKeyCode> {
#[inline]
fn get_code(ev: id, raw: bool) -> Option<VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters.chars().next().map_or(None, |c| char_to_keycode(c))
}
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
// If we don't get a match, then we fall back to unmodified characters.
let code = get_code(event, false)
.or_else(|| {
get_code(event, true)
});
// We've checked all layout related keys, so fall through to scancode.
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
//
// We're additionally checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
code.or_else(|| {
let scancode = get_scancode(event);
scancode_to_keycode(scancode)
.or_else(|| {
check_function_keys(&get_characters(event, true))
})
})
}
extern fn key_down(this: &Object, _sel: Sel, event: id) {
//println!("keyDown");
trace!("Triggered `keyDown`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_id = WindowId(get_window_id(state.window));
let window_id = WindowId(get_window_id(state.nswindow));
let characters = get_characters(event, false);
state.raw_characters = get_characters(event);
state.raw_characters = Some(characters.clone());
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let keycode: c_ushort = msg_send![event, keyCode];
// We are checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
let virtual_keycode = to_virtual_key_code(keycode)
.or_else(|| {
check_additional_virtual_key_codes(&state.raw_characters)
});
let scancode = keycode as u32;
let is_repeat = msg_send![event, isARepeat];
let window_event = Event::WindowEvent {
@ -436,65 +581,46 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
},
};
let characters: id = msg_send![event, characters];
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
state.raw_characters = {
Some(string.to_owned())
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
let pass_along = {
AppState::queue_event(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down{
for character in string.chars() {
let window_event = Event::WindowEvent {
if is_repeat && state.is_key_down {
for character in characters.chars() {
AppState::queue_event(Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
};
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
});
}
false
} else {
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
// keys to generate twice as many characters.
let array: id = msg_send![class!(NSArray), arrayWithObject:event];
let (): _ = msg_send![this, interpretKeyEvents:array];
true
}
};
if pass_along {
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
// keys to generate twice as many characters.
let array: id = msg_send![class!(NSArray), arrayWithObject:event];
let _: () = msg_send![this, interpretKeyEvents:array];
}
}
trace!("Completed `keyDown`");
}
extern fn key_up(this: &Object, _sel: Sel, event: id) {
//println!("keyUp");
trace!("Triggered `keyUp`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
state.is_key_down = false;
// We need characters here to check for additional keys such as
// F21-F24.
let characters = get_characters(event);
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);
let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_key_code(keycode)
.or_else(|| {
check_additional_virtual_key_codes(&characters)
});
let scancode = keycode as u32;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
@ -506,13 +632,63 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) {
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
AppState::queue_event(window_event);
}
trace!("Completed `keyUp`");
}
extern fn flags_changed(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `flagsChanged`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let mut events = VecDeque::with_capacity(4);
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSShiftKeyMask,
state.modifiers.shift_pressed,
) {
state.modifiers.shift_pressed = !state.modifiers.shift_pressed;
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSControlKeyMask,
state.modifiers.ctrl_pressed,
) {
state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed;
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSCommandKeyMask,
state.modifiers.win_pressed,
) {
state.modifiers.win_pressed = !state.modifiers.win_pressed;
events.push_back(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSAlternateKeyMask,
state.modifiers.alt_pressed,
) {
state.modifiers.alt_pressed = !state.modifiers.alt_pressed;
events.push_back(window_event);
}
for event in events {
AppState::queue_event(Event::WindowEvent {
window_id: WindowId(get_window_id(state.nswindow)),
event,
});
}
}
trace!("Completed `flagsChanged`");
}
extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) {
@ -537,13 +713,45 @@ extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) {
}
}
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
trace!("Triggered `cancelOperation`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let scancode = 0x2f;
let virtual_keycode = scancode_to_keycode(scancode);
debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period));
let event: id = msg_send![NSApp(), currentEvent];
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Pressed,
scancode: scancode as _,
virtual_keycode,
modifiers: event_mods(event),
},
},
};
AppState::queue_event(window_event);
}
trace!("Completed `cancelOperation`");
}
fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::MouseInput {
device_id: DEVICE_ID,
state: button_state,
@ -552,12 +760,7 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
AppState::queue_event(window_event);
}
}
@ -609,7 +812,7 @@ fn mouse_motion(this: &Object, event: id) {
let y = view_rect.size.height as f64 - view_point.y as f64;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
@ -617,12 +820,7 @@ fn mouse_motion(this: &Object, event: id) {
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
AppState::queue_event(window_event);
}
}
@ -642,7 +840,125 @@ extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
mouse_motion(this, event);
}
extern fn mouse_entered(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `mouseEntered`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let enter_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::CursorEntered { device_id: DEVICE_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.nswindow)),
event: WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: (x, y).into(),
modifiers: event_mods(event),
}
}
};
AppState::queue_event(enter_event);
AppState::queue_event(move_event);
}
trace!("Completed `mouseEntered`");
}
extern fn mouse_exited(this: &Object, _sel: Sel, _event: id) {
trace!("Triggered `mouseExited`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::CursorLeft { device_id: DEVICE_ID },
};
AppState::queue_event(window_event);
}
trace!("Completed `mouseExited`");
}
extern fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `scrollWheel`");
unsafe {
let delta = {
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
if event.hasPreciseScrollingDeltas() == YES {
MouseScrollDelta::PixelDelta((x as f64, y as f64).into())
} else {
MouseScrollDelta::LineDelta(x as f32, y as f32)
}
};
let phase = match event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
_ => TouchPhase::Moved,
};
let device_event = Event::DeviceEvent {
device_id: DEVICE_ID,
event: DeviceEvent::MouseWheel { delta },
};
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
phase,
modifiers: event_mods(event),
},
};
AppState::queue_event(device_event);
AppState::queue_event(window_event);
}
trace!("Completed `scrollWheel`");
}
extern fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
trace!("Triggered `pressureChangeWithEvent`");
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let pressure = event.pressure();
let stage = event.stage();
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::TouchpadPressure {
device_id: DEVICE_ID,
pressure,
stage,
},
};
AppState::queue_event(window_event);
}
trace!("Completed `pressureChangeWithEvent`");
}
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
// Note that this *doesn't* help with any missing Cmd inputs.
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL {
extern fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL {
YES
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,460 @@
use std::{f64, os::raw::c_void, sync::{Arc, Weak}};
use cocoa::{
appkit::{self, NSView, NSWindow}, base::{id, nil},
foundation::NSAutoreleasePool,
};
use objc::{runtime::{Class, Object, Sel, BOOL, YES, NO}, declare::ClassDecl};
use {dpi::LogicalSize, event::{Event, WindowEvent}, window::WindowId};
use platform_impl::platform::{
app_state::AppState, util::{self, IdRef},
window::{get_window_id, UnownedWindow},
};
pub struct WindowDelegateState {
nswindow: IdRef, // never changes
nsview: IdRef, // never changes
window: Weak<UnownedWindow>,
// TODO: It's possible for delegate methods to be called asynchronously,
// causing data races / `RefCell` panics.
// This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen`
initial_fullscreen: bool,
// During `windowDidResize`, we use this to only send Moved if the position changed.
previous_position: Option<(f64, f64)>,
// Used to prevent redundant events.
previous_dpi_factor: f64,
}
impl WindowDelegateState {
pub fn new(
window: &Arc<UnownedWindow>,
initial_fullscreen: bool,
) -> Self {
let dpi_factor = window.hidpi_factor();
let mut delegate_state = WindowDelegateState {
nswindow: window.nswindow.clone(),
nsview: window.nsview.clone(),
window: Arc::downgrade(&window),
initial_fullscreen,
previous_position: None,
previous_dpi_factor: dpi_factor,
};
if dpi_factor != 1.0 {
delegate_state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor));
delegate_state.emit_resize_event();
}
delegate_state
}
fn with_window<F, T>(&mut self, callback: F) -> Option<T>
where F: FnOnce(&UnownedWindow) -> T
{
self.window
.upgrade()
.map(|ref window| callback(window))
}
pub fn emit_event(&mut self, event: WindowEvent) {
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*self.nswindow)),
event,
};
AppState::queue_event(event);
}
pub fn emit_resize_event(&mut self) {
let rect = unsafe { NSView::frame(*self.nsview) };
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*self.nswindow)),
event: WindowEvent::Resized(size),
};
AppState::send_event_immediately(event);
}
fn emit_move_event(&mut self) {
let rect = unsafe { NSWindow::frame(*self.nswindow) };
let x = rect.origin.x as f64;
let y = util::bottom_left_to_top_left(rect);
let moved = self.previous_position != Some((x, y));
if moved {
self.previous_position = Some((x, y));
self.emit_event(WindowEvent::Moved((x, y).into()));
}
}
}
pub fn new_delegate(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> IdRef {
let state = WindowDelegateState::new(window, initial_fullscreen);
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let delegate: id = msg_send![WINDOW_DELEGATE_CLASS.0, alloc];
IdRef::new(msg_send![delegate, initWithWinit:state_ptr])
}
}
struct WindowDelegateClass(*const Class);
unsafe impl Send for WindowDelegateClass {}
unsafe impl Sync for WindowDelegateClass {}
lazy_static! {
static ref WINDOW_DELEGATE_CLASS: WindowDelegateClass = unsafe {
let superclass = class!(NSResponder);
let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap();
decl.add_method(
sel!(dealloc),
dealloc as extern fn(&Object, Sel),
);
decl.add_method(
sel!(initWithWinit:),
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
);
decl.add_method(
sel!(windowShouldClose:),
window_should_close as extern fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(windowWillClose:),
window_will_close as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidResize:),
window_did_resize as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidMove:),
window_did_move as extern fn(&Object, Sel, id));
decl.add_method(
sel!(windowDidChangeScreen:),
window_did_change_screen as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidChangeBackingProperties:),
window_did_change_backing_properties as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidBecomeKey:),
window_did_become_key as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidResignKey:),
window_did_resign_key as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(draggingEntered:),
dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(prepareForDragOperation:),
prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(performDragOperation:),
perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(concludeDragOperation:),
conclude_drag_operation as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(draggingExited:),
dragging_exited as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidEnterFullScreen:),
window_did_enter_fullscreen as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowWillEnterFullScreen:),
window_will_enter_fullscreen as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidExitFullScreen:),
window_did_exit_fullscreen as extern fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidFailToEnterFullScreen:),
window_did_fail_to_enter_fullscreen as extern fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>("winitState");
WindowDelegateClass(decl.register())
};
}
// This function is definitely unsafe, but labeling that would increase
// boilerplate and wouldn't really clarify anything...
fn with_state<F: FnOnce(&mut WindowDelegateState) -> T, T>(this: &Object, callback: F) {
let state_ptr = unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
&mut *(state_ptr as *mut WindowDelegateState)
};
callback(state_ptr);
}
extern fn dealloc(this: &Object, _sel: Sel) {
with_state(this, |state| unsafe {
Box::from_raw(state as *mut WindowDelegateState);
});
}
extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
unsafe {
let this: id = msg_send![this, init];
if this != nil {
(*this).set_ivar("winitState", state);
with_state(&*this, |state| {
let () = msg_send![*state.nswindow, setDelegate:this];
});
}
this
}
}
extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
trace!("Triggered `windowShouldClose:`");
with_state(this, |state| state.emit_event(WindowEvent::CloseRequested));
trace!("Completed `windowShouldClose:`");
NO
}
extern fn window_will_close(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowWillClose:`");
with_state(this, |state| unsafe {
// `setDelegate:` retains the previous value and then autoreleases it
let pool = NSAutoreleasePool::new(nil);
// Since El Capitan, we need to be careful that delegate methods can't
// be called after the window closes.
let () = msg_send![*state.nswindow, setDelegate:nil];
pool.drain();
state.emit_event(WindowEvent::Destroyed);
});
trace!("Completed `windowWillClose:`");
}
extern fn window_did_resize(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidResize:`");
with_state(this, |state| {
state.emit_resize_event();
state.emit_move_event();
});
trace!("Completed `windowDidResize:`");
}
// This won't be triggered if the move was part of a resize.
extern fn window_did_move(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidMove:`");
with_state(this, |state| {
state.emit_move_event();
});
trace!("Completed `windowDidMove:`");
}
extern fn window_did_change_screen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidChangeScreen:`");
with_state(this, |state| {
let dpi_factor = unsafe {
NSWindow::backingScaleFactor(*state.nswindow)
} as f64;
if state.previous_dpi_factor != dpi_factor {
state.previous_dpi_factor = dpi_factor;
state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor));
state.emit_resize_event();
}
});
trace!("Completed `windowDidChangeScreen:`");
}
// This will always be called before `window_did_change_screen`.
extern fn window_did_change_backing_properties(this: &Object, _:Sel, _:id) {
trace!("Triggered `windowDidChangeBackingProperties:`");
with_state(this, |state| {
let dpi_factor = unsafe {
NSWindow::backingScaleFactor(*state.nswindow)
} as f64;
if state.previous_dpi_factor != dpi_factor {
state.previous_dpi_factor = dpi_factor;
state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor));
state.emit_resize_event();
}
});
trace!("Completed `windowDidChangeBackingProperties:`");
}
extern fn window_did_become_key(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidBecomeKey:`");
with_state(this, |state| {
// TODO: center the cursor if the window had mouse grab when it
// lost focus
state.emit_event(WindowEvent::Focused(true));
});
trace!("Completed `windowDidBecomeKey:`");
}
extern fn window_did_resign_key(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidResignKey:`");
with_state(this, |state| {
state.emit_event(WindowEvent::Focused(false));
});
trace!("Completed `windowDidResignKey:`");
}
/// Invoked when the dragged image enters destination bounds or frame
extern fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL {
trace!("Triggered `draggingEntered:`");
use cocoa::appkit::NSPasteboard;
use cocoa::foundation::NSFastEnumeration;
use std::path::PathBuf;
let pb: id = unsafe { msg_send![sender, draggingPasteboard] };
let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) };
for file in unsafe { filenames.iter() } {
use cocoa::foundation::NSString;
use std::ffi::CStr;
unsafe {
let f = NSString::UTF8String(file);
let path = CStr::from_ptr(f).to_string_lossy().into_owned();
with_state(this, |state| {
state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path)));
});
}
};
trace!("Completed `draggingEntered:`");
YES
}
/// Invoked when the image is released
extern fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL {
trace!("Triggered `prepareForDragOperation:`");
trace!("Completed `prepareForDragOperation:`");
YES
}
/// Invoked after the released image has been removed from the screen
extern fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL {
trace!("Triggered `performDragOperation:`");
use cocoa::appkit::NSPasteboard;
use cocoa::foundation::NSFastEnumeration;
use std::path::PathBuf;
let pb: id = unsafe { msg_send![sender, draggingPasteboard] };
let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) };
for file in unsafe { filenames.iter() } {
use cocoa::foundation::NSString;
use std::ffi::CStr;
unsafe {
let f = NSString::UTF8String(file);
let path = CStr::from_ptr(f).to_string_lossy().into_owned();
with_state(this, |state| {
state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path)));
});
}
};
trace!("Completed `performDragOperation:`");
YES
}
/// Invoked when the dragging operation is complete
extern fn conclude_drag_operation(_: &Object, _: Sel, _: id) {
trace!("Triggered `concludeDragOperation:`");
trace!("Completed `concludeDragOperation:`");
}
/// Invoked when the dragging operation is cancelled
extern fn dragging_exited(this: &Object, _: Sel, _: id) {
trace!("Triggered `draggingExited:`");
with_state(this, |state| state.emit_event(WindowEvent::HoveredFileCancelled));
trace!("Completed `draggingExited:`");
}
/// Invoked when before enter fullscreen
extern fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowWillEnterFullscreen:`");
with_state(this, |state| state.with_window(|window| {
trace!("Locked shared state in `window_will_enter_fullscreen`");
window.shared_state.lock().unwrap().maximized = window.is_zoomed();
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
}));
trace!("Completed `windowWillEnterFullscreen:`");
}
/// Invoked when entered fullscreen
extern fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidEnterFullscreen:`");
with_state(this, |state| {
state.with_window(|window| {
let monitor = window.current_monitor();
trace!("Locked shared state in `window_did_enter_fullscreen`");
window.shared_state.lock().unwrap().fullscreen = Some(monitor);
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
});
state.initial_fullscreen = false;
});
trace!("Completed `windowDidEnterFullscreen:`");
}
/// Invoked when exited fullscreen
extern fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidExitFullscreen:`");
with_state(this, |state| state.with_window(|window| {
window.restore_state_from_fullscreen();
}));
trace!("Completed `windowDidExitFullscreen:`");
}
/// Invoked when fail to enter fullscreen
///
/// When this window launch from a fullscreen app (e.g. launch from VS Code
/// terminal), it creates a new virtual destkop and a transition animation.
/// This animation takes one second and cannot be disable without
/// elevated privileges. In this animation time, all toggleFullscreen events
/// will be failed. In this implementation, we will try again by using
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
/// was set).
///
/// From Apple doc:
/// In some cases, the transition to enter full-screen mode can fail,
/// due to being in the midst of handling some other animation or user gesture.
/// This method indicates that there was an error, and you should clean up any
/// work you may have done to prepare to enter full-screen mode.
extern fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidFailToEnterFullscreen:`");
with_state(this, |state| {
if state.initial_fullscreen {
let _: () = unsafe { msg_send![*state.nswindow,
performSelector:sel!(toggleFullScreen:)
withObject:nil
afterDelay: 0.5
] };
} else {
state.with_window(|window| window.restore_state_from_fullscreen());
}
});
trace!("Completed `windowDidFailToEnterFullscreen:`");
}

View file

@ -81,11 +81,11 @@ impl<T> EventLoop<T> {
}
}
pub fn get_available_monitors(&self) -> VecDequeIter<MonitorHandle> {
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
VecDeque::new().into_iter()
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}

View file

@ -1,3 +1,5 @@
use std::fmt;
mod event_loop;
mod events;
mod window;
@ -6,6 +8,14 @@ pub use self::event_loop::{DeviceId, EventLoop, EventLoopRunnerShared, EventLoop
pub use self::window::{MonitorHandle, Window, WindowId, PlatformSpecificWindowBuilderAttributes};
pub use self::events::{button_mapping, mouse_modifiers_state, mouse_button, keyboard_modifiers_state, scancode};
#[derive(Debug)]
pub struct OsError(String);
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
// TODO: dpi
// TODO: close events (stdweb PR required)

View file

@ -1,10 +1,11 @@
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use error::{ExternalError, NotSupportedError, OsError as RootOE};
use event::{Event, WindowEvent};
use icon::Icon;
use platform::stdweb::WindowExtStdweb;
use monitor::{MonitorHandle as RootMH};
use window::{CreationError, MouseCursor, Window as RootWindow, WindowAttributes, WindowId as RootWI};
use super::{EventLoopWindowTarget, register};
use window::{CursorIcon, Window as RootWindow, WindowAttributes, WindowId as RootWI};
use super::{EventLoopWindowTarget, OsError, register};
use std::collections::VecDeque;
use std::collections::vec_deque::IntoIter as VecDequeIter;
use std::cell::RefCell;
@ -21,19 +22,19 @@ use stdweb::web::{
pub struct MonitorHandle;
impl MonitorHandle {
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
1.0
}
pub fn get_position(&self) -> PhysicalPosition {
pub fn position(&self) -> PhysicalPosition {
unimplemented!();
}
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
unimplemented!();
}
pub fn get_name(&self) -> Option<String> {
pub fn name(&self) -> Option<String> {
unimplemented!();
}
}
@ -59,14 +60,14 @@ pub struct Window {
impl Window {
pub fn new<T>(target: &EventLoopWindowTarget<T>, attr: WindowAttributes,
_: PlatformSpecificWindowBuilderAttributes) -> Result<Self, CreationError> {
_: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
let element = document()
.create_element("canvas")
.map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?;
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
let canvas: CanvasElement = element.try_into()
.map_err(|_| CreationError::OsError("Failed to create canvas element".to_owned()))?;
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
document().body()
.ok_or_else(|| CreationError::OsError("Failed to find body node".to_owned()))?
.ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))?
.append_child(&canvas);
register(&target.runner, &canvas);
@ -90,24 +91,20 @@ impl Window {
})
};
if let Some(dimensions) = attr.dimensions {
window.set_inner_size(dimensions);
if let Some(inner_size) = attr.inner_size {
window.set_inner_size(inner_size);
} else {
window.set_inner_size(LogicalSize {
width: 1024.0,
height: 768.0,
})
}
window.set_min_dimensions(attr.min_dimensions);
window.set_max_dimensions(attr.max_dimensions);
window.set_min_inner_size(attr.min_inner_size);
window.set_max_inner_size(attr.max_inner_size);
window.set_resizable(attr.resizable);
window.set_title(&attr.title);
window.set_maximized(attr.maximized);
if attr.visible {
window.show();
} else {
window.hide();
}
window.set_visible(attr.visible);
//window.set_transparent(attr.transparent);
window.set_decorations(attr.decorations);
window.set_always_on_top(attr.always_on_top);
@ -120,11 +117,7 @@ impl Window {
document().set_title(title);
}
pub fn show(&self) {
// Intentionally a no-op
}
pub fn hide(&self) {
pub fn set_visible(&self, _visible: bool) {
// Intentionally a no-op
}
@ -132,19 +125,19 @@ impl Window {
(self.redraw)();
}
pub fn get_position(&self) -> Option<LogicalPosition> {
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let bounds = self.canvas.get_bounding_client_rect();
Some(LogicalPosition {
Ok(LogicalPosition {
x: bounds.get_x(),
y: bounds.get_y(),
})
}
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
Some(*self.position.borrow())
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
Ok(*self.position.borrow())
}
pub fn set_position(&self, position: LogicalPosition) {
pub fn set_outer_position(&self, position: LogicalPosition) {
*self.position.borrow_mut() = position;
self.canvas.set_attribute("position", "fixed")
.expect("Setting the position for the canvas");
@ -155,19 +148,19 @@ impl Window {
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(LogicalSize {
pub fn inner_size(&self) -> LogicalSize {
LogicalSize {
width: self.canvas.width() as f64,
height: self.canvas.height() as f64
})
}
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
Some(LogicalSize {
pub fn outer_size(&self) -> LogicalSize {
LogicalSize {
width: self.canvas.width() as f64,
height: self.canvas.height() as f64
})
}
}
#[inline]
@ -177,12 +170,12 @@ impl Window {
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize>) {
// Intentionally a no-op: users can't resize canvas elements
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize>) {
// Intentionally a no-op: users can't resize canvas elements
}
@ -192,50 +185,50 @@ impl Window {
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
1.0
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let text = match cursor {
MouseCursor::Default => "auto",
MouseCursor::Crosshair => "crosshair",
MouseCursor::Hand => "pointer",
MouseCursor::Arrow => "default",
MouseCursor::Move => "move",
MouseCursor::Text => "text",
MouseCursor::Wait => "wait",
MouseCursor::Help => "help",
MouseCursor::Progress => "progress",
CursorIcon::Default => "auto",
CursorIcon::Crosshair => "crosshair",
CursorIcon::Hand => "pointer",
CursorIcon::Arrow => "default",
CursorIcon::Move => "move",
CursorIcon::Text => "text",
CursorIcon::Wait => "wait",
CursorIcon::Help => "help",
CursorIcon::Progress => "progress",
MouseCursor::NotAllowed => "not-allowed",
MouseCursor::ContextMenu => "context-menu",
MouseCursor::Cell => "cell",
MouseCursor::VerticalText => "vertical-text",
MouseCursor::Alias => "alias",
MouseCursor::Copy => "copy",
MouseCursor::NoDrop => "no-drop",
MouseCursor::Grab => "grab",
MouseCursor::Grabbing => "grabbing",
MouseCursor::AllScroll => "all-scroll",
MouseCursor::ZoomIn => "zoom-in",
MouseCursor::ZoomOut => "zoom-out",
CursorIcon::NotAllowed => "not-allowed",
CursorIcon::ContextMenu => "context-menu",
CursorIcon::Cell => "cell",
CursorIcon::VerticalText => "vertical-text",
CursorIcon::Alias => "alias",
CursorIcon::Copy => "copy",
CursorIcon::NoDrop => "no-drop",
CursorIcon::Grab => "grab",
CursorIcon::Grabbing => "grabbing",
CursorIcon::AllScroll => "all-scroll",
CursorIcon::ZoomIn => "zoom-in",
CursorIcon::ZoomOut => "zoom-out",
MouseCursor::EResize => "e-resize",
MouseCursor::NResize => "n-resize",
MouseCursor::NeResize => "ne-resize",
MouseCursor::NwResize => "nw-resize",
MouseCursor::SResize => "s-resize",
MouseCursor::SeResize => "se-resize",
MouseCursor::SwResize => "sw-resize",
MouseCursor::WResize => "w-resize",
MouseCursor::EwResize => "ew-resize",
MouseCursor::NsResize => "ns-resize",
MouseCursor::NeswResize => "nesw-resize",
MouseCursor::NwseResize => "nwse-resize",
MouseCursor::ColResize => "col-resize",
MouseCursor::RowResize => "row-resize",
CursorIcon::EResize => "e-resize",
CursorIcon::NResize => "n-resize",
CursorIcon::NeResize => "ne-resize",
CursorIcon::NwResize => "nw-resize",
CursorIcon::SResize => "s-resize",
CursorIcon::SeResize => "se-resize",
CursorIcon::SwResize => "sw-resize",
CursorIcon::WResize => "w-resize",
CursorIcon::EwResize => "ew-resize",
CursorIcon::NsResize => "ns-resize",
CursorIcon::NeswResize => "nesw-resize",
CursorIcon::NwseResize => "nwse-resize",
CursorIcon::ColResize => "col-resize",
CursorIcon::RowResize => "row-resize",
};
*self.previous_pointer.borrow_mut() = text;
self.canvas.set_attribute("cursor", text)
@ -243,20 +236,20 @@ impl Window {
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> {
// TODO: pointer capture
Ok(())
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
// TODO: pointer capture
Ok(())
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
if hide {
pub fn set_cursor_visible(&self, visible: bool) {
if !visible {
self.canvas.set_attribute("cursor", "none")
.expect("Setting the cursor on the canvas");
} else {
@ -270,6 +263,12 @@ impl Window {
// TODO: should there be a maximization / fullscreen API?
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMH> {
// TODO: should there be a maximization / fullscreen API?
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMH>) {
// TODO: should there be a maximization / fullscreen API?
@ -291,24 +290,24 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, _position: LogicalPosition) {
pub fn set_ime_position(&self, _position: LogicalPosition) {
// TODO: what is this?
}
#[inline]
pub fn get_current_monitor(&self) -> RootMH {
pub fn current_monitor(&self) -> RootMH {
RootMH {
inner: MonitorHandle
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDequeIter<MonitorHandle> {
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
VecDeque::new().into_iter()
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}

View file

@ -144,7 +144,7 @@ pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
dpi as f64 / BASE_DPI as f64
}
pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
let hdc = winuser::GetDC(hwnd);
if hdc.is_null() {
panic!("[winit] `GetDC` returned null!");
@ -184,6 +184,6 @@ pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
}
}
pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 {
dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) })
pub fn hwnd_scale_factor(hwnd: HWND) -> f64 {
dpi_to_scale_factor(unsafe { hwnd_dpi(hwnd) })
}

View file

@ -1,30 +1,84 @@
use std::char;
use std::{char, ptr};
use std::os::raw::c_int;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use event::{ScanCode, ModifiersState, VirtualKeyCode};
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT};
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT, HKL, HKL__};
use winapi::um::winuser;
fn key_pressed(vkey: c_int) -> bool {
unsafe {
(winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15)
}
}
pub fn get_key_mods() -> ModifiersState {
let mut mods = ModifiersState::default();
unsafe {
if winuser::GetKeyState(winuser::VK_SHIFT) & (1 << 15) == (1 << 15) {
mods.shift = true;
}
if winuser::GetKeyState(winuser::VK_CONTROL) & (1 << 15) == (1 << 15) {
mods.ctrl = true;
}
if winuser::GetKeyState(winuser::VK_MENU) & (1 << 15) == (1 << 15) {
mods.alt = true;
}
if (winuser::GetKeyState(winuser::VK_LWIN) | winuser::GetKeyState(winuser::VK_RWIN)) & (1 << 15) == (1 << 15) {
mods.logo = true;
}
}
let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU);
mods.shift = key_pressed(winuser::VK_SHIFT);
mods.ctrl = key_pressed(winuser::VK_CONTROL) && !filter_out_altgr;
mods.alt = key_pressed(winuser::VK_MENU) && !filter_out_altgr;
mods.logo = key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN);
mods
}
unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<char> {
let mut unicode_bytes = [0u16; 5];
let len = winuser::ToUnicodeEx(v_key, 0, keyboard_state.as_ptr(), unicode_bytes.as_mut_ptr(), unicode_bytes.len() as _, 0, hkl);
if len >= 1 {
char::decode_utf16(unicode_bytes.into_iter().cloned()).next().and_then(|c| c.ok())
} else {
None
}
}
/// Figures out if the keyboard layout has an AltGr key instead of an Alt key.
///
/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So,
/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every
/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If
/// pressing AltGr outputs characters that are different from the standard characters, the layout
/// uses AltGr. Otherwise, it doesn't.
///
/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416
fn layout_uses_altgr() -> bool {
unsafe {
static ACTIVE_LAYOUT: AtomicPtr<HKL__> = AtomicPtr::new(ptr::null_mut());
static USES_ALTGR: AtomicBool = AtomicBool::new(false);
let hkl = winuser::GetKeyboardLayout(0);
let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst);
if hkl == old_hkl {
return USES_ALTGR.load(Ordering::SeqCst);
}
let mut keyboard_state_altgr = [0u8; 256];
// AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses
// we have to emulate to do an AltGr test.
keyboard_state_altgr[winuser::VK_MENU as usize] = 0x80;
keyboard_state_altgr[winuser::VK_CONTROL as usize] = 0x80;
let keyboard_state_empty = [0u8; 256];
for v_key in 0..=255 {
let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl);
let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl);
if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) {
if noaltgr != altgr {
USES_ALTGR.store(true, Ordering::SeqCst);
return true;
}
}
}
USES_ALTGR.store(false, Ordering::SeqCst);
false
}
}
pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
// VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
match vkey {

View file

@ -52,7 +52,7 @@ use platform_impl::platform::dpi::{
become_dpi_aware,
dpi_to_scale_factor,
enable_non_client_dpi_scaling,
get_hwnd_scale_factor,
hwnd_scale_factor,
};
use platform_impl::platform::drop_handler::FileDropHandler;
use platform_impl::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey};
@ -380,6 +380,7 @@ impl<T> EventLoopRunner<T> {
// deferred.
if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state {
match self.control_flow {
ControlFlow::Exit |
ControlFlow::Wait => {
self.call_event_handler(
Event::NewEvents(StartCause::WaitCancelled {
@ -409,7 +410,6 @@ impl<T> EventLoopRunner<T> {
ControlFlow::Poll => {
self.call_event_handler(Event::NewEvents(StartCause::Poll))
},
ControlFlow::Exit => unreachable!()
}
}
@ -799,6 +799,20 @@ unsafe extern "system" fn public_window_callback<T>(
commctrl::DefSubclassProc(window, msg, wparam, lparam)
},
winuser::WM_NCLBUTTONDOWN => {
// jumpstart the modal loop
winuser::RedrawWindow(
window,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT
);
if wparam == winuser::HTCAPTION as _ {
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
}
commctrl::DefSubclassProc(window, msg, wparam, lparam)
},
winuser::WM_CLOSE => {
use event::WindowEvent::CloseRequested;
subclass_input.send_event(Event::WindowEvent {
@ -825,12 +839,32 @@ unsafe extern "system" fn public_window_callback<T>(
use event::WindowEvent::RedrawRequested;
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
if let Some(ref mut runner) = *runner {
match runner.runner_state {
RunnerState::Idle(..) |
RunnerState::DeferredNewEvents(..) => runner.call_event_handler(Event::WindowEvent {
// This check makes sure that calls to `request_redraw()` during `EventsCleared`
// handling dispatch `RedrawRequested` immediately after `EventsCleared`, without
// spinning up a new event loop iteration. We do this because that's what the API
// says to do.
let control_flow = runner.control_flow;
let runner_state = runner.runner_state;
let mut request_redraw = || {
runner.call_event_handler(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: RedrawRequested,
}),
});
};
match runner_state {
RunnerState::Idle(..) |
RunnerState::DeferredNewEvents(..) => request_redraw(),
RunnerState::HandlingEvents => {
match control_flow {
ControlFlow::Poll => request_redraw(),
ControlFlow::WaitUntil(resume_time) => {
if resume_time <= Instant::now() {
request_redraw()
}
},
_ => ()
}
}
_ => ()
}
}
@ -838,25 +872,10 @@ unsafe extern "system" fn public_window_callback<T>(
},
winuser::WM_PAINT => {
use event::WindowEvent::RedrawRequested;
let event = || Event::WindowEvent {
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: RedrawRequested,
};
let mut send_event = false;
{
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
if let Some(ref mut runner) = *runner {
match runner.runner_state {
RunnerState::Idle(..) |
RunnerState::DeferredNewEvents(..) => runner.call_event_handler(event()),
_ => send_event = true
}
}
}
if send_event {
subclass_input.send_event(event());
}
});
commctrl::DefSubclassProc(window, msg, wparam, lparam)
},
@ -866,7 +885,7 @@ unsafe extern "system" fn public_window_callback<T>(
let windowpos = lparam as *const winuser::WINDOWPOS;
if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE {
let dpi_factor = get_hwnd_scale_factor(window);
let dpi_factor = hwnd_scale_factor(window);
let logical_position = LogicalPosition::from_physical(
((*windowpos).x, (*windowpos).y),
dpi_factor,
@ -886,7 +905,7 @@ unsafe extern "system" fn public_window_callback<T>(
let w = LOWORD(lparam as DWORD) as u32;
let h = HIWORD(lparam as DWORD) as u32;
let dpi_factor = get_hwnd_scale_factor(window);
let dpi_factor = hwnd_scale_factor(window);
let logical_size = LogicalSize::from_physical((w, h), dpi_factor);
let event = Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
@ -907,7 +926,6 @@ unsafe extern "system" fn public_window_callback<T>(
},
winuser::WM_CHAR => {
use std::mem;
use event::WindowEvent::ReceivedCharacter;
let chr: char = mem::transmute(wparam as u32);
subclass_input.send_event(Event::WindowEvent {
@ -952,7 +970,7 @@ unsafe extern "system" fn public_window_callback<T>(
let x = windowsx::GET_X_LPARAM(lparam) as f64;
let y = windowsx::GET_Y_LPARAM(lparam) as f64;
let dpi_factor = get_hwnd_scale_factor(window);
let dpi_factor = hwnd_scale_factor(window);
let position = LogicalPosition::from_physical((x, y), dpi_factor);
subclass_input.send_event(Event::WindowEvent {
@ -980,7 +998,6 @@ unsafe extern "system" fn public_window_callback<T>(
winuser::WM_MOUSEWHEEL => {
use event::MouseScrollDelta::LineDelta;
use event::TouchPhase;
let value = (wparam >> 16) as i16;
let value = value as i32;
@ -994,6 +1011,21 @@ unsafe extern "system" fn public_window_callback<T>(
0
},
winuser::WM_MOUSEHWHEEL => {
use event::MouseScrollDelta::LineDelta;
let value = (wparam >> 16) as i16;
let value = value as i32;
let value = value as f32 / winuser::WHEEL_DELTA as f32;
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, modifiers: event::get_key_mods() },
});
0
},
winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => {
use event::ElementState::Pressed;
use event::VirtualKeyCode;
@ -1289,7 +1321,7 @@ unsafe extern "system" fn public_window_callback<T>(
inputs.as_mut_ptr(),
mem::size_of::<winuser::TOUCHINPUT>() as INT,
) > 0 {
let dpi_factor = get_hwnd_scale_factor(window);
let dpi_factor = hwnd_scale_factor(window);
for input in &inputs {
let x = (input.x as f64) / 100f64;
let y = (input.y as f64) / 100f64;
@ -1319,22 +1351,12 @@ unsafe extern "system" fn public_window_callback<T>(
}
winuser::WM_SETFOCUS => {
use event::WindowEvent::{Focused, CursorMoved};
use event::WindowEvent::Focused;
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: Focused(true),
});
let x = windowsx::GET_X_LPARAM(lparam) as f64;
let y = windowsx::GET_Y_LPARAM(lparam) as f64;
let dpi_factor = get_hwnd_scale_factor(window);
let position = LogicalPosition::from_physical((x, y), dpi_factor);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() },
});
0
},

View file

@ -1,4 +1,4 @@
use std::{self, mem, ptr};
use std::{mem, ptr, io};
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
@ -8,7 +8,6 @@ use winapi::shared::windef::{HICON, HWND};
use winapi::um::winuser;
use icon::{Pixel, PIXEL_SIZE, Icon};
use platform_impl::platform::util;
impl Pixel {
fn to_bgra(&mut self) {
@ -31,7 +30,7 @@ unsafe impl Send for WinIcon {}
impl WinIcon {
#[allow(dead_code)]
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, util::WinError> {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let wide_path: Vec<u16> = path.as_ref().as_os_str().encode_wide().collect();
let handle = unsafe {
winuser::LoadImageW(
@ -46,15 +45,15 @@ impl WinIcon {
if !handle.is_null() {
Ok(WinIcon { handle })
} else {
Err(util::WinError::from_last_error())
Err(io::Error::last_os_error())
}
}
pub fn from_icon(icon: Icon) -> Result<Self, util::WinError> {
pub fn from_icon(icon: Icon) -> Result<Self, io::Error> {
Self::from_rgba(icon.rgba, icon.width, icon.height)
}
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, util::WinError> {
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, io::Error> {
assert_eq!(rgba.len() % PIXEL_SIZE, 0);
let pixel_count = rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (width * height) as usize);
@ -80,7 +79,7 @@ impl WinIcon {
if !handle.is_null() {
Ok(WinIcon { handle })
} else {
Err(util::WinError::from_last_error())
Err(io::Error::last_os_error())
}
}

View file

@ -36,7 +36,7 @@ impl DeviceId {
}
impl DeviceId {
pub fn get_persistent_identifier(&self) -> Option<String> {
pub fn persistent_identifier(&self) -> Option<String> {
if self.0 != 0 {
raw_input::get_raw_input_device_name(self.0 as _)
} else {
@ -52,6 +52,8 @@ fn wrap_device_id(id: u32) -> RootDeviceId {
RootDeviceId(DeviceId(id))
}
pub type OsError = std::io::Error;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(HWND);
unsafe impl Send for WindowId {}

View file

@ -3,7 +3,7 @@ use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
use winapi::um::winnt::LONG;
use winapi::um::winuser;
use std::{mem, ptr};
use std::{mem, ptr, io};
use std::collections::VecDeque;
use super::{EventLoop, util};
@ -50,7 +50,7 @@ unsafe extern "system" fn monitor_enum_proc(
TRUE // continue enumeration
}
pub fn get_available_monitors() -> VecDeque<MonitorHandle> {
pub fn available_monitors() -> VecDeque<MonitorHandle> {
let mut monitors: VecDeque<MonitorHandle> = VecDeque::new();
unsafe {
winuser::EnumDisplayMonitors(
@ -63,7 +63,7 @@ pub fn get_available_monitors() -> VecDeque<MonitorHandle> {
monitors
}
pub fn get_primary_monitor() -> MonitorHandle {
pub fn primary_monitor() -> MonitorHandle {
const ORIGIN: POINT = POINT { x: 0, y: 0 };
let hmonitor = unsafe {
winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY)
@ -71,7 +71,7 @@ pub fn get_primary_monitor() -> MonitorHandle {
MonitorHandle::from_hmonitor(hmonitor)
}
pub fn get_current_monitor(hwnd: HWND) -> MonitorHandle {
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
let hmonitor = unsafe {
winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST)
};
@ -80,26 +80,26 @@ pub fn get_current_monitor(hwnd: HWND) -> MonitorHandle {
impl<T> EventLoop<T> {
// TODO: Investigate opportunities for caching
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors()
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
available_monitors()
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor()
pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor()
}
}
impl Window {
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors()
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
available_monitors()
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor()
pub fn primary_monitor(&self) -> MonitorHandle {
primary_monitor()
}
}
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, util::WinError> {
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, io::Error> {
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() };
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
let status = unsafe {
@ -109,7 +109,7 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINF
)
};
if status == 0 {
Err(util::WinError::from_last_error())
Err(io::Error::last_os_error())
} else {
Ok(monitor_info)
}
@ -142,32 +142,32 @@ impl MonitorHandle {
}
#[inline]
pub fn get_name(&self) -> Option<String> {
pub fn name(&self) -> Option<String> {
Some(self.monitor_name.clone())
}
#[inline]
pub fn get_native_identifier(&self) -> String {
pub fn native_identifier(&self) -> String {
self.monitor_name.clone()
}
#[inline]
pub fn get_hmonitor(&self) -> HMONITOR {
pub fn hmonitor(&self) -> HMONITOR {
self.hmonitor.0
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
pub fn dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
pub fn position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}

View file

@ -1,26 +1,12 @@
use std::{self, mem, ptr, slice, io};
use std::{mem, ptr, slice, io};
use std::ops::BitAnd;
use std::sync::atomic::{AtomicBool, Ordering};
use window::MouseCursor;
use window::CursorIcon;
use winapi::ctypes::wchar_t;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::winbase::{
FormatMessageW,
FORMAT_MESSAGE_ALLOCATE_BUFFER,
FORMAT_MESSAGE_FROM_SYSTEM,
FORMAT_MESSAGE_IGNORE_INSERTS,
lstrlenW,
LocalFree,
};
use winapi::um::winnt::{
LPCWSTR,
MAKELANGID,
LANG_NEUTRAL,
SUBLANG_DEFAULT,
};
use winapi::um::winbase::lstrlenW;
use winapi::um::winuser;
pub fn has_flag<T>(bitset: T, flag: T) -> bool
@ -117,66 +103,27 @@ pub fn is_focused(window: HWND) -> bool {
window == unsafe{ winuser::GetActiveWindow() }
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct WinError(Option<String>);
impl WinError {
pub fn from_last_error() -> Self {
WinError(unsafe { get_last_error() })
}
}
pub unsafe fn get_last_error() -> Option<String> {
let err = GetLastError();
if err != 0 {
let buf_addr: LPCWSTR = {
let mut buf_addr: LPCWSTR = mem::uninitialized();
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
ptr::null(),
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) as DWORD,
// This is a pointer to a pointer
&mut buf_addr as *mut LPCWSTR as *mut _,
0,
ptr::null_mut(),
);
buf_addr
};
if !buf_addr.is_null() {
let buf_len = lstrlenW(buf_addr) as usize;
let buf_slice = std::slice::from_raw_parts(buf_addr, buf_len);
let string = wchar_to_string(buf_slice);
LocalFree(buf_addr as *mut _);
return Some(string);
}
}
None
}
impl MouseCursor {
impl CursorIcon {
pub(crate) fn to_windows_cursor(self) -> *const wchar_t {
match self {
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
MouseCursor::Hand => winuser::IDC_HAND,
MouseCursor::Crosshair => winuser::IDC_CROSS,
MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM,
MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO,
MouseCursor::Grab | MouseCursor::Grabbing |
MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL,
MouseCursor::EResize | MouseCursor::WResize |
MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE,
MouseCursor::NResize | MouseCursor::SResize |
MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS,
MouseCursor::NeResize | MouseCursor::SwResize |
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
MouseCursor::NwResize | MouseCursor::SeResize |
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
MouseCursor::Wait => winuser::IDC_WAIT,
MouseCursor::Progress => winuser::IDC_APPSTARTING,
MouseCursor::Help => winuser::IDC_HELP,
CursorIcon::Arrow | CursorIcon::Default => winuser::IDC_ARROW,
CursorIcon::Hand => winuser::IDC_HAND,
CursorIcon::Crosshair => winuser::IDC_CROSS,
CursorIcon::Text | CursorIcon::VerticalText => winuser::IDC_IBEAM,
CursorIcon::NotAllowed | CursorIcon::NoDrop => winuser::IDC_NO,
CursorIcon::Grab | CursorIcon::Grabbing |
CursorIcon::Move | CursorIcon::AllScroll => winuser::IDC_SIZEALL,
CursorIcon::EResize | CursorIcon::WResize |
CursorIcon::EwResize | CursorIcon::ColResize => winuser::IDC_SIZEWE,
CursorIcon::NResize | CursorIcon::SResize |
CursorIcon::NsResize | CursorIcon::RowResize => winuser::IDC_SIZENS,
CursorIcon::NeResize | CursorIcon::SwResize |
CursorIcon::NeswResize => winuser::IDC_SIZENESW,
CursorIcon::NwResize | CursorIcon::SeResize |
CursorIcon::NwseResize => winuser::IDC_SIZENWSE,
CursorIcon::Wait => winuser::IDC_WAIT,
CursorIcon::Progress => winuser::IDC_APPSTARTING,
CursorIcon::Help => winuser::IDC_HELP,
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
}
}

View file

@ -18,12 +18,13 @@ use winapi::um::wingdi::{CreateRectRgn, DeleteObject};
use winapi::um::oleidl::LPDROPTARGET;
use winapi::um::winnt::{LONG, LPCWSTR};
use window::{CreationError, Icon, MouseCursor, WindowAttributes};
use window::{Icon, CursorIcon, WindowAttributes};
use error::{ExternalError, NotSupportedError, OsError as RootOsError};
use dpi::{LogicalPosition, LogicalSize, PhysicalSize};
use monitor::MonitorHandle as RootMonitorHandle;
use platform_impl::platform::{
{PlatformSpecificWindowBuilderAttributes, WindowId},
dpi::{dpi_to_scale_factor, get_hwnd_dpi},
dpi::{dpi_to_scale_factor, hwnd_dpi},
drop_handler::FileDropHandler,
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID, REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID},
icon::{self, IconType, WinIcon},
@ -50,7 +51,7 @@ impl Window {
event_loop: &EventLoopWindowTarget<T>,
w_attr: WindowAttributes,
pl_attr: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
) -> Result<Window, RootOsError> {
// We dispatch an `init` function because of code style.
// First person to remove the need for cloning here gets a cookie!
//
@ -103,16 +104,10 @@ impl Window {
}
#[inline]
pub fn show(&self) {
unsafe {
winuser::ShowWindow(self.window.0, winuser::SW_SHOW);
}
}
#[inline]
pub fn hide(&self) {
unsafe {
winuser::ShowWindow(self.window.0, winuser::SW_HIDE);
pub fn set_visible(&self, visible: bool) {
match visible {
true => unsafe { winuser::ShowWindow(self.window.0, winuser::SW_SHOW); },
false => unsafe { winuser::ShowWindow(self.window.0, winuser::SW_HIDE); },
}
}
@ -132,35 +127,32 @@ impl Window {
}
}
pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> {
pub(crate) fn outer_position_physical(&self) -> (i32, i32) {
util::get_window_rect(self.window.0)
.map(|rect| (rect.left as i32, rect.top as i32))
.unwrap()
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
self.get_position_physical()
.map(|physical_position| {
let dpi_factor = self.get_hidpi_factor();
LogicalPosition::from_physical(physical_position, dpi_factor)
})
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let physical_position = self.outer_position_physical();
let dpi_factor = self.hidpi_factor();
Ok(LogicalPosition::from_physical(physical_position, dpi_factor))
}
pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> {
pub(crate) fn inner_position_physical(&self) -> (i32, i32) {
let mut position: POINT = unsafe { mem::zeroed() };
if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 {
return None;
panic!("Unexpected ClientToScreen failure: please report this error to https://github.com/rust-windowing/winit")
}
Some((position.x, position.y))
(position.x, position.y)
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
self.get_inner_position_physical()
.map(|physical_position| {
let dpi_factor = self.get_hidpi_factor();
LogicalPosition::from_physical(physical_position, dpi_factor)
})
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
let physical_position = self.inner_position_physical();
let dpi_factor = self.hidpi_factor();
Ok(LogicalPosition::from_physical(physical_position, dpi_factor))
}
pub(crate) fn set_position_physical(&self, x: i32, y: i32) {
@ -179,47 +171,44 @@ impl Window {
}
#[inline]
pub fn set_position(&self, logical_position: LogicalPosition) {
let dpi_factor = self.get_hidpi_factor();
pub fn set_outer_position(&self, logical_position: LogicalPosition) {
let dpi_factor = self.hidpi_factor();
let (x, y) = logical_position.to_physical(dpi_factor).into();
self.set_position_physical(x, y);
}
pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> {
pub(crate) fn inner_size_physical(&self) -> (u32, u32) {
let mut rect: RECT = unsafe { mem::uninitialized() };
if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 {
return None;
panic!("Unexpected GetClientRect failure: please report this error to https://github.com/rust-windowing/winit")
}
Some((
(
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32,
))
)
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
self.get_inner_size_physical()
.map(|physical_size| {
let dpi_factor = self.get_hidpi_factor();
LogicalSize::from_physical(physical_size, dpi_factor)
})
pub fn inner_size(&self) -> LogicalSize {
let physical_size = self.inner_size_physical();
let dpi_factor = self.hidpi_factor();
LogicalSize::from_physical(physical_size, dpi_factor)
}
pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> {
pub(crate) fn outer_size_physical(&self) -> (u32, u32) {
util::get_window_rect(self.window.0)
.map(|rect| (
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32,
))
.unwrap()
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_outer_size_physical()
.map(|physical_size| {
let dpi_factor = self.get_hidpi_factor();
LogicalSize::from_physical(physical_size, dpi_factor)
})
pub fn outer_size(&self) -> LogicalSize {
let physical_size = self.outer_size_physical();
let dpi_factor = self.hidpi_factor();
LogicalSize::from_physical(physical_size, dpi_factor)
}
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
@ -254,41 +243,41 @@ impl Window {
#[inline]
pub fn set_inner_size(&self, logical_size: LogicalSize) {
let dpi_factor = self.get_hidpi_factor();
let dpi_factor = self.hidpi_factor();
let (width, height) = logical_size.to_physical(dpi_factor).into();
self.set_inner_size_physical(width, height);
}
pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) {
self.window_state.lock().min_size = dimensions.map(Into::into);
// Make windows re-check the window size bounds.
self.get_inner_size_physical()
.map(|(width, height)| self.set_inner_size_physical(width, height));
let (width, height) = self.inner_size_physical();
self.set_inner_size_physical(width, height);
}
#[inline]
pub fn set_min_dimensions(&self, logical_size: Option<LogicalSize>) {
pub fn set_min_inner_size(&self, logical_size: Option<LogicalSize>) {
let physical_size = logical_size.map(|logical_size| {
let dpi_factor = self.get_hidpi_factor();
let dpi_factor = self.hidpi_factor();
logical_size.to_physical(dpi_factor).into()
});
self.set_min_dimensions_physical(physical_size);
self.set_min_inner_size_physical(physical_size);
}
pub fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
pub fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) {
self.window_state.lock().max_size = dimensions.map(Into::into);
// Make windows re-check the window size bounds.
self.get_inner_size_physical()
.map(|(width, height)| self.set_inner_size_physical(width, height));
let (width, height) = self.inner_size_physical();
self.set_inner_size_physical(width, height);
}
#[inline]
pub fn set_max_dimensions(&self, logical_size: Option<LogicalSize>) {
pub fn set_max_inner_size(&self, logical_size: Option<LogicalSize>) {
let physical_size = logical_size.map(|logical_size| {
let dpi_factor = self.get_hidpi_factor();
let dpi_factor = self.hidpi_factor();
logical_size.to_physical(dpi_factor).into()
});
self.set_max_dimensions_physical(physical_size);
self.set_max_inner_size_physical(physical_size);
}
#[inline]
@ -313,7 +302,7 @@ impl Window {
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window_state.lock().mouse.cursor = cursor;
self.thread_executor.execute_in_thread(move || unsafe {
let cursor = winuser::LoadCursorW(
@ -325,7 +314,7 @@ impl Window {
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
@ -333,21 +322,21 @@ impl Window {
self.thread_executor.execute_in_thread(move || {
let result = window_state.lock().mouse
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab))
.map_err(|e| e.to_string());
.map_err(|e| ExternalError::Os(os_error!(e)));
let _ = tx.send(result);
});
rx.recv().unwrap()
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
pub fn set_cursor_visible(&self, visible: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
self.thread_executor.execute_in_thread(move || {
let result = window_state.lock().mouse
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, hide))
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, !visible))
.map_err(|e| e.to_string());
let _ = tx.send(result);
});
@ -355,26 +344,26 @@ impl Window {
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
pub fn hidpi_factor(&self) -> f64 {
self.window_state.lock().dpi_factor
}
fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> {
fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> {
let mut point = POINT { x, y };
unsafe {
if winuser::ClientToScreen(self.window.0, &mut point) == 0 {
return Err("`ClientToScreen` failed".to_owned());
return Err(ExternalError::Os(os_error!(io::Error::last_os_error())));
}
if winuser::SetCursorPos(point.x, point.y) == 0 {
return Err("`SetCursorPos` failed".to_owned());
return Err(ExternalError::Os(os_error!(io::Error::last_os_error())));
}
}
Ok(())
}
#[inline]
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> {
let dpi_factor = self.get_hidpi_factor();
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), ExternalError> {
let dpi_factor = self.hidpi_factor();
let (x, y) = logical_position.to_physical(dpi_factor).into();
self.set_cursor_position_physical(x, y)
}
@ -399,6 +388,12 @@ impl Window {
});
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
let window_state = self.window_state.lock();
window_state.fullscreen.clone()
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
unsafe {
@ -407,8 +402,8 @@ impl Window {
match &monitor {
&Some(RootMonitorHandle { ref inner }) => {
let (x, y): (i32, i32) = inner.get_position().into();
let (width, height): (u32, u32) = inner.get_dimensions().into();
let (x, y): (i32, i32) = inner.position().into();
let (width, height): (u32, u32) = inner.dimensions().into();
let mut monitor = monitor.clone();
self.thread_executor.execute_in_thread(move || {
@ -490,9 +485,9 @@ impl Window {
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorHandle {
pub fn current_monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: monitor::get_current_monitor(self.window.0),
inner: monitor::current_monitor(self.window.0),
}
}
@ -523,7 +518,7 @@ impl Window {
}
#[inline]
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
pub fn set_ime_position(&self, _logical_spot: LogicalPosition) {
unimplemented!();
}
}
@ -569,9 +564,9 @@ pub unsafe fn adjust_size(
unsafe fn init<T: 'static>(
mut attributes: WindowAttributes,
mut pl_attribs: PlatformSpecificWindowBuilderAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
event_loop: &EventLoopWindowTarget<T>,
) -> Result<Window, CreationError> {
) -> Result<Window, RootOsError> {
let title = OsStr::new(&attributes.title)
.encode_wide()
.chain(Some(0).into_iter())
@ -581,22 +576,18 @@ unsafe fn init<T: 'static>(
let icon = attributes.window_icon
.take()
.map(WinIcon::from_icon);
if icon.is_some() {
Some(icon.unwrap().map_err(|err| {
CreationError::OsError(format!("Failed to create `ICON_SMALL`: {:?}", err))
})?)
if let Some(icon) = icon {
Some(icon.map_err(|e| os_error!(e))?)
} else {
None
}
};
let taskbar_icon = {
let icon = pl_attribs.taskbar_icon
let icon = attributes.window_icon
.take()
.map(WinIcon::from_icon);
if icon.is_some() {
Some(icon.unwrap().map_err(|err| {
CreationError::OsError(format!("Failed to create `ICON_BIG`: {:?}", err))
})?)
if let Some(icon) = icon {
Some(icon.map_err(|e| os_error!(e))?)
} else {
None
}
@ -606,17 +597,17 @@ unsafe fn init<T: 'static>(
let class_name = register_window_class(&window_icon, &taskbar_icon);
let guessed_dpi_factor = {
let monitors = monitor::get_available_monitors();
let monitors = monitor::available_monitors();
let dpi_factor = if !monitors.is_empty() {
let mut dpi_factor = Some(monitors[0].get_hidpi_factor());
let mut dpi_factor = Some(monitors[0].hidpi_factor());
for monitor in &monitors {
if Some(monitor.get_hidpi_factor()) != dpi_factor {
if Some(monitor.hidpi_factor()) != dpi_factor {
dpi_factor = None;
}
}
dpi_factor
} else {
return Err(CreationError::OsError(format!("No monitors were detected.")));
return Err(os_error!(io::Error::new(io::ErrorKind::NotFound, "No monitors were detected.")));
};
dpi_factor.unwrap_or_else(|| {
util::get_cursor_pos()
@ -624,7 +615,7 @@ unsafe fn init<T: 'static>(
let mut dpi_factor = None;
for monitor in &monitors {
if monitor.contains_point(&cursor_pos) {
dpi_factor = Some(monitor.get_hidpi_factor());
dpi_factor = Some(monitor.hidpi_factor());
break;
}
}
@ -635,7 +626,7 @@ unsafe fn init<T: 'static>(
};
info!("Guessed window DPI factor: {}", guessed_dpi_factor);
let dimensions = attributes.dimensions.unwrap_or_else(|| (1024, 768).into());
let dimensions = attributes.inner_size.unwrap_or_else(|| (1024, 768).into());
let mut window_flags = WindowFlags::empty();
window_flags.set(WindowFlags::DECORATIONS, attributes.decorations);
@ -664,13 +655,9 @@ unsafe fn init<T: 'static>(
);
if handle.is_null() {
return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}",
format!("{}", io::Error::last_os_error()))));
return Err(os_error!(io::Error::last_os_error()));
}
winuser::SetWindowLongW(handle, winuser::GWL_STYLE, 0);
winuser::SetWindowLongW(handle, winuser::GWL_EXSTYLE, 0);
WindowWrapper(handle)
};
@ -685,7 +672,7 @@ unsafe fn init<T: 'static>(
}
}
let dpi = get_hwnd_dpi(real_window.0);
let dpi = hwnd_dpi(real_window.0);
let dpi_factor = dpi_to_scale_factor(dpi);
if dpi_factor != guessed_dpi_factor {
let (width, height): (u32, u32) = dimensions.into();
@ -733,7 +720,7 @@ unsafe fn init<T: 'static>(
window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized);
let window_state = {
let mut window_state = WindowState::new(
let window_state = WindowState::new(
&attributes,
window_icon,
taskbar_icon,
@ -760,7 +747,7 @@ unsafe fn init<T: 'static>(
force_window_active(win.window.0);
}
if let Some(dimensions) = attributes.dimensions {
if let Some(dimensions) = attributes.inner_size {
win.set_inner_size(dimensions);
}

View file

@ -1,5 +1,5 @@
use monitor::MonitorHandle;
use window::{MouseCursor, WindowAttributes};
use window::{CursorIcon, WindowAttributes};
use std::{io, ptr};
use parking_lot::MutexGuard;
use dpi::LogicalSize;
@ -36,7 +36,7 @@ pub struct SavedWindow {
#[derive(Clone)]
pub struct MouseProperties {
pub cursor: MouseCursor,
pub cursor: CursorIcon,
pub buttons_down: u32,
cursor_flags: CursorFlags,
}
@ -90,13 +90,13 @@ impl WindowState {
) -> WindowState {
WindowState {
mouse: MouseProperties {
cursor: MouseCursor::default(),
cursor: CursorIcon::default(),
buttons_down: 0,
cursor_flags: CursorFlags::empty(),
},
min_size: attributes.min_dimensions,
max_size: attributes.max_dimensions,
min_size: attributes.min_inner_size,
max_size: attributes.max_inner_size,
window_icon,
taskbar_icon,

View file

@ -1,7 +1,8 @@
//! The `Window` struct and associated types.
use std::{fmt, error};
use std::fmt;
use platform_impl;
use error::{ExternalError, NotSupportedError, OsError};
use event_loop::EventLoopWindowTarget;
use monitor::{AvailableMonitorsIter, MonitorHandle};
use dpi::{LogicalPosition, LogicalSize};
@ -84,17 +85,17 @@ pub struct WindowAttributes {
/// used.
///
/// The default is `None`.
pub dimensions: Option<LogicalSize>,
pub inner_size: Option<LogicalSize>,
/// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved).
///
/// The default is `None`.
pub min_dimensions: Option<LogicalSize>,
pub min_inner_size: Option<LogicalSize>,
/// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform.
///
/// The default is `None`.
pub max_dimensions: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>,
/// Whether the window is resizable or not.
///
@ -141,19 +142,15 @@ pub struct WindowAttributes {
///
/// The default is `None`.
pub window_icon: Option<Icon>,
/// [iOS only] Enable multitouch,
/// see [multipleTouchEnabled](https://developer.apple.com/documentation/uikit/uiview/1622519-multipletouchenabled)
pub multitouch: bool,
}
impl Default for WindowAttributes {
#[inline]
fn default() -> WindowAttributes {
WindowAttributes {
dimensions: None,
min_dimensions: None,
max_dimensions: None,
inner_size: None,
min_inner_size: None,
max_inner_size: None,
resizable: true,
title: "winit window".to_owned(),
maximized: false,
@ -163,7 +160,6 @@ impl Default for WindowAttributes {
decorations: true,
always_on_top: false,
window_icon: None,
multitouch: false,
}
}
}
@ -179,22 +175,22 @@ impl WindowBuilder {
/// Requests the window to be of specific dimensions.
#[inline]
pub fn with_dimensions(mut self, size: LogicalSize) -> WindowBuilder {
self.window.dimensions = Some(size);
pub fn with_inner_size(mut self, size: LogicalSize) -> WindowBuilder {
self.window.inner_size = Some(size);
self
}
/// Sets a minimum dimension size for the window
#[inline]
pub fn with_min_dimensions(mut self, min_size: LogicalSize) -> WindowBuilder {
self.window.min_dimensions = Some(min_size);
pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> WindowBuilder {
self.window.min_inner_size = Some(min_size);
self
}
/// Sets a maximum dimension size for the window
#[inline]
pub fn with_max_dimensions(mut self, max_size: LogicalSize) -> WindowBuilder {
self.window.max_dimensions = Some(max_size);
pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> WindowBuilder {
self.window.max_inner_size = Some(max_size);
self
}
@ -282,23 +278,16 @@ impl WindowBuilder {
self
}
/// Enables multitouch.
#[inline]
pub fn with_multitouch(mut self) -> WindowBuilder {
self.window.multitouch = true;
self
}
/// Builds the window.
///
/// Error should be very rare and only occur in case of permission denied, incompatible system,
/// out of memory, etc.
#[inline]
pub fn build<T: 'static>(mut self, window_target: &EventLoopWindowTarget<T>) -> Result<Window, CreationError> {
self.window.dimensions = Some(self.window.dimensions.unwrap_or_else(|| {
pub fn build<T: 'static>(mut self, window_target: &EventLoopWindowTarget<T>) -> Result<Window, OsError> {
self.window.inner_size = Some(self.window.inner_size.unwrap_or_else(|| {
if let Some(ref monitor) = self.window.fullscreen {
// resizing the window to the dimensions of the monitor when fullscreen
LogicalSize::from_physical(monitor.get_dimensions(), 1.0)
LogicalSize::from_physical(monitor.dimensions(), 1.0)
} else {
// default dimensions
(1024, 768).into()
@ -314,6 +303,7 @@ impl WindowBuilder {
}
}
/// Base Window functions.
impl Window {
/// Creates a new Window for platforms where this is appropriate.
///
@ -322,39 +312,36 @@ impl Window {
/// Error should be very rare and only occur in case of permission denied, incompatible system,
/// out of memory, etc.
#[inline]
pub fn new<T: 'static>(event_loop: &EventLoopWindowTarget<T>) -> Result<Window, CreationError> {
pub fn new<T: 'static>(event_loop: &EventLoopWindowTarget<T>) -> Result<Window, OsError> {
let builder = WindowBuilder::new();
builder.build(event_loop)
}
/// Modifies the title of the window.
///
/// This is a no-op if the window has already been closed.
/// Returns an identifier unique to the window.
#[inline]
pub fn set_title(&self, title: &str) {
self.window.set_title(title)
pub fn id(&self) -> WindowId {
WindowId(self.window.id())
}
/// Shows the window if it was hidden.
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](dpi/index.html) module for more information.
///
/// Note that this value can change depending on user action (for example if the window is
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
/// the most robust way to track the DPI you need to use to draw.
///
/// ## Platform-specific
///
/// - Has no effect on Android
/// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
/// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s
/// [`contentScaleFactor`].
///
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
#[inline]
pub fn show(&self) {
self.window.show()
}
/// Hides the window if it was visible.
///
/// ## Platform-specific
///
/// - Has no effect on Android
///
#[inline]
pub fn hide(&self) {
self.window.hide()
pub fn hidpi_factor(&self) -> f64 {
self.window.hidpi_factor()
}
/// Emits a `WindowEvent::RedrawRequested` event in the associated event loop after all OS
@ -368,9 +355,33 @@ impl Window {
/// * While processing `EventsCleared`.
/// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any
/// directly subsequent `RedrawRequested` event.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
#[inline]
pub fn request_redraw(&self) {
self.window.request_redraw()
}
}
/// Position and size functions.
impl Window {
/// Returns the position of the top-left hand corner of the window's client area relative to the
/// top-left hand corner of the desktop.
///
/// The same conditions that apply to `outer_position` apply to this method.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the
/// window's [safe area] in the screen space coordinate system.
///
/// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[inline]
pub fn inner_position(&self) -> Result<LogicalPosition, NotSupportedError> {
self.window.inner_position()
}
/// Returns the position of the top-left hand corner of the window relative to the
/// top-left hand corner of the desktop.
@ -382,29 +393,28 @@ impl Window {
/// The coordinates can be negative if the top-left hand corner of the window is outside
/// of the visible screen region.
///
/// Returns `None` if the window no longer exists.
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
self.window.get_position()
}
/// Returns the position of the top-left hand corner of the window's client area relative to the
/// top-left hand corner of the desktop.
/// ## Platform-specific
///
/// The same conditions that apply to `get_position` apply to this method.
/// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the
/// window in the screen space coordinate system.
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
self.window.get_inner_position()
pub fn outer_position(&self) -> Result<LogicalPosition, NotSupportedError> {
self.window.outer_position()
}
/// Modifies the position of the window.
///
/// See `get_position` for more information about the coordinates.
/// See `outer_position` for more information about the coordinates.
///
/// This is a no-op if the window has already been closed.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the
/// window in the screen space coordinate system.
#[inline]
pub fn set_position(&self, position: LogicalPosition) {
self.window.set_position(position)
pub fn set_outer_position(&self, position: LogicalPosition) {
self.window.set_outer_position(position)
}
/// Returns the logical size of the window's client area.
@ -413,43 +423,87 @@ impl Window {
///
/// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be.
///
/// Returns `None` if the window no longer exists.
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
self.window.get_inner_size()
}
/// Returns the logical size of the entire window.
/// ## Platform-specific
///
/// These dimensions include the title bar and borders. If you don't want that (and you usually don't),
/// use `get_inner_size` instead.
/// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window's
/// [safe area] in screen space coordinates.
///
/// Returns `None` if the window no longer exists.
/// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.window.get_outer_size()
pub fn inner_size(&self) -> LogicalSize {
self.window.inner_size()
}
/// Modifies the inner size of the window.
///
/// See `get_inner_size` for more information about the values.
/// See `inner_size` for more information about the values.
///
/// This is a no-op if the window has already been closed.
/// ## Platform-specific
///
/// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size`
/// would mean for iOS.
#[inline]
pub fn set_inner_size(&self, size: LogicalSize) {
self.window.set_inner_size(size)
}
/// Sets a minimum dimension size for the window.
/// Returns the logical size of the entire window.
///
/// These dimensions include the title bar and borders. If you don't want that (and you usually don't),
/// use `inner_size` instead.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window in
/// screen space coordinates.
#[inline]
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_min_dimensions(dimensions)
pub fn outer_size(&self) -> LogicalSize {
self.window.outer_size()
}
/// Sets a minimum dimension size for the window.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline]
pub fn set_min_inner_size(&self, dimensions: Option<LogicalSize>) {
self.window.set_min_inner_size(dimensions)
}
/// Sets a maximum dimension size for the window.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline]
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_max_dimensions(dimensions)
pub fn set_max_inner_size(&self, dimensions: Option<LogicalSize>) {
self.window.set_max_inner_size(dimensions)
}
}
/// Misc. attribute functions.
impl Window {
/// Modifies the title of the window.
///
/// ## Platform-specific
///
/// - Has no effect on iOS.
#[inline]
pub fn set_title(&self, title: &str) {
self.window.set_title(title)
}
/// Modifies the window's visibility.
///
/// If `false`, this will hide the window. If `true`, this will show the window.
/// ## Platform-specific
///
/// - **Android:** Has no effect.
/// - **iOS:** Can only be called on the main thread.
#[inline]
pub fn set_visible(&self, visible: bool) {
self.window.set_visible(visible)
}
/// Sets whether the window is resizable or not.
@ -462,87 +516,63 @@ impl Window {
/// This only has an effect on desktop platforms.
///
/// Due to a bug in XFCE, this has no effect on Xfwm.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.window.set_resizable(resizable)
}
/// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa.
///
/// See the [`dpi`](dpi/index.html) module for more information.
///
/// Note that this value can change depending on user action (for example if the window is
/// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is
/// the most robust way to track the DPI you need to use to draw.
/// Sets the window to maximized or back.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.window.get_hidpi_factor()
}
/// Modifies the mouse cursor of the window.
/// Has no effect on Android.
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
self.window.set_cursor(cursor);
}
/// Changes the position of the cursor in window coordinates.
#[inline]
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
self.window.set_cursor_position(position)
}
/// Grabs the cursor, preventing it from leaving the window.
///
/// ## Platform-specific
///
/// On macOS, this presently merely locks the cursor in a fixed location, which looks visually awkward.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
self.window.grab_cursor(grab)
}
/// Hides the cursor, making it invisible but still usable.
///
/// ## Platform-specific
///
/// On Windows and X11, the cursor is only hidden within the confines of the window.
///
/// On macOS, the cursor is hidden as long as the window has input focus, even if the cursor is outside of the
/// window.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn hide_cursor(&self, hide: bool) {
self.window.hide_cursor(hide)
}
/// Sets the window to maximized or back
/// - **iOS:** Has no effect.
#[inline]
pub fn set_maximized(&self, maximized: bool) {
self.window.set_maximized(maximized)
}
/// Sets the window to fullscreen or back
/// Sets the window to fullscreen or back.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
#[inline]
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) {
self.window.set_fullscreen(monitor)
}
/// Gets the window's current fullscreen state.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
#[inline]
pub fn fullscreen(&self) -> Option<MonitorHandle> {
self.window.fullscreen()
}
/// Turn window decorations on or off.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden
/// via [`setPrefersStatusBarHidden`].
///
/// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc
#[inline]
pub fn set_decorations(&self, decorations: bool) {
self.window.set_decorations(decorations)
}
/// Change whether or not the window will always be on top of other windows.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
self.window.set_always_on_top(always_on_top)
@ -562,74 +592,110 @@ impl Window {
}
/// Sets location of IME candidate box in client area coordinates relative to the top left.
///
/// ## Platform-specific
///
/// **iOS:** Has no effect.
#[inline]
pub fn set_ime_spot(&self, position: LogicalPosition) {
self.window.set_ime_spot(position)
pub fn set_ime_position(&self, position: LogicalPosition) {
self.window.set_ime_position(position)
}
}
/// Cursor functions.
impl Window {
/// Modifies the cursor icon of the window.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
/// - **Android:** Has no effect.
#[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window.set_cursor_icon(cursor);
}
/// Returns the monitor on which the window currently resides
/// Changes the position of the cursor in window coordinates.
///
/// ## Platform-specific
///
/// - **iOS:** Always returns an `Err`.
#[inline]
pub fn get_current_monitor(&self) -> MonitorHandle {
self.window.get_current_monitor()
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> {
self.window.set_cursor_position(position)
}
/// Grabs the cursor, preventing it from leaving the window.
///
/// ## Platform-specific
///
/// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually
/// awkward.
/// - **Android:** Has no effect.
/// - **iOS:** Always returns an Err.
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
self.window.set_cursor_grab(grab)
}
/// Hides the cursor, making it invisible but still usable.
///
/// ## Platform-specific
///
/// - **Windows:** The cursor is only hidden within the confines of the window.
/// - **X11:** The cursor is only hidden within the confines of the window.
/// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
/// outside of the window.
/// - **iOS:** Has no effect.
/// - **Android:** Has no effect.
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window.set_cursor_visible(visible)
}
}
/// Monitor info functions.
impl Window {
/// Returns the monitor on which the window currently resides
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
#[inline]
pub fn current_monitor(&self) -> MonitorHandle {
self.window.current_monitor()
}
/// Returns the list of all the monitors available on the system.
///
/// This is the same as `EventLoop::get_available_monitors`, and is provided for convenience.
/// This is the same as `EventLoop::available_monitors`, and is provided for convenience.
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
#[inline]
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
let data = self.window.get_available_monitors();
pub fn available_monitors(&self) -> AvailableMonitorsIter {
let data = self.window.available_monitors();
AvailableMonitorsIter { data: data.into_iter() }
}
/// Returns the primary monitor of the system.
///
/// This is the same as `EventLoop::get_primary_monitor`, and is provided for convenience.
/// This is the same as `EventLoop::primary_monitor`, and is provided for convenience.
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.window.get_primary_monitor() }
}
/// Returns an identifier unique to the window.
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.window.id())
}
}
/// Error that can happen while creating a window or a headless renderer.
#[derive(Debug, Clone)]
pub enum CreationError {
OsError(String),
/// TODO: remove this error
NotSupported,
}
impl CreationError {
fn to_string(&self) -> &str {
match *self {
CreationError::OsError(ref text) => &text,
CreationError::NotSupported => "Some of the requested attributes are not supported",
}
}
}
impl fmt::Display for CreationError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
formatter.write_str(self.to_string())
}
}
impl error::Error for CreationError {
fn description(&self) -> &str {
self.to_string()
pub fn primary_monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.window.primary_monitor() }
}
}
/// Describes the appearance of the mouse cursor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseCursor {
pub enum CursorIcon {
/// The platform-dependent default cursor.
Default,
/// A simple crosshair.
@ -683,8 +749,8 @@ pub enum MouseCursor {
RowResize,
}
impl Default for MouseCursor {
impl Default for CursorIcon {
fn default() -> Self {
MouseCursor::Default
CursorIcon::Default
}
}

View file

@ -3,7 +3,7 @@
extern crate serde;
extern crate winit;
use winit::window::{MouseCursor};
use winit::window::{CursorIcon};
use winit::event::{
KeyboardInput, TouchPhase, ElementState, MouseButton, MouseScrollDelta, VirtualKeyCode,
ModifiersState
@ -15,7 +15,7 @@ fn needs_serde<S: Serialize + Deserialize<'static>>() {}
#[test]
fn window_serde() {
needs_serde::<MouseCursor>();
needs_serde::<CursorIcon>();
}
#[test]