mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a9c17a520 | ||
|
|
b208daa271 | ||
|
|
e85a80dd65 | ||
|
|
b1d8ce24e9 | ||
|
|
098fd5d602 | ||
|
|
2f27f64cdb | ||
|
|
cbb60d29a2 | ||
|
|
e707052f66 | ||
|
|
71bd6e73ca | ||
|
|
b8326f6452 | ||
|
|
ece2e70a53 | ||
|
|
2b14ec23d5 | ||
|
|
9999f53329 | ||
|
|
522a6e3298 | ||
|
|
76d0dd7ec3 | ||
|
|
d1073dcecb | ||
|
|
e88e8bc194 | ||
|
|
bc29931434 | ||
|
|
505f312d5f | ||
|
|
f0093d3c54 | ||
|
|
83b60beba6 | ||
|
|
5f52d7c9d0 | ||
|
|
a1b65f7080 | ||
|
|
96df858961 | ||
|
|
4eddd1e5bc | ||
|
|
28f0eb598d | ||
|
|
c1eb7f9629 | ||
|
|
2f8aa5c52a | ||
|
|
22dcc19898 | ||
|
|
e295104199 | ||
|
|
66fe69edd9 | ||
|
|
fd946feac4 | ||
|
|
8856b6ecb7 | ||
|
|
0ae78db6cb | ||
|
|
3e3bb8a8f1 | ||
|
|
e48262a797 | ||
|
|
d934f94704 | ||
|
|
1fe4a7a4ea | ||
|
|
9daa0738a9 | ||
|
|
ad7d4939a8 | ||
|
|
c4d07952cb | ||
|
|
dc302b0db4 | ||
|
|
a6d180cefb | ||
|
|
1ddceeb063 | ||
|
|
633d0deeae | ||
|
|
9e3844ddd9 | ||
|
|
4b618bd6a6 | ||
|
|
09c4ed0694 | ||
|
|
d15eb04f9e | ||
|
|
02ac7456e4 | ||
|
|
6b0875728c | ||
|
|
6a330a2894 |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
Check_Formatting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: stable
|
||||
@@ -39,7 +39,6 @@ jobs:
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||
- { target: x86_64-apple-darwin, os: macos-latest, }
|
||||
- { target: x86_64-apple-ios, os: macos-latest, }
|
||||
- { target: armv7-apple-ios, os: macos-latest, }
|
||||
- { target: aarch64-apple-ios, os: macos-latest, }
|
||||
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
|
||||
# doesn't currently work on Linux.
|
||||
@@ -55,7 +54,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
# Used to cache cargo-web
|
||||
- name: Cache cargo folder
|
||||
uses: actions/cache@v1
|
||||
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: stable
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@ Cargo.lock
|
||||
target/
|
||||
rls/
|
||||
.vscode/
|
||||
util/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
|
||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,4 +1,40 @@
|
||||
# Unreleased
|
||||
# 0.22.0 (2020-03-07)
|
||||
|
||||
- On Windows, fix minor timing issue in wait_until_time_or_msg
|
||||
- On Windows, rework handling of request_redraw() to address panics.
|
||||
- On macOS, fix `set_simple_screen` to remember frame excluding title bar.
|
||||
- On Wayland, fix coordinates in touch events when scale factor isn't 1.
|
||||
- On Wayland, fix color from `close_button_icon_color` not applying.
|
||||
- Ignore locale if unsupported by X11 backend
|
||||
- On Wayland, Add HiDPI cursor support
|
||||
- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change.
|
||||
- Fix `Event::to_static` returning `None` for user events.
|
||||
- On Wayland, Hide CSD for fullscreen windows.
|
||||
- On Windows, ignore spurious mouse move messages.
|
||||
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
|
||||
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
|
||||
- Add `BadIcon::OsError` variant for when OS icon functionality fails
|
||||
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
|
||||
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
|
||||
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
|
||||
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
|
||||
|
||||
# 0.21.0 (2020-02-04)
|
||||
|
||||
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
|
||||
- On macOS, fix set_minimized(true) works only with decorations.
|
||||
- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`.
|
||||
- On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
|
||||
- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates.
|
||||
- On macOS, fix issue where unbundled applications would sometimes open without being focused.
|
||||
- On macOS, fix `run_return` does not return unless it receives a message.
|
||||
- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop.
|
||||
- On X11, fix deadlock on window state when handling certain window events.
|
||||
- `WindowBuilder` now implements `Default`.
|
||||
- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends
|
||||
- On Wayland, fix coordinates in mouse events when scale factor isn't 1
|
||||
- On Web, add the ability to provide a custom canvas
|
||||
- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration
|
||||
|
||||
# 0.20.0 (2020-01-05)
|
||||
|
||||
@@ -20,7 +56,7 @@
|
||||
- On all platforms except mobile and WASM, implement `Window::set_minimized`.
|
||||
- On X11, fix `CursorEntered` event being generated for non-winit windows.
|
||||
- On macOS, fix crash when starting maximized without decorations.
|
||||
- On macOS, fix application not to terminate on `run_return`.
|
||||
- On macOS, fix application not terminating on `run_return`.
|
||||
- On Wayland, fix cursor icon updates on window borders when using CSD.
|
||||
- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode.
|
||||
- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark".
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.20.0"
|
||||
version = "0.22.0"
|
||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
description = "Cross-platform window creation library."
|
||||
edition = "2018"
|
||||
@@ -28,8 +28,8 @@ raw-window-handle = "0.3"
|
||||
bitflags = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
image = "0.21"
|
||||
env_logger = "0.5"
|
||||
image = "0.23"
|
||||
simple_logger = "1"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
||||
version = "0.2"
|
||||
@@ -41,7 +41,7 @@ objc = "0.2.3"
|
||||
cocoa = "0.19.1"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.3"
|
||||
dispatch = "0.1.4"
|
||||
dispatch = "0.2.0"
|
||||
objc = "0.2.6"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
||||
@@ -77,7 +77,7 @@ features = [
|
||||
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
|
||||
mio = "0.6"
|
||||
mio-extras = "2.0"
|
||||
smithay-client-toolkit = "0.6"
|
||||
smithay-client-toolkit = "^0.6.6"
|
||||
x11-dl = "2.18.3"
|
||||
percent-encoding = "2.0"
|
||||
|
||||
@@ -101,6 +101,8 @@ features = [
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MediaQueryListEvent',
|
||||
'MouseEvent',
|
||||
'Node',
|
||||
'PointerEvent',
|
||||
|
||||
@@ -149,6 +149,9 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||
* Getting the device idiom
|
||||
* Getting the preferred video mode
|
||||
|
||||
### Web
|
||||
* Get if systems preferred color scheme is "dark"
|
||||
|
||||
## Usability
|
||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.20.0"
|
||||
winit = "0.22.0"
|
||||
```
|
||||
|
||||
## [Documentation](https://docs.rs/winit)
|
||||
@@ -46,12 +46,14 @@ fn main() {
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
119
examples/control_flow.rs
Normal file
119
examples/control_flow.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use std::{thread, time};
|
||||
|
||||
use winit::{
|
||||
event::{Event, KeyboardInput, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
Wait,
|
||||
WaitUntil,
|
||||
Poll,
|
||||
}
|
||||
|
||||
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
|
||||
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
|
||||
println!("Press '1' to switch to Wait mode.");
|
||||
println!("Press '2' to switch to WaitUntil mode.");
|
||||
println!("Press '3' to switch to Poll mode.");
|
||||
println!("Press 'R' to toggle request_redraw() calls.");
|
||||
println!("Press 'Esc' to close the window.");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut mode = Mode::Wait;
|
||||
let mut request_redraw = false;
|
||||
let mut wait_cancelled = false;
|
||||
let mut close_requested = false;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::{ElementState, StartCause, VirtualKeyCode};
|
||||
println!("{:?}", event);
|
||||
match event {
|
||||
Event::NewEvents(start_cause) => {
|
||||
wait_cancelled = mode == Mode::WaitUntil;
|
||||
match start_cause {
|
||||
StartCause::ResumeTimeReached {
|
||||
start: _,
|
||||
requested_resume: _,
|
||||
} => {
|
||||
wait_cancelled = false;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
close_requested = true;
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode: Some(virtual_code),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => match virtual_code {
|
||||
VirtualKeyCode::Key1 => {
|
||||
mode = Mode::Wait;
|
||||
println!("\nmode: {:?}\n", mode);
|
||||
}
|
||||
VirtualKeyCode::Key2 => {
|
||||
mode = Mode::WaitUntil;
|
||||
println!("\nmode: {:?}\n", mode);
|
||||
}
|
||||
VirtualKeyCode::Key3 => {
|
||||
mode = Mode::Poll;
|
||||
println!("\nmode: {:?}\n", mode);
|
||||
}
|
||||
VirtualKeyCode::R => {
|
||||
request_redraw = !request_redraw;
|
||||
println!("\nrequest_redraw: {}\n", request_redraw);
|
||||
}
|
||||
VirtualKeyCode::Escape => {
|
||||
close_requested = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => {
|
||||
if request_redraw && !wait_cancelled && !close_requested {
|
||||
window.request_redraw();
|
||||
}
|
||||
if close_requested {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_window_id) => {}
|
||||
Event::RedrawEventsCleared => {
|
||||
*control_flow = match mode {
|
||||
Mode::Wait => ControlFlow::Wait,
|
||||
Mode::WaitUntil => {
|
||||
if wait_cancelled {
|
||||
*control_flow
|
||||
} else {
|
||||
ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME)
|
||||
}
|
||||
}
|
||||
Mode::Poll => {
|
||||
thread::sleep(POLL_SLEEP_TIME);
|
||||
ControlFlow::Poll
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
@@ -37,6 +38,7 @@ fn main() {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(m) => modifiers = m,
|
||||
_ => (),
|
||||
},
|
||||
Event::DeviceEvent { event, .. } => match event {
|
||||
@@ -45,7 +47,6 @@ fn main() {
|
||||
ElementState::Pressed => println!("mouse button {} pressed", button),
|
||||
ElementState::Released => println!("mouse button {} released", button),
|
||||
},
|
||||
DeviceEvent::ModifiersChanged(m) => modifiers = m,
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
|
||||
@@ -11,6 +11,7 @@ fn main() {
|
||||
Timer,
|
||||
}
|
||||
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::monitor::{MonitorHandle, VideoMode};
|
||||
use winit::window::{Fullscreen, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
|
||||
@@ -6,6 +6,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::WindowBuilder;
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
extern crate env_logger;
|
||||
|
||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||
|
||||
use winit::{
|
||||
@@ -14,7 +12,7 @@ fn main() {
|
||||
const WINDOW_COUNT: usize = 3;
|
||||
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
|
||||
|
||||
env_logger::init();
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
||||
for _ in 0..WINDOW_COUNT {
|
||||
|
||||
@@ -6,6 +6,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut windows = HashMap::new();
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
39
examples/request_redraw_threaded.rs
Normal file
39
examples/request_redraw_threaded.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::{thread, time};
|
||||
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
window.request_redraw();
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
println!("{:?}", event);
|
||||
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
},
|
||||
Event::RedrawRequested(_) => {
|
||||
println!("\nredrawing!\n");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let mut resizable = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let _window = WindowBuilder::new()
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use winit::event_loop::EventLoop;
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
let monitor = event_loop.primary_monitor();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -8,6 +8,7 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
|
||||
@@ -7,6 +7,8 @@ use winit::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
|
||||
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
|
||||
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
|
||||
// since it seems to work well enough in most cases. Be careful about going too high, or
|
||||
@@ -43,13 +45,11 @@ fn main() {
|
||||
|
||||
fn load_icon(path: &Path) -> Icon {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let image = image::open(path).expect("Failed to open icon path");
|
||||
use image::{GenericImageView, Pixel};
|
||||
let image = image::open(path)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba();
|
||||
let (width, height) = image.dimensions();
|
||||
let mut rgba = Vec::with_capacity((width * height) as usize * 4);
|
||||
for (_, _, pixel) in image.pixels() {
|
||||
rgba.extend_from_slice(&pixel.to_rgba().data);
|
||||
}
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
||||
|
||||
@@ -18,6 +18,7 @@ fn main() {
|
||||
};
|
||||
let mut event_loop = EventLoop::new();
|
||||
|
||||
simple_logger::init().unwrap();
|
||||
let _window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.build(&event_loop)
|
||||
@@ -27,6 +28,8 @@ fn main() {
|
||||
|
||||
while !quit {
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent { event, .. } = &event {
|
||||
// Print only Window events to reduce noise
|
||||
println!("{:?}", event);
|
||||
@@ -42,7 +45,7 @@ fn main() {
|
||||
Event::MainEventsCleared => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
_ => *control_flow = ControlFlow::Wait,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
71
src/dpi.rs
71
src/dpi.rs
@@ -151,8 +151,8 @@ impl Pixel for f64 {
|
||||
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
|
||||
/// otherwise, you risk panics.
|
||||
#[inline]
|
||||
pub fn validate_scale_factor(dpi_factor: f64) -> bool {
|
||||
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
|
||||
pub fn validate_scale_factor(scale_factor: f64) -> bool {
|
||||
scale_factor.is_sign_positive() && scale_factor.is_normal()
|
||||
}
|
||||
|
||||
/// A position represented in logical pixels.
|
||||
@@ -178,16 +178,16 @@ impl<P: Pixel> LogicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
|
||||
physical: T,
|
||||
dpi_factor: f64,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
physical.into().to_logical(dpi_factor)
|
||||
physical.into().to_logical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<X> {
|
||||
assert!(validate_scale_factor(dpi_factor));
|
||||
let x = self.x.into() * dpi_factor;
|
||||
let y = self.y.into() * dpi_factor;
|
||||
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let x = self.x.into() * scale_factor;
|
||||
let y = self.y.into() * scale_factor;
|
||||
PhysicalPosition::new(x, y).cast()
|
||||
}
|
||||
|
||||
@@ -243,16 +243,16 @@ impl<P: Pixel> PhysicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
|
||||
logical: T,
|
||||
dpi_factor: f64,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
logical.into().to_physical(dpi_factor)
|
||||
logical.into().to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<X> {
|
||||
assert!(validate_scale_factor(dpi_factor));
|
||||
let x = self.x.into() / dpi_factor;
|
||||
let y = self.y.into() / dpi_factor;
|
||||
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalPosition<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let x = self.x.into() / scale_factor;
|
||||
let y = self.y.into() / scale_factor;
|
||||
LogicalPosition::new(x, y).cast()
|
||||
}
|
||||
|
||||
@@ -306,15 +306,18 @@ impl<P> LogicalSize<P> {
|
||||
|
||||
impl<P: Pixel> LogicalSize<P> {
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(physical: T, dpi_factor: f64) -> Self {
|
||||
physical.into().to_logical(dpi_factor)
|
||||
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(
|
||||
physical: T,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
physical.into().to_logical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<X> {
|
||||
assert!(validate_scale_factor(dpi_factor));
|
||||
let width = self.width.into() * dpi_factor;
|
||||
let height = self.height.into() * dpi_factor;
|
||||
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalSize<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let width = self.width.into() * scale_factor;
|
||||
let height = self.height.into() * scale_factor;
|
||||
PhysicalSize::new(width, height).cast()
|
||||
}
|
||||
|
||||
@@ -368,15 +371,15 @@ impl<P> PhysicalSize<P> {
|
||||
|
||||
impl<P: Pixel> PhysicalSize<P> {
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, dpi_factor: f64) -> Self {
|
||||
logical.into().to_physical(dpi_factor)
|
||||
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self {
|
||||
logical.into().to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalSize<X> {
|
||||
assert!(validate_scale_factor(dpi_factor));
|
||||
let width = self.width.into() / dpi_factor;
|
||||
let height = self.height.into() / dpi_factor;
|
||||
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalSize<X> {
|
||||
assert!(validate_scale_factor(scale_factor));
|
||||
let width = self.width.into() / scale_factor;
|
||||
let height = self.height.into() / scale_factor;
|
||||
LogicalSize::new(width, height).cast()
|
||||
}
|
||||
|
||||
@@ -426,17 +429,17 @@ impl Size {
|
||||
size.into()
|
||||
}
|
||||
|
||||
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalSize<P> {
|
||||
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> {
|
||||
match *self {
|
||||
Size::Physical(size) => size.to_logical(dpi_factor),
|
||||
Size::Physical(size) => size.to_logical(scale_factor),
|
||||
Size::Logical(size) => size.cast(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<P> {
|
||||
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> {
|
||||
match *self {
|
||||
Size::Physical(size) => size.cast(),
|
||||
Size::Logical(size) => size.to_physical(dpi_factor),
|
||||
Size::Logical(size) => size.to_physical(scale_factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,17 +471,17 @@ impl Position {
|
||||
position.into()
|
||||
}
|
||||
|
||||
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<P> {
|
||||
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
|
||||
match *self {
|
||||
Position::Physical(position) => position.to_logical(dpi_factor),
|
||||
Position::Physical(position) => position.to_logical(scale_factor),
|
||||
Position::Logical(position) => position.cast(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<P> {
|
||||
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> {
|
||||
match *self {
|
||||
Position::Physical(position) => position.cast(),
|
||||
Position::Logical(position) => position.to_physical(dpi_factor),
|
||||
Position::Logical(position) => position.to_physical(scale_factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
src/event.rs
32
src/event.rs
@@ -139,7 +139,7 @@ impl<'a, T> Event<'a, T> {
|
||||
WindowEvent { window_id, event } => event
|
||||
.to_static()
|
||||
.map(|event| WindowEvent { window_id, event }),
|
||||
UserEvent(_) => None,
|
||||
UserEvent(event) => Some(UserEvent(event)),
|
||||
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
|
||||
NewEvents(cause) => Some(NewEvents(cause)),
|
||||
MainEventsCleared => Some(MainEventsCleared),
|
||||
@@ -185,7 +185,7 @@ pub enum WindowEvent<'a> {
|
||||
Resized(PhysicalSize<u32>),
|
||||
|
||||
/// The position of the window has changed. Contains the window's new position.
|
||||
Moved(PhysicalPosition<u32>),
|
||||
Moved(PhysicalPosition<i32>),
|
||||
|
||||
/// The window has been requested to close.
|
||||
CloseRequested,
|
||||
@@ -235,6 +235,13 @@ pub enum WindowEvent<'a> {
|
||||
is_synthetic: bool,
|
||||
},
|
||||
|
||||
/// The keyboard modifiers have changed.
|
||||
///
|
||||
/// Platform-specific behavior:
|
||||
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
|
||||
/// issue, and it should get fixed - but it's the current state of the API.
|
||||
ModifiersChanged(ModifiersState),
|
||||
|
||||
/// The cursor has moved on the window.
|
||||
CursorMoved {
|
||||
device_id: DeviceId,
|
||||
@@ -242,8 +249,8 @@ pub enum WindowEvent<'a> {
|
||||
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
|
||||
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
|
||||
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
|
||||
position: PhysicalPosition<i32>,
|
||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
||||
position: PhysicalPosition<f64>,
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
@@ -258,7 +265,7 @@ pub enum WindowEvent<'a> {
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
@@ -267,7 +274,7 @@ pub enum WindowEvent<'a> {
|
||||
device_id: DeviceId,
|
||||
state: ElementState,
|
||||
button: MouseButton,
|
||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
@@ -341,6 +348,7 @@ impl<'a> WindowEvent<'a> {
|
||||
input,
|
||||
is_synthetic,
|
||||
}),
|
||||
ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)),
|
||||
#[allow(deprecated)]
|
||||
CursorMoved {
|
||||
device_id,
|
||||
@@ -464,16 +472,6 @@ pub enum DeviceEvent {
|
||||
|
||||
Key(KeyboardInput),
|
||||
|
||||
/// The keyboard modifiers have changed.
|
||||
///
|
||||
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
|
||||
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
|
||||
///
|
||||
/// Platform-specific behavior:
|
||||
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
|
||||
/// issue, and it should get fixed - but it's the current state of the API.
|
||||
ModifiersChanged(ModifiersState),
|
||||
|
||||
Text {
|
||||
codepoint: char,
|
||||
},
|
||||
@@ -502,7 +500,7 @@ pub struct KeyboardInput {
|
||||
///
|
||||
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
|
||||
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
|
||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
||||
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
|
||||
pub modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
|
||||
@@ -215,14 +215,10 @@ impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct EventLoopClosed<T>(pub T);
|
||||
|
||||
impl<T: fmt::Debug> fmt::Display for EventLoopClosed<T> {
|
||||
impl<T> fmt::Display for EventLoopClosed<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", error::Error::description(self))
|
||||
f.write_str("Tried to wake up a closed `EventLoop`")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {
|
||||
fn description(&self) -> &str {
|
||||
"Tried to wake up a closed `EventLoop`"
|
||||
}
|
||||
}
|
||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
|
||||
|
||||
109
src/icon.rs
109
src/icon.rs
@@ -1,4 +1,5 @@
|
||||
use std::{error::Error, fmt, mem};
|
||||
use crate::platform_impl::PlatformIcon;
|
||||
use std::{error::Error, fmt, io, mem};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
@@ -11,7 +12,7 @@ pub(crate) struct Pixel {
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug)]
|
||||
/// An error produced when using `Icon::from_rgba` with invalid arguments.
|
||||
pub enum BadIcon {
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
@@ -25,72 +26,110 @@ pub enum BadIcon {
|
||||
width_x_height: usize,
|
||||
pixel_count: usize,
|
||||
},
|
||||
/// Produced when underlying OS functionality failed to create the icon
|
||||
OsError(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for BadIcon {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let msg = match self {
|
||||
&BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!(
|
||||
match self {
|
||||
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
|
||||
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
||||
byte_count,
|
||||
),
|
||||
&BadIcon::DimensionsVsPixelCount {
|
||||
BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => format!(
|
||||
} => write!(f,
|
||||
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
|
||||
width, height, pixel_count, width_x_height,
|
||||
),
|
||||
};
|
||||
write!(f, "{}", msg)
|
||||
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn description(&self) -> &str {
|
||||
"A valid icon cannot be created from these arguments"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
pub struct Icon {
|
||||
pub(crate) struct RgbaIcon {
|
||||
pub(crate) rgba: Vec<u8>,
|
||||
pub(crate) width: u32,
|
||||
pub(crate) height: u32,
|
||||
}
|
||||
|
||||
/// For platforms which don't have window icons (e.g. web)
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct NoIcon;
|
||||
|
||||
#[allow(dead_code)] // These are not used on every platform
|
||||
mod constructors {
|
||||
use super::*;
|
||||
|
||||
impl RgbaIcon {
|
||||
/// Creates an `Icon` from 32bpp RGBA data.
|
||||
///
|
||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 {
|
||||
byte_count: rgba.len(),
|
||||
});
|
||||
}
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
if pixel_count != (width * height) as usize {
|
||||
Err(BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height: (width * height) as usize,
|
||||
pixel_count,
|
||||
})
|
||||
} else {
|
||||
Ok(RgbaIcon {
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoIcon {
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
// Create the rgba icon anyway to validate the input
|
||||
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
|
||||
Ok(NoIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
#[derive(Clone)]
|
||||
pub struct Icon {
|
||||
pub(crate) inner: PlatformIcon,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Icon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fmt::Debug::fmt(&self.inner, formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Creates an `Icon` from 32bpp RGBA data.
|
||||
///
|
||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(BadIcon::ByteCountNotDivisibleBy4 {
|
||||
byte_count: rgba.len(),
|
||||
});
|
||||
}
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
if pixel_count != (width * height) as usize {
|
||||
Err(BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height: (width * height) as usize,
|
||||
pixel_count,
|
||||
})
|
||||
} else {
|
||||
Ok(Icon {
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
Ok(Icon {
|
||||
inner: PlatformIcon::from_rgba(rgba, width, height)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "serde")]
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event_loop::EventLoopWindowTarget,
|
||||
monitor::MonitorHandle,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
@@ -209,3 +210,17 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
self.inner.ns_screen().map(|s| s as *mut c_void)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to macOS.
|
||||
pub trait EventLoopWindowTargetExtMacOS {
|
||||
/// Hide the entire application. In most applications this is typically triggered with Command-H.
|
||||
fn hide_application(&self);
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||
fn hide_application(&self) {
|
||||
let cls = objc::runtime::Class::get("NSApplication").unwrap();
|
||||
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
|
||||
unsafe { msg_send![app, hide: 0] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::{os::raw, ptr, sync::Arc};
|
||||
|
||||
use smithay_client_toolkit::window::{ButtonState, Theme};
|
||||
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
|
||||
|
||||
use crate::{
|
||||
dpi::Size,
|
||||
@@ -23,74 +23,6 @@ pub use crate::platform_impl::x11;
|
||||
|
||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
||||
|
||||
/// Theme for wayland client side decorations
|
||||
///
|
||||
/// Colors must be in ARGB8888 format
|
||||
pub struct WaylandTheme {
|
||||
/// Primary color when the window is focused
|
||||
pub primary_active: [u8; 4],
|
||||
/// Primary color when the window is unfocused
|
||||
pub primary_inactive: [u8; 4],
|
||||
/// Secondary color when the window is focused
|
||||
pub secondary_active: [u8; 4],
|
||||
/// Secondary color when the window is unfocused
|
||||
pub secondary_inactive: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub close_button_hovered: [u8; 4],
|
||||
/// Close button color
|
||||
pub close_button: [u8; 4],
|
||||
/// Close button color when hovered over
|
||||
pub maximize_button_hovered: [u8; 4],
|
||||
/// Maximize button color
|
||||
pub maximize_button: [u8; 4],
|
||||
/// Minimize button color when hovered over
|
||||
pub minimize_button_hovered: [u8; 4],
|
||||
/// Minimize button color
|
||||
pub minimize_button: [u8; 4],
|
||||
}
|
||||
|
||||
struct WaylandThemeObject(WaylandTheme);
|
||||
|
||||
impl Theme for WaylandThemeObject {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.primary_active
|
||||
} else {
|
||||
self.0.primary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
// Used for division line
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
if active {
|
||||
self.0.secondary_active
|
||||
} else {
|
||||
self.0.secondary_inactive
|
||||
}
|
||||
}
|
||||
|
||||
fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.close_button_hovered,
|
||||
_ => self.0.close_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.maximize_button_hovered,
|
||||
_ => self.0.maximize_button,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] {
|
||||
match state {
|
||||
ButtonState::Hovered => self.0.minimize_button_hovered,
|
||||
_ => self.0.minimize_button,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
|
||||
pub trait EventLoopWindowTargetExtUnix {
|
||||
/// True if the `EventLoopWindowTarget` uses Wayland.
|
||||
@@ -275,7 +207,7 @@ pub trait WindowExtUnix {
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
||||
|
||||
/// Sets the color theme of the client side window decorations on wayland
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme);
|
||||
fn set_wayland_theme<T: Theme>(&self, theme: T);
|
||||
|
||||
/// Check if the window is ready for drawing
|
||||
///
|
||||
@@ -353,9 +285,9 @@ impl WindowExtUnix for Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_wayland_theme(&self, theme: WaylandTheme) {
|
||||
fn set_wayland_theme<T: Theme>(&self, theme: T) {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)),
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -461,3 +393,97 @@ impl MonitorHandleExtUnix for MonitorHandle {
|
||||
self.inner.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for implementing SCTK's theme trait.
|
||||
struct WaylandTheme<T: Theme>(T);
|
||||
|
||||
pub trait Theme: Send + 'static {
|
||||
/// Primary color of the scheme.
|
||||
fn primary_color(&self, window_active: bool) -> [u8; 4];
|
||||
|
||||
/// Secondary color of the scheme.
|
||||
fn secondary_color(&self, window_active: bool) -> [u8; 4];
|
||||
|
||||
/// Color for the close button.
|
||||
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the close button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the maximize button.
|
||||
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the maximize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the minimize button.
|
||||
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the minimize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.primary_color(active)
|
||||
}
|
||||
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.secondary_color(active)
|
||||
}
|
||||
|
||||
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.close_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.close_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.maximize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.maximize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.minimize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.minimize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ButtonState {
|
||||
/// Button is being hovered over by pointer.
|
||||
Hovered,
|
||||
/// Button is not being hovered over by pointer.
|
||||
Idle,
|
||||
/// Button is disabled.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl ButtonState {
|
||||
fn from_sctk(button_state: SCTKButtonState) -> Self {
|
||||
match button_state {
|
||||
SCTKButtonState::Hovered => Self::Hovered,
|
||||
SCTKButtonState::Idle => Self::Idle,
|
||||
SCTKButtonState::Disabled => Self::Disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
//! The web target does not automatically insert the canvas element object into the web page, to
|
||||
//! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or
|
||||
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
|
||||
//! Window.
|
||||
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
|
||||
//! your own canvas.
|
||||
|
||||
use crate::window::WindowBuilder;
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
use stdweb::web::html_element::CanvasElement;
|
||||
@@ -11,6 +14,9 @@ use stdweb::web::html_element::CanvasElement;
|
||||
#[cfg(feature = "stdweb")]
|
||||
pub trait WindowExtStdweb {
|
||||
fn canvas(&self) -> CanvasElement;
|
||||
|
||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
@@ -19,4 +25,35 @@ use web_sys::HtmlCanvasElement;
|
||||
#[cfg(feature = "web-sys")]
|
||||
pub trait WindowExtWebSys {
|
||||
fn canvas(&self) -> HtmlCanvasElement;
|
||||
|
||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
||||
fn is_dark_mode(&self) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
pub trait WindowBuilderExtStdweb {
|
||||
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdweb")]
|
||||
impl WindowBuilderExtStdweb for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
pub trait WindowBuilderExtWebSys {
|
||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-sys")]
|
||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
||||
self.platform_specific.canvas = canvas;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use std::path::Path;
|
||||
|
||||
use libc;
|
||||
use winapi::shared::minwindef::WORD;
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
use crate::{
|
||||
dpi::PhysicalSize,
|
||||
event::DeviceId,
|
||||
event_loop::EventLoop,
|
||||
monitor::MonitorHandle,
|
||||
platform_impl::EventLoop as WindowsEventLoop,
|
||||
window::{Icon, Window, WindowBuilder},
|
||||
platform_impl::{EventLoop as WindowsEventLoop, WinIcon},
|
||||
window::{BadIcon, Icon, Window, WindowBuilder},
|
||||
};
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
@@ -171,3 +174,40 @@ impl DeviceIdExtWindows for DeviceId {
|
||||
self.0.persistent_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `Icon` that are specific to Windows.
|
||||
pub trait IconExtWindows: Sized {
|
||||
/// Create an icon from a file path.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
|
||||
-> Result<Self, BadIcon>;
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
}
|
||||
|
||||
impl IconExtWindows for Icon {
|
||||
fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_path(path, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
fn from_resource(ordinal: WORD, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ use crate::{
|
||||
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
|
||||
use CreationError::OsError;
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
pub type OsError = std::io::Error;
|
||||
|
||||
pub struct EventLoop {
|
||||
@@ -61,10 +63,10 @@ impl EventLoop {
|
||||
while let Ok(event) = self.event_rx.try_recv() {
|
||||
let e = match event {
|
||||
android_glue::Event::EventMotion(motion) => {
|
||||
let dpi_factor = MonitorHandle.scale_factor();
|
||||
let scale_factor = MonitorHandle.scale_factor();
|
||||
let location = LogicalPosition::from_physical(
|
||||
(motion.x as f64, motion.y as f64),
|
||||
dpi_factor,
|
||||
scale_factor,
|
||||
);
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
@@ -102,9 +104,9 @@ impl EventLoop {
|
||||
if native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let dpi_factor = MonitorHandle.scale_factor();
|
||||
let scale_factor = MonitorHandle.scale_factor();
|
||||
let physical_size = MonitorHandle.size();
|
||||
let size = LogicalSize::from_physical(physical_size, dpi_factor);
|
||||
let size = LogicalSize::from_physical(physical_size, scale_factor);
|
||||
Some(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId),
|
||||
event: WindowEvent::Resized(size),
|
||||
@@ -319,9 +321,9 @@ impl Window {
|
||||
if self.native_window.is_null() {
|
||||
None
|
||||
} else {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let scale_factor = self.scale_factor();
|
||||
let physical_size = self.current_monitor().size();
|
||||
Some(LogicalSize::from_physical(physical_size, dpi_factor))
|
||||
Some(LogicalSize::from_physical(physical_size, scale_factor))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,10 +215,10 @@ fn setup_control_flow_observers() {
|
||||
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
||||
// priority to be 0, in order to send EventsCleared before RedrawRequested. This value was
|
||||
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
|
||||
// chosen conservatively to guard against apple using different priorities for their redraw
|
||||
// observers in different OS's or on different devices. If it so happens that it's too
|
||||
// conservative, the main symptom would be non-redraw events coming in after `EventsCleared`.
|
||||
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
|
||||
@@ -83,6 +83,8 @@ pub use self::{
|
||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId {
|
||||
uiscreen: ffi::id,
|
||||
|
||||
@@ -127,12 +127,12 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect =
|
||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
||||
let dpi_factor: CGFloat = msg_send![screen, scale];
|
||||
let scale_factor: CGFloat = msg_send![screen, scale];
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
}
|
||||
.to_physical(dpi_factor.into());
|
||||
.to_physical(scale_factor.into());
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Resized(size),
|
||||
@@ -162,15 +162,15 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
||||
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
|
||||
// content scale factor to a device-specific default value", so we can't use the
|
||||
// parameter here. We can query the actual factor using the getter
|
||||
let dpi_factor: CGFloat = msg_send![object, contentScaleFactor];
|
||||
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
|
||||
assert!(
|
||||
!dpi_factor.is_nan()
|
||||
&& dpi_factor.is_finite()
|
||||
&& dpi_factor.is_sign_positive()
|
||||
&& dpi_factor > 0.0,
|
||||
!scale_factor.is_nan()
|
||||
&& scale_factor.is_finite()
|
||||
&& scale_factor.is_sign_positive()
|
||||
&& scale_factor > 0.0,
|
||||
"invalid scale_factor set on UIView",
|
||||
);
|
||||
let scale_factor: f64 = dpi_factor.into();
|
||||
let scale_factor: f64 = scale_factor.into();
|
||||
let bounds: CGRect = msg_send![object, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
|
||||
@@ -83,8 +83,8 @@ impl Inner {
|
||||
x: safe_area.origin.x as f64,
|
||||
y: safe_area.origin.y as f64,
|
||||
};
|
||||
let dpi_factor = self.scale_factor();
|
||||
Ok(position.to_physical(dpi_factor))
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,15 +95,15 @@ impl Inner {
|
||||
x: screen_frame.origin.x as f64,
|
||||
y: screen_frame.origin.y as f64,
|
||||
};
|
||||
let dpi_factor = self.scale_factor();
|
||||
Ok(position.to_physical(dpi_factor))
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_outer_position(&self, physical_position: Position) {
|
||||
unsafe {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let position = physical_position.to_logical::<f64>(dpi_factor);
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = physical_position.to_logical::<f64>(scale_factor);
|
||||
let screen_frame = self.screen_frame();
|
||||
let new_screen_frame = CGRect {
|
||||
origin: CGPoint {
|
||||
@@ -119,25 +119,25 @@ impl Inner {
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let scale_factor = self.scale_factor();
|
||||
let safe_area = self.safe_area_screen_space();
|
||||
let size = LogicalSize {
|
||||
width: safe_area.size.width as f64,
|
||||
height: safe_area.size.height as f64,
|
||||
};
|
||||
size.to_physical(dpi_factor)
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let scale_factor = self.scale_factor();
|
||||
let screen_frame = self.screen_frame();
|
||||
let size = LogicalSize {
|
||||
width: screen_frame.size.width as f64,
|
||||
height: screen_frame.size.height as f64,
|
||||
};
|
||||
size.to_physical(dpi_factor)
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,8 +355,8 @@ impl Window {
|
||||
|
||||
let frame = match window_attributes.inner_size {
|
||||
Some(dim) => {
|
||||
let dpi_factor = msg_send![screen, scale];
|
||||
let size = dim.to_logical::<f64>(dpi_factor);
|
||||
let scale_factor = msg_send![screen, scale];
|
||||
let size = dim.to_logical::<f64>(scale_factor);
|
||||
CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
size: CGSize {
|
||||
@@ -400,8 +400,8 @@ impl Window {
|
||||
|
||||
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
|
||||
// event on window creation if the DPI factor != 1.0
|
||||
let dpi_factor: CGFloat = msg_send![view, contentScaleFactor];
|
||||
let scale_factor: f64 = dpi_factor.into();
|
||||
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
|
||||
let scale_factor: f64 = scale_factor.into();
|
||||
if scale_factor != 1.0 {
|
||||
let bounds: CGRect = msg_send![view, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
|
||||
@@ -18,6 +18,8 @@ use crate::{
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
|
||||
pub mod wayland;
|
||||
pub mod x11;
|
||||
|
||||
@@ -584,13 +586,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
}
|
||||
|
||||
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
||||
X11_BACKEND
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(Arc::clone)
|
||||
.map(x11::EventLoop::new)
|
||||
.map(EventLoop::X)
|
||||
.map_err(|err| err.clone())
|
||||
let xconn = match X11_BACKEND.lock().as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(err) => return Err(err.clone()),
|
||||
};
|
||||
|
||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -39,7 +39,10 @@ use crate::{
|
||||
window::{CursorIcon, WindowId as RootWindowId},
|
||||
};
|
||||
|
||||
use super::{window::WindowStore, DeviceId, WindowId};
|
||||
use super::{
|
||||
window::{DecorationsAction, WindowStore},
|
||||
DeviceId, WindowId,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
output::OutputMgr,
|
||||
@@ -90,6 +93,7 @@ pub struct CursorManager {
|
||||
locked_pointers: Vec<ZwpLockedPointerV1>,
|
||||
cursor_visible: bool,
|
||||
current_cursor: CursorIcon,
|
||||
scale_factor: u32,
|
||||
}
|
||||
|
||||
impl CursorManager {
|
||||
@@ -101,6 +105,7 @@ impl CursorManager {
|
||||
locked_pointers: Vec::new(),
|
||||
cursor_visible: true,
|
||||
current_cursor: CursorIcon::default(),
|
||||
scale_factor: 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +150,11 @@ impl CursorManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_scale_factor(&mut self, scale: u32) {
|
||||
self.scale_factor = scale;
|
||||
self.reload_cursor_style();
|
||||
}
|
||||
|
||||
fn set_cursor_icon_impl(&mut self, cursor: CursorIcon) {
|
||||
let cursor = match cursor {
|
||||
CursorIcon::Alias => "link",
|
||||
@@ -193,7 +203,7 @@ impl CursorManager {
|
||||
for pointer in self.pointers.iter() {
|
||||
// Ignore erros, since we don't want to fail hard in case we can't find a proper cursor
|
||||
// in a given theme.
|
||||
let _ = pointer.set_cursor(cursor, None);
|
||||
let _ = pointer.set_cursor_with_scale(cursor, self.scale_factor, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,6 +716,13 @@ impl<T> EventLoop<T> {
|
||||
crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid));
|
||||
if let Some(frame) = window.frame {
|
||||
if let Some((w, h)) = window.newsize {
|
||||
// Update decorations state
|
||||
match window.decorations_action {
|
||||
Some(DecorationsAction::Hide) => frame.set_decorate(false),
|
||||
Some(DecorationsAction::Show) => frame.set_decorate(true),
|
||||
None => (),
|
||||
}
|
||||
|
||||
// mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case
|
||||
// it overlaps mutter's `bounding box`, so we can't avoid this resize call,
|
||||
// which calls `set_geometry` under the hood, for now.
|
||||
@@ -728,6 +745,13 @@ impl<T> EventLoop<T> {
|
||||
}
|
||||
|
||||
if let Some(dpi) = window.new_dpi {
|
||||
// Update cursor scale factor
|
||||
{
|
||||
self.cursor_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update_scale_factor(dpi as u32);
|
||||
};
|
||||
let dpi = dpi as f64;
|
||||
let logical_size = LogicalSize::<f64>::from(*window.size);
|
||||
let mut new_inner_size = logical_size.to_physical(dpi);
|
||||
@@ -742,6 +766,8 @@ impl<T> EventLoop<T> {
|
||||
|
||||
let (w, h) = new_inner_size.to_logical::<u32>(dpi).into();
|
||||
frame.resize(w, h);
|
||||
// Refresh frame to rescale decorations
|
||||
frame.refresh();
|
||||
*window.size = (w, h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ use smithay_client_toolkit::{
|
||||
reexports::client::protocol::{wl_keyboard, wl_seat},
|
||||
};
|
||||
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
|
||||
};
|
||||
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
|
||||
|
||||
pub fn init_keyboard(
|
||||
seat: &wl_seat::WlSeat,
|
||||
@@ -33,9 +31,24 @@ pub fn init_keyboard(
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send_window_event(WindowEvent::Focused(true), wid);
|
||||
*target.lock().unwrap() = Some(wid);
|
||||
|
||||
let modifiers = *modifiers_tracker.lock().unwrap();
|
||||
|
||||
if !modifiers.is_empty() {
|
||||
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
|
||||
}
|
||||
}
|
||||
KbEvent::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
let modifiers = *modifiers_tracker.lock().unwrap();
|
||||
|
||||
if !modifiers.is_empty() {
|
||||
my_sink.send_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
||||
wid,
|
||||
);
|
||||
}
|
||||
|
||||
my_sink.send_window_event(WindowEvent::Focused(false), wid);
|
||||
*target.lock().unwrap() = None;
|
||||
}
|
||||
@@ -88,7 +101,9 @@ pub fn init_keyboard(
|
||||
|
||||
*modifiers_tracker.lock().unwrap() = modifiers;
|
||||
|
||||
my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId);
|
||||
if let Some(wid) = *target.lock().unwrap() {
|
||||
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
WindowEvent,
|
||||
@@ -7,10 +8,13 @@ use crate::event::{
|
||||
|
||||
use super::{
|
||||
event_loop::{CursorManager, EventsSink},
|
||||
make_wid,
|
||||
window::WindowStore,
|
||||
DeviceId,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::surface;
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_pointer::{self, Event as PtrEvent, WlPointer},
|
||||
wl_seat,
|
||||
@@ -36,6 +40,7 @@ pub fn implement_pointer(
|
||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
||||
) -> WlPointer {
|
||||
seat.get_pointer(|pointer| {
|
||||
// Currently focused winit surface
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
@@ -53,8 +58,10 @@ pub fn implement_pointer(
|
||||
..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
|
||||
if let Some(wid) = wid {
|
||||
mouse_focus = Some(wid);
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
mouse_focus = Some(surface);
|
||||
|
||||
// Reload cursor style only when we enter winit's surface. Calling
|
||||
// this function every time on `PtrEvent::Enter` could interfere with
|
||||
@@ -70,12 +77,16 @@ pub fn implement_pointer(
|
||||
},
|
||||
wid,
|
||||
);
|
||||
|
||||
let position = LogicalPosition::new(surface_x, surface_y)
|
||||
.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position: (surface_x, surface_y).into(),
|
||||
position,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
@@ -101,13 +112,19 @@ pub fn implement_pointer(
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
let position = LogicalPosition::new(surface_x, surface_y)
|
||||
.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position: (surface_x, surface_y).into(),
|
||||
position,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
@@ -115,7 +132,7 @@ pub fn implement_pointer(
|
||||
}
|
||||
}
|
||||
PtrEvent::Button { button, state, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
@@ -137,12 +154,13 @@ pub fn implement_pointer(
|
||||
button,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
make_wid(surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Axis { axis, value, .. } => {
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
if pointer.as_ref().version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
// old seat compatibility
|
||||
@@ -184,7 +202,8 @@ pub fn implement_pointer(
|
||||
PtrEvent::Frame => {
|
||||
let axis_buffer = axis_buffer.take();
|
||||
let axis_discrete_buffer = axis_discrete_buffer.take();
|
||||
if let Some(wid) = mouse_focus {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{TouchPhase, WindowEvent};
|
||||
|
||||
use super::{event_loop::EventsSink, window::WindowStore, DeviceId, WindowId};
|
||||
use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId};
|
||||
|
||||
use smithay_client_toolkit::surface;
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_seat,
|
||||
wl_surface::WlSurface,
|
||||
wl_touch::{Event as TouchEvent, WlTouch},
|
||||
};
|
||||
|
||||
// location is in logical coordinates.
|
||||
struct TouchPoint {
|
||||
wid: WindowId,
|
||||
location: (f64, f64),
|
||||
surface: WlSurface,
|
||||
position: LogicalPosition<f64>,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
@@ -31,21 +36,24 @@ pub(crate) fn implement_touch(
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
let position = LogicalPosition::new(x, y);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Started,
|
||||
location: (x, y).into(),
|
||||
location: position.to_physical(scale_factor),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
wid,
|
||||
);
|
||||
pending_ids.push(TouchPoint {
|
||||
wid,
|
||||
location: (x, y),
|
||||
surface,
|
||||
position,
|
||||
id,
|
||||
});
|
||||
}
|
||||
@@ -54,52 +62,63 @@ pub(crate) fn implement_touch(
|
||||
let idx = pending_ids.iter().position(|p| p.id == id);
|
||||
if let Some(idx) = idx {
|
||||
let pt = pending_ids.remove(idx);
|
||||
|
||||
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
|
||||
let location = pt.position.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Ended,
|
||||
location: pt.location.into(),
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
make_wid(&pt.surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let pt = pending_ids.iter_mut().find(|p| p.id == id);
|
||||
if let Some(pt) = pt {
|
||||
pt.location = (x, y);
|
||||
pt.position = LogicalPosition::new(x, y);
|
||||
|
||||
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
|
||||
let location = pt.position.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Moved,
|
||||
location: (x, y).into(),
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
make_wid(&pt.surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => {
|
||||
for pt in pending_ids.drain(..) {
|
||||
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
|
||||
let location = pt.position.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location: pt.location.into(),
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: pt.id as u64,
|
||||
}),
|
||||
pt.wid,
|
||||
make_wid(&pt.surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,13 @@ pub struct Window {
|
||||
need_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
|
||||
decorated: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DecorationsAction {
|
||||
Hide,
|
||||
Show,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -69,6 +76,9 @@ impl Window {
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
|
||||
let decorated = Arc::new(Mutex::new(attributes.decorations));
|
||||
let pending_decorations_action = Arc::new(Mutex::new(None));
|
||||
|
||||
let my_surface = surface.clone();
|
||||
let mut frame = SWindow::<ConceptFrame>::init_from_env(
|
||||
&evlp.env,
|
||||
@@ -83,7 +93,23 @@ impl Window {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.newsize = new_size;
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
|
||||
{
|
||||
// Get whether we're in fullscreen
|
||||
let mut fullscreen = window.fullscreen.lock().unwrap();
|
||||
// Fullscreen state was changed, so update decorations
|
||||
if *fullscreen != is_fullscreen {
|
||||
let decorated = { *window.decorated.lock().unwrap() };
|
||||
if decorated {
|
||||
*window.pending_decorations_action.lock().unwrap() =
|
||||
if is_fullscreen {
|
||||
Some(DecorationsAction::Hide)
|
||||
} else {
|
||||
Some(DecorationsAction::Show)
|
||||
};
|
||||
}
|
||||
}
|
||||
*fullscreen = is_fullscreen;
|
||||
}
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
@@ -174,6 +200,8 @@ impl Window {
|
||||
frame: Arc::downgrade(&frame),
|
||||
current_dpi: 1,
|
||||
new_dpi: None,
|
||||
decorated: decorated.clone(),
|
||||
pending_decorations_action: pending_decorations_action.clone(),
|
||||
});
|
||||
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
|
||||
|
||||
@@ -189,6 +217,7 @@ impl Window {
|
||||
cursor_manager,
|
||||
fullscreen,
|
||||
cursor_grab_changed,
|
||||
decorated,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,6 +306,7 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
*(self.decorated.lock().unwrap()) = decorate;
|
||||
self.frame.lock().unwrap().set_decorate(decorate);
|
||||
*(self.need_frame_refresh.lock().unwrap()) = true;
|
||||
}
|
||||
@@ -409,6 +439,8 @@ struct InternalWindow {
|
||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
||||
current_dpi: i32,
|
||||
new_dpi: Option<i32>,
|
||||
decorated: Arc<Mutex<bool>>,
|
||||
pending_decorations_action: Arc<Mutex<Option<DecorationsAction>>>,
|
||||
}
|
||||
|
||||
pub struct WindowStore {
|
||||
@@ -425,6 +457,7 @@ pub struct WindowStoreForEach<'a> {
|
||||
pub surface: &'a wl_surface::WlSurface,
|
||||
pub wid: WindowId,
|
||||
pub frame: Option<&'a mut SWindow<ConceptFrame>>,
|
||||
pub decorations_action: Option<DecorationsAction>,
|
||||
}
|
||||
|
||||
impl WindowStore {
|
||||
@@ -481,9 +514,11 @@ impl WindowStore {
|
||||
for window in &mut self.windows {
|
||||
let opt_arc = window.frame.upgrade();
|
||||
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
|
||||
let mut size = { *window.size.lock().unwrap() };
|
||||
let decorations_action = { window.pending_decorations_action.lock().unwrap().take() };
|
||||
f(WindowStoreForEach {
|
||||
newsize: window.newsize.take(),
|
||||
size: &mut *(window.size.lock().unwrap()),
|
||||
size: &mut size,
|
||||
prev_dpi: window.current_dpi,
|
||||
new_dpi: window.new_dpi,
|
||||
closed: window.closed,
|
||||
@@ -491,7 +526,9 @@ impl WindowStore {
|
||||
surface: &window.surface,
|
||||
wid: make_wid(&window.surface),
|
||||
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
|
||||
decorations_action,
|
||||
});
|
||||
*window.size.lock().unwrap() = size;
|
||||
if let Some(dpi) = window.new_dpi.take() {
|
||||
window.current_dpi = dpi;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
|
||||
|
||||
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
|
||||
|
||||
use parking_lot::MutexGuard;
|
||||
|
||||
use super::{
|
||||
events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd,
|
||||
DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
|
||||
@@ -30,6 +32,8 @@ pub(super) struct EventProcessor<T: 'static> {
|
||||
// Number of touch events currently in progress
|
||||
pub(super) num_touch: u32,
|
||||
pub(super) first_touch: Option<u64>,
|
||||
// Currently focused window belonging to this process
|
||||
pub(super) active_window: Option<ffi::Window>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventProcessor<T> {
|
||||
@@ -134,11 +138,12 @@ impl<T: 'static> EventProcessor<T> {
|
||||
if let Some(modifiers) =
|
||||
self.device_mod_state.update_state(&state, modifier)
|
||||
{
|
||||
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
|
||||
callback(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::ModifiersChanged(modifiers),
|
||||
});
|
||||
if let Some(window_id) = self.active_window {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window_id),
|
||||
event: WindowEvent::ModifiersChanged(modifiers),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,9 +389,12 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
|
||||
shared_state_lock.position = Some(outer);
|
||||
if moved {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Moved(outer.into()),
|
||||
// Temporarily unlock shared state to prevent deadlock
|
||||
MutexGuard::unlocked(&mut shared_state_lock, || {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Moved(outer.into()),
|
||||
});
|
||||
});
|
||||
}
|
||||
outer
|
||||
@@ -426,12 +434,15 @@ impl<T: 'static> EventProcessor<T> {
|
||||
let old_inner_size = PhysicalSize::new(width, height);
|
||||
let mut new_inner_size = PhysicalSize::new(new_width, new_height);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
},
|
||||
// Temporarily unlock shared state to prevent deadlock
|
||||
MutexGuard::unlocked(&mut shared_state_lock, || {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor: new_scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if new_inner_size != old_inner_size {
|
||||
@@ -461,6 +472,9 @@ impl<T: 'static> EventProcessor<T> {
|
||||
}
|
||||
|
||||
if resized {
|
||||
// Drop the shared state lock to prevent deadlock
|
||||
drop(shared_state_lock);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Resized(new_inner_size.into()),
|
||||
@@ -712,8 +726,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
|
||||
});
|
||||
if cursor_moved == Some(true) {
|
||||
let position =
|
||||
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
@@ -819,8 +832,7 @@ impl<T: 'static> EventProcessor<T> {
|
||||
event: CursorEntered { device_id },
|
||||
});
|
||||
|
||||
let position =
|
||||
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
// The mods field on this event isn't actually populated, so query the
|
||||
// pointer device. In the future, we can likely remove this round-trip by
|
||||
@@ -863,45 +875,58 @@ impl<T: 'static> EventProcessor<T> {
|
||||
ffi::XI_FocusIn => {
|
||||
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
|
||||
|
||||
let window_id = mkwid(xev.event);
|
||||
|
||||
wt.ime
|
||||
.borrow_mut()
|
||||
.focus(xev.event)
|
||||
.expect("Failed to focus input context");
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: Focused(true),
|
||||
});
|
||||
|
||||
let modifiers = ModifiersState::from_x11(&xev.mods);
|
||||
|
||||
update_modifiers!(modifiers, None);
|
||||
self.device_mod_state.update_state(&modifiers, None);
|
||||
|
||||
// The deviceid for this event is for a keyboard instead of a pointer,
|
||||
// so we have to do a little extra work.
|
||||
let pointer_id = self
|
||||
.devices
|
||||
.borrow()
|
||||
.get(&DeviceId(xev.deviceid))
|
||||
.map(|device| device.attachment)
|
||||
.unwrap_or(2);
|
||||
if self.active_window != Some(xev.event) {
|
||||
self.active_window = Some(xev.event);
|
||||
|
||||
let position =
|
||||
PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32);
|
||||
let window_id = mkwid(xev.event);
|
||||
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: CursorMoved {
|
||||
device_id: mkdid(pointer_id),
|
||||
position,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: Focused(true),
|
||||
});
|
||||
|
||||
// Issue key press events for all pressed keys
|
||||
self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback);
|
||||
if !modifiers.is_empty() {
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(modifiers),
|
||||
});
|
||||
}
|
||||
|
||||
// The deviceid for this event is for a keyboard instead of a pointer,
|
||||
// so we have to do a little extra work.
|
||||
let pointer_id = self
|
||||
.devices
|
||||
.borrow()
|
||||
.get(&DeviceId(xev.deviceid))
|
||||
.map(|device| device.attachment)
|
||||
.unwrap_or(2);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: CursorMoved {
|
||||
device_id: mkdid(pointer_id),
|
||||
position,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
|
||||
// Issue key press events for all pressed keys
|
||||
self.handle_pressed_keys(
|
||||
window_id,
|
||||
ElementState::Pressed,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
ffi::XI_FocusOut => {
|
||||
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
|
||||
@@ -913,15 +938,26 @@ impl<T: 'static> EventProcessor<T> {
|
||||
.unfocus(xev.event)
|
||||
.expect("Failed to unfocus input context");
|
||||
|
||||
let window_id = mkwid(xev.event);
|
||||
if self.active_window.take() == Some(xev.event) {
|
||||
let window_id = mkwid(xev.event);
|
||||
|
||||
// Issue key release events for all pressed keys
|
||||
self.handle_pressed_keys(window_id, ElementState::Released, &mut callback);
|
||||
// Issue key release events for all pressed keys
|
||||
self.handle_pressed_keys(
|
||||
window_id,
|
||||
ElementState::Released,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: Focused(false),
|
||||
})
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
||||
});
|
||||
|
||||
callback(Event::WindowEvent {
|
||||
window_id,
|
||||
event: Focused(false),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
|
||||
@@ -1076,10 +1112,12 @@ impl<T: 'static> EventProcessor<T> {
|
||||
let new_modifiers = self.device_mod_state.modifiers();
|
||||
|
||||
if modifiers != new_modifiers {
|
||||
callback(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::ModifiersChanged(new_modifiers),
|
||||
});
|
||||
if let Some(window_id) = self.active_window {
|
||||
callback(Event::WindowEvent {
|
||||
window_id: mkwid(window_id),
|
||||
event: WindowEvent::ModifiersChanged(new_modifiers),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<f
|
||||
// XSetLocaleModifiers returns...
|
||||
// * The current locale modifiers if it's given a NULL pointer.
|
||||
// * The new locale modifiers if we succeeded in setting them.
|
||||
// * NULL if the locale modifiers string is malformed.
|
||||
// * NULL if the locale modifiers string is malformed or if the
|
||||
// current locale is not supported by Xlib.
|
||||
(xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr());
|
||||
|
||||
let im = (xconn.xlib.XOpenIM)(
|
||||
|
||||
@@ -29,6 +29,7 @@ use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::Deref,
|
||||
os::raw::*,
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice,
|
||||
sync::{mpsc, Arc, Mutex, Weak},
|
||||
@@ -105,7 +106,25 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Input methods will open successfully without setting the locale, but it won't be
|
||||
// possible to actually commit pre-edit sequences.
|
||||
unsafe {
|
||||
// Remember default locale to restore it if target locale is unsupported
|
||||
// by Xlib
|
||||
let default_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
|
||||
|
||||
// Check if set locale is supported by Xlib.
|
||||
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
|
||||
// will fail.
|
||||
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
|
||||
if !locale_supported {
|
||||
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
warn!(
|
||||
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
|
||||
CStr::from_ptr(unsupported_locale).to_string_lossy(),
|
||||
CStr::from_ptr(default_locale).to_string_lossy()
|
||||
);
|
||||
// Restore default locale
|
||||
setlocale(LC_CTYPE, default_locale);
|
||||
}
|
||||
}
|
||||
let ime = RefCell::new({
|
||||
let result = Ime::new(Arc::clone(&xconn));
|
||||
@@ -206,6 +225,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
device_mod_state: Default::default(),
|
||||
num_touch: 0,
|
||||
first_touch: None,
|
||||
active_window: None,
|
||||
};
|
||||
|
||||
// Register for device hotplug events
|
||||
@@ -395,7 +415,6 @@ impl<T: 'static> EventLoop<T> {
|
||||
let mut xev = MaybeUninit::uninit();
|
||||
|
||||
let wt = get_xtarget(&self.target);
|
||||
let mut pending_redraws = wt.pending_redraws.lock().unwrap();
|
||||
|
||||
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
||||
let mut xev = unsafe { xev.assume_init() };
|
||||
@@ -409,7 +428,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
super::WindowId::X(wid),
|
||||
)) = event
|
||||
{
|
||||
pending_redraws.insert(wid);
|
||||
wt.pending_redraws.lock().unwrap().insert(wid);
|
||||
} else {
|
||||
callback(event, window_target, control_flow);
|
||||
}
|
||||
@@ -490,21 +509,9 @@ impl<'a> Deref for DeviceInfo<'a> {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId(ffi::Window);
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId(c_int);
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window(Arc<UnownedWindow>);
|
||||
|
||||
impl Deref for Window {
|
||||
|
||||
@@ -43,62 +43,4 @@ impl XConnection {
|
||||
};
|
||||
self.send_event(target_window, event_mask, event)
|
||||
}
|
||||
|
||||
// Prepare yourself for the ultimate in unsafety!
|
||||
// You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you
|
||||
// to send more than one message worth of data.
|
||||
pub fn send_client_msg_multi<T: Formattable>(
|
||||
&self,
|
||||
window: c_ulong, // The window this is "about"; not necessarily this window
|
||||
target_window: c_ulong, // The window we're sending to
|
||||
message_type: ffi::Atom,
|
||||
event_mask: Option<c_long>,
|
||||
data: &[T],
|
||||
) -> Flusher<'_> {
|
||||
let format = T::FORMAT;
|
||||
let size_of_t = mem::size_of::<T>();
|
||||
debug_assert_eq!(size_of_t, format.get_actual_size());
|
||||
let mut event = ffi::XClientMessageEvent {
|
||||
type_: ffi::ClientMessage,
|
||||
display: self.display,
|
||||
window,
|
||||
message_type,
|
||||
format: format as c_int,
|
||||
data: ffi::ClientMessageData::new(),
|
||||
// These fields are ignored by `XSendEvent`
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
let t_per_payload = format.get_payload_size() / size_of_t;
|
||||
assert!(t_per_payload > 0);
|
||||
let payload_count = data.len() / t_per_payload;
|
||||
let payload_remainder = data.len() % t_per_payload;
|
||||
let payload_ptr = data.as_ptr() as *const ClientMsgPayload;
|
||||
|
||||
let mut payload_index = 0;
|
||||
while payload_index < payload_count {
|
||||
let payload = unsafe { payload_ptr.offset(payload_index as isize) };
|
||||
payload_index += 1;
|
||||
event.data = unsafe { mem::transmute(*payload) };
|
||||
self.send_event(target_window, event_mask, &event).queue();
|
||||
}
|
||||
|
||||
if payload_remainder > 0 {
|
||||
let mut payload: ClientMsgPayload = [0; 5];
|
||||
let t_payload = payload.as_mut_ptr() as *mut T;
|
||||
let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) };
|
||||
let invalid_t_payload = invalid_payload as *const T;
|
||||
let mut t_index = 0;
|
||||
while t_index < payload_remainder {
|
||||
let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) };
|
||||
unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() };
|
||||
t_index += 1;
|
||||
}
|
||||
event.data = unsafe { mem::transmute(payload) };
|
||||
self.send_event(target_window, event_mask, &event).queue();
|
||||
}
|
||||
|
||||
Flusher::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ impl Format {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_same_size_as<T>(&self) -> bool {
|
||||
mem::size_of::<T>() == self.get_actual_size()
|
||||
}
|
||||
|
||||
pub fn get_actual_size(&self) -> usize {
|
||||
match self {
|
||||
&Format::Char => mem::size_of::<c_char>(),
|
||||
@@ -32,15 +28,6 @@ impl Format {
|
||||
&Format::Long => mem::size_of::<c_long>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_payload_size(&self) -> usize {
|
||||
match self {
|
||||
// Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit).
|
||||
&Format::Char => mem::size_of::<c_char>() * 20,
|
||||
&Format::Short => mem::size_of::<c_short>() * 10,
|
||||
&Format::Long => mem::size_of::<c_long>() * 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::cmp;
|
||||
|
||||
use super::*;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
// Friendly neighborhood axis-aligned rectangle
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -89,16 +88,6 @@ impl FrameExtents {
|
||||
pub fn from_border(border: c_ulong) -> Self {
|
||||
Self::new(border, border, border, border)
|
||||
}
|
||||
|
||||
pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents {
|
||||
let logicalize = |value: c_ulong| value as f64 / factor;
|
||||
LogicalFrameExtents {
|
||||
left: logicalize(self.left),
|
||||
right: logicalize(self.right),
|
||||
top: logicalize(self.top),
|
||||
bottom: logicalize(self.bottom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -135,20 +124,6 @@ impl FrameExtentsHeuristic {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_pos_to_outer_logical(
|
||||
&self,
|
||||
mut logical: LogicalPosition<f64>,
|
||||
factor: f64,
|
||||
) -> LogicalPosition<f64> {
|
||||
use self::FrameExtentsHeuristicPath::*;
|
||||
if self.heuristic_path != UnsupportedBordered {
|
||||
let frame_extents = self.frame_extents.as_logical(factor);
|
||||
logical.x -= frame_extents.left;
|
||||
logical.y -= frame_extents.top;
|
||||
}
|
||||
logical
|
||||
}
|
||||
|
||||
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
|
||||
(
|
||||
width.saturating_add(
|
||||
@@ -163,17 +138,6 @@ impl FrameExtentsHeuristic {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn inner_size_to_outer_logical(
|
||||
&self,
|
||||
mut logical: LogicalSize<f64>,
|
||||
factor: f64,
|
||||
) -> LogicalSize<f64> {
|
||||
let frame_extents = self.frame_extents.as_logical(factor);
|
||||
logical.width += frame_extents.left + frame_extents.right;
|
||||
logical.height += frame_extents.top + frame_extents.bottom;
|
||||
logical
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum StateOperation {
|
||||
Remove = 0, // _NET_WM_STATE_REMOVE
|
||||
Add = 1, // _NET_WM_STATE_ADD
|
||||
@@ -189,22 +190,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag(&self, flag: c_long) -> bool {
|
||||
has_flag(self.size_hints.flags, flag)
|
||||
}
|
||||
|
||||
fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> {
|
||||
if self.has_flag(flag) {
|
||||
Some((*field1 as _, *field2 as _))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height)
|
||||
}
|
||||
|
||||
// WARNING: This hint is obsolete
|
||||
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
|
||||
if let Some((width, height)) = size {
|
||||
@@ -216,14 +201,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_max_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(
|
||||
ffi::PMaxSize,
|
||||
&self.size_hints.max_width,
|
||||
&self.size_hints.max_height,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
|
||||
if let Some((max_width, max_height)) = max_size {
|
||||
self.size_hints.flags |= ffi::PMaxSize;
|
||||
@@ -234,14 +211,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(
|
||||
ffi::PMinSize,
|
||||
&self.size_hints.min_width,
|
||||
&self.size_hints.min_height,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
|
||||
if let Some((min_width, min_height)) = min_size {
|
||||
self.size_hints.flags |= ffi::PMinSize;
|
||||
@@ -252,14 +221,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
|
||||
self.getter(
|
||||
ffi::PResizeInc,
|
||||
&self.size_hints.width_inc,
|
||||
&self.size_hints.height_inc,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
|
||||
if let Some((width_inc, height_inc)) = resize_increments {
|
||||
self.size_hints.flags |= ffi::PResizeInc;
|
||||
@@ -270,14 +231,6 @@ impl<'a> NormalHints<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_base_size(&self) -> Option<(u32, u32)> {
|
||||
self.getter(
|
||||
ffi::PBaseSize,
|
||||
&self.size_hints.base_width,
|
||||
&self.size_hints.base_height,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
|
||||
if let Some((base_width, base_height)) = base_size {
|
||||
self.size_hints.flags |= ffi::PBaseSize;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::window::{Icon, Pixel, PIXEL_SIZE};
|
||||
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
|
||||
|
||||
impl Pixel {
|
||||
pub fn to_packed_argb(&self) -> Cardinal {
|
||||
@@ -18,13 +18,14 @@ impl Pixel {
|
||||
|
||||
impl Icon {
|
||||
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
|
||||
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
|
||||
let pixel_count = self.rgba.len() / PIXEL_SIZE;
|
||||
assert_eq!(pixel_count, (self.width * self.height) as usize);
|
||||
let rgba_icon = &self.inner;
|
||||
assert_eq!(rgba_icon.rgba.len() % PIXEL_SIZE, 0);
|
||||
let pixel_count = rgba_icon.rgba.len() / PIXEL_SIZE;
|
||||
assert_eq!(pixel_count, (rgba_icon.width * rgba_icon.height) as usize);
|
||||
let mut data = Vec::with_capacity(pixel_count);
|
||||
data.push(self.width as Cardinal);
|
||||
data.push(self.height as Cardinal);
|
||||
let pixels = self.rgba.as_ptr() as *const Pixel;
|
||||
data.push(rgba_icon.width as Cardinal);
|
||||
data.push(rgba_icon.height as Cardinal);
|
||||
let pixels = rgba_icon.rgba.as_ptr() as *const Pixel;
|
||||
for pixel_index in 0..pixel_count {
|
||||
let pixel = unsafe { &*pixels.offset(pixel_index as isize) };
|
||||
data.push(pixel.to_packed_argb());
|
||||
|
||||
@@ -23,18 +23,12 @@ pub use self::{
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::BitAnd,
|
||||
os::raw::*,
|
||||
ptr,
|
||||
};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B {
|
||||
let b_ptr = a as *const _ as *const B;
|
||||
unsafe { &*b_ptr }
|
||||
}
|
||||
|
||||
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
let wrapped = Some(value);
|
||||
if *field != wrapped {
|
||||
@@ -45,13 +39,6 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
|
||||
pub struct Flusher<'a> {
|
||||
xconn: &'a XConnection,
|
||||
|
||||
@@ -26,6 +26,7 @@ impl GetPropertyError {
|
||||
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum PropMode {
|
||||
Replace = ffi::PropModeReplace as isize,
|
||||
Prepend = ffi::PropModePrepend as isize,
|
||||
|
||||
@@ -136,23 +136,23 @@ impl UnownedWindow {
|
||||
})
|
||||
.unwrap_or_else(|| monitors.swap_remove(0))
|
||||
};
|
||||
let dpi_factor = guessed_monitor.scale_factor();
|
||||
let scale_factor = guessed_monitor.scale_factor();
|
||||
|
||||
info!("Guessed window scale factor: {}", dpi_factor);
|
||||
info!("Guessed window scale factor: {}", scale_factor);
|
||||
|
||||
let max_inner_size: Option<(u32, u32)> = window_attrs
|
||||
.max_inner_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor).into());
|
||||
.map(|size| size.to_physical::<u32>(scale_factor).into());
|
||||
let min_inner_size: Option<(u32, u32)> = window_attrs
|
||||
.min_inner_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor).into());
|
||||
.map(|size| size.to_physical::<u32>(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
|
||||
let mut dimensions: (u32, u32) = window_attrs
|
||||
.inner_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor))
|
||||
.map(|size| size.to_physical::<u32>(scale_factor))
|
||||
.or_else(|| Some((800, 600).into()))
|
||||
.map(Into::into)
|
||||
.unwrap();
|
||||
@@ -324,10 +324,10 @@ impl UnownedWindow {
|
||||
{
|
||||
let mut min_inner_size = window_attrs
|
||||
.min_inner_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor));
|
||||
.map(|size| size.to_physical::<u32>(scale_factor));
|
||||
let mut max_inner_size = window_attrs
|
||||
.max_inner_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor));
|
||||
.map(|size| size.to_physical::<u32>(scale_factor));
|
||||
|
||||
if !window_attrs.resizable {
|
||||
if util::wm_name_is_one_of(&["Xfwm4"]) {
|
||||
@@ -351,12 +351,12 @@ impl UnownedWindow {
|
||||
normal_hints.set_resize_increments(
|
||||
pl_attribs
|
||||
.resize_increments
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
|
||||
.map(|size| size.to_physical::<u32>(scale_factor).into()),
|
||||
);
|
||||
normal_hints.set_base_size(
|
||||
pl_attribs
|
||||
.base_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor).into()),
|
||||
.map(|size| size.to_physical::<u32>(scale_factor).into()),
|
||||
);
|
||||
xconn.set_normal_hints(window.xwindow, normal_hints).queue();
|
||||
}
|
||||
@@ -1053,8 +1053,8 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let (width, height) = size.to_physical::<u32>(dpi_factor).into();
|
||||
let scale_factor = self.scale_factor();
|
||||
let (width, height) = size.to_physical::<u32>(scale_factor).into();
|
||||
self.set_inner_size_physical(width, height);
|
||||
}
|
||||
|
||||
@@ -1097,16 +1097,16 @@ impl UnownedWindow {
|
||||
|
||||
pub(crate) fn adjust_for_dpi(
|
||||
&self,
|
||||
old_dpi_factor: f64,
|
||||
new_dpi_factor: f64,
|
||||
old_scale_factor: f64,
|
||||
new_scale_factor: f64,
|
||||
width: u32,
|
||||
height: u32,
|
||||
shared_state: &SharedState,
|
||||
) -> (u32, u32) {
|
||||
let scale_factor = new_dpi_factor / old_dpi_factor;
|
||||
let scale_factor = new_scale_factor / old_scale_factor;
|
||||
self.update_normal_hints(|normal_hints| {
|
||||
let dpi_adjuster =
|
||||
|size: Size| -> (u32, u32) { size.to_physical::<u32>(new_dpi_factor).into() };
|
||||
|size: Size| -> (u32, u32) { size.to_physical::<u32>(new_scale_factor).into() };
|
||||
let max_size = shared_state.max_inner_size.map(&dpi_adjuster);
|
||||
let min_size = shared_state.min_inner_size.map(&dpi_adjuster);
|
||||
let resize_increments = shared_state.resize_increments.map(&dpi_adjuster);
|
||||
@@ -1146,12 +1146,12 @@ impl UnownedWindow {
|
||||
|
||||
self.set_maximizable_inner(resizable).queue();
|
||||
|
||||
let dpi_factor = self.scale_factor();
|
||||
let scale_factor = self.scale_factor();
|
||||
let min_inner_size = min_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor))
|
||||
.map(|size| size.to_physical::<u32>(scale_factor))
|
||||
.map(Into::into);
|
||||
let max_inner_size = max_size
|
||||
.map(|size| size.to_physical::<u32>(dpi_factor))
|
||||
.map(|size| size.to_physical::<u32>(scale_factor))
|
||||
.map(Into::into);
|
||||
self.update_normal_hints(|normal_hints| {
|
||||
normal_hints.set_min_size(min_inner_size);
|
||||
|
||||
@@ -111,12 +111,7 @@ pub struct XError {
|
||||
pub minor_code: u8,
|
||||
}
|
||||
|
||||
impl Error for XError {
|
||||
#[inline]
|
||||
fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
}
|
||||
impl Error for XError {}
|
||||
|
||||
impl fmt::Display for XError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
@@ -144,17 +139,18 @@ impl From<ffi::OpenError> for XNotSupported {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for XNotSupported {
|
||||
#[inline]
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
impl XNotSupported {
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
|
||||
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for XNotSupported {
|
||||
#[inline]
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match *self {
|
||||
XNotSupported::LibraryOpenError(ref err) => Some(err),
|
||||
_ => None,
|
||||
|
||||
208
src/platform_impl/macos/activation_hack.rs
Normal file
208
src/platform_impl/macos/activation_hack.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
// Normally when you run or distribute a macOS app, it's bundled: it's in one
|
||||
// of those fun little folders that you have to right click "Show Package
|
||||
// Contents" on, and usually contains myriad delights including, but not
|
||||
// limited to, plists, icons, and of course, your beloved executable. However,
|
||||
// when you use `cargo run`, your app is unbundled - it's just a lonely, bare
|
||||
// executable.
|
||||
//
|
||||
// Apple isn't especially fond of unbundled apps, which is to say, they seem to
|
||||
// barely be supported. If you move the mouse while opening a winit window from
|
||||
// an unbundled app, the window will fail to activate and be in a grayed-out
|
||||
// uninteractable state. Switching to another app and back is the only way to
|
||||
// get the winit window into a normal state. None of this happens if the app is
|
||||
// bundled, i.e. when running via Xcode.
|
||||
//
|
||||
// To workaround this, we just switch focus to the Dock and then switch back to
|
||||
// our app. We only do this for unbundled apps, and only when they fail to
|
||||
// become active on their own.
|
||||
//
|
||||
// This solution was derived from this Godot PR:
|
||||
// https://github.com/godotengine/godot/pull/17187
|
||||
// (which appears to be based on https://stackoverflow.com/a/7602677)
|
||||
// The curious specialness of mouse motions is touched upon here:
|
||||
// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512
|
||||
//
|
||||
// We omit the 2nd step of the solution used in Godot, since it appears to have
|
||||
// no effect - I speculate that it's just technical debt picked up from the SO
|
||||
// answer; the API used is fairly exotic, and was historically used for very
|
||||
// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e.
|
||||
// in previous versions of SDL:
|
||||
// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322
|
||||
//
|
||||
// The `performSelector` delays in the Godot solution are used for sequencing,
|
||||
// since refocusing the app will fail if the call is made before it finishes
|
||||
// unfocusing. The delays used there are much smaller than the ones in the
|
||||
// original SO answer, presumably because they found the fastest delay that
|
||||
// works reliably through trial and error. Instead of using delays, we just
|
||||
// handle `applicationDidResignActive`; despite the app not activating reliably,
|
||||
// that still triggers when we switch focus to the Dock.
|
||||
//
|
||||
// The Godot solution doesn't appear to skip the hack when an unbundled app
|
||||
// activates normally. Checking for this is difficult, since if you call
|
||||
// `isActive` too early, it will always be `NO`. Even though we receive
|
||||
// `applicationDidResignActive` when switching focus to the Dock, we never
|
||||
// receive a preceding `applicationDidBecomeActive` if the app fails to
|
||||
// activate normally. I wasn't able to find a proper point in time to perform
|
||||
// the `isActive` check, so we instead check for the cause of the quirk: if
|
||||
// any mouse motion occurs prior to us receiving `applicationDidResignActive`,
|
||||
// we assume the app failed to become active.
|
||||
//
|
||||
// Fun fact: this issue is still present in GLFW
|
||||
// (https://github.com/glfw/glfw/issues/1515)
|
||||
//
|
||||
// A similar issue was found in SDL, but the resolution doesn't seem to work
|
||||
// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051
|
||||
|
||||
use super::util;
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSApplicationActivateIgnoringOtherApps},
|
||||
base::id,
|
||||
foundation::NSUInteger,
|
||||
};
|
||||
use objc::runtime::{Object, Sel, BOOL, NO, YES};
|
||||
use std::{
|
||||
os::raw::c_void,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
// Indicates that the hack has either completed or been skipped.
|
||||
activated: AtomicBool,
|
||||
// Indicates that the mouse has moved at some point in time.
|
||||
mouse_moved: AtomicBool,
|
||||
// Indicates that the hack is in progress, and that we should refocus when
|
||||
// the app resigns active.
|
||||
needs_refocus: AtomicBool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn name() -> &'static str {
|
||||
"activationHackState"
|
||||
}
|
||||
|
||||
pub fn new() -> *mut c_void {
|
||||
let this = Box::new(Self::default());
|
||||
Box::into_raw(this) as *mut c_void
|
||||
}
|
||||
|
||||
pub unsafe fn free(this: *mut Self) {
|
||||
Box::from_raw(this);
|
||||
}
|
||||
|
||||
pub unsafe fn get_ptr(obj: &Object) -> *mut Self {
|
||||
let this: *mut c_void = *(*obj).get_ivar(Self::name());
|
||||
assert!(!this.is_null(), "`activationHackState` pointer was null");
|
||||
this as *mut Self
|
||||
}
|
||||
|
||||
pub unsafe fn set_activated(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).activated.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
unsafe fn get_activated(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).activated.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub unsafe fn set_mouse_moved(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).mouse_moved.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
pub unsafe fn get_mouse_moved(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).mouse_moved.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub unsafe fn set_needs_refocus(obj: &Object, value: bool) {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).needs_refocus.store(value, Ordering::Release);
|
||||
}
|
||||
|
||||
unsafe fn get_needs_refocus(obj: &Object) -> bool {
|
||||
let this = Self::get_ptr(obj);
|
||||
(*this).needs_refocus.load(Ordering::Acquire)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the entry point for the hack - if the app is unbundled and a mouse
|
||||
// movement occurs before the app activates, it will trigger the hack. Because
|
||||
// mouse movements prior to activation are the cause of this quirk, they should
|
||||
// be a reliable way to determine if the hack needs to be performed.
|
||||
pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `activationHackMouseMoved`");
|
||||
unsafe {
|
||||
if !State::get_activated(this) {
|
||||
// We check if `CFBundleName` is undefined to determine if the
|
||||
// app is unbundled.
|
||||
if let None = util::app_name() {
|
||||
info!("App detected as unbundled");
|
||||
unfocus(this);
|
||||
} else {
|
||||
info!("App detected as bundled");
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("Completed `activationHackMouseMoved`");
|
||||
}
|
||||
|
||||
// Switch focus to the dock.
|
||||
unsafe fn unfocus(this: &Object) {
|
||||
// We only perform the hack if the app failed to activate, since otherwise,
|
||||
// there'd be a gross (but fast) flicker as it unfocused and then refocused.
|
||||
// However, we only enter this function if we detect mouse movement prior
|
||||
// to activation, so this should always be `NO`.
|
||||
//
|
||||
// Note that this check isn't necessarily reliable in detecting a violation
|
||||
// of the invariant above, since it's not guaranteed that activation will
|
||||
// resolve before this point. In other words, it can spuriously return `NO`.
|
||||
// This is also why the mouse motion approach was chosen, since it's not
|
||||
// obvious how to sequence this check - if someone knows how to, then that
|
||||
// would almost surely be a cleaner approach.
|
||||
let active: BOOL = msg_send![NSApp(), isActive];
|
||||
if active == YES {
|
||||
error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!");
|
||||
} else {
|
||||
info!("Performing unbundled app activation hack");
|
||||
let dock_bundle_id = util::ns_string_id_ref("com.apple.dock");
|
||||
let dock_array: id = msg_send![
|
||||
class!(NSRunningApplication),
|
||||
runningApplicationsWithBundleIdentifier: *dock_bundle_id
|
||||
];
|
||||
let dock_array_len: NSUInteger = msg_send![dock_array, count];
|
||||
if dock_array_len == 0 {
|
||||
error!("The Dock doesn't seem to be running, so switching focus to it is impossible");
|
||||
} else {
|
||||
State::set_needs_refocus(this, true);
|
||||
let dock: id = msg_send![dock_array, objectAtIndex: 0];
|
||||
// This will trigger `applicationDidResignActive`, which will in
|
||||
// turn call `refocus`.
|
||||
let status: BOOL = msg_send![
|
||||
dock,
|
||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
||||
];
|
||||
if status == NO {
|
||||
error!("Failed to switch focus to Dock");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch focus back to our app, causing the user to rejoice!
|
||||
pub unsafe fn refocus(this: &Object) {
|
||||
if State::get_needs_refocus(this) {
|
||||
State::set_needs_refocus(this, false);
|
||||
let app: id = msg_send![class!(NSRunningApplication), currentApplication];
|
||||
// Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The
|
||||
// nuanced difference isn't clear to me, but hey, I tried.
|
||||
let success: BOOL = msg_send![
|
||||
app,
|
||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
||||
];
|
||||
if success == NO {
|
||||
error!("Failed to refocus app");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,15 @@ use std::collections::VecDeque;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{self, NSEvent},
|
||||
base::id,
|
||||
base::{id, nil},
|
||||
};
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
event::{DeviceEvent, ElementState, Event},
|
||||
platform_impl::platform::{app_state::AppState, event::EventWrapper, util, DEVICE_ID},
|
||||
};
|
||||
use super::{activation_hack, app_state::AppState, event::EventWrapper, util, DEVICE_ID};
|
||||
use crate::event::{DeviceEvent, ElementState, Event};
|
||||
|
||||
pub struct AppClass(pub *const Class);
|
||||
unsafe impl Send for AppClass {}
|
||||
@@ -51,14 +49,14 @@ extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
|
||||
let key_window: id = msg_send![this, keyWindow];
|
||||
let _: () = msg_send![key_window, sendEvent: event];
|
||||
} else {
|
||||
maybe_dispatch_device_event(event);
|
||||
maybe_dispatch_device_event(this, event);
|
||||
let superclass = util::superclass(this);
|
||||
let _: () = msg_send![super(this, superclass), sendEvent: event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn maybe_dispatch_device_event(event: id) {
|
||||
unsafe fn maybe_dispatch_device_event(this: &Object, event: id) {
|
||||
let event_type = event.eventType();
|
||||
match event_type {
|
||||
appkit::NSMouseMoved
|
||||
@@ -100,6 +98,21 @@ unsafe fn maybe_dispatch_device_event(event: id) {
|
||||
}
|
||||
|
||||
AppState::queue_events(events);
|
||||
|
||||
// Notify the delegate when the first mouse move occurs. This is
|
||||
// used for the unbundled app activation hack, which needs to know
|
||||
// if any mouse motions occurred prior to the app activating.
|
||||
let delegate: id = msg_send![this, delegate];
|
||||
assert_ne!(delegate, nil);
|
||||
if !activation_hack::State::get_mouse_moved(&*delegate) {
|
||||
activation_hack::State::set_mouse_moved(&*delegate, true);
|
||||
let () = msg_send![
|
||||
delegate,
|
||||
performSelector: sel!(activationHackMouseMoved:)
|
||||
withObject: nil
|
||||
afterDelay: 0.0
|
||||
];
|
||||
}
|
||||
}
|
||||
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::{activation_hack, app_state::AppState};
|
||||
use cocoa::base::id;
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel, BOOL, YES},
|
||||
runtime::{Class, Object, Sel},
|
||||
};
|
||||
|
||||
use crate::platform_impl::platform::app_state::AppState;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
pub struct AppDelegateClass(pub *const Class);
|
||||
unsafe impl Send for AppDelegateClass {}
|
||||
@@ -15,90 +15,67 @@ lazy_static! {
|
||||
let superclass = class!(NSResponder);
|
||||
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
|
||||
|
||||
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
|
||||
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
|
||||
decl.add_method(
|
||||
sel!(applicationDidFinishLaunching:),
|
||||
did_finish_launching as extern "C" fn(&Object, Sel, id) -> BOOL,
|
||||
did_finish_launching as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidBecomeActive:),
|
||||
did_become_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillResignActive:),
|
||||
will_resign_active as extern "C" fn(&Object, Sel, id),
|
||||
sel!(applicationDidResignActive:),
|
||||
did_resign_active as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_ivar::<*mut c_void>(activation_hack::State::name());
|
||||
decl.add_method(
|
||||
sel!(applicationWillEnterForeground:),
|
||||
will_enter_foreground as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidEnterBackground:),
|
||||
did_enter_background as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationWillTerminate:),
|
||||
will_terminate as extern "C" fn(&Object, Sel, id),
|
||||
sel!(activationHackMouseMoved:),
|
||||
activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL {
|
||||
trace!("Triggered `didFinishLaunching`");
|
||||
extern "C" fn new(class: &Class, _: Sel) -> id {
|
||||
unsafe {
|
||||
let this: id = msg_send![class, alloc];
|
||||
let this: id = msg_send![this, init];
|
||||
(*this).set_ivar(
|
||||
activation_hack::State::name(),
|
||||
activation_hack::State::new(),
|
||||
);
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn dealloc(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
activation_hack::State::free(activation_hack::State::get_ptr(this));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidFinishLaunching`");
|
||||
AppState::launched();
|
||||
trace!("Completed `didFinishLaunching`");
|
||||
YES
|
||||
trace!("Completed `applicationDidFinishLaunching`");
|
||||
}
|
||||
|
||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `didBecomeActive`");
|
||||
/*unsafe {
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Resumed)
|
||||
}*/
|
||||
trace!("Completed `didBecomeActive`");
|
||||
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidBecomeActive`");
|
||||
unsafe {
|
||||
activation_hack::State::set_activated(this, true);
|
||||
}
|
||||
trace!("Completed `applicationDidBecomeActive`");
|
||||
}
|
||||
|
||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `willResignActive`");
|
||||
/*unsafe {
|
||||
HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended)
|
||||
}*/
|
||||
trace!("Completed `willResignActive`");
|
||||
}
|
||||
|
||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `willEnterForeground`");
|
||||
trace!("Completed `willEnterForeground`");
|
||||
}
|
||||
|
||||
extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `didEnterBackground`");
|
||||
trace!("Completed `didEnterBackground`");
|
||||
}
|
||||
|
||||
extern "C" fn will_terminate(_: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `willTerminate`");
|
||||
/*unsafe {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
let windows: id = msg_send![app, windows];
|
||||
let windows_enum: id = msg_send![windows, objectEnumerator];
|
||||
let mut events = Vec::new();
|
||||
loop {
|
||||
let window: id = msg_send![windows_enum, nextObject];
|
||||
if window == nil {
|
||||
break
|
||||
}
|
||||
let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)];
|
||||
if is_winit_window == YES {
|
||||
events.push(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
event: WindowEvent::Destroyed,
|
||||
});
|
||||
}
|
||||
}
|
||||
HANDLER.lock().unwrap().handle_nonuser_events(events);
|
||||
HANDLER.lock().unwrap().terminated();
|
||||
}*/
|
||||
trace!("Completed `willTerminate`");
|
||||
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `applicationDidResignActive`");
|
||||
unsafe {
|
||||
activation_hack::refocus(this);
|
||||
}
|
||||
trace!("Completed `applicationDidResignActive`");
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ use std::{
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSWindow},
|
||||
base::nil,
|
||||
foundation::{NSAutoreleasePool, NSSize, NSString},
|
||||
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSPoint, NSSize},
|
||||
};
|
||||
|
||||
use objc::runtime::YES;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
@@ -29,7 +31,6 @@ use crate::{
|
||||
},
|
||||
window::WindowId,
|
||||
};
|
||||
use objc::runtime::Object;
|
||||
|
||||
lazy_static! {
|
||||
static ref HANDLER: Handler = Default::default();
|
||||
@@ -339,21 +340,23 @@ impl AppState {
|
||||
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
|
||||
let windows: *const Object = msg_send![NSApp(), windows];
|
||||
let window: *const Object = msg_send![windows, objectAtIndex:0];
|
||||
let windows: id = msg_send![NSApp(), windows];
|
||||
let window: id = msg_send![windows, objectAtIndex:0];
|
||||
assert_ne!(window, nil);
|
||||
|
||||
let title: *const Object = msg_send![window, title];
|
||||
assert_ne!(title, nil);
|
||||
let postfix = NSString::alloc(nil).init_str("*");
|
||||
let some_unique_title: *const Object =
|
||||
msg_send![title, stringByAppendingString: postfix];
|
||||
assert_ne!(some_unique_title, nil);
|
||||
|
||||
// To stop event loop immediately, we need to send some UI event here.
|
||||
let _: () = msg_send![window, setTitle: some_unique_title];
|
||||
// And restore it.
|
||||
let _: () = msg_send![window, setTitle: title];
|
||||
let dummy_event: id = msg_send![class!(NSEvent),
|
||||
otherEventWithType: NSApplicationDefined
|
||||
location: NSPoint::new(0.0, 0.0)
|
||||
modifierFlags: 0
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
context: nil
|
||||
subtype: 0
|
||||
data1: 0
|
||||
data2: 0
|
||||
];
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
|
||||
|
||||
pool.drain();
|
||||
};
|
||||
|
||||
@@ -287,6 +287,7 @@ pub unsafe fn modifier_event(
|
||||
|
||||
let scancode = get_scancode(ns_event);
|
||||
let virtual_keycode = scancode_to_keycode(scancode);
|
||||
#[allow(deprecated)]
|
||||
Some(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
input: KeyboardInput {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![cfg(target_os = "macos")]
|
||||
|
||||
mod activation_hack;
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
@@ -24,6 +25,8 @@ use crate::{
|
||||
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
|
||||
};
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::{collections::VecDeque, fmt};
|
||||
|
||||
use super::ffi;
|
||||
use super::{ffi, util};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
platform_impl::platform::util::IdRef,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
foundation::{NSString, NSUInteger},
|
||||
foundation::NSUInteger,
|
||||
};
|
||||
use core_foundation::{
|
||||
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
|
||||
@@ -303,7 +302,7 @@ impl MonitorHandle {
|
||||
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
|
||||
let screens = NSScreen::screens(nil);
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
let key = util::ns_string_id_ref("NSScreenNumber");
|
||||
for i in 0..count {
|
||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
|
||||
@@ -122,7 +122,7 @@ extern "C" fn control_flow_begin_handler(
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
// without that, LoopDestroyed would get sent after EventsCleared
|
||||
// without that, LoopDestroyed would get sent after MainEventsCleared
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
activity: CFRunLoopActivity,
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
use std::{
|
||||
os::raw::c_void,
|
||||
ops::Deref,
|
||||
sync::{Mutex, Weak},
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSPoint, NSSize, NSString},
|
||||
foundation::{NSPoint, NSSize, NSString},
|
||||
};
|
||||
use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f};
|
||||
use dispatch::Queue;
|
||||
use objc::rc::autoreleasepool;
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
platform_impl::platform::{ffi, util::IdRef, window::SharedState},
|
||||
};
|
||||
|
||||
// Unsafe wrapper type that allows us to dispatch things that aren't Send.
|
||||
// This should *only* be used to dispatch to the main queue.
|
||||
// While it is indeed not guaranteed that these types can safely be sent to
|
||||
// other threads, we know that they're safe to use on the main thread.
|
||||
struct MainThreadSafe<T>(T);
|
||||
|
||||
unsafe impl<T> Send for MainThreadSafe<T> {}
|
||||
|
||||
impl<T> Deref for MainThreadSafe<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
ns_window.setStyleMask_(mask);
|
||||
// If we don't do this, key handling will break
|
||||
@@ -22,203 +38,55 @@ unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
ns_window.makeFirstResponder_(ns_view);
|
||||
}
|
||||
|
||||
struct SetStyleMaskData {
|
||||
ns_window: id,
|
||||
ns_view: id,
|
||||
mask: NSWindowStyleMask,
|
||||
}
|
||||
impl SetStyleMaskData {
|
||||
fn new_ptr(ns_window: id, ns_view: id, mask: NSWindowStyleMask) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetStyleMaskData {
|
||||
ns_window,
|
||||
ns_view,
|
||||
mask,
|
||||
}))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_style_mask_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetStyleMaskData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
set_style_mask(context.ns_window, context.ns_view, context.mask);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// Always use this function instead of trying to modify `styleMask` directly!
|
||||
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
|
||||
// Otherwise, this would vomit out errors about not being on the main thread
|
||||
// and fail to do anything.
|
||||
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_style_mask_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
Queue::main().exec_async(move || {
|
||||
set_style_mask(*ns_window, *ns_view, mask);
|
||||
});
|
||||
}
|
||||
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask);
|
||||
if msg_send![class!(NSThread), isMainThread] {
|
||||
set_style_mask_callback(context as *mut _);
|
||||
set_style_mask(ns_window, ns_view, mask);
|
||||
} else {
|
||||
dispatch_sync_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_style_mask_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
Queue::main().exec_sync(move || {
|
||||
set_style_mask(*ns_window, *ns_view, mask);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SetContentSizeData {
|
||||
ns_window: id,
|
||||
size: LogicalSize<f64>,
|
||||
}
|
||||
impl SetContentSizeData {
|
||||
fn new_ptr(ns_window: id, size: LogicalSize<f64>) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetContentSizeData { ns_window, size }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_content_size_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetContentSizeData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
NSWindow::setContentSize_(
|
||||
context.ns_window,
|
||||
NSSize::new(
|
||||
context.size.width as CGFloat,
|
||||
context.size.height as CGFloat,
|
||||
),
|
||||
);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
|
||||
// and just fails silently. Anyway, GCD to the rescue!
|
||||
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
|
||||
let context = SetContentSizeData::new_ptr(ns_window, size);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_content_size_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat));
|
||||
});
|
||||
}
|
||||
|
||||
struct SetFrameTopLeftPointData {
|
||||
ns_window: id,
|
||||
point: NSPoint,
|
||||
}
|
||||
impl SetFrameTopLeftPointData {
|
||||
fn new_ptr(ns_window: id, point: NSPoint) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetFrameTopLeftPointData { ns_window, point }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_frame_top_left_point_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetFrameTopLeftPointData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
NSWindow::setFrameTopLeftPoint_(context.ns_window, context.point);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
|
||||
// to log errors.
|
||||
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) {
|
||||
let context = SetFrameTopLeftPointData::new_ptr(ns_window, point);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_frame_top_left_point_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setFrameTopLeftPoint_(point);
|
||||
});
|
||||
}
|
||||
|
||||
struct SetLevelData {
|
||||
ns_window: id,
|
||||
level: ffi::NSWindowLevel,
|
||||
}
|
||||
impl SetLevelData {
|
||||
fn new_ptr(ns_window: id, level: ffi::NSWindowLevel) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetLevelData { ns_window, level }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_level_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetLevelData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
context.ns_window.setLevel_(context.level as _);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
|
||||
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
|
||||
let context = SetLevelData::new_ptr(ns_window, level);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_level_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setLevel_(level as _);
|
||||
});
|
||||
}
|
||||
|
||||
struct ToggleFullScreenData {
|
||||
ns_window: id,
|
||||
ns_view: id,
|
||||
not_fullscreen: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
}
|
||||
impl ToggleFullScreenData {
|
||||
fn new_ptr(
|
||||
ns_window: id,
|
||||
ns_view: id,
|
||||
not_fullscreen: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) -> *mut Self {
|
||||
Box::into_raw(Box::new(ToggleFullScreenData {
|
||||
ns_window,
|
||||
ns_view,
|
||||
not_fullscreen,
|
||||
shared_state,
|
||||
}))
|
||||
}
|
||||
}
|
||||
extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut ToggleFullScreenData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
|
||||
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
if context.not_fullscreen {
|
||||
let curr_mask = context.ns_window.styleMask();
|
||||
let required = NSWindowStyleMask::NSTitledWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask;
|
||||
if !curr_mask.contains(required) {
|
||||
set_style_mask(context.ns_window, context.ns_view, required);
|
||||
if let Some(shared_state) = context.shared_state.upgrade() {
|
||||
trace!("Locked shared state in `toggle_full_screen_callback`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
(*shared_state_lock).saved_style = Some(curr_mask);
|
||||
trace!("Unlocked shared state in `toggle_full_screen_callback`");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
context.ns_window.setLevel_(0);
|
||||
context.ns_window.toggleFullScreen_(nil);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `toggleFullScreen` is thread-safe, but our additional logic to account for
|
||||
// window styles isn't.
|
||||
pub unsafe fn toggle_full_screen_async(
|
||||
@@ -227,91 +95,42 @@ pub unsafe fn toggle_full_screen_async(
|
||||
not_fullscreen: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) {
|
||||
let context = ToggleFullScreenData::new_ptr(ns_window, ns_view, not_fullscreen, shared_state);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
toggle_full_screen_callback,
|
||||
);
|
||||
}
|
||||
|
||||
extern "C" fn restore_display_mode_callback(screen: *mut c_void) {
|
||||
unsafe {
|
||||
let screen = Box::from_raw(screen as *mut u32);
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess);
|
||||
}
|
||||
}
|
||||
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
Box::into_raw(Box::new(ns_screen)) as *mut _,
|
||||
restore_display_mode_callback,
|
||||
);
|
||||
}
|
||||
|
||||
struct SetMaximizedData {
|
||||
ns_window: id,
|
||||
is_zoomed: bool,
|
||||
maximized: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
}
|
||||
impl SetMaximizedData {
|
||||
fn new_ptr(
|
||||
ns_window: id,
|
||||
is_zoomed: bool,
|
||||
maximized: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetMaximizedData {
|
||||
ns_window,
|
||||
is_zoomed,
|
||||
maximized,
|
||||
shared_state,
|
||||
}))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_maximized_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetMaximizedData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
|
||||
if let Some(shared_state) = context.shared_state.upgrade() {
|
||||
trace!("Locked shared state in `set_maximized`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
|
||||
// Save the standard frame sized if it is not zoomed
|
||||
if !context.is_zoomed {
|
||||
shared_state_lock.standard_frame = Some(NSWindow::frame(context.ns_window));
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
let shared_state = MainThreadSafe(shared_state);
|
||||
Queue::main().exec_async(move || {
|
||||
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
if not_fullscreen {
|
||||
let curr_mask = ns_window.styleMask();
|
||||
let required =
|
||||
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
|
||||
if !curr_mask.contains(required) {
|
||||
set_style_mask(*ns_window, *ns_view, required);
|
||||
if let Some(shared_state) = shared_state.upgrade() {
|
||||
trace!("Locked shared state in `toggle_full_screen_callback`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
(*shared_state_lock).saved_style = Some(curr_mask);
|
||||
trace!("Unlocked shared state in `toggle_full_screen_callback`");
|
||||
}
|
||||
|
||||
shared_state_lock.maximized = context.maximized;
|
||||
|
||||
let curr_mask = context.ns_window.styleMask();
|
||||
if shared_state_lock.fullscreen.is_some() {
|
||||
// Handle it in window_did_exit_fullscreen
|
||||
return;
|
||||
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
|
||||
// Just use the native zoom if resizable
|
||||
context.ns_window.zoom_(nil);
|
||||
} else {
|
||||
// if it's not resizable, we set the frame directly
|
||||
let new_rect = if context.maximized {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
NSScreen::visibleFrame(screen)
|
||||
} else {
|
||||
shared_state_lock.saved_standard_frame()
|
||||
};
|
||||
context.ns_window.setFrame_display_(new_rect, 0);
|
||||
}
|
||||
|
||||
trace!("Unlocked shared state in `set_maximized`");
|
||||
}
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
ns_window.setLevel_(0);
|
||||
ns_window.toggleFullScreen_(nil);
|
||||
});
|
||||
}
|
||||
|
||||
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
||||
Queue::main().exec_async(move || {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
|
||||
});
|
||||
}
|
||||
|
||||
// `setMaximized` is not thread-safe
|
||||
pub unsafe fn set_maximized_async(
|
||||
ns_window: id,
|
||||
@@ -319,127 +138,79 @@ pub unsafe fn set_maximized_async(
|
||||
maximized: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) {
|
||||
let context = SetMaximizedData::new_ptr(ns_window, is_zoomed, maximized, shared_state);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_maximized_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let shared_state = MainThreadSafe(shared_state);
|
||||
Queue::main().exec_async(move || {
|
||||
if let Some(shared_state) = shared_state.upgrade() {
|
||||
trace!("Locked shared state in `set_maximized`");
|
||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
||||
|
||||
// Save the standard frame sized if it is not zoomed
|
||||
if !is_zoomed {
|
||||
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window));
|
||||
}
|
||||
|
||||
shared_state_lock.maximized = maximized;
|
||||
|
||||
let curr_mask = ns_window.styleMask();
|
||||
if shared_state_lock.fullscreen.is_some() {
|
||||
// Handle it in window_did_exit_fullscreen
|
||||
return;
|
||||
} else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) {
|
||||
// Just use the native zoom if resizable
|
||||
ns_window.zoom_(nil);
|
||||
} else {
|
||||
// if it's not resizable, we set the frame directly
|
||||
let new_rect = if maximized {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
NSScreen::visibleFrame(screen)
|
||||
} else {
|
||||
shared_state_lock.saved_standard_frame()
|
||||
};
|
||||
ns_window.setFrame_display_(new_rect, 0);
|
||||
}
|
||||
|
||||
trace!("Unlocked shared state in `set_maximized`");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
struct OrderOutData {
|
||||
ns_window: id,
|
||||
}
|
||||
impl OrderOutData {
|
||||
fn new_ptr(ns_window: id) -> *mut Self {
|
||||
Box::into_raw(Box::new(OrderOutData { ns_window }))
|
||||
}
|
||||
}
|
||||
extern "C" fn order_out_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut OrderOutData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
context.ns_window.orderOut_(nil);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
|
||||
// but with an odd delay.
|
||||
pub unsafe fn order_out_async(ns_window: id) {
|
||||
let context = OrderOutData::new_ptr(ns_window);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
order_out_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.orderOut_(nil);
|
||||
});
|
||||
}
|
||||
|
||||
struct MakeKeyAndOrderFrontData {
|
||||
ns_window: id,
|
||||
}
|
||||
impl MakeKeyAndOrderFrontData {
|
||||
fn new_ptr(ns_window: id) -> *mut Self {
|
||||
Box::into_raw(Box::new(MakeKeyAndOrderFrontData { ns_window }))
|
||||
}
|
||||
}
|
||||
extern "C" fn make_key_and_order_front_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut MakeKeyAndOrderFrontData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
context.ns_window.makeKeyAndOrderFront_(nil);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
|
||||
// actually works, but with an odd delay.
|
||||
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
|
||||
let context = MakeKeyAndOrderFrontData::new_ptr(ns_window);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
make_key_and_order_front_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.makeKeyAndOrderFront_(nil);
|
||||
});
|
||||
}
|
||||
|
||||
struct SetTitleData {
|
||||
ns_window: id,
|
||||
title: String,
|
||||
}
|
||||
impl SetTitleData {
|
||||
fn new_ptr(ns_window: id, title: String) -> *mut Self {
|
||||
Box::into_raw(Box::new(SetTitleData { ns_window, title }))
|
||||
}
|
||||
}
|
||||
extern "C" fn set_title_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut SetTitleData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
let title = IdRef::new(NSString::alloc(nil).init_str(&context.title));
|
||||
context.ns_window.setTitle_(*title);
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
|
||||
// window drag regions, which throws an exception when not done in the main
|
||||
// thread
|
||||
pub unsafe fn set_title_async(ns_window: id, title: String) {
|
||||
let context = SetTitleData::new_ptr(ns_window, title);
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
context as *mut _,
|
||||
set_title_callback,
|
||||
);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
|
||||
ns_window.setTitle_(*title);
|
||||
});
|
||||
}
|
||||
|
||||
struct CloseData {
|
||||
ns_window: id,
|
||||
}
|
||||
impl CloseData {
|
||||
fn new_ptr(ns_window: id) -> *mut Self {
|
||||
Box::into_raw(Box::new(CloseData { ns_window }))
|
||||
}
|
||||
}
|
||||
extern "C" fn close_callback(context: *mut c_void) {
|
||||
unsafe {
|
||||
let context_ptr = context as *mut CloseData;
|
||||
{
|
||||
let context = &*context_ptr;
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
context.ns_window.close();
|
||||
pool.drain();
|
||||
}
|
||||
Box::from_raw(context_ptr);
|
||||
}
|
||||
}
|
||||
// `close:` is thread-safe, but we want the event to be triggered from the main
|
||||
// thread. Though, it's a good idea to look into that more...
|
||||
pub unsafe fn close_async(ns_window: id) {
|
||||
let context = CloseData::new_ptr(ns_window);
|
||||
dispatch_async_f(dispatch_get_main_queue(), context as *mut _, close_callback);
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
Queue::main().exec_async(move || {
|
||||
autoreleasepool(move || {
|
||||
ns_window.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::ops::{BitAnd, Deref};
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSWindowStyleMask},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSRect, NSUInteger},
|
||||
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
|
||||
};
|
||||
use core_graphics::display::CGDisplay;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL, YES};
|
||||
@@ -91,6 +91,22 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
|
||||
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
|
||||
}
|
||||
|
||||
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
|
||||
IdRef::new(NSString::alloc(nil).init_str(s))
|
||||
}
|
||||
|
||||
pub unsafe fn app_name() -> Option<id> {
|
||||
let bundle: id = msg_send![class!(NSBundle), mainBundle];
|
||||
let dict: id = msg_send![bundle, infoDictionary];
|
||||
let key = ns_string_id_ref("CFBundleName");
|
||||
let app_name: id = msg_send![dict, objectForKey:*key];
|
||||
if app_name != nil {
|
||||
Some(app_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class {
|
||||
let superclass: id = msg_send![this, superclass];
|
||||
&*(superclass as *const _)
|
||||
|
||||
@@ -17,6 +17,7 @@ use objc::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalPosition,
|
||||
event::{
|
||||
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton,
|
||||
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
|
||||
@@ -49,16 +50,22 @@ impl Default for CursorState {
|
||||
}
|
||||
}
|
||||
|
||||
struct ViewState {
|
||||
pub(super) struct ViewState {
|
||||
ns_window: id,
|
||||
pub cursor_state: Arc<Mutex<CursorState>>,
|
||||
ime_spot: Option<(f64, f64)>,
|
||||
raw_characters: Option<String>,
|
||||
is_key_down: bool,
|
||||
modifiers: ModifiersState,
|
||||
pub(super) modifiers: ModifiersState,
|
||||
tracking_rect: Option<NSInteger>,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
fn get_scale_factor(&self) -> f64 {
|
||||
(unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
|
||||
let cursor_state = Default::default();
|
||||
let cursor_access = Arc::downgrade(&cursor_state);
|
||||
@@ -616,6 +623,19 @@ fn retrieve_keycode(event: id) -> Option<VirtualKeyCode> {
|
||||
})
|
||||
}
|
||||
|
||||
// Update `state.modifiers` if `event` has something different
|
||||
fn update_potentially_stale_modifiers(state: &mut ViewState, event: id) {
|
||||
let event_modifiers = event_mods(event);
|
||||
if state.modifiers != event_modifiers {
|
||||
state.modifiers = event_modifiers;
|
||||
|
||||
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::ModifiersChanged(state.modifiers),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
|
||||
trace!("Triggered `keyDown`");
|
||||
unsafe {
|
||||
@@ -631,6 +651,9 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) {
|
||||
|
||||
let is_repeat = msg_send![event, isARepeat];
|
||||
|
||||
update_potentially_stale_modifiers(state, event);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -683,6 +706,9 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) {
|
||||
let scancode = get_scancode(event) as u32;
|
||||
let virtual_keycode = retrieve_keycode(event);
|
||||
|
||||
update_potentially_stale_modifiers(state, event);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -746,16 +772,18 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) {
|
||||
events.push_back(window_event);
|
||||
}
|
||||
|
||||
let window_id = WindowId(get_window_id(state.ns_window));
|
||||
|
||||
for event in events {
|
||||
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
window_id,
|
||||
event,
|
||||
}));
|
||||
}
|
||||
|
||||
AppState::queue_event(EventWrapper::StaticEvent(Event::DeviceEvent {
|
||||
device_id: DEVICE_ID,
|
||||
event: DeviceEvent::ModifiersChanged(state.modifiers),
|
||||
AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(state.modifiers),
|
||||
}));
|
||||
}
|
||||
trace!("Completed `flagsChanged`");
|
||||
@@ -797,6 +825,9 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||
|
||||
let event: id = msg_send![NSApp(), currentEvent];
|
||||
|
||||
update_potentially_stale_modifiers(state, event);
|
||||
|
||||
#[allow(deprecated)]
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -821,6 +852,8 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
update_potentially_stale_modifiers(state, event);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::MouseInput {
|
||||
@@ -882,12 +915,15 @@ fn mouse_motion(this: &Object, event: id) {
|
||||
|
||||
let x = view_point.x as f64;
|
||||
let y = view_rect.size.height as f64 - view_point.y as f64;
|
||||
let logical_position = LogicalPosition::new(x, y);
|
||||
|
||||
update_potentially_stale_modifiers(state, event);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
position: logical_position.to_physical(state.get_scale_factor()),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
};
|
||||
@@ -912,7 +948,7 @@ extern "C" fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) {
|
||||
mouse_motion(this, event);
|
||||
}
|
||||
|
||||
extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
|
||||
extern "C" fn mouse_entered(this: &Object, _sel: Sel, _event: id) {
|
||||
trace!("Triggered `mouseEntered`");
|
||||
unsafe {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
@@ -925,27 +961,7 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) {
|
||||
},
|
||||
};
|
||||
|
||||
let move_event = {
|
||||
let window_point = event.locationInWindow();
|
||||
let view_point: NSPoint = msg_send![this,
|
||||
convertPoint:window_point
|
||||
fromView:nil // convert from window coordinates
|
||||
];
|
||||
let view_rect: NSRect = msg_send![this, frame];
|
||||
let x = view_point.x as f64;
|
||||
let y = (view_rect.size.height - view_point.y) as f64;
|
||||
Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position: (x, y).into(),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
AppState::queue_event(EventWrapper::StaticEvent(enter_event));
|
||||
AppState::queue_event(EventWrapper::StaticEvent(move_event));
|
||||
}
|
||||
trace!("Completed `mouseEntered`");
|
||||
}
|
||||
@@ -995,6 +1011,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) {
|
||||
let state_ptr: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state_ptr as *mut ViewState);
|
||||
|
||||
update_potentially_stale_modifiers(state, event);
|
||||
|
||||
let window_event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(state.ns_window)),
|
||||
event: WindowEvent::MouseWheel {
|
||||
|
||||
@@ -36,7 +36,7 @@ use cocoa::{
|
||||
NSWindow, NSWindowButton, NSWindowStyleMask,
|
||||
},
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize},
|
||||
};
|
||||
use core_graphics::display::{CGDisplay, CGDisplayMode};
|
||||
use objc::{
|
||||
@@ -150,10 +150,14 @@ fn create_window(
|
||||
let mut masks = if !attrs.decorations && !screen.is_some() {
|
||||
// Resizable UnownedWindow without a titlebar or borders
|
||||
// if decorations is set to false, ignore pl_attrs
|
||||
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
} else if pl_attrs.titlebar_hidden {
|
||||
// if the titlebar is hidden, ignore other pl_attrs
|
||||
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
} else {
|
||||
// default case, resizable window with titlebar and titlebar buttons
|
||||
NSWindowStyleMask::NSClosableWindowMask
|
||||
@@ -178,7 +182,7 @@ fn create_window(
|
||||
NO,
|
||||
));
|
||||
let res = ns_window.non_nil().map(|ns_window| {
|
||||
let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title));
|
||||
let title = util::ns_string_id_ref(&attrs.title);
|
||||
ns_window.setReleasedWhenClosed_(NO);
|
||||
ns_window.setTitle_(*title);
|
||||
ns_window.setAcceptsMouseMovedEvents_(YES);
|
||||
@@ -336,7 +340,7 @@ impl UnownedWindow {
|
||||
|
||||
let input_context = unsafe { util::create_input_context(*ns_view) };
|
||||
|
||||
let dpi_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
|
||||
let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 };
|
||||
|
||||
unsafe {
|
||||
if win_attribs.transparent {
|
||||
@@ -346,11 +350,11 @@ impl UnownedWindow {
|
||||
|
||||
ns_app.activateIgnoringOtherApps_(YES);
|
||||
win_attribs.min_inner_size.map(|dim| {
|
||||
let logical_dim = dim.to_logical(dpi_factor);
|
||||
let logical_dim = dim.to_logical(scale_factor);
|
||||
set_min_inner_size(*ns_window, logical_dim)
|
||||
});
|
||||
win_attribs.max_inner_size.map(|dim| {
|
||||
let logical_dim = dim.to_logical(dpi_factor);
|
||||
let logical_dim = dim.to_logical(scale_factor);
|
||||
set_max_inner_size(*ns_window, logical_dim)
|
||||
});
|
||||
|
||||
@@ -374,7 +378,7 @@ impl UnownedWindow {
|
||||
let decorations = win_attribs.decorations;
|
||||
let inner_rect = win_attribs
|
||||
.inner_size
|
||||
.map(|size| size.to_physical(dpi_factor));
|
||||
.map(|size| size.to_physical(scale_factor));
|
||||
|
||||
let window = Arc::new(UnownedWindow {
|
||||
ns_view,
|
||||
@@ -446,8 +450,8 @@ impl UnownedWindow {
|
||||
frame_rect.origin.x as f64,
|
||||
util::bottom_left_to_top_left(frame_rect),
|
||||
);
|
||||
let dpi_factor = self.scale_factor();
|
||||
Ok(position.to_physical(dpi_factor))
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
@@ -458,13 +462,13 @@ impl UnownedWindow {
|
||||
content_rect.origin.x as f64,
|
||||
util::bottom_left_to_top_left(content_rect),
|
||||
);
|
||||
let dpi_factor = self.scale_factor();
|
||||
Ok(position.to_physical(dpi_factor))
|
||||
let scale_factor = self.scale_factor();
|
||||
Ok(position.to_physical(scale_factor))
|
||||
}
|
||||
|
||||
pub fn set_outer_position(&self, position: Position) {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let position = position.to_logical(dpi_factor);
|
||||
let scale_factor = self.scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
let dummy = NSRect::new(
|
||||
NSPoint::new(
|
||||
position.x,
|
||||
@@ -484,8 +488,8 @@ impl UnownedWindow {
|
||||
let view_frame = unsafe { NSView::frame(*self.ns_view) };
|
||||
let logical: LogicalSize<f64> =
|
||||
(view_frame.size.width as f64, view_frame.size.height as f64).into();
|
||||
let dpi_factor = self.scale_factor();
|
||||
logical.to_physical(dpi_factor)
|
||||
let scale_factor = self.scale_factor();
|
||||
logical.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -493,15 +497,15 @@ impl UnownedWindow {
|
||||
let view_frame = unsafe { NSWindow::frame(*self.ns_window) };
|
||||
let logical: LogicalSize<f64> =
|
||||
(view_frame.size.width as f64, view_frame.size.height as f64).into();
|
||||
let dpi_factor = self.scale_factor();
|
||||
logical.to_physical(dpi_factor)
|
||||
let scale_factor = self.scale_factor();
|
||||
logical.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
unsafe {
|
||||
let dpi_factor = self.scale_factor();
|
||||
util::set_content_size_async(*self.ns_window, size.to_logical(dpi_factor));
|
||||
let scale_factor = self.scale_factor();
|
||||
util::set_content_size_async(*self.ns_window, size.to_logical(scale_factor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,8 +515,8 @@ impl UnownedWindow {
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
}));
|
||||
let dpi_factor = self.scale_factor();
|
||||
set_min_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
|
||||
let scale_factor = self.scale_factor();
|
||||
set_min_inner_size(*self.ns_window, dimensions.to_logical(scale_factor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,8 +526,8 @@ impl UnownedWindow {
|
||||
width: std::f32::MAX as f64,
|
||||
height: std::f32::MAX as f64,
|
||||
}));
|
||||
let dpi_factor = self.scale_factor();
|
||||
set_max_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor));
|
||||
let scale_factor = self.scale_factor();
|
||||
set_max_inner_size(*self.ns_window, dimensions.to_logical(scale_factor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,9 +594,9 @@ impl UnownedWindow {
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> {
|
||||
let physical_window_position = self.inner_position().unwrap();
|
||||
let dpi_factor = self.scale_factor();
|
||||
let window_position = physical_window_position.to_logical::<CGFloat>(dpi_factor);
|
||||
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(dpi_factor);
|
||||
let scale_factor = self.scale_factor();
|
||||
let window_position = physical_window_position.to_logical::<CGFloat>(scale_factor);
|
||||
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(scale_factor);
|
||||
let point = appkit::CGPoint {
|
||||
x: logical_cursor_position.x + window_position.x,
|
||||
y: logical_cursor_position.y + window_position.y,
|
||||
@@ -929,8 +933,8 @@ impl UnownedWindow {
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, spot: Position) {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let logical_spot = spot.to_logical(dpi_factor);
|
||||
let scale_factor = self.scale_factor();
|
||||
let logical_spot = spot.to_logical(scale_factor);
|
||||
unsafe {
|
||||
view::set_ime_position(
|
||||
*self.ns_view,
|
||||
@@ -946,7 +950,7 @@ impl UnownedWindow {
|
||||
unsafe {
|
||||
let screen: id = msg_send![*self.ns_window, screen];
|
||||
let desc = NSScreen::deviceDescription(screen);
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
let key = util::ns_string_id_ref("NSScreenNumber");
|
||||
let value = NSDictionary::valueForKey_(desc, *key);
|
||||
let display_id = msg_send![value, unsignedIntegerValue];
|
||||
RootMonitorHandle {
|
||||
@@ -1024,7 +1028,11 @@ impl WindowExtMacOS for UnownedWindow {
|
||||
|
||||
if fullscreen {
|
||||
// Remember the original window's settings
|
||||
shared_state_lock.standard_frame = Some(NSWindow::frame(*self.ns_window));
|
||||
// Exclude title bar
|
||||
shared_state_lock.standard_frame = Some(NSWindow::contentRectForFrameRect_(
|
||||
*self.ns_window,
|
||||
NSWindow::frame(*self.ns_window),
|
||||
));
|
||||
shared_state_lock.saved_style = Some(self.ns_window.styleMask());
|
||||
shared_state_lock.save_presentation_opts = Some(app.presentationOptions_());
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@ use objc::{
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event::{Event, ModifiersState, WindowEvent},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
event::{EventProxy, EventWrapper},
|
||||
util::{self, IdRef},
|
||||
view::ViewState,
|
||||
window::{get_window_id, UnownedWindow},
|
||||
},
|
||||
window::{Fullscreen, WindowId},
|
||||
@@ -43,7 +44,7 @@ pub struct WindowDelegateState {
|
||||
previous_position: Option<(f64, f64)>,
|
||||
|
||||
// Used to prevent redundant events.
|
||||
previous_dpi_factor: f64,
|
||||
previous_scale_factor: f64,
|
||||
}
|
||||
|
||||
impl WindowDelegateState {
|
||||
@@ -55,7 +56,7 @@ impl WindowDelegateState {
|
||||
window: Arc::downgrade(&window),
|
||||
initial_fullscreen,
|
||||
previous_position: None,
|
||||
previous_dpi_factor: scale_factor,
|
||||
previous_scale_factor: scale_factor,
|
||||
};
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
@@ -82,11 +83,11 @@ impl WindowDelegateState {
|
||||
|
||||
pub fn emit_static_scale_factor_changed_event(&mut self) {
|
||||
let scale_factor = self.get_scale_factor();
|
||||
if scale_factor == self.previous_dpi_factor {
|
||||
if scale_factor == self.previous_scale_factor {
|
||||
return ();
|
||||
};
|
||||
|
||||
self.previous_dpi_factor = scale_factor;
|
||||
self.previous_scale_factor = scale_factor;
|
||||
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
||||
ns_window: IdRef::retain(*self.ns_window),
|
||||
suggested_size: self.view_size(),
|
||||
@@ -319,6 +320,29 @@ extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) {
|
||||
extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) {
|
||||
trace!("Triggered `windowDidResignKey:`");
|
||||
with_state(this, |state| {
|
||||
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
||||
// NSWindowDelegate will receive a didResignKey event despite no event
|
||||
// being received when the modifiers are released. This is because
|
||||
// flagsChanged events are received by the NSView instead of the
|
||||
// NSWindowDelegate, and as a result a tracked modifiers state can quite
|
||||
// easily fall out of synchrony with reality. This requires us to emit
|
||||
// a synthetic ModifiersChanged event when we lose focus.
|
||||
//
|
||||
// Here we (very unsafely) acquire the winitState (a ViewState) from the
|
||||
// Object referenced by state.ns_view (an IdRef, which is dereferenced
|
||||
// to an id)
|
||||
let view_state: &mut ViewState = unsafe {
|
||||
let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref");
|
||||
let state_ptr: *mut c_void = *ns_view.get_ivar("winitState");
|
||||
&mut *(state_ptr as *mut ViewState)
|
||||
};
|
||||
|
||||
// Both update the state and emit a ModifiersChanged event.
|
||||
if !view_state.modifiers.is_empty() {
|
||||
view_state.modifiers = ModifiersState::empty();
|
||||
state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers));
|
||||
}
|
||||
|
||||
state.emit_event(WindowEvent::Focused(false));
|
||||
});
|
||||
trace!("Completed `windowDidResignKey:`");
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::{
|
||||
cell::RefCell,
|
||||
clone::Clone,
|
||||
collections::{HashSet, VecDeque},
|
||||
iter,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
@@ -60,7 +61,7 @@ impl<T: 'static> Shared<T> {
|
||||
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
|
||||
) {
|
||||
self.0.runner.replace(Some(Runner::new(event_handler)));
|
||||
self.send_event(Event::NewEvents(StartCause::Init));
|
||||
self.init();
|
||||
|
||||
let close_instance = self.clone();
|
||||
backend::on_unload(move || close_instance.handle_unload());
|
||||
@@ -79,55 +80,98 @@ impl<T: 'static> Shared<T> {
|
||||
self.0.redraw_pending.borrow_mut().insert(id);
|
||||
}
|
||||
|
||||
// Add an event to the event loop runner
|
||||
pub fn init(&self) {
|
||||
let start_cause = Event::NewEvents(StartCause::Init);
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Run the polling logic for the Poll ControlFlow, which involves clearing the queue
|
||||
pub fn poll(&self) {
|
||||
let start_cause = Event::NewEvents(StartCause::Poll);
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Run the logic for waking from a WaitUntil, which involves clearing the queue
|
||||
// Generally there shouldn't be events built up when this is called
|
||||
pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) {
|
||||
let start_cause = Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
start,
|
||||
requested_resume,
|
||||
});
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Add an event to the event loop runner, from the user or an event handler
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub fn send_event(&self, event: Event<'static, T>) {
|
||||
self.send_events(iter::once(event));
|
||||
}
|
||||
|
||||
// Add a series of events to the event loop runner
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub fn send_events(&self, events: impl Iterator<Item = Event<'static, T>>) {
|
||||
// If the event loop is closed, it should discard any new events
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if event handling is in process, and then release the borrow on the runner
|
||||
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
|
||||
Some(ref runner) if !runner.is_busy => {
|
||||
if let Event::NewEvents(cause) = event {
|
||||
(cause, true)
|
||||
} else {
|
||||
(
|
||||
match runner.state {
|
||||
State::Init => StartCause::Init,
|
||||
State::Poll { .. } => StartCause::Poll,
|
||||
State::Wait { start } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(end),
|
||||
},
|
||||
State::Exit => {
|
||||
return;
|
||||
}
|
||||
},
|
||||
false,
|
||||
)
|
||||
// If we can run the event processing right now, or need to queue this and wait for later
|
||||
let mut process_immediately = true;
|
||||
if let Some(ref runner) = &*self.0.runner.borrow() {
|
||||
// If we're currently polling, queue this and wait for the poll() method to be called
|
||||
if let State::Poll { .. } = runner.state {
|
||||
process_immediately = false;
|
||||
}
|
||||
// If the runner is busy, queue this and wait for it to process it later
|
||||
if runner.is_busy {
|
||||
process_immediately = false;
|
||||
}
|
||||
} else {
|
||||
// The runner still hasn't been attached: queue this event and wait for it to be
|
||||
process_immediately = false;
|
||||
}
|
||||
if !process_immediately {
|
||||
// Queue these events to look at later
|
||||
self.0.events.borrow_mut().extend(events);
|
||||
return;
|
||||
}
|
||||
// At this point, we know this is a fresh set of events
|
||||
// Now we determine why new events are incoming, and handle the events
|
||||
let start_cause = if let Some(runner) = &*self.0.runner.borrow() {
|
||||
match runner.state {
|
||||
State::Init => StartCause::Init,
|
||||
State::Poll { .. } => StartCause::Poll,
|
||||
State::Wait { start } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: None,
|
||||
},
|
||||
State::WaitUntil { start, end, .. } => StartCause::WaitCancelled {
|
||||
start,
|
||||
requested_resume: Some(end),
|
||||
},
|
||||
State::Exit => {
|
||||
// If we're in the exit state, don't do event processing
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Events are currently being handled, so queue this one and don't try to
|
||||
// double-process the event queue
|
||||
self.0.events.borrow_mut().push_back(event);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
unreachable!("The runner cannot process events when it is not attached");
|
||||
};
|
||||
// Take the start event, then the events provided to this function, and run an iteration of
|
||||
// the event loop
|
||||
let start_event = Event::NewEvents(start_cause);
|
||||
let events = iter::once(start_event).chain(events);
|
||||
self.run_until_cleared(events);
|
||||
}
|
||||
|
||||
// Given the set of new events, run the event loop until the main events and redraw events are
|
||||
// cleared
|
||||
//
|
||||
// This will also process any events that have been queued or that are queued during processing
|
||||
fn run_until_cleared(&self, events: impl Iterator<Item = Event<'static, T>>) {
|
||||
let mut control = self.current_control_flow();
|
||||
// Handle starting a new batch of events
|
||||
//
|
||||
// The user is informed via Event::NewEvents that there is a batch of events to process
|
||||
// However, there is only one of these per batch of events
|
||||
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
||||
if !event_is_start {
|
||||
for event in events {
|
||||
self.handle_event(event, &mut control);
|
||||
}
|
||||
self.handle_event(Event::MainEventsCleared, &mut control);
|
||||
@@ -196,10 +240,7 @@ impl<T: 'static> Shared<T> {
|
||||
root::ControlFlow::Poll => {
|
||||
let cloned = self.clone();
|
||||
State::Poll {
|
||||
timeout: backend::Timeout::new(
|
||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
||||
Duration::from_millis(0),
|
||||
),
|
||||
timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)),
|
||||
}
|
||||
}
|
||||
root::ControlFlow::Wait => State::Wait {
|
||||
@@ -220,7 +261,7 @@ impl<T: 'static> Shared<T> {
|
||||
start,
|
||||
end,
|
||||
timeout: backend::Timeout::new(
|
||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
||||
move || cloned.resume_time_reached(start, end),
|
||||
delay,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{backend, device, proxy::Proxy, runner, window};
|
||||
use crate::dpi::{PhysicalSize, Size};
|
||||
use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
|
||||
use crate::event_loop::ControlFlow;
|
||||
use crate::window::WindowId;
|
||||
use crate::window::{Theme, WindowId};
|
||||
use std::clone::Clone;
|
||||
|
||||
pub struct WindowTarget<T: 'static> {
|
||||
@@ -57,6 +57,7 @@ impl<T> WindowTarget<T> {
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| {
|
||||
#[allow(deprecated)]
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -74,6 +75,7 @@ impl<T> WindowTarget<T> {
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| {
|
||||
#[allow(deprecated)]
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -197,5 +199,18 @@ impl<T> WindowTarget<T> {
|
||||
});
|
||||
runner.request_redraw(WindowId(id));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_dark_mode(move |is_dark_mode| {
|
||||
let theme = if is_dark_mode {
|
||||
Theme::Dark
|
||||
} else {
|
||||
Theme::Light
|
||||
};
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: WindowId(id),
|
||||
event: WindowEvent::ThemeChanged(theme),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
// TODO: close events (port from old stdweb branch)
|
||||
// TODO: pointer locking (stdweb PR required)
|
||||
// TODO: fullscreen API (stdweb PR required)
|
||||
// Brief introduction to the internals of the web backend:
|
||||
// Currently, the web backend supports both wasm-bindgen and stdweb as methods of binding to the
|
||||
// environment. Because they are both supporting the same underlying APIs, the actual web bindings
|
||||
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
|
||||
//
|
||||
// When adding support for new events or interactions with the browser, first consult trusted
|
||||
// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers.
|
||||
// Once you have decided on the relevant web APIs, add support to both backends.
|
||||
//
|
||||
// The backend is used by the rest of the module to implement Winit's business logic, which forms
|
||||
// the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures
|
||||
// for winit's cross-platform structures. They are all relatively simple translations.
|
||||
//
|
||||
// The event_loop module handles listening for and processing events. 'Proxy' implements
|
||||
// EventLoopProxy and 'WindowTarget' implements EventLoopWindowTarget. WindowTarget also handles
|
||||
// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking
|
||||
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
|
||||
// compliant way.
|
||||
|
||||
mod device;
|
||||
mod error;
|
||||
@@ -29,3 +44,5 @@ pub use self::window::{
|
||||
Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
|
||||
Window,
|
||||
};
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
@@ -2,10 +2,11 @@ use super::event;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::error::OsError as RootOE;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::OsError;
|
||||
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use stdweb::js;
|
||||
use stdweb::traits::IPointerEvent;
|
||||
use stdweb::unstable::TryInto;
|
||||
use stdweb::web::event::{
|
||||
@@ -43,12 +44,15 @@ impl Drop for Canvas {
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let canvas: CanvasElement = document()
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.try_into()
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?;
|
||||
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
||||
let canvas = match attr.canvas {
|
||||
Some(canvas) => canvas,
|
||||
None => document()
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.try_into()
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?,
|
||||
};
|
||||
|
||||
// A tabindex is needed in order to capture local keyboard events.
|
||||
// A "0" value means that the element should be focusable in
|
||||
@@ -207,7 +211,7 @@ impl Canvas {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
// todo
|
||||
self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
|
||||
@@ -237,6 +241,22 @@ impl Canvas {
|
||||
self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler()));
|
||||
}
|
||||
|
||||
pub fn on_dark_mode<F>(&mut self, handler: F)
|
||||
where
|
||||
F: 'static + FnMut(bool),
|
||||
{
|
||||
// TODO: upstream to stdweb
|
||||
js! {
|
||||
var handler = @{handler};
|
||||
|
||||
if (window.matchMedia) {
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addListener(function(e) {
|
||||
handler(event.matches)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
|
||||
where
|
||||
E: ConcreteEvent,
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::platform::web::WindowExtStdweb;
|
||||
use crate::window::Window;
|
||||
|
||||
use stdweb::js;
|
||||
use stdweb::unstable::TryInto;
|
||||
use stdweb::web::event::BeforeUnloadEvent;
|
||||
use stdweb::web::window;
|
||||
use stdweb::web::IEventTarget;
|
||||
@@ -31,6 +32,15 @@ impl WindowExtStdweb for Window {
|
||||
fn canvas(&self) -> CanvasElement {
|
||||
self.window.canvas().raw().clone()
|
||||
}
|
||||
|
||||
fn is_dark_mode(&self) -> bool {
|
||||
// TODO: upstream to stdweb
|
||||
let is_dark_mode = js! {
|
||||
return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
};
|
||||
|
||||
is_dark_mode.try_into().expect("should return a bool")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_size() -> LogicalSize<f64> {
|
||||
@@ -72,3 +82,5 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub type RawCanvasType = CanvasElement;
|
||||
|
||||
@@ -2,13 +2,16 @@ use super::event;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
use crate::error::OsError as RootOE;
|
||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||
use crate::platform_impl::OsError;
|
||||
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
|
||||
use web_sys::{
|
||||
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, PointerEvent,
|
||||
WheelEvent,
|
||||
};
|
||||
|
||||
pub struct Canvas {
|
||||
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
||||
@@ -26,6 +29,7 @@ pub struct Canvas {
|
||||
on_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
|
||||
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
|
||||
wants_fullscreen: Rc<RefCell<bool>>,
|
||||
on_dark_mode: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
|
||||
}
|
||||
|
||||
impl Drop for Canvas {
|
||||
@@ -35,18 +39,23 @@ impl Drop for Canvas {
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
pub fn create() -> Result<Self, RootOE> {
|
||||
let window =
|
||||
web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
|
||||
pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
|
||||
let canvas = match attr.canvas {
|
||||
Some(canvas) => canvas,
|
||||
None => {
|
||||
let window = web_sys::window()
|
||||
.ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?;
|
||||
|
||||
let document = window
|
||||
.document()
|
||||
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
|
||||
let document = window
|
||||
.document()
|
||||
.ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?;
|
||||
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.unchecked_into();
|
||||
document
|
||||
.create_element("canvas")
|
||||
.map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
|
||||
.unchecked_into()
|
||||
}
|
||||
};
|
||||
|
||||
// A tabindex is needed in order to capture local keyboard events.
|
||||
// A "0" value means that the element should be focusable in
|
||||
@@ -72,6 +81,7 @@ impl Canvas {
|
||||
on_mouse_wheel: None,
|
||||
on_fullscreen_change: None,
|
||||
wants_fullscreen: Rc::new(RefCell::new(false)),
|
||||
on_dark_mode: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -216,7 +226,7 @@ impl Canvas {
|
||||
|
||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(i32, PhysicalPosition<i32>, ModifiersState),
|
||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||
{
|
||||
self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| {
|
||||
handler(
|
||||
@@ -246,6 +256,28 @@ impl Canvas {
|
||||
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
|
||||
}
|
||||
|
||||
pub fn on_dark_mode<F>(&mut self, mut handler: F)
|
||||
where
|
||||
F: 'static + FnMut(bool),
|
||||
{
|
||||
let window = web_sys::window().expect("Failed to obtain window");
|
||||
|
||||
self.on_dark_mode = window
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|media| {
|
||||
let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| {
|
||||
handler(event.matches())
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
||||
media
|
||||
.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref()))
|
||||
.map(|_| closure)
|
||||
.ok()
|
||||
});
|
||||
}
|
||||
|
||||
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
||||
where
|
||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
||||
|
||||
@@ -38,6 +38,17 @@ impl WindowExtWebSys for Window {
|
||||
fn canvas(&self) -> HtmlCanvasElement {
|
||||
self.window.canvas().raw().clone()
|
||||
}
|
||||
|
||||
fn is_dark_mode(&self) -> bool {
|
||||
let window = web_sys::window().expect("Failed to obtain window");
|
||||
|
||||
window
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|media| media.matches())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_size() -> LogicalSize<f64> {
|
||||
@@ -91,3 +102,5 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub type RawCanvasType = HtmlCanvasElement;
|
||||
|
||||
@@ -23,13 +23,13 @@ impl Window {
|
||||
pub fn new<T>(
|
||||
target: &EventLoopWindowTarget<T>,
|
||||
attr: WindowAttributes,
|
||||
_: PlatformSpecificBuilderAttributes,
|
||||
platform_attr: PlatformSpecificBuilderAttributes,
|
||||
) -> Result<Self, RootOE> {
|
||||
let runner = target.runner.clone();
|
||||
|
||||
let id = target.generate_id();
|
||||
|
||||
let mut canvas = backend::Canvas::create()?;
|
||||
let mut canvas = backend::Canvas::create(platform_attr)?;
|
||||
|
||||
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id)));
|
||||
|
||||
@@ -281,5 +281,7 @@ impl Id {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct PlatformSpecificBuilderAttributes;
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PlatformSpecificBuilderAttributes {
|
||||
pub(crate) canvas: Option<backend::RawCanvasType>,
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ use std::os::windows::ffi::OsStrExt;
|
||||
use winapi::{
|
||||
shared::{
|
||||
basetsd::SIZE_T,
|
||||
minwindef::{BOOL, DWORD, UINT, ULONG, WORD},
|
||||
minwindef::{BOOL, DWORD, FALSE, UINT, ULONG, WORD},
|
||||
ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR},
|
||||
windef::HWND,
|
||||
winerror::S_OK,
|
||||
},
|
||||
um::{libloaderapi, uxtheme, winuser},
|
||||
};
|
||||
@@ -44,9 +45,8 @@ lazy_static! {
|
||||
};
|
||||
|
||||
let status = (rtl_get_version)(&mut vi as _);
|
||||
assert!(NT_SUCCESS(status));
|
||||
|
||||
if vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
|
||||
if NT_SUCCESS(status) && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
|
||||
Some(vi.dwBuildNumber)
|
||||
} else {
|
||||
None
|
||||
@@ -82,22 +82,15 @@ pub fn try_dark_mode(hwnd: HWND) -> bool {
|
||||
LIGHT_THEME_NAME.as_ptr()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
0,
|
||||
uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null())
|
||||
);
|
||||
let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) };
|
||||
|
||||
set_dark_mode_for_window(hwnd, is_dark_mode)
|
||||
}
|
||||
|
||||
is_dark_mode
|
||||
status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
|
||||
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
|
||||
// Uses Windows undocumented API SetWindowCompositionAttribute,
|
||||
// as seen in win32-darkmode example linked at top of file.
|
||||
|
||||
@@ -132,11 +125,12 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
|
||||
cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
set_window_composition_attribute(hwnd, &mut data as *mut _)
|
||||
);
|
||||
let status = set_window_composition_attribute(hwnd, &mut data as *mut _);
|
||||
|
||||
status != FALSE
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +199,7 @@ fn is_high_contrast() -> bool {
|
||||
)
|
||||
};
|
||||
|
||||
(ok > 0) && ((HCF_HIGHCONTRASTON & hc.dwFlags) == 1)
|
||||
ok != FALSE && (HCF_HIGHCONTRASTON & hc.dwFlags) == 1
|
||||
}
|
||||
|
||||
fn widestring(src: &'static str) -> Vec<u16> {
|
||||
|
||||
@@ -50,9 +50,7 @@ use crate::{
|
||||
dark_mode::try_dark_mode,
|
||||
dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling},
|
||||
drop_handler::FileDropHandler,
|
||||
event::{
|
||||
self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide,
|
||||
},
|
||||
event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey},
|
||||
monitor, raw_input, util,
|
||||
window_state::{CursorFlags, WindowFlags, WindowState},
|
||||
wrap_device_id, WindowId, DEVICE_ID,
|
||||
@@ -100,23 +98,18 @@ pub(crate) struct SubclassInput<T: 'static> {
|
||||
}
|
||||
|
||||
impl<T> SubclassInput<T> {
|
||||
unsafe fn send_event(&self, event: Event<'static, T>) {
|
||||
unsafe fn send_event(&self, event: Event<'_, T>) {
|
||||
self.event_loop_runner.send_event(event);
|
||||
}
|
||||
|
||||
unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> {
|
||||
self.event_loop_runner.send_event_unbuffered(event)
|
||||
}
|
||||
}
|
||||
|
||||
struct ThreadMsgTargetSubclassInput<T: 'static> {
|
||||
event_loop_runner: EventLoopRunnerShared<T>,
|
||||
user_event_receiver: Receiver<T>,
|
||||
modifiers_state: ModifiersStateSide,
|
||||
}
|
||||
|
||||
impl<T> ThreadMsgTargetSubclassInput<T> {
|
||||
unsafe fn send_event(&self, event: Event<'static, T>) {
|
||||
unsafe fn send_event(&self, event: Event<'_, T>) {
|
||||
self.event_loop_runner.send_event(event);
|
||||
}
|
||||
}
|
||||
@@ -216,49 +209,77 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
unsafe {
|
||||
let mut msg = mem::zeroed();
|
||||
let mut msg_unprocessed = false;
|
||||
let mut unread_message_exists = false;
|
||||
|
||||
'main: loop {
|
||||
if let Err(payload) = runner.take_panic_error() {
|
||||
runner.destroy_runner();
|
||||
panic::resume_unwind(payload);
|
||||
}
|
||||
|
||||
runner.new_events();
|
||||
loop {
|
||||
if !msg_unprocessed {
|
||||
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) {
|
||||
if !unread_message_exists {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
winuser::PM_REMOVE,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
|
||||
msg_unprocessed = false;
|
||||
}
|
||||
runner.events_cleared();
|
||||
if let Err(payload) = runner.take_panic_error() {
|
||||
runner.destroy_runner();
|
||||
panic::resume_unwind(payload);
|
||||
}
|
||||
unread_message_exists = false;
|
||||
|
||||
if !msg_unprocessed {
|
||||
let control_flow = runner.control_flow();
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break 'main,
|
||||
ControlFlow::Wait => {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
}
|
||||
msg_unprocessed = true;
|
||||
}
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
}
|
||||
ControlFlow::Poll => (),
|
||||
if msg.message == winuser::WM_PAINT {
|
||||
// An "external" redraw was requested.
|
||||
// Note that the WM_PAINT has been dispatched and
|
||||
// has caused the event loop to emit the MainEventsCleared event.
|
||||
// See EventLoopRunner::process_event().
|
||||
// The call to main_events_cleared() below will do nothing.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Make sure we emit the MainEventsCleared event if no WM_PAINT message was received.
|
||||
runner.main_events_cleared();
|
||||
// Drain eventual WM_PAINT messages sent if user called request_redraw()
|
||||
// during handling of MainEventsCleared.
|
||||
loop {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
winuser::WM_PAINT,
|
||||
winuser::WM_PAINT,
|
||||
winuser::PM_QS_PAINT | winuser::PM_REMOVE,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
runner.redraw_events_cleared();
|
||||
match runner.control_flow() {
|
||||
ControlFlow::Exit => break 'main,
|
||||
ControlFlow::Wait => {
|
||||
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
|
||||
break 'main;
|
||||
}
|
||||
unread_message_exists = true;
|
||||
}
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
wait_until_time_or_msg(resume_time);
|
||||
}
|
||||
ControlFlow::Poll => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
runner.call_event_handler(Event::LoopDestroyed);
|
||||
}
|
||||
runner.destroy_loop();
|
||||
runner.destroy_runner();
|
||||
}
|
||||
|
||||
@@ -295,11 +316,9 @@ fn main_thread_id() -> DWORD {
|
||||
unsafe { MAIN_THREAD_ID }
|
||||
}
|
||||
|
||||
// Returns true if the wait time was reached, and false if a message must be processed.
|
||||
unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool {
|
||||
let mut msg = mem::zeroed();
|
||||
unsafe fn wait_until_time_or_msg(wait_until: Instant) {
|
||||
let now = Instant::now();
|
||||
if now <= wait_until {
|
||||
if now < wait_until {
|
||||
// MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond
|
||||
// from the requested time and spinlock for the remainder to compensate for that.
|
||||
let resume_reason = winuser::MsgWaitForMultipleObjectsEx(
|
||||
@@ -311,16 +330,16 @@ unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool {
|
||||
);
|
||||
|
||||
if resume_reason == winerror::WAIT_TIMEOUT {
|
||||
let mut msg = mem::zeroed();
|
||||
while Instant::now() < wait_until {
|
||||
if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs
|
||||
fn dur2timeout(dur: Duration) -> DWORD {
|
||||
// Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the
|
||||
@@ -530,7 +549,6 @@ fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) ->
|
||||
let subclass_input = ThreadMsgTargetSubclassInput {
|
||||
event_loop_runner,
|
||||
user_event_receiver: rx,
|
||||
modifiers_state: ModifiersStateSide::default(),
|
||||
};
|
||||
let input_ptr = Box::into_raw(Box::new(subclass_input));
|
||||
let subclass_result = commctrl::SetWindowSubclass(
|
||||
@@ -583,6 +601,27 @@ fn normalize_pointer_pressure(pressure: u32) -> Option<Force> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a `ModifiersChanged` event whenever modifiers have changed.
|
||||
fn update_modifiers<T>(window: HWND, subclass_input: &SubclassInput<T>) {
|
||||
use crate::event::WindowEvent::ModifiersChanged;
|
||||
|
||||
let modifiers = event::get_key_mods();
|
||||
let mut window_state = subclass_input.window_state.lock();
|
||||
if window_state.modifiers_state != modifiers {
|
||||
window_state.modifiers_state = modifiers;
|
||||
|
||||
// Drop lock
|
||||
drop(window_state);
|
||||
|
||||
unsafe {
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: ModifiersChanged(modifiers),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Any window whose callback is configured to this function will have its events propagated
|
||||
/// through the events loop of the thread the window was created in.
|
||||
//
|
||||
@@ -615,13 +654,6 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
}
|
||||
|
||||
winuser::WM_NCLBUTTONDOWN => {
|
||||
// jumpstart the modal loop
|
||||
winuser::RedrawWindow(
|
||||
window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
if wparam == winuser::HTCAPTION as _ {
|
||||
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
|
||||
}
|
||||
@@ -707,7 +739,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
let windowpos = lparam as *const winuser::WINDOWPOS;
|
||||
if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE {
|
||||
let physical_position =
|
||||
PhysicalPosition::new((*windowpos).x as u32, (*windowpos).y as u32);
|
||||
PhysicalPosition::new((*windowpos).x as i32, (*windowpos).y as i32);
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: Moved(physical_position),
|
||||
@@ -829,18 +861,30 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
let x = windowsx::GET_X_LPARAM(lparam) as i32;
|
||||
let y = windowsx::GET_Y_LPARAM(lparam) as i32;
|
||||
let x = windowsx::GET_X_LPARAM(lparam) as f64;
|
||||
let y = windowsx::GET_Y_LPARAM(lparam) as f64;
|
||||
let position = PhysicalPosition::new(x, y);
|
||||
let cursor_moved;
|
||||
{
|
||||
// handle spurious WM_MOUSEMOVE messages
|
||||
// see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343
|
||||
// and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html
|
||||
let mut w = subclass_input.window_state.lock();
|
||||
cursor_moved = w.mouse.last_position != Some(position);
|
||||
w.mouse.last_position = Some(position);
|
||||
}
|
||||
if cursor_moved {
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position,
|
||||
modifiers: event::get_key_mods(),
|
||||
},
|
||||
});
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: CursorMoved {
|
||||
device_id: DEVICE_ID,
|
||||
position,
|
||||
modifiers: event::get_key_mods(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
@@ -871,6 +915,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
let value = value as i32;
|
||||
let value = value as f32 / winuser::WHEEL_DELTA as f32;
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::MouseWheel {
|
||||
@@ -891,6 +937,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
let value = value as i32;
|
||||
let value = value as f32 / winuser::WHEEL_DELTA as f32;
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::MouseWheel {
|
||||
@@ -910,6 +958,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
commctrl::DefSubclassProc(window, msg, wparam, lparam)
|
||||
} else {
|
||||
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -939,6 +990,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
winuser::WM_KEYUP | winuser::WM_SYSKEYUP => {
|
||||
use crate::event::ElementState::Released;
|
||||
if let Some((scancode, vkey)) = process_key_params(wparam, lparam) {
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -961,6 +1015,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -980,6 +1036,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -999,6 +1057,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -1018,6 +1078,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -1037,6 +1099,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -1056,6 +1120,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -1076,6 +1142,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
capture_mouse(window, &mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -1096,6 +1164,8 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
release_mouse(&mut *subclass_input.window_state.lock());
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: MouseInput {
|
||||
@@ -1304,6 +1374,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC);
|
||||
let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode);
|
||||
|
||||
update_modifiers(window, subclass_input);
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -1328,12 +1401,17 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
}
|
||||
|
||||
winuser::WM_KILLFOCUS => {
|
||||
use crate::event::{ElementState::Released, WindowEvent::Focused};
|
||||
use crate::event::{
|
||||
ElementState::Released,
|
||||
ModifiersState,
|
||||
WindowEvent::{Focused, ModifiersChanged},
|
||||
};
|
||||
for windows_keycode in event::get_pressed_keys() {
|
||||
let scancode =
|
||||
winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC);
|
||||
let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode);
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: WindowEvent::KeyboardInput {
|
||||
@@ -1349,6 +1427,12 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
})
|
||||
}
|
||||
|
||||
subclass_input.window_state.lock().modifiers_state = ModifiersState::empty();
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: ModifiersChanged(ModifiersState::empty()),
|
||||
});
|
||||
|
||||
subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: Focused(false),
|
||||
@@ -1392,7 +1476,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
if window_state.min_size.is_some() || window_state.max_size.is_some() {
|
||||
if let Some(min_size) = window_state.min_size {
|
||||
let min_size = min_size.to_physical(window_state.dpi_factor);
|
||||
let min_size = min_size.to_physical(window_state.scale_factor);
|
||||
let (width, height): (u32, u32) = util::adjust_size(window, min_size).into();
|
||||
(*mmi).ptMinTrackSize = POINT {
|
||||
x: width as i32,
|
||||
@@ -1400,7 +1484,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
};
|
||||
}
|
||||
if let Some(max_size) = window_state.max_size {
|
||||
let max_size = max_size.to_physical(window_state.dpi_factor);
|
||||
let max_size = max_size.to_physical(window_state.scale_factor);
|
||||
let (width, height): (u32, u32) = util::adjust_size(window, max_size).into();
|
||||
(*mmi).ptMaxTrackSize = POINT {
|
||||
x: width as i32,
|
||||
@@ -1422,15 +1506,15 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
// application since they are the same".
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx
|
||||
let new_dpi_x = u32::from(LOWORD(wparam as DWORD));
|
||||
let new_dpi_factor = dpi_to_scale_factor(new_dpi_x);
|
||||
let old_dpi_factor: f64;
|
||||
let new_scale_factor = dpi_to_scale_factor(new_dpi_x);
|
||||
let old_scale_factor: f64;
|
||||
|
||||
let allow_resize = {
|
||||
let mut window_state = subclass_input.window_state.lock();
|
||||
old_dpi_factor = window_state.dpi_factor;
|
||||
window_state.dpi_factor = new_dpi_factor;
|
||||
old_scale_factor = window_state.scale_factor;
|
||||
window_state.scale_factor = new_scale_factor;
|
||||
|
||||
if new_dpi_factor == old_dpi_factor {
|
||||
if new_scale_factor == old_scale_factor {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1440,7 +1524,6 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
|
||||
let style = winuser::GetWindowLongW(window, winuser::GWL_STYLE) as _;
|
||||
let style_ex = winuser::GetWindowLongW(window, winuser::GWL_EXSTYLE) as _;
|
||||
let b_menu = !winuser::GetMenu(window).is_null() as BOOL;
|
||||
|
||||
// New size as suggested by Windows.
|
||||
let suggested_rect = *(lparam as *const RECT);
|
||||
@@ -1454,14 +1537,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
// let margin_right: i32;
|
||||
// let margin_bottom: i32;
|
||||
{
|
||||
let mut adjusted_rect = suggested_rect;
|
||||
winuser::AdjustWindowRectExForDpi(
|
||||
&mut adjusted_rect,
|
||||
style,
|
||||
b_menu,
|
||||
style_ex,
|
||||
new_dpi_x,
|
||||
);
|
||||
let adjusted_rect =
|
||||
util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect)
|
||||
.unwrap_or(suggested_rect);
|
||||
margin_left = suggested_rect.left - adjusted_rect.left;
|
||||
margin_top = suggested_rect.top - adjusted_rect.top;
|
||||
// margin_right = adjusted_rect.right - suggested_rect.right;
|
||||
@@ -1492,15 +1570,15 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
// We calculate our own size because the default suggested rect doesn't do a great job
|
||||
// of preserving the window's logical size.
|
||||
true => old_physical_inner_size
|
||||
.to_logical::<f64>(old_dpi_factor)
|
||||
.to_physical::<u32>(new_dpi_factor),
|
||||
.to_logical::<f64>(old_scale_factor)
|
||||
.to_physical::<u32>(new_scale_factor),
|
||||
false => old_physical_inner_size,
|
||||
};
|
||||
|
||||
let _ = subclass_input.send_event_unbuffered(Event::WindowEvent {
|
||||
let _ = subclass_input.send_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(WindowId(window)),
|
||||
event: ScaleFactorChanged {
|
||||
scale_factor: new_dpi_factor,
|
||||
scale_factor: new_scale_factor,
|
||||
new_inner_size: &mut new_physical_inner_size,
|
||||
},
|
||||
});
|
||||
@@ -1526,13 +1604,13 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
|
||||
bottom: suggested_ul.1 + new_physical_inner_size.height as LONG,
|
||||
};
|
||||
|
||||
winuser::AdjustWindowRectExForDpi(
|
||||
&mut conservative_rect,
|
||||
conservative_rect = util::adjust_window_rect_with_styles(
|
||||
window,
|
||||
style,
|
||||
b_menu,
|
||||
style_ex,
|
||||
new_dpi_x,
|
||||
);
|
||||
conservative_rect,
|
||||
)
|
||||
.unwrap_or(conservative_rect);
|
||||
|
||||
// If we're not dragging the window, offset the window so that the cursor's
|
||||
// relative horizontal position in the title bar is preserved.
|
||||
@@ -1696,34 +1774,28 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
};
|
||||
let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop();
|
||||
if in_modal_loop {
|
||||
let runner = &subclass_input.event_loop_runner;
|
||||
runner.main_events_cleared();
|
||||
// Drain eventual WM_PAINT messages sent if user called request_redraw()
|
||||
// during handling of MainEventsCleared.
|
||||
let mut msg = mem::zeroed();
|
||||
loop {
|
||||
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) {
|
||||
if 0 == winuser::PeekMessageW(
|
||||
&mut msg,
|
||||
ptr::null_mut(),
|
||||
winuser::WM_PAINT,
|
||||
winuser::WM_PAINT,
|
||||
winuser::PM_QS_PAINT | winuser::PM_REMOVE,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
// Clear all paint/timer messages from the queue before sending the events cleared message.
|
||||
match msg.message {
|
||||
// Flush the event queue of WM_PAINT messages.
|
||||
winuser::WM_PAINT | winuser::WM_TIMER => {
|
||||
// Remove the message from the message queue.
|
||||
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1);
|
||||
|
||||
if msg.hwnd != window {
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
}
|
||||
// If the message isn't one of those three, it may be handled by the modal
|
||||
// loop so we should return control flow to it.
|
||||
_ => {
|
||||
queue_call_again();
|
||||
return 0;
|
||||
}
|
||||
if msg.hwnd != window {
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
}
|
||||
|
||||
let runner = &subclass_input.event_loop_runner;
|
||||
runner.events_cleared();
|
||||
runner.redraw_events_cleared();
|
||||
match runner.control_flow() {
|
||||
// Waiting is handled by the modal loop.
|
||||
ControlFlow::Exit | ControlFlow::Wait => runner.new_events(),
|
||||
@@ -1758,10 +1830,9 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
|
||||
winuser::WM_INPUT => {
|
||||
use crate::event::{
|
||||
DeviceEvent::{Button, Key, ModifiersChanged, Motion, MouseMotion, MouseWheel},
|
||||
DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel},
|
||||
ElementState::{Pressed, Released},
|
||||
MouseScrollDelta::LineDelta,
|
||||
VirtualKeyCode,
|
||||
};
|
||||
|
||||
if let Some(data) = raw_input::get_raw_input_data(lparam as _) {
|
||||
@@ -1840,55 +1911,14 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
|
||||
{
|
||||
let virtual_keycode = vkey_to_winit_vkey(vkey);
|
||||
|
||||
// If we ever change the DeviceEvent API to only emit events when a
|
||||
// window is focused, we'll need to emit synthetic `ModifiersChanged`
|
||||
// events when Winit windows lose focus so that these don't drift out
|
||||
// of sync with the actual modifier state.
|
||||
let old_modifiers_state =
|
||||
subclass_input.modifiers_state.filter_out_altgr().into();
|
||||
match virtual_keycode {
|
||||
Some(VirtualKeyCode::LShift) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::LSHIFT, pressed),
|
||||
Some(VirtualKeyCode::RShift) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::RSHIFT, pressed),
|
||||
Some(VirtualKeyCode::LControl) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::LCTRL, pressed),
|
||||
Some(VirtualKeyCode::RControl) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::RCTRL, pressed),
|
||||
Some(VirtualKeyCode::LAlt) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::LALT, pressed),
|
||||
Some(VirtualKeyCode::RAlt) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::RALT, pressed),
|
||||
Some(VirtualKeyCode::LWin) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::LLOGO, pressed),
|
||||
Some(VirtualKeyCode::RWin) => subclass_input
|
||||
.modifiers_state
|
||||
.set(ModifiersStateSide::RLOGO, pressed),
|
||||
_ => (),
|
||||
}
|
||||
let new_modifiers_state =
|
||||
subclass_input.modifiers_state.filter_out_altgr().into();
|
||||
if new_modifiers_state != old_modifiers_state {
|
||||
subclass_input.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: ModifiersChanged(new_modifiers_state),
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
subclass_input.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: Key(KeyboardInput {
|
||||
scancode,
|
||||
state,
|
||||
virtual_keycode,
|
||||
modifiers: new_modifiers_state,
|
||||
modifiers: event::get_key_mods(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,18 +3,19 @@ use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::R
|
||||
use winapi::{shared::windef::HWND, um::winuser};
|
||||
|
||||
use crate::{
|
||||
event::{Event, StartCause},
|
||||
dpi::PhysicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
platform_impl::platform::event_loop::EventLoop,
|
||||
platform_impl::platform::event_loop::{util, EventLoop},
|
||||
window::WindowId,
|
||||
};
|
||||
|
||||
pub(crate) type EventLoopRunnerShared<T> = Rc<ELRShared<T>>;
|
||||
pub(crate) struct ELRShared<T: 'static> {
|
||||
runner: RefCell<Option<EventLoopRunner<T>>>,
|
||||
buffer: RefCell<VecDeque<Event<'static, T>>>,
|
||||
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
|
||||
buffer: RefCell<VecDeque<BufferedEvent<T>>>,
|
||||
}
|
||||
|
||||
struct EventLoopRunner<T: 'static> {
|
||||
control_flow: ControlFlow,
|
||||
runner_state: RunnerState,
|
||||
@@ -22,16 +23,56 @@ struct EventLoopRunner<T: 'static> {
|
||||
in_modal_loop: bool,
|
||||
event_handler: Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
|
||||
panic_error: Option<PanicError>,
|
||||
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
|
||||
}
|
||||
|
||||
pub type PanicError = Box<dyn Any + Send + 'static>;
|
||||
|
||||
pub enum BufferedEvent<T: 'static> {
|
||||
Event(Event<'static, T>),
|
||||
ScaleFactorChanged(WindowId, f64, PhysicalSize<u32>),
|
||||
}
|
||||
|
||||
impl<T> BufferedEvent<T> {
|
||||
pub fn from_event(event: Event<'_, T>) -> BufferedEvent<T> {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size,
|
||||
},
|
||||
window_id,
|
||||
} => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size),
|
||||
event => BufferedEvent::Event(event.to_static().unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) {
|
||||
match self {
|
||||
Self::Event(event) => dispatch(event),
|
||||
Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => {
|
||||
dispatch(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut new_inner_size,
|
||||
},
|
||||
});
|
||||
util::set_inner_size_physical(
|
||||
(window_id.0).0,
|
||||
new_inner_size.width as _,
|
||||
new_inner_size.height as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ELRShared<T> {
|
||||
pub(crate) fn new() -> ELRShared<T> {
|
||||
ELRShared {
|
||||
runner: RefCell::new(None),
|
||||
buffer: RefCell::new(VecDeque::new()),
|
||||
redraw_buffer: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,18 +80,11 @@ impl<T> ELRShared<T> {
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &mut ControlFlow),
|
||||
{
|
||||
let mut runner = EventLoopRunner::new(event_loop, self.redraw_buffer.clone(), f);
|
||||
let mut runner = EventLoopRunner::new(event_loop, f);
|
||||
{
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
loop {
|
||||
let event = self.buffer.borrow_mut().pop_front();
|
||||
match event {
|
||||
Some(e) => {
|
||||
runner.process_event(e);
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
// Dispatch any events that were buffered during the creation of the window
|
||||
self.dispatch_buffered_events(&mut runner);
|
||||
*runner_ref = Some(runner);
|
||||
}
|
||||
}
|
||||
@@ -63,58 +97,75 @@ impl<T> ELRShared<T> {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.new_events();
|
||||
// Dispatch any events that were buffered during the call `new_events`
|
||||
self.dispatch_buffered_events(runner);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn send_event(&self, event: Event<'static, T>) {
|
||||
pub(crate) fn send_event(&self, event: Event<'_, T>) {
|
||||
if let Err(event) = self.send_event_unbuffered(event) {
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation. Add
|
||||
// the event to a buffer to be processed later.
|
||||
self.buffer_event(event);
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation.
|
||||
// Add the event to a buffer to be processed later.
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
panic!("buffering RedrawRequested event");
|
||||
}
|
||||
self.buffer
|
||||
.borrow_mut()
|
||||
.push_back(BufferedEvent::from_event(event));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn send_event_unbuffered<'e>(
|
||||
&self,
|
||||
event: Event<'e, T>,
|
||||
) -> Result<(), Event<'e, T>> {
|
||||
fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> {
|
||||
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.process_event(event);
|
||||
|
||||
// Dispatch any events that were buffered during the call to `process_event`.
|
||||
loop {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
// of the loop's body and attempts to add events to the event buffer while in
|
||||
// `process_event` will fail.
|
||||
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(event) => runner.process_event(event),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
self.dispatch_buffered_events(runner);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(event)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn call_event_handler(&self, event: Event<'static, T>) {
|
||||
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.call_event_handler(event);
|
||||
return;
|
||||
fn dispatch_buffered_events(&self, runner: &mut EventLoopRunner<T>) {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
// of the loop's body and attempts to add events to the event buffer while in
|
||||
// `process_event` will fail.
|
||||
loop {
|
||||
let buffered_event_opt = self.buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(e) => e.dispatch_event(|e| runner.process_event(e)),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn events_cleared(&self) {
|
||||
pub(crate) fn main_events_cleared(&self) {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.events_cleared();
|
||||
runner.main_events_cleared();
|
||||
if !self.buffer.borrow().is_empty() {
|
||||
warn!("Buffered events while dispatching MainEventsCleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn redraw_events_cleared(&self) {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.redraw_events_cleared();
|
||||
if !self.buffer.borrow().is_empty() {
|
||||
warn!("Buffered events while dispatching RedrawEventsCleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn destroy_loop(&self) {
|
||||
if let Ok(mut runner_ref) = self.runner.try_borrow_mut() {
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.call_event_handler(Event::LoopDestroyed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +182,17 @@ impl<T> ELRShared<T> {
|
||||
let mut runner_ref = self.runner.borrow_mut();
|
||||
if let Some(ref mut runner) = *runner_ref {
|
||||
runner.in_modal_loop = in_modal_loop;
|
||||
if in_modal_loop {
|
||||
// jumpstart the modal loop
|
||||
unsafe {
|
||||
winuser::RedrawWindow(
|
||||
runner.modal_redraw_window,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
winuser::RDW_INTERNALPAINT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,15 +213,6 @@ impl<T> ELRShared<T> {
|
||||
ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_event(&self, event: Event<'static, T>) {
|
||||
match event {
|
||||
Event::RedrawRequested(window_id) => {
|
||||
self.redraw_buffer.borrow_mut().push_back(window_id)
|
||||
}
|
||||
_ => self.buffer.borrow_mut().push_back(event),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -173,17 +226,15 @@ enum RunnerState {
|
||||
/// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`.
|
||||
DeferredNewEvents(Instant),
|
||||
/// The event loop is handling the OS's events and sending them to the user's callback.
|
||||
/// `NewEvents` has been sent, and `EventsCleared` hasn't.
|
||||
/// `NewEvents` has been sent, and `MainEventsCleared` hasn't.
|
||||
HandlingEvents,
|
||||
/// The event loop is handling the redraw events and sending them to the user's callback.
|
||||
/// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't.
|
||||
HandlingRedraw,
|
||||
}
|
||||
|
||||
impl<T> EventLoopRunner<T> {
|
||||
unsafe fn new<F>(
|
||||
event_loop: &EventLoop<T>,
|
||||
redraw_buffer: Rc<RefCell<VecDeque<WindowId>>>,
|
||||
f: F,
|
||||
) -> EventLoopRunner<T>
|
||||
unsafe fn new<F>(event_loop: &EventLoop<T>, f: F) -> EventLoopRunner<T>
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &mut ControlFlow),
|
||||
{
|
||||
@@ -197,7 +248,6 @@ impl<T> EventLoopRunner<T> {
|
||||
Box<dyn FnMut(Event<'_, T>, &mut ControlFlow)>,
|
||||
>(Box::new(f)),
|
||||
panic_error: None,
|
||||
redraw_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +273,8 @@ impl<T> EventLoopRunner<T> {
|
||||
}
|
||||
|
||||
// When `NewEvents` gets sent after an idle depends on the control flow...
|
||||
// Some `NewEvents` are deferred because not all Windows messages trigger an event_loop event.
|
||||
// So we defer the `NewEvents` to when we actually process an event.
|
||||
RunnerState::Idle(wait_start) => {
|
||||
match self.control_flow {
|
||||
// If we're polling, send `NewEvents` and immediately move into event processing.
|
||||
@@ -308,25 +360,26 @@ impl<T> EventLoopRunner<T> {
|
||||
self.call_event_handler(Event::NewEvents(start_cause));
|
||||
}
|
||||
// This can be reached if the control flow is changed to poll during a `RedrawRequested`
|
||||
// that was sent after `EventsCleared`.
|
||||
// that was sent after `MainEventsCleared`.
|
||||
ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)),
|
||||
}
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
}
|
||||
|
||||
match (self.runner_state, &event) {
|
||||
(RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => {
|
||||
self.call_event_handler(event)
|
||||
}
|
||||
(_, Event::RedrawRequested(_)) => {
|
||||
(RunnerState::HandlingEvents, Event::RedrawRequested(window_id)) => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
self.call_event_handler(event);
|
||||
self.call_event_handler(Event::RedrawRequested(*window_id));
|
||||
}
|
||||
(RunnerState::HandlingRedraw, Event::RedrawRequested(window_id)) => {
|
||||
self.call_event_handler(Event::RedrawRequested(*window_id));
|
||||
}
|
||||
(RunnerState::HandlingRedraw, _) => {
|
||||
warn!("Non-redraw event dispatched durning redraw phase");
|
||||
self.events_cleared();
|
||||
self.new_events();
|
||||
self.call_event_handler(event);
|
||||
warn!(
|
||||
"non-redraw event in redraw phase: {:?}",
|
||||
event.map_nonuser_event::<()>().ok()
|
||||
);
|
||||
}
|
||||
(_, _) => {
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
@@ -335,30 +388,17 @@ impl<T> EventLoopRunner<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_redraws(&mut self) {
|
||||
loop {
|
||||
let redraw_window_opt = self.redraw_buffer.borrow_mut().pop_front();
|
||||
match redraw_window_opt {
|
||||
Some(window_id) => self.process_event(Event::RedrawRequested(window_id)),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn events_cleared(&mut self) {
|
||||
fn main_events_cleared(&mut self) {
|
||||
match self.runner_state {
|
||||
// If we were handling events, send the EventsCleared message.
|
||||
// If we were handling events, send the MainEventsCleared message.
|
||||
RunnerState::HandlingEvents => {
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.flush_redraws();
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::Idle(Instant::now());
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
|
||||
RunnerState::HandlingRedraw => {
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::Idle(Instant::now());
|
||||
}
|
||||
// We already cleared the main events, we don't have to do anything.
|
||||
// This happens when process_events() processed a RedrawRequested event.
|
||||
RunnerState::HandlingRedraw => {}
|
||||
|
||||
// If we *weren't* handling events, we don't have to do anything.
|
||||
RunnerState::New | RunnerState::Idle(..) => (),
|
||||
@@ -367,15 +407,15 @@ impl<T> EventLoopRunner<T> {
|
||||
// branch handles those.
|
||||
RunnerState::DeferredNewEvents(wait_start) => {
|
||||
match self.control_flow {
|
||||
// If we had deferred a Poll, send the Poll NewEvents and EventsCleared.
|
||||
// If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared.
|
||||
ControlFlow::Poll => {
|
||||
self.call_event_handler(Event::NewEvents(StartCause::Poll));
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.flush_redraws();
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
// If we had deferred a WaitUntil and the resume time has since been reached,
|
||||
// send the resume notification and EventsCleared event.
|
||||
// send the resume notification and MainEventsCleared event.
|
||||
ControlFlow::WaitUntil(resume_time) => {
|
||||
if Instant::now() >= resume_time {
|
||||
self.call_event_handler(Event::NewEvents(
|
||||
@@ -384,21 +424,36 @@ impl<T> EventLoopRunner<T> {
|
||||
requested_resume: resume_time,
|
||||
},
|
||||
));
|
||||
self.runner_state = RunnerState::HandlingEvents;
|
||||
self.call_event_handler(Event::MainEventsCleared);
|
||||
self.flush_redraws();
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::HandlingRedraw;
|
||||
}
|
||||
}
|
||||
// If we deferred a wait and no events were received, the user doesn't have to
|
||||
// get an event.
|
||||
ControlFlow::Wait | ControlFlow::Exit => (),
|
||||
}
|
||||
// Mark that we've entered an idle state.
|
||||
self.runner_state = RunnerState::Idle(wait_start)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_events_cleared(&mut self) {
|
||||
match self.runner_state {
|
||||
// If we were handling redraws, send the RedrawEventsCleared message.
|
||||
RunnerState::HandlingRedraw => {
|
||||
self.call_event_handler(Event::RedrawEventsCleared);
|
||||
self.runner_state = RunnerState::Idle(Instant::now());
|
||||
}
|
||||
// No event was processed, we don't have to do anything.
|
||||
RunnerState::DeferredNewEvents(_) => (),
|
||||
// Should not happen.
|
||||
_ => warn!(
|
||||
"unexpected state in redraw_events_cleared: {:?}",
|
||||
self.runner_state
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn call_event_handler(&mut self, event: Event<'_, T>) {
|
||||
if self.panic_error.is_none() {
|
||||
let EventLoopRunner {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use std::{io, mem, os::windows::ffi::OsStrExt, path::Path, ptr};
|
||||
use std::{fmt, io, iter::once, mem, os::windows::ffi::OsStrExt, path::Path, ptr, sync::Arc};
|
||||
|
||||
use winapi::{
|
||||
ctypes::{c_int, wchar_t},
|
||||
shared::{
|
||||
minwindef::{BYTE, LPARAM, WPARAM},
|
||||
minwindef::{BYTE, LPARAM, WORD, WPARAM},
|
||||
windef::{HICON, HWND},
|
||||
},
|
||||
um::libloaderapi,
|
||||
um::winuser,
|
||||
};
|
||||
|
||||
use crate::icon::{Icon, Pixel, PIXEL_SIZE};
|
||||
use crate::dpi::PhysicalSize;
|
||||
use crate::icon::*;
|
||||
|
||||
impl Pixel {
|
||||
fn to_bgra(&mut self) {
|
||||
@@ -17,92 +19,149 @@ impl Pixel {
|
||||
}
|
||||
}
|
||||
|
||||
impl RgbaIcon {
|
||||
fn into_windows_icon(self) -> Result<WinIcon, BadIcon> {
|
||||
let mut rgba = self.rgba;
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
let mut and_mask = Vec::with_capacity(pixel_count);
|
||||
let pixels =
|
||||
unsafe { std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count) };
|
||||
for pixel in pixels {
|
||||
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
|
||||
pixel.to_bgra();
|
||||
}
|
||||
assert_eq!(and_mask.len(), pixel_count);
|
||||
let handle = unsafe {
|
||||
winuser::CreateIcon(
|
||||
ptr::null_mut(),
|
||||
self.width as c_int,
|
||||
self.height as c_int,
|
||||
1,
|
||||
(PIXEL_SIZE * 8) as BYTE,
|
||||
and_mask.as_ptr() as *const BYTE,
|
||||
rgba.as_ptr() as *const BYTE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IconType {
|
||||
Small = winuser::ICON_SMALL as isize,
|
||||
Big = winuser::ICON_BIG as isize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
struct RaiiIcon {
|
||||
handle: HICON,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WinIcon {
|
||||
pub handle: HICON,
|
||||
inner: Arc<RaiiIcon>,
|
||||
}
|
||||
|
||||
unsafe impl Send for WinIcon {}
|
||||
|
||||
impl WinIcon {
|
||||
#[allow(dead_code)]
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
|
||||
let wide_path: Vec<u16> = path.as_ref().as_os_str().encode_wide().collect();
|
||||
pub fn as_raw_handle(&self) -> HICON {
|
||||
self.inner.handle
|
||||
}
|
||||
|
||||
pub fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let wide_path: Vec<u16> = path
|
||||
.as_ref()
|
||||
.as_os_str()
|
||||
.encode_wide()
|
||||
.chain(once(0))
|
||||
.collect();
|
||||
|
||||
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
|
||||
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
|
||||
|
||||
let handle = unsafe {
|
||||
winuser::LoadImageW(
|
||||
ptr::null_mut(),
|
||||
wide_path.as_ptr() as *const wchar_t,
|
||||
winuser::IMAGE_ICON,
|
||||
0, // 0 indicates that we want to use the actual width
|
||||
0, // and height
|
||||
winuser::LR_LOADFROMFILE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon { handle })
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_icon(icon: Icon) -> Result<Self, io::Error> {
|
||||
Self::from_rgba(icon.rgba, icon.width, icon.height)
|
||||
}
|
||||
|
||||
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, io::Error> {
|
||||
assert_eq!(rgba.len() % PIXEL_SIZE, 0);
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
assert_eq!(pixel_count, (width * height) as usize);
|
||||
let mut and_mask = Vec::with_capacity(pixel_count);
|
||||
let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust
|
||||
for pixel_index in 0..pixel_count {
|
||||
let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) };
|
||||
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
|
||||
pixel.to_bgra();
|
||||
}
|
||||
assert_eq!(and_mask.len(), pixel_count);
|
||||
let handle = unsafe {
|
||||
winuser::CreateIcon(
|
||||
ptr::null_mut(),
|
||||
width as c_int,
|
||||
height as c_int,
|
||||
1,
|
||||
(PIXEL_SIZE * 8) as BYTE,
|
||||
and_mask.as_ptr() as *const BYTE,
|
||||
rgba.as_ptr() as *const BYTE,
|
||||
winuser::LR_DEFAULTSIZE | winuser::LR_LOADFROMFILE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon { handle })
|
||||
Ok(WinIcon::from_handle(handle))
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_resource(
|
||||
resource_id: WORD,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
|
||||
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
|
||||
let handle = unsafe {
|
||||
winuser::LoadImageW(
|
||||
libloaderapi::GetModuleHandleW(ptr::null_mut()),
|
||||
winuser::MAKEINTRESOURCEW(resource_id),
|
||||
winuser::IMAGE_ICON,
|
||||
width as c_int,
|
||||
height as c_int,
|
||||
winuser::LR_DEFAULTSIZE,
|
||||
) as HICON
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?;
|
||||
rgba_icon.into_windows_icon()
|
||||
}
|
||||
|
||||
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
|
||||
unsafe {
|
||||
winuser::SendMessageW(
|
||||
hwnd,
|
||||
winuser::WM_SETICON,
|
||||
icon_type as WPARAM,
|
||||
self.handle as LPARAM,
|
||||
self.as_raw_handle() as LPARAM,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn from_handle(handle: HICON) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RaiiIcon { handle }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinIcon {
|
||||
impl Drop for RaiiIcon {
|
||||
fn drop(&mut self) {
|
||||
unsafe { winuser::DestroyIcon(self.handle) };
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WinIcon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
(*self.inner).fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unset_for_window(hwnd: HWND, icon_type: IconType) {
|
||||
unsafe {
|
||||
winuser::SendMessageW(hwnd, winuser::WM_SETICON, icon_type as WPARAM, 0 as LPARAM);
|
||||
|
||||
@@ -4,11 +4,15 @@ use winapi::{self, shared::windef::HWND};
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||
icon::WinIcon,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
use crate::{event::DeviceId as RootDeviceId, window::Icon};
|
||||
pub use self::icon::WinIcon as PlatformIcon;
|
||||
|
||||
use crate::event::DeviceId as RootDeviceId;
|
||||
use crate::icon::Icon;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
|
||||
@@ -88,6 +88,38 @@ pub fn adjust_size(hwnd: HWND, size: PhysicalSize<u32>) -> PhysicalSize<u32> {
|
||||
PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _)
|
||||
}
|
||||
|
||||
pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) {
|
||||
unsafe {
|
||||
let rect = adjust_window_rect(
|
||||
window,
|
||||
RECT {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: y as LONG,
|
||||
right: x as LONG,
|
||||
},
|
||||
)
|
||||
.expect("adjust_window_rect failed");
|
||||
|
||||
let outer_x = (rect.right - rect.left).abs() as _;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as _;
|
||||
winuser::SetWindowPos(
|
||||
window,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
outer_x,
|
||||
outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOREPOSITION
|
||||
| winuser::SWP_NOMOVE
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::InvalidateRgn(window, ptr::null_mut(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
|
||||
unsafe {
|
||||
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
|
||||
|
||||
@@ -24,7 +24,7 @@ use winapi::{
|
||||
oleidl::LPDROPTARGET,
|
||||
shobjidl_core::{CLSID_TaskbarList, ITaskbarList2},
|
||||
wingdi::{CreateRectRgn, DeleteObject},
|
||||
winnt::{LONG, LPCWSTR},
|
||||
winnt::LPCWSTR,
|
||||
winuser,
|
||||
},
|
||||
};
|
||||
@@ -32,18 +32,19 @@ use winapi::{
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
icon::Icon,
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform_impl::platform::{
|
||||
dark_mode::try_dark_mode,
|
||||
dpi::{dpi_to_scale_factor, hwnd_dpi},
|
||||
drop_handler::FileDropHandler,
|
||||
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID},
|
||||
icon::{self, IconType, WinIcon},
|
||||
icon::{self, IconType},
|
||||
monitor, util,
|
||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||
PlatformSpecificWindowBuilderAttributes, WindowId,
|
||||
},
|
||||
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
@@ -187,7 +188,7 @@ impl Window {
|
||||
| winuser::SWP_NOSIZE
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
winuser::InvalidateRgn(self.window.0, ptr::null_mut(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,42 +216,10 @@ impl Window {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
|
||||
unsafe {
|
||||
let rect = util::adjust_window_rect(
|
||||
self.window.0,
|
||||
RECT {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: y as LONG,
|
||||
right: x as LONG,
|
||||
},
|
||||
)
|
||||
.expect("adjust_window_rect failed");
|
||||
|
||||
let outer_x = (rect.right - rect.left).abs() as c_int;
|
||||
let outer_y = (rect.top - rect.bottom).abs() as c_int;
|
||||
winuser::SetWindowPos(
|
||||
self.window.0,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
outer_x,
|
||||
outer_y,
|
||||
winuser::SWP_ASYNCWINDOWPOS
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOREPOSITION
|
||||
| winuser::SWP_NOMOVE
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::UpdateWindow(self.window.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let (width, height) = size.to_physical::<u32>(dpi_factor).into();
|
||||
let scale_factor = self.scale_factor();
|
||||
let (width, height) = size.to_physical::<u32>(scale_factor).into();
|
||||
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window.clone();
|
||||
@@ -260,7 +229,7 @@ impl Window {
|
||||
});
|
||||
});
|
||||
|
||||
self.set_inner_size_physical(width, height);
|
||||
util::set_inner_size_physical(self.window.0, width, height);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -357,13 +326,13 @@ impl Window {
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
self.window_state.lock().dpi_factor
|
||||
self.window_state.lock().scale_factor
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
||||
let dpi_factor = self.scale_factor();
|
||||
let (x, y) = position.to_physical::<i32>(dpi_factor).into();
|
||||
let scale_factor = self.scale_factor();
|
||||
let (x, y) = position.to_physical::<i32>(scale_factor).into();
|
||||
|
||||
let mut point = POINT { x, y };
|
||||
unsafe {
|
||||
@@ -434,7 +403,7 @@ impl Window {
|
||||
let client_rect = util::get_client_rect(window.0).unwrap();
|
||||
window_state_lock.saved_window = Some(SavedWindow {
|
||||
client_rect,
|
||||
dpi_factor: window_state_lock.dpi_factor,
|
||||
scale_factor: window_state_lock.scale_factor,
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
@@ -538,17 +507,17 @@ impl Window {
|
||||
size.1 as i32,
|
||||
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
|
||||
);
|
||||
winuser::UpdateWindow(window.0);
|
||||
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut window_state_lock = window_state.lock();
|
||||
if let Some(SavedWindow {
|
||||
client_rect,
|
||||
dpi_factor,
|
||||
scale_factor,
|
||||
}) = window_state_lock.saved_window.take()
|
||||
{
|
||||
window_state_lock.dpi_factor = dpi_factor;
|
||||
window_state_lock.scale_factor = scale_factor;
|
||||
drop(window_state_lock);
|
||||
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();
|
||||
|
||||
@@ -564,7 +533,7 @@ impl Window {
|
||||
| winuser::SWP_NOZORDER
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::UpdateWindow(window.0);
|
||||
winuser::InvalidateRgn(window.0, ptr::null_mut(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,12 +577,11 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_icon(&self, mut window_icon: Option<Icon>) {
|
||||
let window_icon = window_icon
|
||||
.take()
|
||||
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`"));
|
||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
||||
if let Some(ref window_icon) = window_icon {
|
||||
window_icon.set_for_window(self.window.0, IconType::Small);
|
||||
window_icon
|
||||
.inner
|
||||
.set_for_window(self.window.0, IconType::Small);
|
||||
} else {
|
||||
icon::unset_for_window(self.window.0, IconType::Small);
|
||||
}
|
||||
@@ -621,12 +589,11 @@ impl Window {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_taskbar_icon(&self, mut taskbar_icon: Option<Icon>) {
|
||||
let taskbar_icon = taskbar_icon
|
||||
.take()
|
||||
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`"));
|
||||
pub fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
if let Some(ref taskbar_icon) = taskbar_icon {
|
||||
taskbar_icon.set_for_window(self.window.0, IconType::Big);
|
||||
taskbar_icon
|
||||
.inner
|
||||
.set_for_window(self.window.0, IconType::Big);
|
||||
} else {
|
||||
icon::unset_for_window(self.window.0, IconType::Big);
|
||||
}
|
||||
@@ -668,7 +635,7 @@ unsafe impl Sync for WindowWrapper {}
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
unsafe fn init<T: 'static>(
|
||||
mut attributes: WindowAttributes,
|
||||
attributes: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
) -> Result<Window, RootOsError> {
|
||||
@@ -677,25 +644,8 @@ unsafe fn init<T: 'static>(
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let window_icon = {
|
||||
let icon = attributes.window_icon.take().map(WinIcon::from_icon);
|
||||
if let Some(icon) = icon {
|
||||
Some(icon.map_err(|e| os_error!(e))?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let taskbar_icon = {
|
||||
let icon = attributes.window_icon.take().map(WinIcon::from_icon);
|
||||
if let Some(icon) = icon {
|
||||
Some(icon.map_err(|e| os_error!(e))?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// registering the window class
|
||||
let class_name = register_window_class(&window_icon, &taskbar_icon);
|
||||
let class_name = register_window_class(&attributes.window_icon, &pl_attribs.taskbar_icon);
|
||||
|
||||
let mut window_flags = WindowFlags::empty();
|
||||
window_flags.set(WindowFlags::DECORATIONS, attributes.decorations);
|
||||
@@ -744,7 +694,7 @@ unsafe fn init<T: 'static>(
|
||||
}
|
||||
|
||||
let dpi = hwnd_dpi(real_window.0);
|
||||
let dpi_factor = dpi_to_scale_factor(dpi);
|
||||
let scale_factor = dpi_to_scale_factor(dpi);
|
||||
|
||||
// making the window transparent
|
||||
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
|
||||
@@ -788,9 +738,8 @@ unsafe fn init<T: 'static>(
|
||||
let window_state = {
|
||||
let window_state = WindowState::new(
|
||||
&attributes,
|
||||
window_icon,
|
||||
taskbar_icon,
|
||||
dpi_factor,
|
||||
pl_attribs.taskbar_icon,
|
||||
scale_factor,
|
||||
dark_mode,
|
||||
);
|
||||
let window_state = Arc::new(Mutex::new(window_state));
|
||||
@@ -819,8 +768,8 @@ unsafe fn init<T: 'static>(
|
||||
}
|
||||
|
||||
unsafe fn register_window_class(
|
||||
window_icon: &Option<WinIcon>,
|
||||
taskbar_icon: &Option<WinIcon>,
|
||||
window_icon: &Option<Icon>,
|
||||
taskbar_icon: &Option<Icon>,
|
||||
) -> Vec<u16> {
|
||||
let class_name: Vec<_> = OsStr::new("Window Class")
|
||||
.encode_wide()
|
||||
@@ -829,11 +778,11 @@ unsafe fn register_window_class(
|
||||
|
||||
let h_icon = taskbar_icon
|
||||
.as_ref()
|
||||
.map(|icon| icon.handle)
|
||||
.map(|icon| icon.inner.as_raw_handle())
|
||||
.unwrap_or(ptr::null_mut());
|
||||
let h_icon_small = window_icon
|
||||
.as_ref()
|
||||
.map(|icon| icon.handle)
|
||||
.map(|icon| icon.inner.as_raw_handle())
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
let class = winuser::WNDCLASSEXW {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::{
|
||||
dpi::Size,
|
||||
platform_impl::platform::{event_loop, icon::WinIcon, util},
|
||||
dpi::{PhysicalPosition, Size},
|
||||
event::ModifiersState,
|
||||
icon::Icon,
|
||||
platform_impl::platform::{event_loop, util},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
use parking_lot::MutexGuard;
|
||||
@@ -14,7 +16,6 @@ use winapi::{
|
||||
};
|
||||
|
||||
/// Contains information about states and the window that the callback is going to use.
|
||||
#[derive(Clone)]
|
||||
pub struct WindowState {
|
||||
pub mouse: MouseProperties,
|
||||
|
||||
@@ -22,16 +23,14 @@ pub struct WindowState {
|
||||
pub min_size: Option<Size>,
|
||||
pub max_size: Option<Size>,
|
||||
|
||||
pub window_icon: Option<WinIcon>,
|
||||
pub taskbar_icon: Option<WinIcon>,
|
||||
pub window_icon: Option<Icon>,
|
||||
pub taskbar_icon: Option<Icon>,
|
||||
|
||||
pub saved_window: Option<SavedWindow>,
|
||||
pub dpi_factor: f64,
|
||||
pub scale_factor: f64,
|
||||
|
||||
pub modifiers_state: ModifiersState,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
|
||||
/// times in `EventsCleared`.
|
||||
pub queued_out_of_band_redraw: bool,
|
||||
pub is_dark_mode: bool,
|
||||
pub high_surrogate: Option<u16>,
|
||||
window_flags: WindowFlags,
|
||||
@@ -40,7 +39,7 @@ pub struct WindowState {
|
||||
#[derive(Clone)]
|
||||
pub struct SavedWindow {
|
||||
pub client_rect: RECT,
|
||||
pub dpi_factor: f64,
|
||||
pub scale_factor: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -48,6 +47,7 @@ pub struct MouseProperties {
|
||||
pub cursor: CursorIcon,
|
||||
pub buttons_down: u32,
|
||||
cursor_flags: CursorFlags,
|
||||
pub last_position: Option<PhysicalPosition<f64>>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -96,9 +96,8 @@ bitflags! {
|
||||
impl WindowState {
|
||||
pub fn new(
|
||||
attributes: &WindowAttributes,
|
||||
window_icon: Option<WinIcon>,
|
||||
taskbar_icon: Option<WinIcon>,
|
||||
dpi_factor: f64,
|
||||
taskbar_icon: Option<Icon>,
|
||||
scale_factor: f64,
|
||||
is_dark_mode: bool,
|
||||
) -> WindowState {
|
||||
WindowState {
|
||||
@@ -106,19 +105,20 @@ impl WindowState {
|
||||
cursor: CursorIcon::default(),
|
||||
buttons_down: 0,
|
||||
cursor_flags: CursorFlags::empty(),
|
||||
last_position: None,
|
||||
},
|
||||
|
||||
min_size: attributes.min_inner_size,
|
||||
max_size: attributes.max_inner_size,
|
||||
|
||||
window_icon,
|
||||
window_icon: attributes.window_icon.clone(),
|
||||
taskbar_icon,
|
||||
|
||||
saved_window: None,
|
||||
dpi_factor,
|
||||
scale_factor,
|
||||
|
||||
modifiers_state: ModifiersState::default(),
|
||||
fullscreen: None,
|
||||
queued_out_of_band_redraw: false,
|
||||
is_dark_mode,
|
||||
high_surrogate: None,
|
||||
window_flags: WindowFlags::empty(),
|
||||
@@ -268,7 +268,7 @@ impl WindowFlags {
|
||||
| winuser::SWP_NOSIZE
|
||||
| winuser::SWP_NOACTIVATE,
|
||||
);
|
||||
winuser::UpdateWindow(window);
|
||||
winuser::InvalidateRgn(window, ptr::null_mut(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
platform_impl,
|
||||
};
|
||||
|
||||
pub use crate::icon::*;
|
||||
pub use crate::icon::{BadIcon, Icon};
|
||||
|
||||
/// Represents a window.
|
||||
///
|
||||
@@ -26,12 +26,14 @@ pub use crate::icon::*;
|
||||
/// let window = Window::new(&event_loop).unwrap();
|
||||
///
|
||||
/// event_loop.run(move |event, _, control_flow| {
|
||||
/// *control_flow = ControlFlow::Wait;
|
||||
///
|
||||
/// match event {
|
||||
/// Event::WindowEvent {
|
||||
/// event: WindowEvent::CloseRequested,
|
||||
/// ..
|
||||
/// } => *control_flow = ControlFlow::Exit,
|
||||
/// _ => *control_flow = ControlFlow::Wait,
|
||||
/// _ => (),
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
@@ -78,7 +80,7 @@ impl WindowId {
|
||||
}
|
||||
|
||||
/// Object that allows you to build windows.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct WindowBuilder {
|
||||
/// The attributes to use to create the window.
|
||||
pub window: WindowAttributes,
|
||||
@@ -185,10 +187,7 @@ impl WindowBuilder {
|
||||
/// Initializes a new `WindowBuilder` with default values.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
WindowBuilder {
|
||||
window: Default::default(),
|
||||
platform_specific: Default::default(),
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Requests the window to be of specific dimensions.
|
||||
@@ -393,10 +392,10 @@ impl Window {
|
||||
/// This is the **strongly encouraged** method of redrawing windows, as it can integrate with
|
||||
/// OS-requested redraws (e.g. when a window gets resized).
|
||||
///
|
||||
/// This function can cause `RedrawRequested` events to be emitted after `Event::EventsCleared`
|
||||
/// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared`
|
||||
/// but before `Event::NewEvents` if called in the following circumstances:
|
||||
/// * While processing `EventsCleared`.
|
||||
/// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any
|
||||
/// * While processing `MainEventsCleared`.
|
||||
/// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any
|
||||
/// directly subsequent `RedrawRequested` event.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
|
||||
Reference in New Issue
Block a user