mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
316d8306ab | ||
|
|
91591c4e94 | ||
|
|
3f155167ba | ||
|
|
078b9719cc | ||
|
|
41d9826ee9 | ||
|
|
0152508a39 | ||
|
|
cdeb1c3828 | ||
|
|
0986fae066 | ||
|
|
277515636d | ||
|
|
45aacd8407 | ||
|
|
e8cdf8b092 | ||
|
|
1c4d6e7613 | ||
|
|
04b4e48265 | ||
|
|
dabcb1834d | ||
|
|
629cd86c7c | ||
|
|
ba704c4eb4 | ||
|
|
0487876826 | ||
|
|
ca9c05368e | ||
|
|
0d634a0061 | ||
|
|
86748fbc68 | ||
|
|
599477d754 | ||
|
|
889258f538 | ||
|
|
ffe2143d14 | ||
|
|
98470393d1 | ||
|
|
4192d04a53 | ||
|
|
3571dcd68c | ||
|
|
952edcb804 | ||
|
|
10a94c0794 | ||
|
|
dd32ace9ab | ||
|
|
7e0c6ee097 | ||
|
|
b1be34c6a0 | ||
|
|
b9307a9967 | ||
|
|
b1d353180b | ||
|
|
bd99eb1347 | ||
|
|
f79c01b0cf | ||
|
|
3f1e09ec0e | ||
|
|
05125029c6 | ||
|
|
05fe983757 | ||
|
|
d1a7749df5 | ||
|
|
9d63fc7ca0 | ||
|
|
38fccebe1f | ||
|
|
c05952b813 | ||
|
|
932cbe40bf | ||
|
|
39573d65d0 |
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -2,8 +2,8 @@ name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths: "Cargo.toml"
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
jobs:
|
||||
Publish:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ rls/
|
||||
*.ts
|
||||
*.js
|
||||
#*#
|
||||
.DS_Store
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -1,25 +1,58 @@
|
||||
# 0.25.0 (2021-05-15)
|
||||
|
||||
- **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy`
|
||||
- On macOS, wait with activating the application until the application has initialized.
|
||||
- On macOS, fix creating new windows when the application has a main menu.
|
||||
- On Windows, fix fractional deltas for mouse wheel device events.
|
||||
- On macOS, fix segmentation fault after dropping the main window.
|
||||
- On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode.
|
||||
- Added `is_maximized` method to `Window`.
|
||||
- On Windows, fix bug where clicking the decoration bar would make the cursor blink.
|
||||
- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor.
|
||||
- On macOS, wake up the event loop immediately when a redraw is requested.
|
||||
- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600).
|
||||
- On Windows, fix bug causing mouse capture to not be released.
|
||||
- On Windows, fix fullscreen not preserving minimized/maximized state.
|
||||
- On Android, unimplemented events are marked as unhandled on the native event loop.
|
||||
- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time.
|
||||
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
|
||||
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
|
||||
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
|
||||
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
|
||||
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
|
||||
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
|
||||
- On X11, bump `mio` to 0.7.
|
||||
- On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows.
|
||||
- On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows.
|
||||
- On macOS, emit `RedrawRequested` events immediately while the window is being resized.
|
||||
- Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`.
|
||||
- On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`)
|
||||
- On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click".
|
||||
- Implement mint (math interoperability standard types) conversions (under feature flag `mint`).
|
||||
|
||||
# 0.24.0 (2020-12-09)
|
||||
|
||||
- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory.
|
||||
- On Windows, implement `Window::set_ime_position`.
|
||||
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
|
||||
- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`.
|
||||
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
|
||||
- On Windows, fix bug causing message boxes to appear delayed.
|
||||
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
|
||||
- On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag.
|
||||
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
|
||||
- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag.
|
||||
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.
|
||||
- **Breaking:** On Windows, include prefix byte in scancodes.
|
||||
- On Wayland, fix window not being resizeable when using `with_min_inner_size` in `WindowBuilder`.
|
||||
- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`.
|
||||
- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland.
|
||||
- On Windows, fix use after free crash during window destruction.
|
||||
- On Windows, fix use-after-free crash during window destruction.
|
||||
- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input.
|
||||
- On macOS, fix compilation when targeting aarch64
|
||||
- On macOS, fix compilation when targeting aarch64.
|
||||
- On X11, fix `Window::request_redraw` not waking the event loop.
|
||||
- On Wayland, the keypad arrow keys are now recognized.
|
||||
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`.
|
||||
- Added `request_user_attention` method to `Window`.
|
||||
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
|
||||
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
|
||||
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
|
||||
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
|
||||
- On Wayland, default font size in CSD increased from 11 to 17.
|
||||
- On Windows, fix bug causing message boxes to appear delayed.
|
||||
- On Android, support multi-touch.
|
||||
@@ -34,7 +67,7 @@
|
||||
- On X11, fix deadlock when calling `set_fullscreen_inner`.
|
||||
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
|
||||
- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling
|
||||
- On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`.
|
||||
- On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
|
||||
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
|
||||
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
|
||||
- On android added support for `run_return`.
|
||||
@@ -107,7 +140,6 @@
|
||||
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
|
||||
- On Web, fix a possible panic during event handling
|
||||
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
|
||||
- On Windows, drag and drop can now be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
|
||||
|
||||
# 0.22.0 (2020-03-09)
|
||||
|
||||
|
||||
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.24.0"
|
||||
version = "0.25.0"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
@@ -20,7 +20,7 @@ targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux
|
||||
default = ["x11", "wayland"]
|
||||
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
||||
stdweb = ["std_web", "instant/stdweb"]
|
||||
x11 = ["x11-dl", "mio", "mio-extras", "percent-encoding", "parking_lot"]
|
||||
x11 = ["x11-dl", "mio", "mio-misc", "percent-encoding", "parking_lot"]
|
||||
wayland = ["wayland-client", "sctk"]
|
||||
|
||||
[dependencies]
|
||||
@@ -31,15 +31,16 @@ log = "0.4"
|
||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
||||
raw-window-handle = "0.3"
|
||||
bitflags = "1"
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.23.12"
|
||||
simple_logger = "1.9"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk = "0.2.0"
|
||||
ndk = "0.3"
|
||||
ndk-sys = "0.2.0"
|
||||
ndk-glue = "0.2.0"
|
||||
ndk-glue = "0.3"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
objc = "0.2.7"
|
||||
@@ -49,6 +50,7 @@ cocoa = "0.24"
|
||||
core-foundation = "0.9"
|
||||
core-graphics = "0.22"
|
||||
dispatch = "0.2.0"
|
||||
scopeguard = "1.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
version = "0.1.4"
|
||||
@@ -85,9 +87,9 @@ features = [
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.12", optional = true }
|
||||
mio = { version = "0.6", optional = true }
|
||||
mio-extras = { version = "2.0", optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.12.3", optional = true }
|
||||
mio = { version = "0.7", features = ["os-ext"], optional = true }
|
||||
mio-misc = { version = "1.0", optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
parking_lot = { version = "0.11.0", optional = true }
|
||||
|
||||
@@ -116,6 +116,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
### Windows
|
||||
* Setting the taskbar icon
|
||||
* Setting the parent window
|
||||
* Setting a menu bar
|
||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
||||
|
||||
@@ -208,6 +209,7 @@ Legend:
|
||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|
||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|
||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|
||||
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||
|
||||
### Pending API Reworks
|
||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
||||
|
||||
13
README.md
13
README.md
@@ -6,7 +6,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.24.0"
|
||||
winit = "0.25.0"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -66,6 +66,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
||||
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
||||
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
|
||||
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
|
||||
* `mint`: Enables mint (math interoperability standard types) conversions.
|
||||
|
||||
### Platform-specific usage
|
||||
|
||||
@@ -110,3 +111,13 @@ fn main() {
|
||||
```
|
||||
|
||||
And run the application with `cargo apk run --example request_redraw_threaded`
|
||||
|
||||
#### MacOS
|
||||
|
||||
To ensure compatibility with older MacOS systems, winit links to
|
||||
CGDisplayCreateUUIDFromDisplayID through the CoreGraphics framework.
|
||||
However, under certain setups this function is only available to be linked
|
||||
through the newer ColorSync framework. So, winit provides the
|
||||
`WINIT_LINK_COLORSYNC` environment variable which can be set to `1` or `true`
|
||||
while compiling to enable linking via ColorSync.
|
||||
|
||||
|
||||
10
build.rs
Normal file
10
build.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
fn main() {
|
||||
// If building for macos and WINIT_LINK_COLORSYNC is set to true
|
||||
// use CGDisplayCreateUUIDFromDisplayID from ColorSync instead of CoreGraphics
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").map_or(false, |os| os == "macos")
|
||||
&& std::env::var("WINIT_LINK_COLORSYNC")
|
||||
.map_or(false, |v| v == "1" || v.eq_ignore_ascii_case("true"))
|
||||
{
|
||||
println!("cargo:rustc-cfg=use_colorsync_cgdisplaycreateuuidfromdisplayid");
|
||||
}
|
||||
}
|
||||
73
examples/drag_window.rs
Normal file
73
examples/drag_window.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{
|
||||
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
|
||||
},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
let mut switched = false;
|
||||
let mut entered_id = window_2.id();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
eprintln!("Switch which window is to be dragged by pressing \"x\".")
|
||||
}
|
||||
Event::WindowEvent { event, window_id } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
} => {
|
||||
let window = if (window_id == window_1.id() && switched)
|
||||
|| (window_id == window_2.id() && !switched)
|
||||
{
|
||||
&window_2
|
||||
} else {
|
||||
&window_1
|
||||
};
|
||||
|
||||
window.drag_window().unwrap()
|
||||
}
|
||||
WindowEvent::CursorEntered { .. } => {
|
||||
entered_id = window_id;
|
||||
name_windows(entered_id, switched, &window_1, &window_2)
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state: ElementState::Released,
|
||||
virtual_keycode: Some(VirtualKeyCode::X),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
switched = !switched;
|
||||
name_windows(entered_id, switched, &window_1, &window_2);
|
||||
println!("Switched!")
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
|
||||
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
|
||||
let (drag_target, other) =
|
||||
if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) {
|
||||
(&window_2, &window_1)
|
||||
} else {
|
||||
(&window_1, &window_2)
|
||||
};
|
||||
drag_target.set_title("drag target");
|
||||
other.set_title("winit window");
|
||||
}
|
||||
@@ -23,7 +23,6 @@ fn main() {
|
||||
_ => panic!("Please enter a valid number"),
|
||||
});
|
||||
|
||||
let mut is_maximized = false;
|
||||
let mut decorations = true;
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
@@ -59,8 +58,8 @@ fn main() {
|
||||
println!("window.fullscreen {:?}", window.fullscreen());
|
||||
}
|
||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||
is_maximized = !is_maximized;
|
||||
window.set_maximized(is_maximized);
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
||||
decorations = !decorations;
|
||||
|
||||
48
examples/mouse_wheel.rs
Normal file
48
examples/mouse_wheel.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use simple_logger::SimpleLogger;
|
||||
use winit::{
|
||||
event::{DeviceEvent, Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Mouse Wheel events")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
DeviceEvent::MouseWheel { delta } => match delta {
|
||||
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
||||
println!("mouse wheel Line Delta: ({},{})", x, y);
|
||||
let pixels_per_line = 120.0;
|
||||
let mut pos = window.outer_position().unwrap();
|
||||
pos.x -= (x * pixels_per_line) as i32;
|
||||
pos.y -= (y * pixels_per_line) as i32;
|
||||
window.set_outer_position(pos)
|
||||
}
|
||||
winit::event::MouseScrollDelta::PixelDelta(p) => {
|
||||
println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y);
|
||||
let mut pos = window.outer_position().unwrap();
|
||||
pos.x -= p.x as i32;
|
||||
pos.y -= p.y as i32;
|
||||
window.set_outer_position(pos)
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -28,7 +28,6 @@ fn main() {
|
||||
eprintln!(" (X) Toggle maximized");
|
||||
|
||||
let mut minimized = false;
|
||||
let mut maximized = false;
|
||||
let mut visible = true;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
@@ -109,8 +108,8 @@ fn main() {
|
||||
window.set_visible(visible);
|
||||
}
|
||||
VirtualKeyCode::X => {
|
||||
maximized = !maximized;
|
||||
window.set_maximized(maximized);
|
||||
let is_maximized = window.is_maximized();
|
||||
window.set_maximized(!is_maximized);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
|
||||
83
src/dpi.rs
83
src/dpi.rs
@@ -69,9 +69,10 @@
|
||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
|
||||
//! global and changing it requires logging out. See [this article][windows_1] for technical
|
||||
//! details.
|
||||
//! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0.
|
||||
//! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale
|
||||
//! factor, given the use of the command line.
|
||||
//! - **macOS:** Recent versions of macOS allow the user to change the scaling factor for certain
|
||||
//! displays. When this is available, the user may pick a per-monitor scaling factor from a set
|
||||
//! of pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default but
|
||||
//! the specific value varies across devices.
|
||||
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
|
||||
//! currently uses a three-pronged approach:
|
||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
||||
@@ -163,7 +164,7 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
|
||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
||||
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
|
||||
/// implementation is provided which does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalPosition<P> {
|
||||
pub x: P,
|
||||
@@ -227,8 +228,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
|
||||
fn from(mint: mint::Point2<P>) -> Self {
|
||||
Self::new(mint.x, mint.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
|
||||
fn from(winit: LogicalPosition<P>) -> Self {
|
||||
mint::Point2 {
|
||||
x: winit.x,
|
||||
y: winit.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A position represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalPosition<P> {
|
||||
pub x: P,
|
||||
@@ -292,8 +310,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
|
||||
fn from(mint: mint::Point2<P>) -> Self {
|
||||
Self::new(mint.x, mint.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
|
||||
fn from(winit: PhysicalPosition<P>) -> Self {
|
||||
mint::Point2 {
|
||||
x: winit.x,
|
||||
y: winit.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in logical pixels.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LogicalSize<P> {
|
||||
pub width: P,
|
||||
@@ -357,8 +392,25 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
|
||||
fn from(mint: mint::Vector2<P>) -> Self {
|
||||
Self::new(mint.x, mint.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
|
||||
fn from(winit: LogicalSize<P>) -> Self {
|
||||
mint::Vector2 {
|
||||
x: winit.width,
|
||||
y: winit.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A size represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct PhysicalSize<P> {
|
||||
pub width: P,
|
||||
@@ -419,6 +471,23 @@ impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
|
||||
fn from(mint: mint::Vector2<P>) -> Self {
|
||||
Self::new(mint.x, mint.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
|
||||
fn from(winit: PhysicalSize<P>) -> Self {
|
||||
mint::Vector2 {
|
||||
x: winit.width,
|
||||
y: winit.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A size that's either physical or logical.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
||||
@@ -4,8 +4,9 @@ use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::EventLoopWindowTarget,
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::get_aux_state_mut,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -100,8 +101,6 @@ impl Default for ActivationPolicy {
|
||||
/// - `with_titlebar_buttons_hidden`
|
||||
/// - `with_fullsize_content_view`
|
||||
pub trait WindowBuilderExtMacOS {
|
||||
/// Sets the activation policy for the window being built.
|
||||
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
|
||||
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
|
||||
fn with_movable_by_window_background(self, movable_by_window_background: bool)
|
||||
-> WindowBuilder;
|
||||
@@ -122,12 +121,6 @@ pub trait WindowBuilderExtMacOS {
|
||||
}
|
||||
|
||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_movable_by_window_background(
|
||||
mut self,
|
||||
@@ -186,6 +179,39 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoopExtMacOS {
|
||||
/// Sets the activation policy for the application. It is set to
|
||||
/// `NSApplicationActivationPolicyRegular` by default.
|
||||
///
|
||||
/// This function only takes effect if it's called before calling [`run`](crate::event_loop::EventLoop::run) or
|
||||
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
|
||||
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy);
|
||||
|
||||
/// Used to prevent a default menubar menu from getting created
|
||||
///
|
||||
/// The default menu creation is enabled by default.
|
||||
///
|
||||
/// This function only takes effect if it's called before calling
|
||||
/// [`run`](crate::event_loop::EventLoop::run) or
|
||||
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return)
|
||||
fn enable_default_menu_creation(&mut self, enable: bool);
|
||||
}
|
||||
impl<T> EventLoopExtMacOS for EventLoop<T> {
|
||||
#[inline]
|
||||
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
|
||||
unsafe {
|
||||
get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enable_default_menu_creation(&mut self, enable: bool) {
|
||||
unsafe {
|
||||
get_aux_state_mut(&**self.event_loop.delegate).create_default_menu = enable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `MonitorHandle` that are specific to MacOS.
|
||||
pub trait MonitorHandleExtMacOS {
|
||||
/// Returns the identifier of the monitor for Cocoa.
|
||||
|
||||
@@ -5,14 +5,14 @@ use std::path::Path;
|
||||
|
||||
use libc;
|
||||
use winapi::shared::minwindef::WORD;
|
||||
use winapi::shared::windef::HWND;
|
||||
use winapi::shared::windef::{HMENU, HWND};
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::DeviceId,
|
||||
event_loop::EventLoop,
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
|
||||
platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon},
|
||||
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -78,6 +78,21 @@ pub trait WindowExtWindows {
|
||||
/// The pointer will become invalid when the native window was destroyed.
|
||||
fn hwnd(&self) -> *mut libc::c_void;
|
||||
|
||||
/// Enables or disables mouse and keyboard input to the specified window.
|
||||
///
|
||||
/// A window must be enabled before it can be activated.
|
||||
/// If an application has create a modal dialog box by disabling its owner window
|
||||
/// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable
|
||||
/// the owner window before destroying the dialog box.
|
||||
/// Otherwise, another window will receive the keyboard focus and be activated.
|
||||
///
|
||||
/// If a child window is disabled, it is ignored when the system tries to determine which
|
||||
/// window should receive mouse messages.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
|
||||
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
|
||||
fn set_enable(&self, enabled: bool);
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
||||
|
||||
@@ -96,6 +111,13 @@ impl WindowExtWindows for Window {
|
||||
self.window.hwnd() as *mut _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_enable(&self, enabled: bool) {
|
||||
unsafe {
|
||||
winapi::um::winuser::EnableWindow(self.hwnd() as _, enabled as _);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
self.window.set_taskbar_icon(taskbar_icon)
|
||||
@@ -110,8 +132,34 @@ impl WindowExtWindows for Window {
|
||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
||||
pub trait WindowBuilderExtWindows {
|
||||
/// Sets a parent to the window to be created.
|
||||
///
|
||||
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
|
||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
|
||||
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
|
||||
/// on the owner window to create a modal dialog box.
|
||||
///
|
||||
/// From MSDN:
|
||||
/// - An owned window is always above its owner in the z-order.
|
||||
/// - The system automatically destroys an owned window when its owner is destroyed.
|
||||
/// - An owned window is hidden when its owner is minimized.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
|
||||
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
|
||||
|
||||
/// Sets a menu on the window to be created.
|
||||
///
|
||||
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
|
||||
///
|
||||
/// The menu must have been manually created beforehand with [`winapi::um::winuser::CreateMenu`] or similar.
|
||||
///
|
||||
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
|
||||
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
||||
fn with_menu(self, menu: HMENU) -> WindowBuilder;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
|
||||
|
||||
@@ -123,7 +171,7 @@ pub trait WindowBuilderExtWindows {
|
||||
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize
|
||||
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
|
||||
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks for more information.
|
||||
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
|
||||
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
|
||||
|
||||
/// Forces a theme or uses the system settings if `None` was provided.
|
||||
@@ -133,7 +181,19 @@ pub trait WindowBuilderExtWindows {
|
||||
impl WindowBuilderExtWindows for WindowBuilder {
|
||||
#[inline]
|
||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Some(parent);
|
||||
self.platform_specific.parent = Parent::ChildOf(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
|
||||
self.platform_specific.parent = Parent::OwnedBy(parent);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
|
||||
self.platform_specific.menu = Some(menu);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
use ndk::{
|
||||
configuration::Configuration,
|
||||
event::{InputEvent, MotionAction},
|
||||
event::{InputEvent, KeyAction, MotionAction},
|
||||
looper::{ForeignLooper, Poll, ThreadLooper},
|
||||
};
|
||||
use ndk_glue::{Event, Rect};
|
||||
@@ -30,9 +30,9 @@ enum EventSource {
|
||||
|
||||
fn poll(poll: Poll) -> Option<EventSource> {
|
||||
match poll {
|
||||
Poll::Event { data, .. } => match data as usize {
|
||||
0 => Some(EventSource::Callback),
|
||||
1 => Some(EventSource::InputQueue),
|
||||
Poll::Event { ident, .. } => match ident {
|
||||
ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback),
|
||||
ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Poll::Timeout => None,
|
||||
@@ -176,6 +176,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
|
||||
while let Some(event) = input_queue.get_event() {
|
||||
if let Some(event) = input_queue.pre_dispatch(event) {
|
||||
let mut handled = true;
|
||||
let window_id = window::WindowId(WindowId);
|
||||
let device_id = event::DeviceId(DeviceId);
|
||||
match &event {
|
||||
@@ -191,7 +192,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
MotionAction::Cancel => {
|
||||
Some(event::TouchPhase::Cancelled)
|
||||
}
|
||||
_ => None, // TODO mouse events
|
||||
_ => {
|
||||
handled = false;
|
||||
None // TODO mouse events
|
||||
}
|
||||
};
|
||||
if let Some(phase) = phase {
|
||||
let pointers: Box<
|
||||
@@ -235,9 +239,35 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
InputEvent::KeyEvent(_) => {} // TODO
|
||||
InputEvent::KeyEvent(key) => {
|
||||
let state = match key.action() {
|
||||
KeyAction::Down => event::ElementState::Pressed,
|
||||
KeyAction::Up => event::ElementState::Released,
|
||||
_ => event::ElementState::Released,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id,
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
input: event::KeyboardInput {
|
||||
scancode: key.scan_code() as u32,
|
||||
state,
|
||||
virtual_keycode: None,
|
||||
modifiers: event::ModifiersState::default(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
};
|
||||
call_event_handler!(
|
||||
event_handler,
|
||||
self.window_target(),
|
||||
control_flow,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
input_queue.finish_event(event, true);
|
||||
input_queue.finish_event(event, handled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,6 +519,10 @@ impl Window {
|
||||
|
||||
pub fn set_maximized(&self, _maximized: bool) {}
|
||||
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, _monitor: Option<window::Fullscreen>) {
|
||||
warn!("Cannot set fullscreen on Android");
|
||||
}
|
||||
@@ -523,6 +557,12 @@ impl Window {
|
||||
|
||||
pub fn set_cursor_visible(&self, _: bool) {}
|
||||
|
||||
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
|
||||
Err(error::ExternalError::NotSupported(
|
||||
error::NotSupportedError::new(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
|
||||
let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() {
|
||||
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
|
||||
|
||||
@@ -182,6 +182,10 @@ impl Inner {
|
||||
debug!("`Window::set_cursor_visible` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn set_minimized(&self, _minimized: bool) {
|
||||
warn!("`Window::set_minimized` is ignored on iOS")
|
||||
}
|
||||
@@ -190,6 +194,11 @@ impl Inner {
|
||||
warn!("`Window::set_maximized` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
warn!("`Window::is_maximized` is ignored on iOS");
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
unsafe {
|
||||
let uiscreen = match monitor {
|
||||
|
||||
@@ -358,6 +358,11 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
x11_or_wayland!(match self; Window(window) => window.drag_window())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
|
||||
@@ -373,6 +378,12 @@ impl Window {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
// TODO: Not implemented
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
|
||||
@@ -598,11 +609,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
#[cfg(not(feature = "x11"))]
|
||||
let x11_err = "backend disabled";
|
||||
|
||||
let err_string = format!(
|
||||
panic!(
|
||||
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
|
||||
wayland_err, x11_err,
|
||||
);
|
||||
panic!(err_string);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
|
||||
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
@@ -28,6 +29,7 @@ pub(super) fn handle_pointer(
|
||||
event: PointerEvent,
|
||||
pointer_data: &Rc<RefCell<PointerData>>,
|
||||
winit_state: &mut WinitState,
|
||||
seat: WlSeat,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
let mut pointer_data = pointer_data.borrow_mut();
|
||||
@@ -59,6 +61,7 @@ pub(super) fn handle_pointer(
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_entered(winit_pointer);
|
||||
|
||||
@@ -101,6 +104,7 @@ pub(super) fn handle_pointer(
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
seat,
|
||||
};
|
||||
window_handle.pointer_left(winit_pointer);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_p
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
|
||||
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
|
||||
use sctk::window::{ConceptFrame, Window};
|
||||
|
||||
use crate::event::ModifiersState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
@@ -35,6 +36,9 @@ pub struct WinitPointer {
|
||||
|
||||
/// Latest observed serial in pointer events.
|
||||
latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// Seat.
|
||||
seat: WlSeat,
|
||||
}
|
||||
|
||||
impl PartialEq for WinitPointer {
|
||||
@@ -144,6 +148,10 @@ impl WinitPointer {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_window(&self, window: &Window<ConceptFrame>) {
|
||||
window.start_interactive_move(&self.seat, self.latest_serial.get());
|
||||
}
|
||||
}
|
||||
|
||||
/// A pointer wrapper for easy releasing and managing pointers.
|
||||
@@ -172,11 +180,18 @@ impl Pointers {
|
||||
pointer_constraints.clone(),
|
||||
modifiers_state,
|
||||
)));
|
||||
let pointer_seat = seat.detach();
|
||||
let pointer = theme_manager.theme_pointer_with_impl(
|
||||
seat,
|
||||
move |event, pointer, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_pointer(pointer, event, &pointer_data, winit_state);
|
||||
handlers::handle_pointer(
|
||||
pointer,
|
||||
event,
|
||||
&pointer_data,
|
||||
winit_state,
|
||||
pointer_seat.clone(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -586,6 +586,18 @@ impl Window {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
let drag_window_request = WindowRequest::DragWindow;
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(drag_window_request);
|
||||
self.event_loop_awakener.ping();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
|
||||
@@ -34,6 +34,9 @@ pub enum WindowRequest {
|
||||
/// Grab cursor.
|
||||
GrabCursor(bool),
|
||||
|
||||
/// Drag window.
|
||||
DragWindow,
|
||||
|
||||
/// Maximize the window.
|
||||
Maximize(bool),
|
||||
|
||||
@@ -268,6 +271,12 @@ impl WindowHandle {
|
||||
pointer.set_cursor(Some(cursor_icon));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) {
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.drag_window(&self.window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -299,6 +308,9 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
WindowRequest::GrabCursor(grab) => {
|
||||
window_handle.set_cursor_grab(grab);
|
||||
}
|
||||
WindowRequest::DragWindow => {
|
||||
window_handle.drag_window();
|
||||
}
|
||||
WindowRequest::Maximize(maximize) => {
|
||||
if maximize {
|
||||
window_handle.window.set_maximized();
|
||||
|
||||
@@ -32,15 +32,20 @@ use std::{
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice,
|
||||
sync::mpsc::Receiver,
|
||||
sync::{mpsc, Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use libc::{self, setlocale, LC_CTYPE};
|
||||
|
||||
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
|
||||
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
|
||||
|
||||
use mio_extras::channel::{channel, Receiver, SendError, Sender};
|
||||
use mio_misc::{
|
||||
channel::{channel, SendError, Sender},
|
||||
queue::NotificationQueue,
|
||||
NotificationId,
|
||||
};
|
||||
|
||||
use self::{
|
||||
dnd::{Dnd, DndState},
|
||||
@@ -57,8 +62,7 @@ use crate::{
|
||||
};
|
||||
|
||||
const X_TOKEN: Token = Token(0);
|
||||
const USER_TOKEN: Token = Token(1);
|
||||
const REDRAW_TOKEN: Token = Token(2);
|
||||
const USER_REDRAW_TOKEN: Token = Token(1);
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
xconn: Arc<XConnection>,
|
||||
@@ -131,7 +135,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
let ime = RefCell::new({
|
||||
let result = Ime::new(Arc::clone(&xconn));
|
||||
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
||||
panic!(format!("Failed to open input method: {:#?}", state));
|
||||
panic!("Failed to open input method: {:#?}", state);
|
||||
}
|
||||
result.expect("Failed to set input method destruction callback")
|
||||
});
|
||||
@@ -180,33 +184,16 @@ impl<T: 'static> EventLoop<T> {
|
||||
mod_keymap.reset_from_x_connection(&xconn);
|
||||
|
||||
let poll = Poll::new().unwrap();
|
||||
let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap());
|
||||
let queue = Arc::new(NotificationQueue::new(waker));
|
||||
|
||||
let (user_sender, user_channel) = channel();
|
||||
let (redraw_sender, redraw_channel) = channel();
|
||||
poll.registry()
|
||||
.register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE)
|
||||
.unwrap();
|
||||
|
||||
poll.register(
|
||||
&EventedFd(&xconn.x11_fd),
|
||||
X_TOKEN,
|
||||
Ready::readable(),
|
||||
PollOpt::level(),
|
||||
)
|
||||
.unwrap();
|
||||
let (user_sender, user_channel) = channel(queue.clone(), NotificationId::gen_next());
|
||||
|
||||
poll.register(
|
||||
&user_channel,
|
||||
USER_TOKEN,
|
||||
Ready::readable(),
|
||||
PollOpt::level(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
poll.register(
|
||||
&redraw_channel,
|
||||
REDRAW_TOKEN,
|
||||
Ready::readable(),
|
||||
PollOpt::level(),
|
||||
)
|
||||
.unwrap();
|
||||
let (redraw_sender, redraw_channel) = channel(queue, NotificationId::gen_next());
|
||||
|
||||
let target = Rc::new(RootELW {
|
||||
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
||||
|
||||
@@ -27,12 +27,11 @@ impl XConnection {
|
||||
(self.xlib.XInternAtom)(self.display, name.as_ptr() as *const c_char, ffi::False)
|
||||
};
|
||||
if atom == 0 {
|
||||
let msg = format!(
|
||||
panic!(
|
||||
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
|
||||
name,
|
||||
self.check_errors(),
|
||||
);
|
||||
panic!(msg);
|
||||
}
|
||||
/*println!(
|
||||
"XInternAtom name:{:?} atom:{:?}",
|
||||
|
||||
@@ -190,6 +190,24 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
||||
if has_flag(self.size_hints.flags, ffi::PPosition) {
|
||||
Some((self.size_hints.x as i32, self.size_hints.y as i32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, position: Option<(i32, i32)>) {
|
||||
if let Some((x, y)) = position {
|
||||
self.size_hints.flags |= ffi::PPosition;
|
||||
self.size_hints.x = x as c_int;
|
||||
self.size_hints.y = y as c_int;
|
||||
} else {
|
||||
self.size_hints.flags &= !ffi::PPosition;
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: This hint is obsolete
|
||||
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
|
||||
if let Some((width, height)) = size {
|
||||
|
||||
@@ -23,6 +23,7 @@ pub use self::{
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::BitAnd,
|
||||
os::raw::*,
|
||||
ptr,
|
||||
};
|
||||
@@ -39,6 +40,13 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
|
||||
pub struct Flusher<'a> {
|
||||
xconn: &'a XConnection,
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{
|
||||
};
|
||||
|
||||
use libc;
|
||||
use mio_extras::channel::Sender;
|
||||
use mio_misc::channel::Sender;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
@@ -146,6 +146,10 @@ impl UnownedWindow {
|
||||
.min_inner_size
|
||||
.map(|size| size.to_physical::<u32>(scale_factor).into());
|
||||
|
||||
let position = window_attrs
|
||||
.position
|
||||
.map(|position| position.to_physical::<i32>(scale_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
|
||||
@@ -211,8 +215,8 @@ impl UnownedWindow {
|
||||
(xconn.xlib.XCreateWindow)(
|
||||
xconn.display,
|
||||
root,
|
||||
0,
|
||||
0,
|
||||
position.map_or(0, |p: PhysicalPosition<i32>| p.x as c_int),
|
||||
position.map_or(0, |p: PhysicalPosition<i32>| p.y as c_int),
|
||||
dimensions.0 as c_uint,
|
||||
dimensions.1 as c_uint,
|
||||
0,
|
||||
@@ -344,6 +348,7 @@ impl UnownedWindow {
|
||||
}
|
||||
|
||||
let mut normal_hints = util::NormalHints::new(xconn);
|
||||
normal_hints.set_position(position.map(|PhysicalPosition { x, y }| (x, y)));
|
||||
normal_hints.set_size(Some(dimensions));
|
||||
normal_hints.set_min_size(min_inner_size.map(Into::into));
|
||||
normal_hints.set_max_size(max_inner_size.map(Into::into));
|
||||
@@ -439,6 +444,12 @@ impl UnownedWindow {
|
||||
window
|
||||
.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
||||
.map(|flusher| flusher.queue());
|
||||
|
||||
if let Some(PhysicalPosition { x, y }) = position {
|
||||
let shared_state = window.shared_state.get_mut();
|
||||
|
||||
shared_state.restore_position = Some((x, y));
|
||||
}
|
||||
}
|
||||
if window_attrs.always_on_top {
|
||||
window
|
||||
@@ -1276,6 +1287,46 @@ impl UnownedWindow {
|
||||
self.set_cursor_position_physical(x, y)
|
||||
}
|
||||
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
let pointer = self
|
||||
.xconn
|
||||
.query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
|
||||
|
||||
let window = self.inner_position().map_err(ExternalError::NotSupported)?;
|
||||
|
||||
let message = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_MOVERESIZE\0") };
|
||||
|
||||
// we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
|
||||
// if the cursor isn't currently grabbed
|
||||
let mut grabbed_lock = self.cursor_grabbed.lock();
|
||||
unsafe {
|
||||
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
|
||||
}
|
||||
self.xconn
|
||||
.flush_requests()
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
|
||||
*grabbed_lock = false;
|
||||
|
||||
// we keep the lock until we are done
|
||||
self.xconn
|
||||
.send_client_msg(
|
||||
self.xwindow,
|
||||
self.root,
|
||||
message,
|
||||
Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask),
|
||||
[
|
||||
(window.x as c_long + pointer.win_x as c_long),
|
||||
(window.y as c_long + pointer.win_y as c_long),
|
||||
8, // _NET_WM_MOVERESIZE_MOVE
|
||||
ffi::Button1 as c_long,
|
||||
1,
|
||||
],
|
||||
)
|
||||
.flush()
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
|
||||
}
|
||||
|
||||
pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
|
||||
let _ = self
|
||||
.ime_sender
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
// Normally when you run or distribute a macOS app, it's bundled: it's in one
|
||||
// of those fun little folders that you have to right click "Show Package
|
||||
// Contents" on, and usually contains myriad delights including, but not
|
||||
// limited to, plists, icons, and of course, your beloved executable. However,
|
||||
// when you use `cargo run`, your app is unbundled - it's just a lonely, bare
|
||||
// executable.
|
||||
//
|
||||
// Apple isn't especially fond of unbundled apps, which is to say, they seem to
|
||||
// barely be supported. If you move the mouse while opening a winit window from
|
||||
// an unbundled app, the window will fail to activate and be in a grayed-out
|
||||
// uninteractable state. Switching to another app and back is the only way to
|
||||
// get the winit window into a normal state. None of this happens if the app is
|
||||
// bundled, i.e. when running via Xcode.
|
||||
//
|
||||
// To workaround this, we just switch focus to the Dock and then switch back to
|
||||
// our app. We only do this for unbundled apps, and only when they fail to
|
||||
// become active on their own.
|
||||
//
|
||||
// This solution was derived from this Godot PR:
|
||||
// https://github.com/godotengine/godot/pull/17187
|
||||
// (which appears to be based on https://stackoverflow.com/a/7602677)
|
||||
// The curious specialness of mouse motions is touched upon here:
|
||||
// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512
|
||||
//
|
||||
// We omit the 2nd step of the solution used in Godot, since it appears to have
|
||||
// no effect - I speculate that it's just technical debt picked up from the SO
|
||||
// answer; the API used is fairly exotic, and was historically used for very
|
||||
// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e.
|
||||
// in previous versions of SDL:
|
||||
// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322
|
||||
//
|
||||
// The `performSelector` delays in the Godot solution are used for sequencing,
|
||||
// since refocusing the app will fail if the call is made before it finishes
|
||||
// unfocusing. The delays used there are much smaller than the ones in the
|
||||
// original SO answer, presumably because they found the fastest delay that
|
||||
// works reliably through trial and error. Instead of using delays, we just
|
||||
// handle `applicationDidResignActive`; despite the app not activating reliably,
|
||||
// that still triggers when we switch focus to the Dock.
|
||||
//
|
||||
// The Godot solution doesn't appear to skip the hack when an unbundled app
|
||||
// activates normally. Checking for this is difficult, since if you call
|
||||
// `isActive` too early, it will always be `NO`. Even though we receive
|
||||
// `applicationDidResignActive` when switching focus to the Dock, we never
|
||||
// receive a preceding `applicationDidBecomeActive` if the app fails to
|
||||
// activate normally. I wasn't able to find a proper point in time to perform
|
||||
// the `isActive` check, so we instead check for the cause of the quirk: if
|
||||
// any mouse motion occurs prior to us receiving `applicationDidResignActive`,
|
||||
// we assume the app failed to become active.
|
||||
//
|
||||
// Fun fact: this issue is still present in GLFW
|
||||
// (https://github.com/glfw/glfw/issues/1515)
|
||||
//
|
||||
// A similar issue was found in SDL, but the resolution doesn't seem to work
|
||||
// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051
|
||||
|
||||
use super::util;
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSApplicationActivateIgnoringOtherApps},
|
||||
base::id,
|
||||
foundation::NSUInteger,
|
||||
};
|
||||
use objc::runtime::{Object, Sel, BOOL, NO, YES};
|
||||
use std::{
|
||||
os::raw::c_void,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
// Indicates that the hack has either completed or been skipped.
|
||||
activated: AtomicBool,
|
||||
// Indicates that the mouse has moved at some point in time.
|
||||
mouse_moved: AtomicBool,
|
||||
// Indicates that the hack is in progress, and that we should refocus when
|
||||
// the app resigns active.
|
||||
needs_refocus: AtomicBool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn name() -> &'static str {
|
||||
"activationHackState"
|
||||
}
|
||||
|
||||
pub fn new() -> *mut c_void {
|
||||
let this = Box::new(Self::default());
|
||||
Box::into_raw(this) as *mut c_void
|
||||
}
|
||||
|
||||
pub unsafe fn free(this: *mut Self) {
|
||||
Box::from_raw(this);
|
||||
}
|
||||
|
||||
pub unsafe fn get_ptr(obj: &Object) -> *mut Self {
|
||||
let this: *mut c_void = *(*obj).get_ivar(Self::name());
|
||||
assert!(!this.is_null(), "`activationHackState` pointer was null");
|
||||
this as *mut Self
|
||||
}
|
||||
|
||||
pub unsafe fn set_activated(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).activated.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
unsafe fn get_activated(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).activated.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub unsafe fn set_mouse_moved(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).mouse_moved.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
pub unsafe fn get_mouse_moved(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).mouse_moved.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub unsafe fn set_needs_refocus(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).needs_refocus.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
unsafe fn get_needs_refocus(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).needs_refocus.load(Ordering::Acquire)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the entry point for the hack - if the app is unbundled and a mouse
|
||||
// movement occurs before the app activates, it will trigger the hack. Because
|
||||
// mouse movements prior to activation are the cause of this quirk, they should
|
||||
// be a reliable way to determine if the hack needs to be performed.
|
||||
pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `activationHackMouseMoved`");
|
||||
unsafe {
|
||||
if !State::get_activated(this) {
|
||||
// We check if `CFBundleName` is undefined to determine if the
|
||||
// app is unbundled.
|
||||
if let None = util::app_name() {
|
||||
info!("App detected as unbundled");
|
||||
unfocus(this);
|
||||
} else {
|
||||
info!("App detected as bundled");
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("Completed `activationHackMouseMoved`");
|
||||
}
|
||||
|
||||
// Switch focus to the dock.
|
||||
unsafe fn unfocus(this: &Object) {
|
||||
// We only perform the hack if the app failed to activate, since otherwise,
|
||||
// there'd be a gross (but fast) flicker as it unfocused and then refocused.
|
||||
// However, we only enter this function if we detect mouse movement prior
|
||||
// to activation, so this should always be `NO`.
|
||||
//
|
||||
// Note that this check isn't necessarily reliable in detecting a violation
|
||||
// of the invariant above, since it's not guaranteed that activation will
|
||||
// resolve before this point. In other words, it can spuriously return `NO`.
|
||||
// This is also why the mouse motion approach was chosen, since it's not
|
||||
// obvious how to sequence this check - if someone knows how to, then that
|
||||
// would almost surely be a cleaner approach.
|
||||
let active: BOOL = msg_send![NSApp(), isActive];
|
||||
if active == YES {
|
||||
error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!");
|
||||
} else {
|
||||
info!("Performing unbundled app activation hack");
|
||||
let dock_bundle_id = util::ns_string_id_ref("com.apple.dock");
|
||||
let dock_array: id = msg_send![
|
||||
class!(NSRunningApplication),
|
||||
runningApplicationsWithBundleIdentifier: *dock_bundle_id
|
||||
];
|
||||
let dock_array_len: NSUInteger = msg_send![dock_array, count];
|
||||
if dock_array_len == 0 {
|
||||
error!("The Dock doesn't seem to be running, so switching focus to it is impossible");
|
||||
} else {
|
||||
State::set_needs_refocus(this, true);
|
||||
let dock: id = msg_send![dock_array, objectAtIndex: 0];
|
||||
// This will trigger `applicationDidResignActive`, which will in
|
||||
// turn call `refocus`.
|
||||
let status: BOOL = msg_send![
|
||||
dock,
|
||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
||||
];
|
||||
if status == NO {
|
||||
error!("Failed to switch focus to Dock");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch focus back to our app, causing the user to rejoice!
|
||||
pub unsafe fn refocus(this: &Object) {
|
||||
if State::get_needs_refocus(this) {
|
||||
State::set_needs_refocus(this, false);
|
||||
let app: id = msg_send![class!(NSRunningApplication), currentApplication];
|
||||
// Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The
|
||||
// nuanced difference isn't clear to me, but hey, I tried.
|
||||
let success: BOOL = msg_send![
|
||||
app,
|
||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
||||
];
|
||||
if success == NO {
|
||||
error!("Failed to refocus app");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@ use std::collections::VecDeque;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{self, NSEvent},
|
||||
base::{id, nil},
|
||||
base::id,
|
||||
};
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
|
||||
use super::{activation_hack, app_state::AppState, event::EventWrapper, util, DEVICE_ID};
|
||||
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID};
|
||||
use crate::event::{DeviceEvent, ElementState, Event};
|
||||
|
||||
pub struct AppClass(pub *const Class);
|
||||
@@ -49,14 +49,14 @@ extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
|
||||
let key_window: id = msg_send![this, keyWindow];
|
||||
let _: () = msg_send![key_window, sendEvent: event];
|
||||
} else {
|
||||
maybe_dispatch_device_event(this, event);
|
||||
maybe_dispatch_device_event(event);
|
||||
let superclass = util::superclass(this);
|
||||
let _: () = msg_send![super(this, superclass), sendEvent: event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
|
||||
unsafe fn maybe_dispatch_device_event(event: id) {
|
||||
let event_type = event.eventType();
|
||||
match event_type {
|
||||
appkit::NSMouseMoved
|
||||
@@ -98,21 +98,6 @@ unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
|
||||
}
|
||||
|
||||
AppState::queue_events(events);
|
||||
|
||||
// Notify the delegate when the first mouse move occurs. This is
|
||||
// used for the unbundled app activation hack, which needs to know
|
||||
// if any mouse motions occurred prior to the app activating.
|
||||
let delegate: id = msg_send![this, delegate];
|
||||
assert_ne!(delegate, nil);
|
||||
if !activation_hack::State::get_mouse_moved(&*delegate) {
|
||||
activation_hack::State::set_mouse_moved(&*delegate, true);
|
||||
let () = msg_send![
|
||||
delegate,
|
||||
performSelector: sel!(activationHackMouseMoved:)
|
||||
withObject: nil
|
||||
afterDelay: 0.0
|
||||
];
|
||||
}
|
||||
}
|
||||
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
use super::{activation_hack, app_state::AppState};
|
||||
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
|
||||
|
||||
use cocoa::base::id;
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
use std::os::raw::c_void;
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
os::raw::c_void,
|
||||
};
|
||||
|
||||
static AUX_DELEGATE_STATE_NAME: &str = "auxState";
|
||||
|
||||
pub struct AuxDelegateState {
|
||||
/// We store this value in order to be able to defer setting the activation policy until
|
||||
/// after the app has finished launching. If the activation policy is set earlier, the
|
||||
/// menubar is initially unresponsive on macOS 10.15 for example.
|
||||
pub activation_policy: ActivationPolicy,
|
||||
|
||||
pub create_default_menu: bool,
|
||||
}
|
||||
|
||||
pub struct AppDelegateClass(pub *const Class);
|
||||
unsafe impl Send for AppDelegateClass {}
|
||||
@@ -17,36 +32,34 @@ lazy_static! {
|
||||
|
||||
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
|
||||
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
|
||||
|
||||
decl.add_method(
|
||||
sel!(applicationDidFinishLaunching:),
|
||||
did_finish_launching as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidResignActive:),
|
||||
did_resign_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_ivar::<*mut c_void>(activation_hack::State::name());
|
||||
decl.add_method(
|
||||
sel!(activationHackMouseMoved:),
|
||||
activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS
|
||||
pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> {
|
||||
let ptr: *mut c_void = *this.get_ivar(AUX_DELEGATE_STATE_NAME);
|
||||
// Watch out that this needs to be the correct type
|
||||
(*(ptr as *mut RefCell<AuxDelegateState>)).borrow_mut()
|
||||
}
|
||||
|
||||
extern "C" fn new(class: &Class, _: Sel) -> id {
|
||||
unsafe {
|
||||
let this: id = msg_send![class, alloc];
|
||||
let this: id = msg_send![this, init];
|
||||
(*this).set_ivar(
|
||||
activation_hack::State::name(),
|
||||
activation_hack::State::new(),
|
||||
AUX_DELEGATE_STATE_NAME,
|
||||
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
|
||||
activation_policy: ActivationPolicy::Regular,
|
||||
create_default_menu: true,
|
||||
}))) as *mut c_void,
|
||||
);
|
||||
this
|
||||
}
|
||||
@@ -54,28 +67,15 @@ extern "C" fn new(class: &Class, _: Sel) -> id {
|
||||
|
||||
extern "C" fn dealloc(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
activation_hack::State::free(activation_hack::State::get_ptr(this));
|
||||
let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME));
|
||||
// As soon as the box is constructed it is immediately dropped, releasing the underlying
|
||||
// memory
|
||||
Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) {
|
||||
extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidFinishLaunching`");
|
||||
AppState::launched();
|
||||
AppState::launched(this);
|
||||
trace!("Completed `applicationDidFinishLaunching`");
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidBecomeActive`");
|
||||
unsafe {
|
||||
activation_hack::State::set_activated(this, true);
|
||||
}
|
||||
trace!("Completed `applicationDidBecomeActive`");
|
||||
}
|
||||
|
||||
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidResignActive`");
|
||||
unsafe {
|
||||
activation_hack::refocus(this);
|
||||
}
|
||||
trace!("Completed `applicationDidResignActive`");
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
collections::VecDeque,
|
||||
fmt::{self, Debug},
|
||||
hint::unreachable_unchecked,
|
||||
mem,
|
||||
rc::Rc,
|
||||
rc::{Rc, Weak},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Mutex, MutexGuard,
|
||||
@@ -12,22 +13,29 @@ use std::{
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
|
||||
appkit::{NSApp, NSApplication, NSWindow},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSPoint, NSSize},
|
||||
foundation::{NSAutoreleasePool, NSSize},
|
||||
};
|
||||
|
||||
use objc::runtime::YES;
|
||||
|
||||
use objc::runtime::Object;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
|
||||
platform_impl::platform::{
|
||||
event::{EventProxy, EventWrapper},
|
||||
observer::EventLoopWaker,
|
||||
util::{IdRef, Never},
|
||||
window::get_window_id,
|
||||
platform::macos::ActivationPolicy,
|
||||
platform_impl::{
|
||||
get_aux_state_mut,
|
||||
platform::{
|
||||
event::{EventProxy, EventWrapper},
|
||||
event_loop::{post_dummy_event, PanicInfo},
|
||||
menu,
|
||||
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
|
||||
util::{IdRef, Never},
|
||||
window::get_window_id,
|
||||
},
|
||||
},
|
||||
window::WindowId,
|
||||
};
|
||||
@@ -52,11 +60,31 @@ pub trait EventHandler: Debug {
|
||||
}
|
||||
|
||||
struct EventLoopHandler<T: 'static> {
|
||||
callback: Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
||||
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
||||
will_exit: bool,
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
}
|
||||
|
||||
impl<T> EventLoopHandler<T> {
|
||||
fn with_callback<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(
|
||||
&mut EventLoopHandler<T>,
|
||||
RefMut<'_, dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
||||
),
|
||||
{
|
||||
if let Some(callback) = self.callback.upgrade() {
|
||||
let callback = callback.borrow_mut();
|
||||
(f)(self, callback);
|
||||
} else {
|
||||
panic!(
|
||||
"Tried to dispatch an event, but the event loop that \
|
||||
owned the event handler callback seems to be destroyed"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for EventLoopHandler<T> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
@@ -68,23 +96,27 @@ impl<T> Debug for EventLoopHandler<T> {
|
||||
|
||||
impl<T> EventHandler for EventLoopHandler<T> {
|
||||
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;
|
||||
}
|
||||
self.with_callback(|this, mut callback| {
|
||||
(callback)(event.userify(), &this.window_target, control_flow);
|
||||
this.will_exit |= *control_flow == ControlFlow::Exit;
|
||||
if this.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.with_callback(|this, mut callback| {
|
||||
let mut will_exit = this.will_exit;
|
||||
for event in this.window_target.p.receiver.try_iter() {
|
||||
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
|
||||
will_exit |= *control_flow == ControlFlow::Exit;
|
||||
if will_exit {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.will_exit = will_exit;
|
||||
this.will_exit = will_exit;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,20 +261,12 @@ pub static INTERRUPT_EVENT_LOOP_EXIT: AtomicBool = AtomicBool::new(false);
|
||||
pub enum AppState {}
|
||||
|
||||
impl AppState {
|
||||
// This function extends lifetime of `callback` to 'static as its side effect
|
||||
pub unsafe fn set_callback<F, T>(callback: F, window_target: Rc<RootWindowTarget<T>>)
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
pub fn set_callback<T>(
|
||||
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
) {
|
||||
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
||||
// This transmute is always safe, in case it was reached through `run`, since our
|
||||
// lifetime will be already 'static. In other cases caller should ensure that all data
|
||||
// they passed to callback will actually outlive it, some apps just can't move
|
||||
// everything to event loop, so this is something that they should care about.
|
||||
callback: mem::transmute::<
|
||||
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
||||
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
||||
>(Box::new(callback)),
|
||||
callback,
|
||||
will_exit: false,
|
||||
window_target,
|
||||
}));
|
||||
@@ -255,9 +279,22 @@ impl AppState {
|
||||
HANDLER.callback.lock().unwrap().take();
|
||||
}
|
||||
|
||||
pub fn launched() {
|
||||
pub fn launched(app_delegate: &Object) {
|
||||
apply_activation_policy(app_delegate);
|
||||
unsafe {
|
||||
let ns_app = NSApp();
|
||||
window_activation_hack(ns_app);
|
||||
// TODO: Consider allowing the user to specify they don't want their application activated
|
||||
ns_app.activateIgnoringOtherApps_(YES);
|
||||
};
|
||||
HANDLER.set_ready();
|
||||
HANDLER.waker().start();
|
||||
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).create_default_menu };
|
||||
if create_default_menu {
|
||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||
// overriding of the default menu even if it's created
|
||||
menu::initialize();
|
||||
}
|
||||
HANDLER.set_in_callback(true);
|
||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
|
||||
StartCause::Init,
|
||||
@@ -265,8 +302,11 @@ impl AppState {
|
||||
HANDLER.set_in_callback(false);
|
||||
}
|
||||
|
||||
pub fn wakeup() {
|
||||
if !HANDLER.is_ready() {
|
||||
pub fn wakeup(panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
if panic_info.is_panicking() || !HANDLER.is_ready() {
|
||||
return;
|
||||
}
|
||||
let start = HANDLER.get_start_time().unwrap();
|
||||
@@ -302,6 +342,14 @@ impl AppState {
|
||||
if !pending_redraw.contains(&window_id) {
|
||||
pending_redraw.push(window_id);
|
||||
}
|
||||
unsafe {
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_redraw(window_id: WindowId) {
|
||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
|
||||
}
|
||||
|
||||
pub fn queue_event(wrapper: EventWrapper) {
|
||||
@@ -318,8 +366,11 @@ impl AppState {
|
||||
HANDLER.events().append(&mut wrappers);
|
||||
}
|
||||
|
||||
pub fn cleared() {
|
||||
if !HANDLER.is_ready() {
|
||||
pub fn cleared(panic_info: Weak<PanicInfo>) {
|
||||
let panic_info = panic_info
|
||||
.upgrade()
|
||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||
if panic_info.is_panicking() || !HANDLER.is_ready() {
|
||||
return;
|
||||
}
|
||||
if !HANDLER.get_in_callback() {
|
||||
@@ -341,9 +392,7 @@ impl AppState {
|
||||
unsafe {
|
||||
let app: id = NSApp();
|
||||
let windows: id = msg_send![app, windows];
|
||||
let window: id = msg_send![windows, objectAtIndex:0];
|
||||
let window_count: usize = msg_send![windows, count];
|
||||
assert_ne!(window, nil);
|
||||
|
||||
let dialog_open = if window_count > 1 {
|
||||
let dialog: id = msg_send![windows, lastObject];
|
||||
@@ -359,30 +408,21 @@ impl AppState {
|
||||
&& !dialog_open
|
||||
&& !dialog_is_closing
|
||||
{
|
||||
let _: () = msg_send![app, stop: nil];
|
||||
|
||||
let dummy_event: id = msg_send![class!(NSEvent),
|
||||
otherEventWithType: NSApplicationDefined
|
||||
location: NSPoint::new(0.0, 0.0)
|
||||
modifierFlags: 0
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: 0
|
||||
data1: 0
|
||||
data2: 0
|
||||
];
|
||||
let () = msg_send![app, stop: nil];
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
|
||||
post_dummy_event(app);
|
||||
}
|
||||
pool.drain();
|
||||
|
||||
let window_has_focus = msg_send![window, isKeyWindow];
|
||||
if !dialog_open && window_has_focus && dialog_is_closing {
|
||||
HANDLER.dialog_is_closing.store(false, Ordering::SeqCst);
|
||||
}
|
||||
if dialog_open {
|
||||
HANDLER.dialog_is_closing.store(true, Ordering::SeqCst);
|
||||
if window_count > 0 {
|
||||
let window: id = msg_send![windows, objectAtIndex:0];
|
||||
let window_has_focus = msg_send![window, isKeyWindow];
|
||||
if !dialog_open && window_has_focus && dialog_is_closing {
|
||||
HANDLER.dialog_is_closing.store(false, Ordering::SeqCst);
|
||||
}
|
||||
if dialog_open {
|
||||
HANDLER.dialog_is_closing.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -396,3 +436,49 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A hack to make activation of multiple windows work when creating them before
|
||||
/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`.
|
||||
///
|
||||
/// Alternative to this would be the user calling `window.set_visible(true)` in
|
||||
/// `StartCause::Init`.
|
||||
///
|
||||
/// If this becomes too bothersome to maintain, it can probably be removed
|
||||
/// without too much damage.
|
||||
unsafe fn window_activation_hack(ns_app: id) {
|
||||
// Get the application's windows
|
||||
// TODO: Proper ordering of the windows
|
||||
let ns_windows: id = msg_send![ns_app, windows];
|
||||
let ns_enumerator: id = msg_send![ns_windows, objectEnumerator];
|
||||
loop {
|
||||
// Enumerate over the windows
|
||||
let ns_window: id = msg_send![ns_enumerator, nextObject];
|
||||
if ns_window == nil {
|
||||
break;
|
||||
}
|
||||
// And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new`
|
||||
// This way we preserve the user's desired initial visiblity status
|
||||
// TODO: Also filter on the type/"level" of the window, and maybe other things?
|
||||
if ns_window.isVisible() == YES {
|
||||
trace!("Activating visible window");
|
||||
ns_window.makeKeyAndOrderFront_(nil);
|
||||
} else {
|
||||
trace!("Skipping activating invisible window");
|
||||
}
|
||||
}
|
||||
}
|
||||
fn apply_activation_policy(app_delegate: &Object) {
|
||||
unsafe {
|
||||
use cocoa::appkit::NSApplicationActivationPolicy::*;
|
||||
let ns_app = NSApp();
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar won't be interactable.
|
||||
let act_pol = get_aux_state_mut(app_delegate).activation_policy;
|
||||
ns_app.setActivationPolicy_(match act_pol {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
use std::{
|
||||
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, rc::Rc,
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
collections::VecDeque,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
os::raw::c_void,
|
||||
panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe},
|
||||
process, ptr,
|
||||
rc::{Rc, Weak},
|
||||
sync::mpsc,
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::NSApp,
|
||||
base::{id, nil},
|
||||
foundation::NSAutoreleasePool,
|
||||
appkit::{NSApp, NSEventType::NSApplicationDefined},
|
||||
base::{id, nil, YES},
|
||||
foundation::{NSAutoreleasePool, NSPoint},
|
||||
};
|
||||
|
||||
use scopeguard::defer;
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
|
||||
@@ -23,6 +33,34 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PanicInfo {
|
||||
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
|
||||
}
|
||||
|
||||
// WARNING:
|
||||
// As long as this struct is used through its `impl`, it is UnwindSafe.
|
||||
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
|
||||
impl UnwindSafe for PanicInfo {}
|
||||
impl RefUnwindSafe for PanicInfo {}
|
||||
impl PanicInfo {
|
||||
pub fn is_panicking(&self) -> bool {
|
||||
let inner = self.inner.take();
|
||||
let result = inner.is_some();
|
||||
self.inner.set(inner);
|
||||
result
|
||||
}
|
||||
/// Overwrites the curret state if the current state is not panicking
|
||||
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
|
||||
if !self.is_panicking() {
|
||||
self.inner.set(Some(p));
|
||||
}
|
||||
}
|
||||
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
|
||||
self.inner.take()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventLoopWindowTarget<T: 'static> {
|
||||
pub sender: mpsc::Sender<T>, // this is only here to be cloned elsewhere
|
||||
pub receiver: mpsc::Receiver<T>,
|
||||
@@ -49,8 +87,18 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
pub(crate) delegate: IdRef,
|
||||
|
||||
window_target: Rc<RootWindowTarget<T>>,
|
||||
_delegate: IdRef,
|
||||
panic_info: Rc<PanicInfo>,
|
||||
|
||||
/// We make sure that the callback closure is dropped during a panic
|
||||
/// by making the event loop own it.
|
||||
///
|
||||
/// Every other reference should be a Weak reference which is only upgraded
|
||||
/// into a strong reference in order to call the callback but then the
|
||||
/// strong reference should be dropped as soon as possible.
|
||||
_callback: Option<Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>>,
|
||||
}
|
||||
|
||||
impl<T> EventLoop<T> {
|
||||
@@ -72,13 +120,16 @@ impl<T> EventLoop<T> {
|
||||
let _: () = msg_send![pool, drain];
|
||||
delegate
|
||||
};
|
||||
setup_control_flow_observers();
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
setup_control_flow_observers(Rc::downgrade(&panic_info));
|
||||
EventLoop {
|
||||
delegate,
|
||||
window_target: Rc::new(RootWindowTarget {
|
||||
p: Default::default(),
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
_delegate: delegate,
|
||||
panic_info,
|
||||
_callback: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,14 +149,37 @@ impl<T> EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
// This transmute is always safe, in case it was reached through `run`, since our
|
||||
// lifetime will be already 'static. In other cases caller should ensure that all data
|
||||
// they passed to callback will actually outlive it, some apps just can't move
|
||||
// everything to event loop, so this is something that they should care about.
|
||||
let callback = unsafe {
|
||||
mem::transmute::<
|
||||
Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
||||
Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
||||
>(Rc::new(RefCell::new(callback)))
|
||||
};
|
||||
|
||||
self._callback = Some(Rc::clone(&callback));
|
||||
|
||||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
defer!(pool.drain());
|
||||
let app = NSApp();
|
||||
assert_ne!(app, nil);
|
||||
AppState::set_callback(callback, Rc::clone(&self.window_target));
|
||||
let _: () = msg_send![app, run];
|
||||
|
||||
// A bit of juggling with the callback references to make sure
|
||||
// that `self.callback` is the only owner of the callback.
|
||||
let weak_cb: Weak<_> = Rc::downgrade(&callback);
|
||||
mem::drop(callback);
|
||||
|
||||
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
|
||||
let () = msg_send![app, run];
|
||||
|
||||
if let Some(panic) = self.panic_info.take() {
|
||||
resume_unwind(panic);
|
||||
}
|
||||
AppState::exit();
|
||||
pool.drain();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +188,56 @@ impl<T> EventLoop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn post_dummy_event(target: id) {
|
||||
let event_class = class!(NSEvent);
|
||||
let dummy_event: id = msg_send![
|
||||
event_class,
|
||||
otherEventWithType: NSApplicationDefined
|
||||
location: NSPoint::new(0.0, 0.0)
|
||||
modifierFlags: 0
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: 0
|
||||
data1: 0
|
||||
data2: 0
|
||||
];
|
||||
let () = msg_send![target, postEvent: dummy_event atStart: YES];
|
||||
}
|
||||
|
||||
/// Catches panics that happen inside `f` and when a panic
|
||||
/// happens, stops the `sharedApplication`
|
||||
#[inline]
|
||||
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
||||
panic_info: Weak<PanicInfo>,
|
||||
f: F,
|
||||
) -> Option<R> {
|
||||
match catch_unwind(f) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
// It's important that we set the panic before requesting a `stop`
|
||||
// because some callback are still called during the `stop` message
|
||||
// and we need to know in those callbacks if the application is currently
|
||||
// panicking
|
||||
{
|
||||
let panic_info = panic_info.upgrade().unwrap();
|
||||
panic_info.set_panic(e);
|
||||
}
|
||||
unsafe {
|
||||
let app_class = class!(NSApplication);
|
||||
let app: id = msg_send![app_class, sharedApplication];
|
||||
let () = msg_send![app, stop: nil];
|
||||
|
||||
// Posting a dummy event to get `stop` to take effect immediately.
|
||||
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
||||
post_dummy_event(app);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Proxy<T> {
|
||||
sender: mpsc::Sender<T>,
|
||||
source: CFRunLoopSourceRef,
|
||||
|
||||
@@ -161,6 +161,18 @@ pub const IO8BitOverlayPixels: &str = "O8";
|
||||
pub type CGWindowLevel = i32;
|
||||
pub type CGDisplayModeRef = *mut libc::c_void;
|
||||
|
||||
#[cfg_attr(
|
||||
not(use_colorsync_cgdisplaycreateuuidfromdisplayid),
|
||||
link(name = "CoreGraphics", kind = "framework")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
use_colorsync_cgdisplaycreateuuidfromdisplayid,
|
||||
link(name = "ColorSync", kind = "framework")
|
||||
)]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
#[link(name = "CoreGraphics", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGRestorePermanentDisplayConfiguration();
|
||||
@@ -189,7 +201,6 @@ extern "C" {
|
||||
synchronous: Boolean,
|
||||
) -> CGError;
|
||||
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
|
||||
pub fn CGDisplaySetDisplayMode(
|
||||
display: CGDirectDisplayID,
|
||||
|
||||
114
src/platform_impl/macos/menu.rs
Normal file
114
src/platform_impl/macos/menu.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use cocoa::appkit::{NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
use cocoa::base::{nil, selector};
|
||||
use cocoa::foundation::{NSAutoreleasePool, NSProcessInfo, NSString};
|
||||
use objc::{
|
||||
rc::autoreleasepool,
|
||||
runtime::{Object, Sel},
|
||||
};
|
||||
|
||||
struct KeyEquivalent<'a> {
|
||||
key: &'a str,
|
||||
masks: Option<NSEventModifierFlags>,
|
||||
}
|
||||
|
||||
pub fn initialize() {
|
||||
autoreleasepool(|| unsafe {
|
||||
let menubar = NSMenu::new(nil).autorelease();
|
||||
let app_menu_item = NSMenuItem::new(nil).autorelease();
|
||||
menubar.addItem_(app_menu_item);
|
||||
let app = NSApp();
|
||||
app.setMainMenu_(menubar);
|
||||
|
||||
let app_menu = NSMenu::new(nil);
|
||||
let process_name = NSProcessInfo::processInfo(nil).processName();
|
||||
|
||||
// About menu item
|
||||
let about_item_prefix = NSString::alloc(nil).init_str("About ");
|
||||
let about_item_title = about_item_prefix.stringByAppendingString_(process_name);
|
||||
let about_item = menu_item(
|
||||
about_item_title,
|
||||
selector("orderFrontStandardAboutPanel:"),
|
||||
None,
|
||||
);
|
||||
|
||||
// Seperator menu item
|
||||
let sep_first = NSMenuItem::separatorItem(nil);
|
||||
|
||||
// Hide application menu item
|
||||
let hide_item_prefix = NSString::alloc(nil).init_str("Hide ");
|
||||
let hide_item_title = hide_item_prefix.stringByAppendingString_(process_name);
|
||||
let hide_item = menu_item(
|
||||
hide_item_title,
|
||||
selector("hide:"),
|
||||
Some(KeyEquivalent {
|
||||
key: "h",
|
||||
masks: None,
|
||||
}),
|
||||
);
|
||||
|
||||
// Hide other applications menu item
|
||||
let hide_others_item_title = NSString::alloc(nil).init_str("Hide Others");
|
||||
let hide_others_item = menu_item(
|
||||
hide_others_item_title,
|
||||
selector("hideOtherApplications:"),
|
||||
Some(KeyEquivalent {
|
||||
key: "h",
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSAlternateKeyMask
|
||||
| NSEventModifierFlags::NSCommandKeyMask,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Show applications menu item
|
||||
let show_all_item_title = NSString::alloc(nil).init_str("Show All");
|
||||
let show_all_item = menu_item(
|
||||
show_all_item_title,
|
||||
selector("unhideAllApplications:"),
|
||||
None,
|
||||
);
|
||||
|
||||
// Seperator menu item
|
||||
let sep = NSMenuItem::separatorItem(nil);
|
||||
|
||||
// Quit application menu item
|
||||
let quit_item_prefix = NSString::alloc(nil).init_str("Quit ");
|
||||
let quit_item_title = quit_item_prefix.stringByAppendingString_(process_name);
|
||||
let quit_item = menu_item(
|
||||
quit_item_title,
|
||||
selector("terminate:"),
|
||||
Some(KeyEquivalent {
|
||||
key: "q",
|
||||
masks: None,
|
||||
}),
|
||||
);
|
||||
|
||||
app_menu.addItem_(about_item);
|
||||
app_menu.addItem_(sep_first);
|
||||
app_menu.addItem_(hide_item);
|
||||
app_menu.addItem_(hide_others_item);
|
||||
app_menu.addItem_(show_all_item);
|
||||
app_menu.addItem_(sep);
|
||||
app_menu.addItem_(quit_item);
|
||||
app_menu_item.setSubmenu_(app_menu);
|
||||
});
|
||||
}
|
||||
|
||||
fn menu_item(
|
||||
title: *mut Object,
|
||||
selector: Sel,
|
||||
key_equivalent: Option<KeyEquivalent<'_>>,
|
||||
) -> *mut Object {
|
||||
unsafe {
|
||||
let (key, masks) = match key_equivalent {
|
||||
Some(ke) => (NSString::alloc(nil).init_str(ke.key), ke.masks),
|
||||
None => (NSString::alloc(nil).init_str(""), None),
|
||||
};
|
||||
let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key);
|
||||
if let Some(masks) = masks {
|
||||
item.setKeyEquivalentModifierMask_(masks)
|
||||
}
|
||||
|
||||
item
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
mod activation_hack;
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod event;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
mod menu;
|
||||
mod monitor;
|
||||
mod observer;
|
||||
mod util;
|
||||
@@ -17,6 +17,7 @@ mod window_delegate;
|
||||
use std::{fmt, ops::Deref, sync::Arc};
|
||||
|
||||
pub use self::{
|
||||
app_delegate::{get_aux_state_mut, AuxDelegateState},
|
||||
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
use std::{self, os::raw::*, ptr, time::Instant};
|
||||
use std::{
|
||||
self,
|
||||
os::raw::*,
|
||||
panic::{AssertUnwindSafe, UnwindSafe},
|
||||
ptr,
|
||||
rc::Weak,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::{app_state::AppState, ffi};
|
||||
use crate::platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
event_loop::{stop_app_on_panic, PanicInfo},
|
||||
ffi,
|
||||
};
|
||||
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
extern "C" {
|
||||
@@ -85,9 +96,20 @@ 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 {}
|
||||
|
||||
/// This mirrors the struct with the same name from Core Foundation.
|
||||
/// https://developer.apple.com/documentation/corefoundation/cfrunloopobservercontext?language=objc
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct CFRunLoopObserverContext {
|
||||
pub version: CFIndex,
|
||||
pub info: *mut c_void,
|
||||
pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>,
|
||||
pub release: Option<extern "C" fn(info: *const c_void)>,
|
||||
pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct CFRunLoopSourceContext {
|
||||
@@ -103,21 +125,42 @@ pub struct CFRunLoopSourceContext {
|
||||
pub perform: Option<extern "C" fn(*mut c_void)>,
|
||||
}
|
||||
|
||||
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
||||
where
|
||||
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
|
||||
{
|
||||
let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo);
|
||||
// Asserting unwind safety on this type should be fine because `PanicInfo` is
|
||||
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
|
||||
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
|
||||
// `from_raw` takes ownership of the data behind the pointer.
|
||||
// But if this scope takes ownership of the weak pointer, then
|
||||
// the weak pointer will get free'd at the end of the scope.
|
||||
// However we want to keep that weak reference around after the function.
|
||||
std::mem::forget(info_from_raw);
|
||||
|
||||
stop_app_on_panic(Weak::clone(&panic_info), move || f(panic_info.0));
|
||||
}
|
||||
|
||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
||||
extern "C" fn control_flow_begin_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
panic_info: *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!(),
|
||||
unsafe {
|
||||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => {
|
||||
//trace!("Triggered `CFRunLoopAfterWaiting`");
|
||||
AppState::wakeup(panic_info);
|
||||
//trace!("Completed `CFRunLoopAfterWaiting`");
|
||||
}
|
||||
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,17 +169,21 @@ extern "C" fn control_flow_begin_handler(
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
panic_info: *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!(),
|
||||
unsafe {
|
||||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => {
|
||||
//trace!("Triggered `CFRunLoopBeforeWaiting`");
|
||||
AppState::cleared(panic_info);
|
||||
//trace!("Completed `CFRunLoopBeforeWaiting`");
|
||||
}
|
||||
kCFRunLoopExit => (), //unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +199,7 @@ impl RunLoop {
|
||||
flags: CFOptionFlags,
|
||||
priority: CFIndex,
|
||||
handler: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) {
|
||||
let observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
@@ -159,24 +207,33 @@ impl RunLoop {
|
||||
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||
priority, // The lower the value, the sooner this will run
|
||||
handler,
|
||||
ptr::null_mut(),
|
||||
context,
|
||||
);
|
||||
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_control_flow_observers() {
|
||||
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
|
||||
unsafe {
|
||||
let mut context = CFRunLoopObserverContext {
|
||||
info: Weak::into_raw(panic_info) as *mut _,
|
||||
version: 0,
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
};
|
||||
let run_loop = RunLoop::get();
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
||||
CFIndex::min_value(),
|
||||
control_flow_begin_handler,
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
CFIndex::max_value(),
|
||||
control_flow_end_handler,
|
||||
&mut context as *mut _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,10 @@ pub unsafe fn set_title_async(ns_window: id, title: String) {
|
||||
|
||||
// `close:` is thread-safe, but we want the event to be triggered from the main
|
||||
// thread. Though, it's a good idea to look into that more...
|
||||
pub unsafe fn close_async(ns_window: id) {
|
||||
//
|
||||
// ArturKovacs: It's important that this operation keeps the underlying window alive
|
||||
// through the `IdRef` because otherwise it would dereference free'd memory
|
||||
pub unsafe fn close_async(ns_window: IdRef) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
autoreleasepool(move || {
|
||||
|
||||
@@ -8,11 +8,12 @@ use std::ops::{BitAnd, Deref};
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSWindowStyleMask},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
|
||||
foundation::{NSAutoreleasePool, NSPoint, NSRect, NSString, NSUInteger},
|
||||
};
|
||||
use core_graphics::display::CGDisplay;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL, YES};
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::platform_impl::platform::ffi;
|
||||
|
||||
// Replace with `!` once stable
|
||||
@@ -91,10 +92,21 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
|
||||
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
|
||||
}
|
||||
|
||||
/// Converts from winit screen-coordinates to macOS screen-coordinates.
|
||||
/// Winit: top-left is (0, 0) and y increasing downwards
|
||||
/// macOS: bottom-left is (0, 0) and y increasing upwards
|
||||
pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
|
||||
NSPoint::new(
|
||||
position.x,
|
||||
CGDisplay::main().pixels_high() as f64 - position.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
|
||||
IdRef::new(NSString::alloc(nil).init_str(s))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // In case we want to use this function in the future
|
||||
pub unsafe fn app_name() -> Option<id> {
|
||||
let bundle: id = msg_send![class!(NSBundle), mainBundle];
|
||||
let dict: id = msg_send![bundle, infoDictionary];
|
||||
|
||||
@@ -255,6 +255,10 @@ lazy_static! {
|
||||
sel!(frameDidChange:),
|
||||
frame_did_change as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(acceptsFirstMouse:),
|
||||
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
|
||||
);
|
||||
decl.add_ivar::<*mut c_void>("winitState");
|
||||
decl.add_ivar::<id>("markedText");
|
||||
let protocol = Protocol::get("NSTextInputClient").unwrap();
|
||||
@@ -346,7 +350,7 @@ extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
AppState::queue_redraw(WindowId(get_window_id(state.ns_window)));
|
||||
AppState::handle_redraw(WindowId(get_window_id(state.ns_window)));
|
||||
|
||||
let superclass = util::superclass(this);
|
||||
let () = msg_send![super(this, superclass), drawRect: rect];
|
||||
@@ -1078,3 +1082,7 @@ extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) {
|
||||
extern "C" fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL {
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL {
|
||||
YES
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
icon::Icon,
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform::macos::{ActivationPolicy, WindowExtMacOS},
|
||||
platform::macos::WindowExtMacOS,
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
app_state::INTERRUPT_EVENT_LOOP_EXIT,
|
||||
@@ -34,9 +34,8 @@ use crate::{
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{
|
||||
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
|
||||
NSApplicationPresentationOptions, NSColor, NSRequestUserAttentionType, NSScreen, NSView,
|
||||
NSWindow, NSWindowButton, NSWindowStyleMask,
|
||||
self, CGFloat, NSApp, NSApplication, NSApplicationPresentationOptions, NSColor,
|
||||
NSRequestUserAttentionType, NSScreen, NSView, NSWindow, NSWindowButton, NSWindowStyleMask,
|
||||
},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize},
|
||||
@@ -64,7 +63,6 @@ pub fn get_window_id(window_cocoa_id: id) -> Id {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub activation_policy: ActivationPolicy,
|
||||
pub movable_by_window_background: bool,
|
||||
pub titlebar_transparent: bool,
|
||||
pub title_hidden: bool,
|
||||
@@ -80,7 +78,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
activation_policy: Default::default(),
|
||||
movable_by_window_background: false,
|
||||
titlebar_transparent: false,
|
||||
title_hidden: false,
|
||||
@@ -94,24 +91,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
|
||||
unsafe {
|
||||
let ns_app = NSApp();
|
||||
if ns_app == nil {
|
||||
None
|
||||
} else {
|
||||
use self::NSApplicationActivationPolicy::*;
|
||||
ns_app.setActivationPolicy_(match activation_policy {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
|
||||
});
|
||||
ns_app.finishLaunching();
|
||||
Some(ns_app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_view(
|
||||
ns_window: id,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
|
||||
@@ -131,8 +110,6 @@ unsafe fn create_view(
|
||||
ns_view.setWantsLayer(YES);
|
||||
}
|
||||
|
||||
ns_window.setContentView_(*ns_view);
|
||||
ns_window.makeFirstResponder_(*ns_view);
|
||||
(ns_view, cursor_state)
|
||||
})
|
||||
}
|
||||
@@ -166,7 +143,17 @@ fn create_window(
|
||||
}
|
||||
None => (800.0, 600.0),
|
||||
};
|
||||
NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height))
|
||||
let (left, bottom) = match attrs.position {
|
||||
Some(position) => {
|
||||
let logical = util::window_position(position.to_logical(scale_factor));
|
||||
// macOS wants the position of the bottom left corner,
|
||||
// but caller is setting the position of top left corner
|
||||
(logical.x, logical.y - height)
|
||||
}
|
||||
// This value is ignored by calling win.center() below
|
||||
None => (0.0, 0.0),
|
||||
};
|
||||
NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -249,8 +236,9 @@ fn create_window(
|
||||
if !pl_attrs.has_shadow {
|
||||
ns_window.setHasShadow_(NO);
|
||||
}
|
||||
|
||||
ns_window.center();
|
||||
if attrs.position.is_none() {
|
||||
ns_window.center();
|
||||
}
|
||||
ns_window
|
||||
});
|
||||
pool.drain();
|
||||
@@ -346,14 +334,9 @@ impl UnownedWindow {
|
||||
panic!("Windows can only be created on the main thread on macOS");
|
||||
}
|
||||
}
|
||||
trace!("Creating new window");
|
||||
|
||||
let pool = unsafe { NSAutoreleasePool::new(nil) };
|
||||
|
||||
let ns_app = create_app(pl_attribs.activation_policy).ok_or_else(|| {
|
||||
unsafe { pool.drain() };
|
||||
os_error!(OsError::CreationError("Couldn't create `NSApplication`"))
|
||||
})?;
|
||||
|
||||
let ns_window = create_window(&win_attribs, &pl_attribs).ok_or_else(|| {
|
||||
unsafe { pool.drain() };
|
||||
os_error!(OsError::CreationError("Couldn't create `NSWindow`"))
|
||||
@@ -365,6 +348,12 @@ impl UnownedWindow {
|
||||
os_error!(OsError::CreationError("Couldn't create `NSView`"))
|
||||
})?;
|
||||
|
||||
// Configure the new view as the "key view" for the window
|
||||
unsafe {
|
||||
ns_window.setContentView_(*ns_view);
|
||||
ns_window.setInitialFirstResponder_(*ns_view);
|
||||
}
|
||||
|
||||
let input_context = unsafe { util::create_input_context(*ns_view) };
|
||||
|
||||
let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
|
||||
@@ -375,7 +364,6 @@ impl UnownedWindow {
|
||||
ns_window.setBackgroundColor_(NSColor::clearColor(nil));
|
||||
}
|
||||
|
||||
ns_app.activateIgnoringOtherApps_(YES);
|
||||
win_attribs.min_inner_size.map(|dim| {
|
||||
let logical_dim = dim.to_logical(scale_factor);
|
||||
set_min_inner_size(*ns_window, logical_dim)
|
||||
@@ -425,12 +413,9 @@ impl UnownedWindow {
|
||||
// Setting the window as key has to happen *after* we set the fullscreen
|
||||
// state, since otherwise we'll briefly see the window at normal size
|
||||
// before it transitions.
|
||||
unsafe {
|
||||
if visible {
|
||||
window.ns_window.makeKeyAndOrderFront_(nil);
|
||||
} else {
|
||||
window.ns_window.makeKeyWindow();
|
||||
}
|
||||
if visible {
|
||||
// Tightly linked with `app_state::window_activation_hack`
|
||||
unsafe { window.ns_window.makeKeyAndOrderFront_(nil) };
|
||||
}
|
||||
|
||||
if maximized {
|
||||
@@ -496,17 +481,8 @@ impl UnownedWindow {
|
||||
pub fn set_outer_position(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
let dummy = NSRect::new(
|
||||
NSPoint::new(
|
||||
position.x,
|
||||
// While it's true that we're setting the top-left position,
|
||||
// it still needs to be in a bottom-left coordinate system.
|
||||
CGDisplay::main().pixels_high() as f64 - position.y,
|
||||
),
|
||||
NSSize::new(0f64, 0f64),
|
||||
);
|
||||
unsafe {
|
||||
util::set_frame_top_left_point_async(*self.ns_window, dummy.origin);
|
||||
util::set_frame_top_left_point_async(*self.ns_window, util::window_position(position));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,6 +612,16 @@ impl UnownedWindow {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
unsafe {
|
||||
let event: id = msg_send![NSApp(), currentEvent];
|
||||
let _: () = msg_send![*self.ns_window, performWindowDragWithEvent: event];
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_zoomed(&self) -> bool {
|
||||
// because `isZoomed` doesn't work if the window's borderless,
|
||||
// we make it resizable temporalily.
|
||||
@@ -730,6 +716,11 @@ impl UnownedWindow {
|
||||
shared_state_lock.fullscreen.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
self.is_zoomed()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
trace!("Locked shared state in `set_fullscreen`");
|
||||
@@ -1153,7 +1144,7 @@ impl Drop for UnownedWindow {
|
||||
trace!("Dropping `UnownedWindow` ({:?})", self as *mut _);
|
||||
// Close the window if it has not yet been closed.
|
||||
if *self.ns_window != nil {
|
||||
unsafe { util::close_async(*self.ns_window) };
|
||||
unsafe { util::close_async(self.ns_window.clone()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::{super::monitor, backend, device, proxy::Proxy, runner, window};
|
||||
use crate::dpi::{PhysicalSize, Size};
|
||||
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
|
||||
use crate::event::{
|
||||
DeviceEvent, DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent,
|
||||
};
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::monitor::MonitorHandle as RootMH;
|
||||
use crate::window::{Theme, WindowId};
|
||||
@@ -130,7 +132,7 @@ impl<T> WindowTarget<T> {
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_cursor_move(move |pointer_id, position, modifiers| {
|
||||
canvas.on_cursor_move(move |pointer_id, position, delta, modifiers| {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::CursorMoved {
|
||||
@@ -139,6 +141,12 @@ impl<T> WindowTarget<T> {
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: DeviceId(device::Id(pointer_id)),
|
||||
event: DeviceEvent::MouseMotion {
|
||||
delta: (delta.x, delta.y),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
|
||||
@@ -17,6 +17,7 @@ use stdweb::web::event::{
|
||||
use stdweb::web::html_element::CanvasElement;
|
||||
use stdweb::web::{document, EventListenerHandle, IElement, IEventTarget, IHtmlElement};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Canvas {
|
||||
/// Note: resizing the CanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
||||
raw: CanvasElement,
|
||||
@@ -222,13 +223,14 @@ impl Canvas {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
// todo
|
||||
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_position(&event).to_physical(super::scale_factor()),
|
||||
event::mouse_delta(&event).to_physical(super::scale_factor()),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
}));
|
||||
|
||||
@@ -30,6 +30,13 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition<f64> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_delta(event: &impl IMouseEvent) -> LogicalPosition<f64> {
|
||||
LogicalPosition {
|
||||
x: event.movement_x() as f64,
|
||||
y: event.movement_y() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option<MouseScrollDelta> {
|
||||
let x = event.delta_x();
|
||||
let y = -event.delta_y();
|
||||
|
||||
@@ -18,6 +18,7 @@ use web_sys::{
|
||||
mod mouse_handler;
|
||||
mod pointer_handler;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Canvas {
|
||||
common: Common,
|
||||
on_focus: Option<EventListenerHandle<dyn FnMut(FocusEvent)>>,
|
||||
@@ -238,7 +239,7 @@ impl Canvas {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
match &mut self.mouse_state {
|
||||
MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler),
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::rc::Rc;
|
||||
|
||||
use web_sys::{EventTarget, MouseEvent};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) struct MouseHandler {
|
||||
on_mouse_leave: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
|
||||
on_mouse_enter: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
|
||||
@@ -160,7 +161,7 @@ impl MouseHandler {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
let mouse_capture_state = self.mouse_capture_state.clone();
|
||||
let canvas = canvas_common.raw.clone();
|
||||
@@ -190,9 +191,11 @@ impl MouseHandler {
|
||||
// use `offsetX`/`offsetY`.
|
||||
event::mouse_position_by_client(&event, &canvas)
|
||||
};
|
||||
let mouse_delta = event::mouse_delta(&event);
|
||||
handler(
|
||||
0,
|
||||
mouse_pos.to_physical(super::super::scale_factor()),
|
||||
mouse_delta.to_physical(super::super::scale_factor()),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::event::{ModifiersState, MouseButton};
|
||||
|
||||
use web_sys::PointerEvent;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) struct PointerHandler {
|
||||
on_cursor_leave: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||
on_cursor_enter: Option<EventListenerHandle<dyn FnMut(PointerEvent)>>,
|
||||
@@ -87,7 +88,7 @@ impl PointerHandler {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
self.on_cursor_move = Some(canvas_common.add_event(
|
||||
"pointermove",
|
||||
@@ -95,6 +96,7 @@ impl PointerHandler {
|
||||
handler(
|
||||
event.pointer_id(),
|
||||
event::mouse_position(&event).to_physical(super::super::scale_factor()),
|
||||
event::mouse_delta(&event).to_physical(super::super::scale_factor()),
|
||||
event::mouse_modifiers(&event),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -29,6 +29,13 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_delta(event: &MouseEvent) -> LogicalPosition<f64> {
|
||||
LogicalPosition {
|
||||
x: event.movement_x() as f64,
|
||||
y: event.movement_y() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_position_by_client(
|
||||
event: &MouseEvent,
|
||||
canvas: &HtmlCanvasElement,
|
||||
|
||||
@@ -222,6 +222,11 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, _minimized: bool) {
|
||||
// Intentionally a no-op, as canvases cannot be 'minimized'
|
||||
@@ -232,6 +237,12 @@ impl Window {
|
||||
// Intentionally a no-op, as canvases cannot be 'maximized'
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
// Canvas cannot be 'maximized'
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
if self.canvas.borrow().is_fullscreen() {
|
||||
|
||||
@@ -81,16 +81,20 @@ pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
|
||||
None => should_use_dark_mode(),
|
||||
};
|
||||
|
||||
let theme_name = if is_dark_mode {
|
||||
DARK_THEME_NAME.as_ptr()
|
||||
let theme = if is_dark_mode {
|
||||
Theme::Dark
|
||||
} else {
|
||||
LIGHT_THEME_NAME.as_ptr()
|
||||
Theme::Light
|
||||
};
|
||||
let theme_name = match theme {
|
||||
Theme::Dark => DARK_THEME_NAME.as_ptr(),
|
||||
Theme::Light => LIGHT_THEME_NAME.as_ptr(),
|
||||
};
|
||||
|
||||
let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) };
|
||||
|
||||
if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) {
|
||||
return Theme::Dark;
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ mod runner;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
cell::Cell,
|
||||
collections::VecDeque,
|
||||
marker::PhantomData,
|
||||
mem, panic, ptr,
|
||||
@@ -19,7 +20,7 @@ use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR};
|
||||
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WPARAM},
|
||||
minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WORD, WPARAM},
|
||||
windef::{HWND, POINT, RECT},
|
||||
windowsx, winerror,
|
||||
},
|
||||
@@ -86,6 +87,8 @@ pub(crate) struct SubclassInput<T: 'static> {
|
||||
pub window_state: Arc<Mutex<WindowState>>,
|
||||
pub event_loop_runner: EventLoopRunnerShared<T>,
|
||||
pub file_drop_handler: Option<FileDropHandler>,
|
||||
pub subclass_removed: Cell<bool>,
|
||||
pub recurse_depth: Cell<u32>,
|
||||
}
|
||||
|
||||
impl<T> SubclassInput<T> {
|
||||
@@ -616,18 +619,31 @@ fn subclass_event_target_window<T>(
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_event_target_window_subclass<T: 'static>(window: HWND) {
|
||||
let removal_result = unsafe {
|
||||
commctrl::RemoveWindowSubclass(
|
||||
window,
|
||||
Some(thread_event_target_callback::<T>),
|
||||
THREAD_EVENT_TARGET_SUBCLASS_ID,
|
||||
)
|
||||
};
|
||||
assert_eq!(removal_result, 1);
|
||||
}
|
||||
|
||||
/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of
|
||||
/// the window.
|
||||
unsafe fn capture_mouse(window: HWND, window_state: &mut WindowState) {
|
||||
window_state.mouse.buttons_down += 1;
|
||||
window_state.mouse.capture_count += 1;
|
||||
winuser::SetCapture(window);
|
||||
}
|
||||
|
||||
/// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor
|
||||
/// is outside the window.
|
||||
unsafe fn release_mouse(window_state: &mut WindowState) {
|
||||
window_state.mouse.buttons_down = window_state.mouse.buttons_down.saturating_sub(1);
|
||||
if window_state.mouse.buttons_down == 0 {
|
||||
unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowState>) {
|
||||
window_state.mouse.capture_count = window_state.mouse.capture_count.saturating_sub(1);
|
||||
if window_state.mouse.capture_count == 0 {
|
||||
// ReleaseCapture() causes a WM_CAPTURECHANGED where we lock the window_state.
|
||||
drop(window_state);
|
||||
winuser::ReleaseCapture();
|
||||
}
|
||||
}
|
||||
@@ -648,6 +664,17 @@ pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>)
|
||||
assert_eq!(subclass_result, 1);
|
||||
}
|
||||
|
||||
fn remove_window_subclass<T: 'static>(window: HWND) {
|
||||
let removal_result = unsafe {
|
||||
commctrl::RemoveWindowSubclass(
|
||||
window,
|
||||
Some(public_window_callback::<T>),
|
||||
WINDOW_SUBCLASS_ID,
|
||||
)
|
||||
};
|
||||
assert_eq!(removal_result, 1);
|
||||
}
|
||||
|
||||
fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
|
||||
match pressure {
|
||||
1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)),
|
||||
@@ -750,11 +777,41 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
msg: UINT,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
_: UINT_PTR,
|
||||
uidsubclass: UINT_PTR,
|
||||
subclass_input_ptr: DWORD_PTR,
|
||||
) -> LRESULT {
|
||||
let subclass_input = &*(subclass_input_ptr as *const SubclassInput<T>);
|
||||
let subclass_input_ptr = subclass_input_ptr as *mut SubclassInput<T>;
|
||||
let (result, subclass_removed, recurse_depth) = {
|
||||
let subclass_input = &*subclass_input_ptr;
|
||||
subclass_input
|
||||
.recurse_depth
|
||||
.set(subclass_input.recurse_depth.get() + 1);
|
||||
|
||||
let result =
|
||||
public_window_callback_inner(window, msg, wparam, lparam, uidsubclass, subclass_input);
|
||||
|
||||
let subclass_removed = subclass_input.subclass_removed.get();
|
||||
let recurse_depth = subclass_input.recurse_depth.get() - 1;
|
||||
subclass_input.recurse_depth.set(recurse_depth);
|
||||
|
||||
(result, subclass_removed, recurse_depth)
|
||||
};
|
||||
|
||||
if subclass_removed && recurse_depth == 0 {
|
||||
Box::from_raw(subclass_input_ptr);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
unsafe fn public_window_callback_inner<T: 'static>(
|
||||
window: HWND,
|
||||
msg: UINT,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
_: UINT_PTR,
|
||||
subclass_input: &SubclassInput<T>,
|
||||
) -> LRESULT {
|
||||
winuser::RedrawWindow(
|
||||
subclass_input.event_loop_runner.thread_msg_target(),
|
||||
ptr::null(),
|
||||
@@ -788,7 +845,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
}
|
||||
winuser::WM_NCLBUTTONDOWN => {
|
||||
if wparam == winuser::HTCAPTION as _ {
|
||||
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
|
||||
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, lparam);
|
||||
}
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
}
|
||||
@@ -814,8 +871,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
}
|
||||
|
||||
winuser::WM_NCDESTROY => {
|
||||
drop(subclass_input);
|
||||
Box::from_raw(subclass_input_ptr as *mut SubclassInput<T>);
|
||||
remove_window_subclass::<T>(window);
|
||||
subclass_input.subclass_removed.set(true);
|
||||
0
|
||||
}
|
||||
|
||||
@@ -1192,7 +1249,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
ElementState::Released, MouseButton::Left, WindowEvent::MouseInput,
|
||||
};
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
release_mouse(subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
@@ -1234,7 +1291,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
ElementState::Released, MouseButton::Right, WindowEvent::MouseInput,
|
||||
};
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
release_mouse(subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
@@ -1276,7 +1333,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput,
|
||||
};
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
release_mouse(subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
@@ -1320,7 +1377,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
};
|
||||
let xbutton = winuser::GET_XBUTTON_WPARAM(wparam);
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
release_mouse(subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
@@ -1336,6 +1393,17 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
0
|
||||
}
|
||||
|
||||
winuser::WM_CAPTURECHANGED => {
|
||||
// lparam here is a handle to the window which is gaining mouse capture.
|
||||
// If it is the same as our window, then we're essentially retaining the capture. This
|
||||
// can happen if `SetCapture` is called on our window when it already has the mouse
|
||||
// capture.
|
||||
if lparam != window as isize {
|
||||
subclass_input.window_state.lock().mouse.capture_count = 0;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
winuser::WM_TOUCH => {
|
||||
let pcount = LOWORD(wparam as DWORD) as usize;
|
||||
let mut inputs = Vec::with_capacity(pcount);
|
||||
@@ -1601,11 +1669,11 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
winuser::WM_SETCURSOR => {
|
||||
let set_cursor_to = {
|
||||
let window_state = subclass_input.window_state.lock();
|
||||
if window_state
|
||||
.mouse
|
||||
.cursor_flags()
|
||||
.contains(CursorFlags::IN_WINDOW)
|
||||
{
|
||||
// The return value for the preceding `WM_NCHITTEST` message is conveniently
|
||||
// provided through the low-order word of lParam. We use that here since
|
||||
// `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement.
|
||||
let in_client_area = LOWORD(lparam as DWORD) == winuser::HTCLIENT as WORD;
|
||||
if in_client_area {
|
||||
Some(window_state.mouse.cursor)
|
||||
} else {
|
||||
None
|
||||
@@ -1923,8 +1991,7 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
_: UINT_PTR,
|
||||
subclass_input_ptr: DWORD_PTR,
|
||||
) -> LRESULT {
|
||||
let subclass_input = &mut *(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput<T>);
|
||||
let runner = subclass_input.event_loop_runner.clone();
|
||||
let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput<T>);
|
||||
|
||||
if msg != winuser::WM_PAINT {
|
||||
winuser::RedrawWindow(
|
||||
@@ -1935,13 +2002,15 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
);
|
||||
}
|
||||
|
||||
let mut subclass_removed = false;
|
||||
|
||||
// I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing
|
||||
// the closure to catch_unwind directly so that the match body indendation wouldn't change and
|
||||
// the git blame and history would be preserved.
|
||||
let callback = || match msg {
|
||||
winuser::WM_NCDESTROY => {
|
||||
Box::from_raw(subclass_input);
|
||||
drop(subclass_input);
|
||||
remove_event_target_window_subclass::<T>(window);
|
||||
subclass_removed = true;
|
||||
0
|
||||
}
|
||||
// Because WM_PAINT comes after all other messages, we use it during modal loops to detect
|
||||
@@ -2032,11 +2101,12 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
}
|
||||
|
||||
if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) {
|
||||
let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA;
|
||||
let delta =
|
||||
mouse.usButtonData as SHORT as f32 / winuser::WHEEL_DELTA as f32;
|
||||
subclass_input.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: MouseWheel {
|
||||
delta: LineDelta(0.0, delta as f32),
|
||||
delta: LineDelta(0.0, delta),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -2148,5 +2218,14 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
_ => commctrl::DefSubclassProc(window, msg, wparam, lparam),
|
||||
};
|
||||
|
||||
runner.catch_unwind(callback).unwrap_or(-1)
|
||||
let result = subclass_input
|
||||
.event_loop_runner
|
||||
.catch_unwind(callback)
|
||||
.unwrap_or(-1);
|
||||
if subclass_removed {
|
||||
mem::drop(subclass_input);
|
||||
} else {
|
||||
Box::into_raw(subclass_input);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use winapi::{self, shared::windef::HWND};
|
||||
use winapi::{self, shared::windef::HMENU, shared::windef::HWND};
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
@@ -15,9 +15,17 @@ use crate::event::DeviceId as RootDeviceId;
|
||||
use crate::icon::Icon;
|
||||
use crate::window::Theme;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Parent {
|
||||
None,
|
||||
ChildOf(HWND),
|
||||
OwnedBy(HWND),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub parent: Option<HWND>,
|
||||
pub parent: Parent,
|
||||
pub menu: Option<HMENU>,
|
||||
pub taskbar_icon: Option<Icon>,
|
||||
pub no_redirection_bitmap: bool,
|
||||
pub drag_and_drop: bool,
|
||||
@@ -27,7 +35,8 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
parent: None,
|
||||
parent: Parent::None,
|
||||
menu: None,
|
||||
taskbar_icon: None,
|
||||
no_redirection_bitmap: false,
|
||||
drag_and_drop: true,
|
||||
|
||||
@@ -14,8 +14,8 @@ use std::{
|
||||
use winapi::{
|
||||
ctypes::c_int,
|
||||
shared::{
|
||||
minwindef::{HINSTANCE, UINT},
|
||||
windef::{HWND, POINT, RECT},
|
||||
minwindef::{HINSTANCE, LPARAM, UINT, WPARAM},
|
||||
windef::{HWND, POINT, POINTS, RECT},
|
||||
},
|
||||
um::{
|
||||
combaseapi, dwmapi,
|
||||
@@ -25,7 +25,8 @@ use winapi::{
|
||||
ole2,
|
||||
oleidl::LPDROPTARGET,
|
||||
shobjidl_core::{CLSID_TaskbarList, ITaskbarList2},
|
||||
winnt::LPCWSTR,
|
||||
wingdi::{CreateRectRgn, DeleteObject},
|
||||
winnt::{LPCWSTR, SHORT},
|
||||
winuser,
|
||||
},
|
||||
};
|
||||
@@ -43,7 +44,7 @@ use crate::{
|
||||
icon::{self, IconType},
|
||||
monitor, util,
|
||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||
PlatformSpecificWindowBuilderAttributes, WindowId,
|
||||
Parent, PlatformSpecificWindowBuilderAttributes, WindowId,
|
||||
},
|
||||
window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes},
|
||||
};
|
||||
@@ -114,6 +115,8 @@ impl Window {
|
||||
window_state: win.window_state.clone(),
|
||||
event_loop_runner: event_loop.runner_shared.clone(),
|
||||
file_drop_handler,
|
||||
subclass_removed: Cell::new(false),
|
||||
recurse_depth: Cell::new(0),
|
||||
};
|
||||
|
||||
event_loop::subclass_window(win.window.0, subclass_input);
|
||||
@@ -276,7 +279,7 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn hinstance(&self) -> HINSTANCE {
|
||||
unsafe { winuser::GetWindowLongW(self.hwnd(), winuser::GWL_HINSTANCE) as *mut _ }
|
||||
unsafe { winuser::GetWindowLongPtrW(self.hwnd(), winuser::GWLP_HINSTANCE) as *mut _ }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -354,6 +357,30 @@ impl Window {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
unsafe {
|
||||
let points = {
|
||||
let mut pos = mem::zeroed();
|
||||
winuser::GetCursorPos(&mut pos);
|
||||
pos
|
||||
};
|
||||
let points = POINTS {
|
||||
x: points.x as SHORT,
|
||||
y: points.y as SHORT,
|
||||
};
|
||||
winuser::ReleaseCapture();
|
||||
winuser::PostMessageW(
|
||||
self.window.0,
|
||||
winuser::WM_NCLBUTTONDOWN,
|
||||
winuser::HTCAPTION as WPARAM,
|
||||
&points as *const _ as LPARAM,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
WindowId(self.window.0)
|
||||
@@ -383,6 +410,12 @@ impl Window {
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
let window_state = self.window_state.lock();
|
||||
window_state.window_flags.contains(WindowFlags::MAXIMIZED)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
let window_state = self.window_state.lock();
|
||||
@@ -403,20 +436,6 @@ impl Window {
|
||||
drop(window_state_lock);
|
||||
|
||||
self.thread_executor.execute_in_thread(move || {
|
||||
let mut window_state_lock = window_state.lock();
|
||||
|
||||
// Save window bounds before entering fullscreen
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
(&None, &Some(_)) => {
|
||||
let client_rect = util::get_client_rect(window.0).unwrap();
|
||||
window_state_lock.saved_window = Some(SavedWindow {
|
||||
client_rect,
|
||||
scale_factor: window_state_lock.scale_factor,
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Change video mode if we're transitioning to or from exclusive
|
||||
// fullscreen
|
||||
match (&old_fullscreen, &fullscreen) {
|
||||
@@ -490,7 +509,7 @@ impl Window {
|
||||
}
|
||||
|
||||
// Update window style
|
||||
WindowState::set_window_flags(window_state_lock, window.0, |f| {
|
||||
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||
f.set(
|
||||
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN,
|
||||
matches!(fullscreen, Some(Fullscreen::Exclusive(_))),
|
||||
@@ -504,6 +523,15 @@ impl Window {
|
||||
// Update window bounds
|
||||
match &fullscreen {
|
||||
Some(fullscreen) => {
|
||||
// Save window bounds before entering fullscreen
|
||||
let placement = unsafe {
|
||||
let mut placement = mem::zeroed();
|
||||
winuser::GetWindowPlacement(window.0, &mut placement);
|
||||
placement
|
||||
};
|
||||
|
||||
window_state.lock().saved_window = Some(SavedWindow { placement });
|
||||
|
||||
let monitor = match &fullscreen {
|
||||
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
||||
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
|
||||
@@ -530,27 +558,10 @@ impl Window {
|
||||
}
|
||||
None => {
|
||||
let mut window_state_lock = window_state.lock();
|
||||
if let Some(SavedWindow {
|
||||
client_rect,
|
||||
scale_factor,
|
||||
}) = window_state_lock.saved_window.take()
|
||||
{
|
||||
window_state_lock.scale_factor = scale_factor;
|
||||
if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() {
|
||||
drop(window_state_lock);
|
||||
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
|
||||
|
||||
unsafe {
|
||||
winuser::SetWindowPos(
|
||||
window.0,
|
||||
ptr::null_mut(),
|
||||
client_rect.left,
|
||||
client_rect.top,
|
||||
client_rect.right - client_rect.left,
|
||||
client_rect.bottom - client_rect.top,
|
||||
winuser::SWP_ASYNCWINDOWPOS
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::SetWindowPlacement(window.0, &placement);
|
||||
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
|
||||
}
|
||||
}
|
||||
@@ -722,8 +733,24 @@ unsafe fn init<T: 'static>(
|
||||
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
|
||||
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
|
||||
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
|
||||
window_flags.set(WindowFlags::CHILD, pl_attribs.parent.is_some());
|
||||
window_flags.set(WindowFlags::ON_TASKBAR, true);
|
||||
|
||||
let parent = match pl_attribs.parent {
|
||||
Parent::ChildOf(parent) => {
|
||||
window_flags.set(WindowFlags::CHILD, true);
|
||||
if pl_attribs.menu.is_some() {
|
||||
warn!("Setting a menu on a child window is unsupported");
|
||||
}
|
||||
Some(parent)
|
||||
}
|
||||
Parent::OwnedBy(parent) => {
|
||||
window_flags.set(WindowFlags::POPUP, true);
|
||||
Some(parent)
|
||||
}
|
||||
Parent::None => {
|
||||
window_flags.set(WindowFlags::ON_TASKBAR, true);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// creating the real window this time, by using the functions in `extra_functions`
|
||||
let real_window = {
|
||||
@@ -737,8 +764,8 @@ unsafe fn init<T: 'static>(
|
||||
winuser::CW_USEDEFAULT,
|
||||
winuser::CW_USEDEFAULT,
|
||||
winuser::CW_USEDEFAULT,
|
||||
pl_attribs.parent.unwrap_or(ptr::null_mut()),
|
||||
ptr::null_mut(),
|
||||
parent.unwrap_or(ptr::null_mut()),
|
||||
pl_attribs.menu.unwrap_or(ptr::null_mut()),
|
||||
libloaderapi::GetModuleHandleW(ptr::null()),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
@@ -763,20 +790,18 @@ unsafe fn init<T: 'static>(
|
||||
|
||||
// making the window transparent
|
||||
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
|
||||
// Empty region for the blur effect, so the window is fully transparent
|
||||
let region = CreateRectRgn(0, 0, -1, -1);
|
||||
|
||||
let bb = dwmapi::DWM_BLURBEHIND {
|
||||
dwFlags: dwmapi::DWM_BB_ENABLE,
|
||||
dwFlags: dwmapi::DWM_BB_ENABLE | dwmapi::DWM_BB_BLURREGION,
|
||||
fEnable: 1,
|
||||
hRgnBlur: ptr::null_mut(),
|
||||
hRgnBlur: region,
|
||||
fTransitionOnMaximized: 0,
|
||||
};
|
||||
|
||||
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
|
||||
|
||||
if attributes.decorations {
|
||||
let opacity = 255;
|
||||
|
||||
winuser::SetLayeredWindowAttributes(real_window.0, 0, opacity, winuser::LWA_ALPHA);
|
||||
}
|
||||
DeleteObject(region as _);
|
||||
}
|
||||
|
||||
// If the system theme is dark, we need to set the window theme now
|
||||
@@ -805,7 +830,7 @@ unsafe fn init<T: 'static>(
|
||||
|
||||
let dimensions = attributes
|
||||
.inner_size
|
||||
.unwrap_or_else(|| PhysicalSize::new(1024, 768).into());
|
||||
.unwrap_or_else(|| PhysicalSize::new(800, 600).into());
|
||||
win.set_inner_size(dimensions);
|
||||
if attributes.maximized {
|
||||
// Need to set MAXIMIZED after setting `inner_size` as
|
||||
@@ -819,6 +844,10 @@ unsafe fn init<T: 'static>(
|
||||
force_window_active(win.window.0);
|
||||
}
|
||||
|
||||
if let Some(position) = attributes.position {
|
||||
win.set_outer_position(position);
|
||||
}
|
||||
|
||||
Ok(win)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,19 +34,18 @@ pub struct WindowState {
|
||||
pub current_theme: Theme,
|
||||
pub preferred_theme: Option<Theme>,
|
||||
pub high_surrogate: Option<u16>,
|
||||
window_flags: WindowFlags,
|
||||
pub window_flags: WindowFlags,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SavedWindow {
|
||||
pub client_rect: RECT,
|
||||
pub scale_factor: f64,
|
||||
pub placement: winuser::WINDOWPLACEMENT,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MouseProperties {
|
||||
pub cursor: CursorIcon,
|
||||
pub buttons_down: u32,
|
||||
pub capture_count: u32,
|
||||
cursor_flags: CursorFlags,
|
||||
pub last_position: Option<PhysicalPosition<f64>>,
|
||||
}
|
||||
@@ -69,6 +68,7 @@ bitflags! {
|
||||
const TRANSPARENT = 1 << 6;
|
||||
const CHILD = 1 << 7;
|
||||
const MAXIMIZED = 1 << 8;
|
||||
const POPUP = 1 << 14;
|
||||
|
||||
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
|
||||
/// included here to make masking easier.
|
||||
@@ -86,11 +86,6 @@ bitflags! {
|
||||
|
||||
const MINIMIZED = 1 << 12;
|
||||
|
||||
const FULLSCREEN_AND_MASK = !(
|
||||
WindowFlags::DECORATIONS.bits |
|
||||
WindowFlags::RESIZABLE.bits |
|
||||
WindowFlags::MAXIMIZED.bits
|
||||
);
|
||||
const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
|
||||
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
||||
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
||||
@@ -108,7 +103,7 @@ impl WindowState {
|
||||
WindowState {
|
||||
mouse: MouseProperties {
|
||||
cursor: CursorIcon::default(),
|
||||
buttons_down: 0,
|
||||
capture_count: 0,
|
||||
cursor_flags: CursorFlags::empty(),
|
||||
last_position: None,
|
||||
},
|
||||
@@ -181,10 +176,7 @@ impl MouseProperties {
|
||||
impl WindowFlags {
|
||||
fn mask(mut self) -> WindowFlags {
|
||||
if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) {
|
||||
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
||||
self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK;
|
||||
} else if self.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) {
|
||||
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
||||
}
|
||||
if !self.contains(WindowFlags::VISIBLE) {
|
||||
self &= WindowFlags::INVISIBLE_AND_MASK;
|
||||
@@ -219,12 +211,12 @@ impl WindowFlags {
|
||||
if self.contains(WindowFlags::NO_BACK_BUFFER) {
|
||||
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
|
||||
}
|
||||
if self.contains(WindowFlags::TRANSPARENT) && self.contains(WindowFlags::DECORATIONS) {
|
||||
style_ex |= WS_EX_LAYERED;
|
||||
}
|
||||
if self.contains(WindowFlags::CHILD) {
|
||||
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
|
||||
}
|
||||
if self.contains(WindowFlags::POPUP) {
|
||||
style |= WS_POPUP;
|
||||
}
|
||||
if self.contains(WindowFlags::MINIMIZED) {
|
||||
style |= WS_MINIMIZE;
|
||||
}
|
||||
@@ -235,6 +227,12 @@ impl WindowFlags {
|
||||
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
|
||||
style_ex |= WS_EX_ACCEPTFILES;
|
||||
|
||||
if self.intersects(
|
||||
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN,
|
||||
) {
|
||||
style &= !WS_OVERLAPPEDWINDOW;
|
||||
}
|
||||
|
||||
(style, style_ex)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,31 @@ pub struct WindowAttributes {
|
||||
/// The default is `None`.
|
||||
pub max_inner_size: Option<Size>,
|
||||
|
||||
/// The desired position of the window. If this is `None`, some platform-specific position
|
||||
/// will be chosen.
|
||||
///
|
||||
/// The default is `None`.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS**: The top left corner position of the window content, the window's "inner"
|
||||
/// position. The window title bar will be placed above it.
|
||||
/// The window will be positioned such that it fits on screen, maintaining
|
||||
/// set `inner_size` if any.
|
||||
/// If you need to precisely position the top left corner of the whole window you have to
|
||||
/// use [`Window::set_outer_position`] after creating the window.
|
||||
/// - **Windows**: The top left corner position of the window title bar, the window's "outer"
|
||||
/// position.
|
||||
/// There may be a small gap between this position and the window due to the specifics of the
|
||||
/// Window Manager.
|
||||
/// - **X11**: The top left corner of the window, the window's "outer" position.
|
||||
/// - **Others**: Ignored.
|
||||
///
|
||||
/// See [`Window::set_outer_position`].
|
||||
///
|
||||
/// [`Window::set_outer_position`]: crate::window::Window::set_outer_position
|
||||
pub position: Option<Position>,
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
///
|
||||
/// The default is `true`.
|
||||
@@ -170,6 +195,7 @@ impl Default for WindowAttributes {
|
||||
inner_size: None,
|
||||
min_inner_size: None,
|
||||
max_inner_size: None,
|
||||
position: None,
|
||||
resizable: true,
|
||||
title: "winit window".to_owned(),
|
||||
maximized: false,
|
||||
@@ -223,6 +249,17 @@ impl WindowBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a desired initial position for the window.
|
||||
///
|
||||
/// See [`WindowAttributes::position`] for details.
|
||||
///
|
||||
/// [`WindowAttributes::position`]: crate::window::WindowAttributes::position
|
||||
#[inline]
|
||||
pub fn with_position<P: Into<Position>>(mut self, position: P) -> Self {
|
||||
self.window.position = Some(position.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether the window is resizable or not.
|
||||
///
|
||||
/// See [`Window::set_resizable`] for details.
|
||||
@@ -526,7 +563,7 @@ impl Window {
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Andraid / Web:** Unsupported.
|
||||
/// - **iOS / Android / Web:** Unsupported.
|
||||
#[inline]
|
||||
pub fn set_max_inner_size<S: Into<Size>>(&self, max_size: Option<S>) {
|
||||
self.window.set_max_inner_size(max_size.map(|s| s.into()))
|
||||
@@ -597,6 +634,17 @@ impl Window {
|
||||
self.window.set_maximized(maximized)
|
||||
}
|
||||
|
||||
/// Gets the window's current maximized state.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Wayland / X11:** Not implemented.
|
||||
/// - **iOS / Android / Web:** Unsupported.
|
||||
#[inline]
|
||||
pub fn is_maximized(&self) -> bool {
|
||||
self.window.is_maximized()
|
||||
}
|
||||
|
||||
/// Sets the window to fullscreen or back.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
@@ -753,6 +801,22 @@ impl Window {
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.window.set_cursor_visible(visible)
|
||||
}
|
||||
|
||||
/// Moves the window with the left mouse button until the button is released.
|
||||
///
|
||||
/// There's no guarantee that this will work unless the left mouse button was pressed
|
||||
/// immediately before this function is called.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Un-grabs the cursor.
|
||||
/// - **Wayland:** Requires the cursor to be inside the window to be dragged.
|
||||
/// - **macOS:** May prevent the button release event to be triggered.
|
||||
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
self.window.drag_window()
|
||||
}
|
||||
}
|
||||
|
||||
/// Monitor info functions.
|
||||
|
||||
Reference in New Issue
Block a user