mirror of
https://github.com/italicsjenga/muda.git
synced 2025-01-10 03:41:30 +11:00
refactor!: use optional Position
type and fallback to cursor pos (#78)
* refactor!: use optional `Position` type and fallback to cursor pos * impl gtk & change to use screen coords * impl macos * revert back to client coordinates * fix build * fix macos impl * enhance examples * fix serde feature * fix tests * lint --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
parent
5fbe39e995
commit
c7ec320738
5
.changes/context-menu-pos.md
Normal file
5
.changes/context-menu-pos.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"muda": "minor"
|
||||
---
|
||||
|
||||
**Breaking Change**: `ContextMenu::show_context_menu_for_hwnd`, `ContextMenu::show_context_menu_for_gtk_window` and `ContextMenu::show_context_menu_for_nsview` has been changed to take an optional `Position` type instead of `x` and `y` and if `None` is provided, it will use the current cursor position.
|
|
@ -14,12 +14,14 @@ categories = [ "gui" ]
|
|||
default = [ "libxdo" ]
|
||||
libxdo = [ "dep:libxdo" ]
|
||||
common-controls-v6 = [ "windows-sys/Win32_UI_Controls" ]
|
||||
serde = [ "dep:serde" ]
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.5"
|
||||
keyboard-types = "0.6"
|
||||
once_cell = "1"
|
||||
thiserror = "1"
|
||||
serde = { version = "1", optional = true }
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
|
@ -30,7 +32,9 @@ features = [
|
|||
"Win32_UI_Shell",
|
||||
"Win32_Globalization",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_System_SystemServices"
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_System_LibraryLoader"
|
||||
]
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
|
|
|
@ -16,6 +16,7 @@ muda is a Menu Utilities library for Desktop Applications.
|
|||
|
||||
- `common-controls-v6`: Use `TaskDialogIndirect` API from `ComCtl32.dll` v6 on Windows for showing the predefined `About` menu item dialog.
|
||||
- `libxdo`: Enables linking to `libxdo` on Linux which is used for the predfined `Copy`, `Cut`, `Paste` and `SelectAll` menu item.
|
||||
- `serde`: Enables de/serializing the dpi types.
|
||||
|
||||
## Dependencies (Linux Only)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
use muda::{
|
||||
accelerator::{Accelerator, Code, Modifiers},
|
||||
AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem,
|
||||
PredefinedMenuItem, Submenu,
|
||||
PhysicalPosition, Position, PredefinedMenuItem, Submenu,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tao::platform::macos::WindowExtMacOS;
|
||||
|
@ -147,9 +147,9 @@ fn main() {
|
|||
}
|
||||
|
||||
let menu_channel = MenuEvent::receiver();
|
||||
let mut window_cursor_position = PhysicalPosition { x: 0., y: 0. };
|
||||
let mut use_window_pos = false;
|
||||
|
||||
let mut x = 0_f64;
|
||||
let mut y = 0_f64;
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
|
@ -163,24 +163,33 @@ fn main() {
|
|||
window_id,
|
||||
..
|
||||
} => {
|
||||
if window_id == window2.id() {
|
||||
x = position.x;
|
||||
y = position.y;
|
||||
}
|
||||
window_cursor_position.x = position.x;
|
||||
window_cursor_position.y = position.y;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
state: ElementState::Released,
|
||||
button: MouseButton::Right,
|
||||
..
|
||||
},
|
||||
window_id,
|
||||
..
|
||||
} => {
|
||||
if window_id == window2.id() {
|
||||
show_context_menu(&window2, &file_m, x, y);
|
||||
}
|
||||
show_context_menu(
|
||||
if window_id == window.id() {
|
||||
&window
|
||||
} else {
|
||||
&window2
|
||||
},
|
||||
&file_m,
|
||||
if use_window_pos {
|
||||
Some(window_cursor_position.into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
use_window_pos = !use_window_pos;
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
|
@ -199,13 +208,14 @@ fn main() {
|
|||
})
|
||||
}
|
||||
|
||||
fn show_context_menu(window: &Window, menu: &dyn ContextMenu, x: f64, y: f64) {
|
||||
fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option<Position>) {
|
||||
println!("Show context menu at position {position:?}");
|
||||
#[cfg(target_os = "windows")]
|
||||
menu.show_context_menu_for_hwnd(window.hwnd() as _, x, y);
|
||||
menu.show_context_menu_for_hwnd(window.hwnd() as _, position);
|
||||
#[cfg(target_os = "linux")]
|
||||
menu.show_context_menu_for_gtk_window(window.gtk_window(), x, y);
|
||||
menu.show_context_menu_for_gtk_window(window.gtk_window(), position);
|
||||
#[cfg(target_os = "macos")]
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, x, y);
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
|
||||
}
|
||||
|
||||
fn load_icon(path: &std::path::Path) -> muda::icon::Icon {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
use muda::{
|
||||
accelerator::{Accelerator, Code, Modifiers},
|
||||
AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem,
|
||||
PredefinedMenuItem, Submenu,
|
||||
PhysicalPosition, Position, PredefinedMenuItem, Submenu,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::{EventLoopBuilderExtMacOS, WindowExtMacOS};
|
||||
|
@ -137,9 +137,9 @@ fn main() {
|
|||
}
|
||||
|
||||
let menu_channel = MenuEvent::receiver();
|
||||
let mut window_cursor_position = PhysicalPosition { x: 0., y: 0. };
|
||||
let mut use_window_pos = false;
|
||||
|
||||
let mut x = 0_f64;
|
||||
let mut y = 0_f64;
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
|
@ -153,10 +153,8 @@ fn main() {
|
|||
window_id,
|
||||
..
|
||||
} => {
|
||||
if window_id == window2.id() {
|
||||
x = position.x;
|
||||
y = position.y;
|
||||
}
|
||||
window_cursor_position.x = position.x;
|
||||
window_cursor_position.y = position.y;
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
|
@ -168,9 +166,20 @@ fn main() {
|
|||
window_id,
|
||||
..
|
||||
} => {
|
||||
if window_id == window2.id() {
|
||||
show_context_menu(&window2, &file_m, x, y);
|
||||
}
|
||||
show_context_menu(
|
||||
if window_id == window.id() {
|
||||
&window
|
||||
} else {
|
||||
&window2
|
||||
},
|
||||
&file_m,
|
||||
if use_window_pos {
|
||||
Some(window_cursor_position.into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
use_window_pos = !use_window_pos;
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
|
@ -187,11 +196,12 @@ fn main() {
|
|||
})
|
||||
}
|
||||
|
||||
fn show_context_menu(window: &Window, menu: &dyn ContextMenu, x: f64, y: f64) {
|
||||
fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option<Position>) {
|
||||
println!("Show context menu at position {position:?}");
|
||||
#[cfg(target_os = "windows")]
|
||||
menu.show_context_menu_for_hwnd(window.hwnd() as _, x, y);
|
||||
menu.show_context_menu_for_hwnd(window.hwnd() as _, position);
|
||||
#[cfg(target_os = "macos")]
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, x, y);
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
|
||||
}
|
||||
|
||||
fn load_icon(path: &std::path::Path) -> muda::icon::Icon {
|
||||
|
|
|
@ -149,14 +149,19 @@ fn main() -> wry::Result<()> {
|
|||
window_m.set_windows_menu_for_nsapp();
|
||||
}
|
||||
|
||||
const HTML: &str = r#"
|
||||
#[cfg(windows)]
|
||||
let condition = "e.button !== 2";
|
||||
#[cfg(not(windows))]
|
||||
let condition = "e.button == 2 && e.buttons === 0";
|
||||
let html: String = format!(
|
||||
r#"
|
||||
<html>
|
||||
<body>
|
||||
<style>
|
||||
main {
|
||||
main {{
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
}}
|
||||
</style>
|
||||
<main>
|
||||
<h4> WRYYYYYYYYYYYYYYYYYYYYYY! </h4>
|
||||
|
@ -164,41 +169,66 @@ fn main() -> wry::Result<()> {
|
|||
<button> Hi </button>
|
||||
</main>
|
||||
<script>
|
||||
window.addEventListener('contextmenu', (e) => {
|
||||
window.addEventListener('contextmenu', (e) => {{
|
||||
e.preventDefault();
|
||||
// if e.button is -1 on chromuim or e.buttons is 0 (as a fallback on webkit2gtk) then this event was fired by keyboard
|
||||
if (e.button === -1 || e.buttons === 0) {
|
||||
window.ipc.postMessage(`showContextMenu:${e.clientX},${e.clientY}`);
|
||||
}
|
||||
})
|
||||
window.addEventListener('mouseup', (e) => {
|
||||
if (e.button === 2) {
|
||||
window.ipc.postMessage(`showContextMenu:${e.clientX},${e.clientY}`);
|
||||
}
|
||||
})
|
||||
console.log(e)
|
||||
// contextmenu was requested from keyboard
|
||||
if ({condition}) {{
|
||||
window.ipc.postMessage(`showContextMenuPos:${{e.clientX}},${{e.clientY}}`);
|
||||
}}
|
||||
}})
|
||||
let x = true;
|
||||
window.addEventListener('mouseup', (e) => {{
|
||||
if (e.button === 2) {{
|
||||
if (x) {{
|
||||
window.ipc.postMessage(`showContextMenuPos:${{e.clientX}},${{e.clientY}}`);
|
||||
}} else {{
|
||||
window.ipc.postMessage(`showContextMenu`);
|
||||
}}
|
||||
x = !x;
|
||||
}}
|
||||
}})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"#;
|
||||
"#,
|
||||
);
|
||||
|
||||
let file_m_c = file_m.clone();
|
||||
let handler = move |window: &Window, req: String| {
|
||||
if let Some(rest) = req.strip_prefix("showContextMenu:") {
|
||||
let (x, y) = rest
|
||||
if &req == "showContextMenu" {
|
||||
show_context_menu(window, &file_m_c, None)
|
||||
} else if let Some(rest) = req.strip_prefix("showContextMenuPos:") {
|
||||
let (x, mut y) = rest
|
||||
.split_once(',')
|
||||
.map(|(x, y)| (x.parse::<f64>().unwrap(), y.parse::<f64>().unwrap()))
|
||||
.map(|(x, y)| (x.parse::<i32>().unwrap(), y.parse::<i32>().unwrap()))
|
||||
.unwrap();
|
||||
if window.id() == window2_id {
|
||||
show_context_menu(window, &window_m, x, y)
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Some(menu_bar) = menu_bar
|
||||
.clone()
|
||||
.gtk_menubar_for_gtk_window(window.gtk_window())
|
||||
{
|
||||
use gtk::prelude::*;
|
||||
y += menu_bar.allocated_height();
|
||||
}
|
||||
}
|
||||
|
||||
show_context_menu(
|
||||
window,
|
||||
&file_m_c,
|
||||
Some(muda::Position::Logical((x, y).into())),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let webview = WebViewBuilder::new(window)?
|
||||
.with_html(HTML)?
|
||||
.with_html(&html)?
|
||||
.with_ipc_handler(handler.clone())
|
||||
.build()?;
|
||||
let webview2 = WebViewBuilder::new(window2)?
|
||||
.with_html(HTML)?
|
||||
.with_html(html)?
|
||||
.with_ipc_handler(handler)
|
||||
.build()?;
|
||||
|
||||
|
@ -226,13 +256,14 @@ fn main() -> wry::Result<()> {
|
|||
})
|
||||
}
|
||||
|
||||
fn show_context_menu(window: &Window, menu: &dyn ContextMenu, x: f64, y: f64) {
|
||||
fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option<muda::Position>) {
|
||||
println!("Show context menu at position {position:?}");
|
||||
#[cfg(target_os = "windows")]
|
||||
menu.show_context_menu_for_hwnd(window.hwnd() as _, x, y);
|
||||
menu.show_context_menu_for_hwnd(window.hwnd() as _, position);
|
||||
#[cfg(target_os = "linux")]
|
||||
menu.show_context_menu_for_gtk_window(window.gtk_window(), x, y);
|
||||
menu.show_context_menu_for_gtk_window(window.gtk_window(), position);
|
||||
#[cfg(target_os = "macos")]
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, x, y);
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, position);
|
||||
}
|
||||
|
||||
fn load_icon(path: &std::path::Path) -> muda::icon::Icon {
|
||||
|
|
233
src/dpi.rs
Normal file
233
src/dpi.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
pub trait Pixel: Copy + Into<f64> {
|
||||
fn from_f64(f: f64) -> Self;
|
||||
fn cast<P: Pixel>(self) -> P {
|
||||
P::from_f64(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixel for u8 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as u8
|
||||
}
|
||||
}
|
||||
impl Pixel for u16 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as u16
|
||||
}
|
||||
}
|
||||
impl Pixel for u32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as u32
|
||||
}
|
||||
}
|
||||
impl Pixel for i8 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as i8
|
||||
}
|
||||
}
|
||||
impl Pixel for i16 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as i16
|
||||
}
|
||||
}
|
||||
impl Pixel for i32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f.round() as i32
|
||||
}
|
||||
}
|
||||
impl Pixel for f32 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f as f32
|
||||
}
|
||||
}
|
||||
impl Pixel for f64 {
|
||||
fn from_f64(f: f64) -> Self {
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the scale factor is a normal positive `f64`.
|
||||
///
|
||||
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
|
||||
/// 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(scale_factor: f64) -> bool {
|
||||
scale_factor.is_sign_positive() && scale_factor.is_normal()
|
||||
}
|
||||
|
||||
/// A position represented in logical pixels.
|
||||
///
|
||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
||||
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
|
||||
/// implementation is provided which does the rounding for you.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct LogicalPosition<P> {
|
||||
pub x: P,
|
||||
pub y: P,
|
||||
}
|
||||
|
||||
impl<P> LogicalPosition<P> {
|
||||
#[inline]
|
||||
pub const fn new(x: P, y: P) -> Self {
|
||||
LogicalPosition { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> LogicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
|
||||
physical: T,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
physical.into().to_logical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
|
||||
LogicalPosition {
|
||||
x: self.x.cast(),
|
||||
y: self.y.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
|
||||
fn from((x, y): (X, X)) -> LogicalPosition<P> {
|
||||
LogicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
|
||||
fn from(p: LogicalPosition<P>) -> (X, X) {
|
||||
(p.x.cast(), p.y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
|
||||
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
|
||||
LogicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
|
||||
fn from(p: LogicalPosition<P>) -> [X; 2] {
|
||||
[p.x.cast(), p.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
/// A position represented in physical pixels.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct PhysicalPosition<P> {
|
||||
pub x: P,
|
||||
pub y: P,
|
||||
}
|
||||
|
||||
impl<P> PhysicalPosition<P> {
|
||||
#[inline]
|
||||
pub const fn new(x: P, y: P) -> Self {
|
||||
PhysicalPosition { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> PhysicalPosition<P> {
|
||||
#[inline]
|
||||
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
|
||||
logical: T,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
logical.into().to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
|
||||
PhysicalPosition {
|
||||
x: self.x.cast(),
|
||||
y: self.y.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
|
||||
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
|
||||
PhysicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
|
||||
fn from(p: PhysicalPosition<P>) -> (X, X) {
|
||||
(p.x.cast(), p.y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
|
||||
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
|
||||
PhysicalPosition::new(x.cast(), y.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
|
||||
fn from(p: PhysicalPosition<P>) -> [X; 2] {
|
||||
[p.x.cast(), p.y.cast()]
|
||||
}
|
||||
}
|
||||
|
||||
/// A position that's either physical or logical.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Position {
|
||||
Physical(PhysicalPosition<i32>),
|
||||
Logical(LogicalPosition<f64>),
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn new<S: Into<Position>>(position: S) -> Position {
|
||||
position.into()
|
||||
}
|
||||
|
||||
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
|
||||
match *self {
|
||||
Position::Physical(position) => position.to_logical(scale_factor),
|
||||
Position::Logical(position) => position.cast(),
|
||||
}
|
||||
}
|
||||
|
||||
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(scale_factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<PhysicalPosition<P>> for Position {
|
||||
#[inline]
|
||||
fn from(position: PhysicalPosition<P>) -> Position {
|
||||
Position::Physical(position.cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> From<LogicalPosition<P>> for Position {
|
||||
#[inline]
|
||||
fn from(position: LogicalPosition<P>) -> Position {
|
||||
Position::Logical(position.cast())
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{util::AddOp, ContextMenu, IsMenuItem, MenuItemType};
|
||||
use crate::{util::AddOp, ContextMenu, IsMenuItem, MenuItemType, Position};
|
||||
|
||||
/// A menu that can be added to a [`Menu`] or another [`Submenu`].
|
||||
///
|
||||
|
@ -155,8 +155,10 @@ impl ContextMenu for Submenu {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
||||
self.0.borrow_mut().show_context_menu_for_hwnd(hwnd, x, y)
|
||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.show_context_menu_for_hwnd(hwnd, position)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -170,10 +172,14 @@ impl ContextMenu for Submenu {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn show_context_menu_for_gtk_window(&self, w: >k::ApplicationWindow, x: f64, y: f64) {
|
||||
fn show_context_menu_for_gtk_window(
|
||||
&self,
|
||||
w: >k::ApplicationWindow,
|
||||
position: Option<Position>,
|
||||
) {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.show_context_menu_for_gtk_window(w, x, y)
|
||||
.show_context_menu_for_gtk_window(w, position)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -182,8 +188,10 @@ impl ContextMenu for Submenu {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) {
|
||||
self.0.borrow_mut().show_context_menu_for_nsview(view, x, y)
|
||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, position: Option<Position>) {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.show_context_menu_for_nsview(view, position)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
27
src/lib.rs
27
src/lib.rs
|
@ -100,14 +100,13 @@
|
|||
//! # #[cfg(target_os = "macos")]
|
||||
//! # let nsview = 0 as *mut objc::runtime::Object;
|
||||
//! // --snip--
|
||||
//! let x = 100.0;
|
||||
//! let y = 120.0;
|
||||
//! let position = muda::PhysicalPosition { x: 100., y: 120. };
|
||||
//! #[cfg(target_os = "windows")]
|
||||
//! menu.show_context_menu_for_hwnd(window_hwnd, x, y);
|
||||
//! menu.show_context_menu_for_hwnd(window_hwnd, Some(position.into()));
|
||||
//! #[cfg(target_os = "linux")]
|
||||
//! menu.show_context_menu_for_gtk_window(>k_window, x, y);
|
||||
//! menu.show_context_menu_for_gtk_window(>k_window, Some(position.into()));
|
||||
//! #[cfg(target_os = "macos")]
|
||||
//! menu.show_context_menu_for_nsview(nsview, x, y);
|
||||
//! menu.show_context_menu_for_nsview(nsview, Some(position.into()));
|
||||
//! ```
|
||||
//! # Processing menu events
|
||||
//!
|
||||
|
@ -133,6 +132,7 @@ use once_cell::sync::{Lazy, OnceCell};
|
|||
mod about_metadata;
|
||||
pub mod accelerator;
|
||||
pub mod builders;
|
||||
mod dpi;
|
||||
mod error;
|
||||
mod items;
|
||||
mod menu;
|
||||
|
@ -144,6 +144,7 @@ mod util;
|
|||
extern crate objc;
|
||||
|
||||
pub use about_metadata::AboutMetadata;
|
||||
pub use dpi::*;
|
||||
pub use error::*;
|
||||
pub use items::*;
|
||||
pub use menu::Menu;
|
||||
|
@ -250,9 +251,9 @@ pub trait ContextMenu {
|
|||
|
||||
/// Shows this menu as a context menu inside a win32 window.
|
||||
///
|
||||
/// `x` and `y` are relative to the window's top-left corner.
|
||||
/// - `position` is relative to the window top-left corner, if `None`, the cursor position is used.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64);
|
||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>);
|
||||
|
||||
/// Attach the menu subclass handler to the given hwnd
|
||||
/// so you can recieve events from that window using [MenuEvent::receiver]
|
||||
|
@ -267,9 +268,13 @@ pub trait ContextMenu {
|
|||
|
||||
/// Shows this menu as a context menu inside a [`gtk::ApplicationWindow`]
|
||||
///
|
||||
/// `x` and `y` are relative to the window's top-left corner.
|
||||
/// - `position` is relative to the window top-left corner, if `None`, the cursor position is used.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn show_context_menu_for_gtk_window(&self, w: >k::ApplicationWindow, x: f64, y: f64);
|
||||
fn show_context_menu_for_gtk_window(
|
||||
&self,
|
||||
w: >k::ApplicationWindow,
|
||||
position: Option<Position>,
|
||||
);
|
||||
|
||||
/// Get the underlying gtk menu reserved for context menus.
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -277,9 +282,9 @@ pub trait ContextMenu {
|
|||
|
||||
/// Shows this menu as a context menu for the specified `NSView`.
|
||||
///
|
||||
/// `x` and `y` are relative to the window's top-left corner.
|
||||
/// - `position` is relative to the window top-left corner, if `None`, the cursor position is used.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64);
|
||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, position: Option<Position>);
|
||||
|
||||
/// Get the underlying NSMenu reserved for context menus.
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
30
src/menu.rs
30
src/menu.rs
|
@ -4,7 +4,7 @@
|
|||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{util::AddOp, ContextMenu, IsMenuItem};
|
||||
use crate::{util::AddOp, ContextMenu, IsMenuItem, Position};
|
||||
|
||||
/// A root menu that can be added to a Window on Windows and Linux
|
||||
/// and used as the app global menu on macOS.
|
||||
|
@ -243,6 +243,16 @@ impl Menu {
|
|||
self.0.borrow().is_visible_on_gtk_window(window)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
/// Returns the [`gtk::MenuBar`] that is associated with this window if it exists.
|
||||
/// This is useful to get information about the menubar for example its height.
|
||||
pub fn gtk_menubar_for_gtk_window<W>(self, window: &W) -> Option<gtk::MenuBar>
|
||||
where
|
||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||
{
|
||||
self.0.borrow().gtk_menubar_for_gtk_window(window)
|
||||
}
|
||||
|
||||
/// Returns whether this menu visible on a on a win32 window
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn is_visible_on_hwnd(&self, hwnd: isize) -> bool {
|
||||
|
@ -269,8 +279,8 @@ impl ContextMenu for Menu {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
||||
self.0.borrow().show_context_menu_for_hwnd(hwnd, x, y)
|
||||
fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) {
|
||||
self.0.borrow().show_context_menu_for_hwnd(hwnd, position)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -284,10 +294,14 @@ impl ContextMenu for Menu {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn show_context_menu_for_gtk_window(&self, window: >k::ApplicationWindow, x: f64, y: f64) {
|
||||
fn show_context_menu_for_gtk_window(
|
||||
&self,
|
||||
window: >k::ApplicationWindow,
|
||||
position: Option<Position>,
|
||||
) {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.show_context_menu_for_gtk_window(window, x, y)
|
||||
.show_context_menu_for_gtk_window(window, position)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -296,8 +310,10 @@ impl ContextMenu for Menu {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, x: f64, y: f64) {
|
||||
self.0.borrow_mut().show_context_menu_for_nsview(view, x, y)
|
||||
fn show_context_menu_for_nsview(&self, view: cocoa::base::id, position: Option<Position>) {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.show_context_menu_for_nsview(view, position)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
icon::{Icon, NativeIcon},
|
||||
items::*,
|
||||
util::{AddOp, Counter},
|
||||
MenuEvent, MenuItemType,
|
||||
MenuEvent, MenuItemType, Position,
|
||||
};
|
||||
use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic};
|
||||
use gtk::{prelude::*, Orientation};
|
||||
|
@ -314,24 +314,19 @@ impl Menu {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn show_context_menu_for_gtk_window(&self, window: &impl IsA<gtk::Widget>, x: f64, y: f64) {
|
||||
if let Some(window) = window.window() {
|
||||
let gtk_menu = gtk::Menu::new();
|
||||
|
||||
for item in self.items() {
|
||||
let gtk_item = item.make_gtk_menu_item(0, None, false).unwrap();
|
||||
gtk_menu.append(>k_item);
|
||||
pub fn gtk_menubar_for_gtk_window<W>(&self, window: &W) -> Option<gtk::MenuBar>
|
||||
where
|
||||
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||
{
|
||||
self.gtk_menubars.get(&(window.as_ptr() as u32)).cloned()
|
||||
}
|
||||
gtk_menu.show_all();
|
||||
|
||||
gtk_menu.popup_at_rect(
|
||||
&window,
|
||||
&gdk::Rectangle::new(x as _, y as _, 0, 0),
|
||||
gdk::Gravity::NorthWest,
|
||||
gdk::Gravity::NorthWest,
|
||||
None,
|
||||
);
|
||||
}
|
||||
pub fn show_context_menu_for_gtk_window(
|
||||
&mut self,
|
||||
widget: &impl IsA<gtk::Widget>,
|
||||
position: Option<Position>,
|
||||
) {
|
||||
show_context_menu(self.gtk_context_menu(), widget, position)
|
||||
}
|
||||
|
||||
pub fn gtk_context_menu(&mut self) -> gtk::Menu {
|
||||
|
@ -775,24 +770,12 @@ impl MenuChild {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn show_context_menu_for_gtk_window(&self, window: &impl IsA<gtk::Widget>, x: f64, y: f64) {
|
||||
if let Some(window) = window.window() {
|
||||
let gtk_menu = gtk::Menu::new();
|
||||
|
||||
for item in self.items() {
|
||||
let gtk_item = item.make_gtk_menu_item(0, None, false).unwrap();
|
||||
gtk_menu.append(>k_item);
|
||||
}
|
||||
gtk_menu.show_all();
|
||||
|
||||
gtk_menu.popup_at_rect(
|
||||
&window,
|
||||
&gdk::Rectangle::new(x as _, y as _, 0, 0),
|
||||
gdk::Gravity::NorthWest,
|
||||
gdk::Gravity::NorthWest,
|
||||
None,
|
||||
);
|
||||
}
|
||||
pub fn show_context_menu_for_gtk_window(
|
||||
&mut self,
|
||||
widget: &impl IsA<gtk::Widget>,
|
||||
position: Option<Position>,
|
||||
) {
|
||||
show_context_menu(self.gtk_context_menu(), widget, position)
|
||||
}
|
||||
|
||||
pub fn gtk_context_menu(&mut self) -> gtk::Menu {
|
||||
|
@ -1197,6 +1180,56 @@ impl dyn crate::IsMenuItem + '_ {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_context_menu(
|
||||
gtk_menu: gtk::Menu,
|
||||
widget: &impl IsA<gtk::Widget>,
|
||||
position: Option<Position>,
|
||||
) {
|
||||
let (pos, window) = if let Some(pos) = position {
|
||||
let window = widget.window();
|
||||
(
|
||||
pos.to_logical::<i32>(window.as_ref().map(|w| w.scale_factor()).unwrap_or(1) as _)
|
||||
.into(),
|
||||
window,
|
||||
)
|
||||
} else {
|
||||
let window = widget.screen().and_then(|s| s.root_window());
|
||||
(
|
||||
window
|
||||
.as_ref()
|
||||
.and_then(|w| {
|
||||
w.display()
|
||||
.default_seat()
|
||||
.and_then(|s| s.pointer())
|
||||
.map(|s| {
|
||||
let p = s.position();
|
||||
(p.1, p.2)
|
||||
})
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
window,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(window) = window {
|
||||
let mut event = gdk::Event::new(gdk::EventType::ButtonPress);
|
||||
event.set_device(
|
||||
window
|
||||
.display()
|
||||
.default_seat()
|
||||
.and_then(|d| d.pointer())
|
||||
.as_ref(),
|
||||
);
|
||||
gtk_menu.popup_at_rect(
|
||||
&window,
|
||||
&gdk::Rectangle::new(pos.0, pos.1, 0, 0),
|
||||
gdk::Gravity::NorthWest,
|
||||
gdk::Gravity::NorthWest,
|
||||
Some(&event),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl PredfinedMenuItemType {
|
||||
#[cfg(feature = "libxdo")]
|
||||
fn xdo_keys(&self) -> &str {
|
||||
|
|
|
@ -28,7 +28,7 @@ use crate::{
|
|||
icon::{Icon, NativeIcon},
|
||||
items::*,
|
||||
util::{AddOp, Counter},
|
||||
IsMenuItem, MenuEvent, MenuItemType,
|
||||
IsMenuItem, LogicalPosition, MenuEvent, MenuItemType, Position,
|
||||
};
|
||||
|
||||
static COUNTER: Counter = Counter::new();
|
||||
|
@ -153,15 +153,8 @@ impl Menu {
|
|||
unsafe { NSApp().setMainMenu_(nil) }
|
||||
}
|
||||
|
||||
pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) {
|
||||
unsafe {
|
||||
let window: id = msg_send![view, window];
|
||||
let scale_factor: CGFloat = msg_send![window, backingScaleFactor];
|
||||
let view_point = NSPoint::new(x / scale_factor, y / scale_factor);
|
||||
let view_rect: NSRect = msg_send![view, frame];
|
||||
let location = NSPoint::new(view_point.x, view_rect.size.height - view_point.y);
|
||||
msg_send![self.ns_menu, popUpMenuPositioningItem: nil atLocation: location inView: view]
|
||||
}
|
||||
pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) {
|
||||
show_context_menu(self.ns_menu, view, position)
|
||||
}
|
||||
|
||||
pub fn ns_menu(&self) -> *mut std::ffi::c_void {
|
||||
|
@ -532,15 +525,8 @@ impl MenuChild {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) {
|
||||
unsafe {
|
||||
let window: id = msg_send![view, window];
|
||||
let scale_factor: CGFloat = msg_send![window, backingScaleFactor];
|
||||
let view_point = NSPoint::new(x / scale_factor, y / scale_factor);
|
||||
let view_rect: NSRect = msg_send![view, frame];
|
||||
let location = NSPoint::new(view_point.x, view_rect.size.height - view_point.y);
|
||||
msg_send![self.ns_menu.1, popUpMenuPositioningItem: nil atLocation: location inView: view]
|
||||
}
|
||||
pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) {
|
||||
show_context_menu(self.ns_menu.1, view, position)
|
||||
}
|
||||
|
||||
pub fn set_windows_menu_for_nsapp(&self) {
|
||||
|
@ -977,6 +963,29 @@ fn menuitem_set_native_icon(menuitem: id, icon: Option<NativeIcon>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_context_menu(ns_menu: id, view: id, position: Option<Position>) {
|
||||
unsafe {
|
||||
let window: id = msg_send![view, window];
|
||||
let scale_factor: CGFloat = msg_send![window, backingScaleFactor];
|
||||
let (location, in_view) = if let Some(pos) = position.map(|p| p.to_logical(scale_factor)) {
|
||||
let view_rect: NSRect = msg_send![view, frame];
|
||||
let location = NSPoint::new(pos.x, view_rect.size.height - pos.y);
|
||||
(location, view)
|
||||
} else {
|
||||
let mouse_location: NSPoint = msg_send![class!(NSEvent), mouseLocation];
|
||||
let pos = Position::Logical(LogicalPosition {
|
||||
x: mouse_location.x,
|
||||
y: mouse_location.y,
|
||||
});
|
||||
let pos = pos.to_logical(scale_factor);
|
||||
let location = NSPoint::new(pos.x, pos.y);
|
||||
(location, nil)
|
||||
};
|
||||
|
||||
msg_send![ns_menu, popUpMenuPositioningItem: nil atLocation: location inView: in_view]
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeIcon {
|
||||
unsafe fn named_img(self) -> id {
|
||||
match self {
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
|||
items::PredfinedMenuItemType,
|
||||
util::{AddOp, Counter},
|
||||
AboutMetadata, CheckMenuItem, IconMenuItem, IsMenuItem, MenuEvent, MenuItem, MenuItemType,
|
||||
PredefinedMenuItem, Submenu,
|
||||
Position, PredefinedMenuItem, Submenu,
|
||||
};
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
|
@ -31,12 +31,13 @@ use windows_sys::Win32::{
|
|||
Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass},
|
||||
WindowsAndMessaging::{
|
||||
AppendMenuW, CreateAcceleratorTableW, CreateMenu, CreatePopupMenu,
|
||||
DestroyAcceleratorTable, DrawMenuBar, EnableMenuItem, GetMenu, GetMenuItemInfoW,
|
||||
InsertMenuW, PostQuitMessage, RemoveMenu, SendMessageW, SetMenu, SetMenuItemInfoW,
|
||||
ShowWindow, TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW, MFS_CHECKED, MFS_DISABLED,
|
||||
MF_BYCOMMAND, MF_BYPOSITION, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP,
|
||||
MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_BITMAP, MIIM_STATE, MIIM_STRING, SW_HIDE,
|
||||
SW_MAXIMIZE, SW_MINIMIZE, TPM_LEFTALIGN, WM_CLOSE, WM_COMMAND, WM_DESTROY,
|
||||
DestroyAcceleratorTable, DrawMenuBar, EnableMenuItem, GetCursorPos, GetMenu,
|
||||
GetMenuItemInfoW, InsertMenuW, PostQuitMessage, RemoveMenu, SendMessageW, SetMenu,
|
||||
SetMenuItemInfoW, ShowWindow, TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW,
|
||||
MFS_CHECKED, MFS_DISABLED, MF_BYCOMMAND, MF_BYPOSITION, MF_CHECKED, MF_DISABLED,
|
||||
MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_BITMAP,
|
||||
MIIM_STATE, MIIM_STRING, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, TPM_LEFTALIGN, WM_CLOSE,
|
||||
WM_COMMAND, WM_DESTROY,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -360,8 +361,8 @@ impl Menu {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
||||
show_context_menu(hwnd, self.hpopupmenu, x, y)
|
||||
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) {
|
||||
show_context_menu(hwnd, self.hpopupmenu, position)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -779,8 +780,8 @@ impl MenuChild {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
||||
show_context_menu(hwnd, self.hpopupmenu, x, y)
|
||||
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) {
|
||||
show_context_menu(hwnd, self.hpopupmenu, position)
|
||||
}
|
||||
|
||||
pub fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {
|
||||
|
@ -826,22 +827,24 @@ fn find_by_id(id: u32, children: &Vec<Rc<RefCell<MenuChild>>>) -> Option<Rc<RefC
|
|||
None
|
||||
}
|
||||
|
||||
fn show_context_menu(hwnd: HWND, hmenu: HMENU, x: f64, y: f64) {
|
||||
fn show_context_menu(hwnd: HWND, hmenu: HMENU, position: Option<Position>) {
|
||||
unsafe {
|
||||
let mut point = POINT {
|
||||
x: x as _,
|
||||
y: y as _,
|
||||
let pt = if let Some(pos) = position {
|
||||
let dpi = util::hwnd_dpi(hwnd);
|
||||
let scale_factor = util::dpi_to_scale_factor(dpi);
|
||||
let pos = pos.to_physical::<i32>(scale_factor);
|
||||
let mut pt = POINT {
|
||||
x: pos.x as _,
|
||||
y: pos.y as _,
|
||||
};
|
||||
ClientToScreen(hwnd, &mut point);
|
||||
TrackPopupMenu(
|
||||
hmenu,
|
||||
TPM_LEFTALIGN,
|
||||
point.x,
|
||||
point.y,
|
||||
0,
|
||||
hwnd,
|
||||
std::ptr::null(),
|
||||
);
|
||||
ClientToScreen(hwnd, &mut pt);
|
||||
pt
|
||||
} else {
|
||||
let mut pt = POINT { x: 0, y: 0 };
|
||||
GetCursorPos(&mut pt);
|
||||
pt
|
||||
};
|
||||
TrackPopupMenu(hmenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, std::ptr::null());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,21 @@
|
|||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::ACCEL;
|
||||
use once_cell::sync::Lazy;
|
||||
use windows_sys::{
|
||||
core::HRESULT,
|
||||
Win32::{
|
||||
Foundation::{FARPROC, HWND, S_OK},
|
||||
Graphics::Gdi::{
|
||||
GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST,
|
||||
},
|
||||
System::LibraryLoader::{GetProcAddress, LoadLibraryW},
|
||||
UI::{
|
||||
HiDpi::{MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE},
|
||||
WindowsAndMessaging::{IsProcessDPIAware, ACCEL},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
|
||||
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
|
||||
|
@ -68,3 +82,85 @@ pub fn get_instance_handle() -> windows_sys::Win32::Foundation::HMODULE {
|
|||
|
||||
unsafe { &__ImageBase as *const _ as _ }
|
||||
}
|
||||
|
||||
fn get_function_impl(library: &str, function: &str) -> FARPROC {
|
||||
let library = encode_wide(library);
|
||||
assert_eq!(function.chars().last(), Some('\0'));
|
||||
|
||||
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
|
||||
let module = unsafe { LoadLibraryW(library.as_ptr()) };
|
||||
if module == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe { GetProcAddress(module, function.as_ptr()) }
|
||||
}
|
||||
|
||||
macro_rules! get_function {
|
||||
($lib:expr, $func:ident) => {
|
||||
crate::platform_impl::platform::util::get_function_impl(
|
||||
$lib,
|
||||
concat!(stringify!($func), '\0'),
|
||||
)
|
||||
.map(|f| unsafe { std::mem::transmute::<_, $func>(f) })
|
||||
};
|
||||
}
|
||||
|
||||
pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
|
||||
pub type GetDpiForMonitor = unsafe extern "system" fn(
|
||||
hmonitor: HMONITOR,
|
||||
dpi_type: MONITOR_DPI_TYPE,
|
||||
dpi_x: *mut u32,
|
||||
dpi_y: *mut u32,
|
||||
) -> HRESULT;
|
||||
|
||||
static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
static GET_DPI_FOR_MONITOR: Lazy<Option<GetDpiForMonitor>> =
|
||||
Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor));
|
||||
|
||||
pub const BASE_DPI: u32 = 96;
|
||||
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
|
||||
dpi as f64 / BASE_DPI as f64
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
let hdc = GetDC(hwnd);
|
||||
if hdc == 0 {
|
||||
panic!("[tao] `GetDC` returned null!");
|
||||
}
|
||||
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
match GetDpiForWindow(hwnd) {
|
||||
0 => BASE_DPI, // 0 is returned if hwnd is invalid
|
||||
dpi => dpi as u32,
|
||||
}
|
||||
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if monitor == 0 {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
|
||||
dpi_x as u32
|
||||
} else {
|
||||
BASE_DPI
|
||||
}
|
||||
} else {
|
||||
// We are on Vista or later.
|
||||
if IsProcessDPIAware() == 1 {
|
||||
// If the process is DPI aware, then scaling must be handled by the application using
|
||||
// this DPI value.
|
||||
GetDeviceCaps(hdc, LOGPIXELSX) as u32
|
||||
} else {
|
||||
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
|
||||
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
|
||||
// application and the WM.
|
||||
BASE_DPI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue