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" ]
|
default = [ "libxdo" ]
|
||||||
libxdo = [ "dep:libxdo" ]
|
libxdo = [ "dep:libxdo" ]
|
||||||
common-controls-v6 = [ "windows-sys/Win32_UI_Controls" ]
|
common-controls-v6 = [ "windows-sys/Win32_UI_Controls" ]
|
||||||
|
serde = [ "dep:serde" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
keyboard-types = "0.6"
|
keyboard-types = "0.6"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
serde = { version = "1", optional = true }
|
||||||
|
|
||||||
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
|
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
|
||||||
version = "0.48"
|
version = "0.48"
|
||||||
|
@ -30,7 +32,9 @@ features = [
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_Globalization",
|
"Win32_Globalization",
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_System_SystemServices"
|
"Win32_System_SystemServices",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
|
"Win32_System_LibraryLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
[target."cfg(target_os = \"linux\")".dependencies]
|
[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.
|
- `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.
|
- `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)
|
## Dependencies (Linux Only)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
use muda::{
|
use muda::{
|
||||||
accelerator::{Accelerator, Code, Modifiers},
|
accelerator::{Accelerator, Code, Modifiers},
|
||||||
AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem,
|
AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem,
|
||||||
PredefinedMenuItem, Submenu,
|
PhysicalPosition, Position, PredefinedMenuItem, Submenu,
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tao::platform::macos::WindowExtMacOS;
|
use tao::platform::macos::WindowExtMacOS;
|
||||||
|
@ -147,9 +147,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu_channel = MenuEvent::receiver();
|
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| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
@ -163,24 +163,33 @@ fn main() {
|
||||||
window_id,
|
window_id,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if window_id == window2.id() {
|
window_cursor_position.x = position.x;
|
||||||
x = position.x;
|
window_cursor_position.y = position.y;
|
||||||
y = position.y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event:
|
event:
|
||||||
WindowEvent::MouseInput {
|
WindowEvent::MouseInput {
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Released,
|
||||||
button: MouseButton::Right,
|
button: MouseButton::Right,
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
window_id,
|
window_id,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if window_id == window2.id() {
|
show_context_menu(
|
||||||
show_context_menu(&window2, &file_m, x, y);
|
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 => {
|
Event::MainEventsCleared => {
|
||||||
window.request_redraw();
|
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")]
|
#[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")]
|
#[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")]
|
#[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 {
|
fn load_icon(path: &std::path::Path) -> muda::icon::Icon {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
use muda::{
|
use muda::{
|
||||||
accelerator::{Accelerator, Code, Modifiers},
|
accelerator::{Accelerator, Code, Modifiers},
|
||||||
AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem,
|
AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuEvent, MenuItem,
|
||||||
PredefinedMenuItem, Submenu,
|
PhysicalPosition, Position, PredefinedMenuItem, Submenu,
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use winit::platform::macos::{EventLoopBuilderExtMacOS, WindowExtMacOS};
|
use winit::platform::macos::{EventLoopBuilderExtMacOS, WindowExtMacOS};
|
||||||
|
@ -137,9 +137,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu_channel = MenuEvent::receiver();
|
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| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
@ -153,10 +153,8 @@ fn main() {
|
||||||
window_id,
|
window_id,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if window_id == window2.id() {
|
window_cursor_position.x = position.x;
|
||||||
x = position.x;
|
window_cursor_position.y = position.y;
|
||||||
y = position.y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event:
|
event:
|
||||||
|
@ -168,9 +166,20 @@ fn main() {
|
||||||
window_id,
|
window_id,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if window_id == window2.id() {
|
show_context_menu(
|
||||||
show_context_menu(&window2, &file_m, x, y);
|
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 => {
|
Event::MainEventsCleared => {
|
||||||
window.request_redraw();
|
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")]
|
#[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")]
|
#[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 {
|
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();
|
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>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<style>
|
<style>
|
||||||
main {
|
main {{
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}}
|
||||||
</style>
|
</style>
|
||||||
<main>
|
<main>
|
||||||
<h4> WRYYYYYYYYYYYYYYYYYYYYYY! </h4>
|
<h4> WRYYYYYYYYYYYYYYYYYYYYYY! </h4>
|
||||||
|
@ -164,41 +169,66 @@ fn main() -> wry::Result<()> {
|
||||||
<button> Hi </button>
|
<button> Hi </button>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('contextmenu', (e) => {
|
window.addEventListener('contextmenu', (e) => {{
|
||||||
e.preventDefault();
|
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
|
console.log(e)
|
||||||
if (e.button === -1 || e.buttons === 0) {
|
// contextmenu was requested from keyboard
|
||||||
window.ipc.postMessage(`showContextMenu:${e.clientX},${e.clientY}`);
|
if ({condition}) {{
|
||||||
}
|
window.ipc.postMessage(`showContextMenuPos:${{e.clientX}},${{e.clientY}}`);
|
||||||
})
|
}}
|
||||||
window.addEventListener('mouseup', (e) => {
|
}})
|
||||||
if (e.button === 2) {
|
let x = true;
|
||||||
window.ipc.postMessage(`showContextMenu:${e.clientX},${e.clientY}`);
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"#;
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let file_m_c = file_m.clone();
|
||||||
let handler = move |window: &Window, req: String| {
|
let handler = move |window: &Window, req: String| {
|
||||||
if let Some(rest) = req.strip_prefix("showContextMenu:") {
|
if &req == "showContextMenu" {
|
||||||
let (x, y) = rest
|
show_context_menu(window, &file_m_c, None)
|
||||||
|
} else if let Some(rest) = req.strip_prefix("showContextMenuPos:") {
|
||||||
|
let (x, mut y) = rest
|
||||||
.split_once(',')
|
.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();
|
.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)?
|
let webview = WebViewBuilder::new(window)?
|
||||||
.with_html(HTML)?
|
.with_html(&html)?
|
||||||
.with_ipc_handler(handler.clone())
|
.with_ipc_handler(handler.clone())
|
||||||
.build()?;
|
.build()?;
|
||||||
let webview2 = WebViewBuilder::new(window2)?
|
let webview2 = WebViewBuilder::new(window2)?
|
||||||
.with_html(HTML)?
|
.with_html(html)?
|
||||||
.with_ipc_handler(handler)
|
.with_ipc_handler(handler)
|
||||||
.build()?;
|
.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")]
|
#[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")]
|
#[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")]
|
#[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 {
|
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 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`].
|
/// A menu that can be added to a [`Menu`] or another [`Submenu`].
|
||||||
///
|
///
|
||||||
|
@ -155,8 +155,10 @@ impl ContextMenu for Submenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[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>) {
|
||||||
self.0.borrow_mut().show_context_menu_for_hwnd(hwnd, x, y)
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.show_context_menu_for_hwnd(hwnd, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
@ -170,10 +172,14 @@ impl ContextMenu for Submenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[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
|
self.0
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.show_context_menu_for_gtk_window(w, x, y)
|
.show_context_menu_for_gtk_window(w, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -182,8 +188,10 @@ impl ContextMenu for Submenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[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>) {
|
||||||
self.0.borrow_mut().show_context_menu_for_nsview(view, x, y)
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.show_context_menu_for_nsview(view, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
27
src/lib.rs
27
src/lib.rs
|
@ -100,14 +100,13 @@
|
||||||
//! # #[cfg(target_os = "macos")]
|
//! # #[cfg(target_os = "macos")]
|
||||||
//! # let nsview = 0 as *mut objc::runtime::Object;
|
//! # let nsview = 0 as *mut objc::runtime::Object;
|
||||||
//! // --snip--
|
//! // --snip--
|
||||||
//! let x = 100.0;
|
//! let position = muda::PhysicalPosition { x: 100., y: 120. };
|
||||||
//! let y = 120.0;
|
|
||||||
//! #[cfg(target_os = "windows")]
|
//! #[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")]
|
//! #[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")]
|
//! #[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
|
//! # Processing menu events
|
||||||
//!
|
//!
|
||||||
|
@ -133,6 +132,7 @@ use once_cell::sync::{Lazy, OnceCell};
|
||||||
mod about_metadata;
|
mod about_metadata;
|
||||||
pub mod accelerator;
|
pub mod accelerator;
|
||||||
pub mod builders;
|
pub mod builders;
|
||||||
|
mod dpi;
|
||||||
mod error;
|
mod error;
|
||||||
mod items;
|
mod items;
|
||||||
mod menu;
|
mod menu;
|
||||||
|
@ -144,6 +144,7 @@ mod util;
|
||||||
extern crate objc;
|
extern crate objc;
|
||||||
|
|
||||||
pub use about_metadata::AboutMetadata;
|
pub use about_metadata::AboutMetadata;
|
||||||
|
pub use dpi::*;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use items::*;
|
pub use items::*;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
|
@ -250,9 +251,9 @@ pub trait ContextMenu {
|
||||||
|
|
||||||
/// Shows this menu as a context menu inside a win32 window.
|
/// 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")]
|
#[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
|
/// Attach the menu subclass handler to the given hwnd
|
||||||
/// so you can recieve events from that window using [MenuEvent::receiver]
|
/// 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`]
|
/// 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")]
|
#[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.
|
/// Get the underlying gtk menu reserved for context menus.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -277,9 +282,9 @@ pub trait ContextMenu {
|
||||||
|
|
||||||
/// Shows this menu as a context menu for the specified `NSView`.
|
/// 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")]
|
#[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.
|
/// Get the underlying NSMenu reserved for context menus.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
30
src/menu.rs
30
src/menu.rs
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
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
|
/// A root menu that can be added to a Window on Windows and Linux
|
||||||
/// and used as the app global menu on macOS.
|
/// and used as the app global menu on macOS.
|
||||||
|
@ -243,6 +243,16 @@ impl Menu {
|
||||||
self.0.borrow().is_visible_on_gtk_window(window)
|
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
|
/// Returns whether this menu visible on a on a win32 window
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn is_visible_on_hwnd(&self, hwnd: isize) -> bool {
|
pub fn is_visible_on_hwnd(&self, hwnd: isize) -> bool {
|
||||||
|
@ -269,8 +279,8 @@ impl ContextMenu for Menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[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>) {
|
||||||
self.0.borrow().show_context_menu_for_hwnd(hwnd, x, y)
|
self.0.borrow().show_context_menu_for_hwnd(hwnd, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
@ -284,10 +294,14 @@ impl ContextMenu for Menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[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
|
self.0
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.show_context_menu_for_gtk_window(window, x, y)
|
.show_context_menu_for_gtk_window(window, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -296,8 +310,10 @@ impl ContextMenu for Menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[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>) {
|
||||||
self.0.borrow_mut().show_context_menu_for_nsview(view, x, y)
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.show_context_menu_for_nsview(view, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
icon::{Icon, NativeIcon},
|
icon::{Icon, NativeIcon},
|
||||||
items::*,
|
items::*,
|
||||||
util::{AddOp, Counter},
|
util::{AddOp, Counter},
|
||||||
MenuEvent, MenuItemType,
|
MenuEvent, MenuItemType, Position,
|
||||||
};
|
};
|
||||||
use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic};
|
use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic};
|
||||||
use gtk::{prelude::*, Orientation};
|
use gtk::{prelude::*, Orientation};
|
||||||
|
@ -314,24 +314,19 @@ impl Menu {
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_context_menu_for_gtk_window(&self, window: &impl IsA<gtk::Widget>, x: f64, y: f64) {
|
pub fn gtk_menubar_for_gtk_window<W>(&self, window: &W) -> Option<gtk::MenuBar>
|
||||||
if let Some(window) = window.window() {
|
where
|
||||||
let gtk_menu = gtk::Menu::new();
|
W: gtk::prelude::IsA<gtk::ApplicationWindow>,
|
||||||
|
{
|
||||||
|
self.gtk_menubars.get(&(window.as_ptr() as u32)).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
for item in self.items() {
|
pub fn show_context_menu_for_gtk_window(
|
||||||
let gtk_item = item.make_gtk_menu_item(0, None, false).unwrap();
|
&mut self,
|
||||||
gtk_menu.append(>k_item);
|
widget: &impl IsA<gtk::Widget>,
|
||||||
}
|
position: Option<Position>,
|
||||||
gtk_menu.show_all();
|
) {
|
||||||
|
show_context_menu(self.gtk_context_menu(), widget, position)
|
||||||
gtk_menu.popup_at_rect(
|
|
||||||
&window,
|
|
||||||
&gdk::Rectangle::new(x as _, y as _, 0, 0),
|
|
||||||
gdk::Gravity::NorthWest,
|
|
||||||
gdk::Gravity::NorthWest,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gtk_context_menu(&mut self) -> gtk::Menu {
|
pub fn gtk_context_menu(&mut self) -> gtk::Menu {
|
||||||
|
@ -775,24 +770,12 @@ impl MenuChild {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_context_menu_for_gtk_window(&self, window: &impl IsA<gtk::Widget>, x: f64, y: f64) {
|
pub fn show_context_menu_for_gtk_window(
|
||||||
if let Some(window) = window.window() {
|
&mut self,
|
||||||
let gtk_menu = gtk::Menu::new();
|
widget: &impl IsA<gtk::Widget>,
|
||||||
|
position: Option<Position>,
|
||||||
for item in self.items() {
|
) {
|
||||||
let gtk_item = item.make_gtk_menu_item(0, None, false).unwrap();
|
show_context_menu(self.gtk_context_menu(), widget, position)
|
||||||
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 gtk_context_menu(&mut self) -> gtk::Menu {
|
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 {
|
impl PredfinedMenuItemType {
|
||||||
#[cfg(feature = "libxdo")]
|
#[cfg(feature = "libxdo")]
|
||||||
fn xdo_keys(&self) -> &str {
|
fn xdo_keys(&self) -> &str {
|
||||||
|
|
|
@ -28,7 +28,7 @@ use crate::{
|
||||||
icon::{Icon, NativeIcon},
|
icon::{Icon, NativeIcon},
|
||||||
items::*,
|
items::*,
|
||||||
util::{AddOp, Counter},
|
util::{AddOp, Counter},
|
||||||
IsMenuItem, MenuEvent, MenuItemType,
|
IsMenuItem, LogicalPosition, MenuEvent, MenuItemType, Position,
|
||||||
};
|
};
|
||||||
|
|
||||||
static COUNTER: Counter = Counter::new();
|
static COUNTER: Counter = Counter::new();
|
||||||
|
@ -153,15 +153,8 @@ impl Menu {
|
||||||
unsafe { NSApp().setMainMenu_(nil) }
|
unsafe { NSApp().setMainMenu_(nil) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) {
|
pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) {
|
||||||
unsafe {
|
show_context_menu(self.ns_menu, view, position)
|
||||||
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 ns_menu(&self) -> *mut std::ffi::c_void {
|
pub fn ns_menu(&self) -> *mut std::ffi::c_void {
|
||||||
|
@ -532,15 +525,8 @@ impl MenuChild {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_context_menu_for_nsview(&self, view: id, x: f64, y: f64) {
|
pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) {
|
||||||
unsafe {
|
show_context_menu(self.ns_menu.1, view, position)
|
||||||
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 set_windows_menu_for_nsapp(&self) {
|
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 {
|
impl NativeIcon {
|
||||||
unsafe fn named_img(self) -> id {
|
unsafe fn named_img(self) -> id {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
items::PredfinedMenuItemType,
|
items::PredfinedMenuItemType,
|
||||||
util::{AddOp, Counter},
|
util::{AddOp, Counter},
|
||||||
AboutMetadata, CheckMenuItem, IconMenuItem, IsMenuItem, MenuEvent, MenuItem, MenuItemType,
|
AboutMetadata, CheckMenuItem, IconMenuItem, IsMenuItem, MenuEvent, MenuItem, MenuItemType,
|
||||||
PredefinedMenuItem, Submenu,
|
Position, PredefinedMenuItem, Submenu,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cell::{RefCell, RefMut},
|
cell::{RefCell, RefMut},
|
||||||
|
@ -31,12 +31,13 @@ use windows_sys::Win32::{
|
||||||
Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass},
|
Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass},
|
||||||
WindowsAndMessaging::{
|
WindowsAndMessaging::{
|
||||||
AppendMenuW, CreateAcceleratorTableW, CreateMenu, CreatePopupMenu,
|
AppendMenuW, CreateAcceleratorTableW, CreateMenu, CreatePopupMenu,
|
||||||
DestroyAcceleratorTable, DrawMenuBar, EnableMenuItem, GetMenu, GetMenuItemInfoW,
|
DestroyAcceleratorTable, DrawMenuBar, EnableMenuItem, GetCursorPos, GetMenu,
|
||||||
InsertMenuW, PostQuitMessage, RemoveMenu, SendMessageW, SetMenu, SetMenuItemInfoW,
|
GetMenuItemInfoW, InsertMenuW, PostQuitMessage, RemoveMenu, SendMessageW, SetMenu,
|
||||||
ShowWindow, TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW, MFS_CHECKED, MFS_DISABLED,
|
SetMenuItemInfoW, ShowWindow, TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW,
|
||||||
MF_BYCOMMAND, MF_BYPOSITION, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP,
|
MFS_CHECKED, MFS_DISABLED, MF_BYCOMMAND, MF_BYPOSITION, MF_CHECKED, MF_DISABLED,
|
||||||
MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_BITMAP, MIIM_STATE, MIIM_STRING, SW_HIDE,
|
MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_BITMAP,
|
||||||
SW_MAXIMIZE, SW_MINIMIZE, TPM_LEFTALIGN, WM_CLOSE, WM_COMMAND, WM_DESTROY,
|
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)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) {
|
||||||
show_context_menu(hwnd, self.hpopupmenu, x, y)
|
show_context_menu(hwnd, self.hpopupmenu, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,8 +780,8 @@ impl MenuChild {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, x: f64, y: f64) {
|
pub fn show_context_menu_for_hwnd(&self, hwnd: isize, position: Option<Position>) {
|
||||||
show_context_menu(hwnd, self.hpopupmenu, x, y)
|
show_context_menu(hwnd, self.hpopupmenu, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_menu_subclass_for_hwnd(&self, hwnd: isize) {
|
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
|
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 {
|
unsafe {
|
||||||
let mut point = POINT {
|
let pt = if let Some(pos) = position {
|
||||||
x: x as _,
|
let dpi = util::hwnd_dpi(hwnd);
|
||||||
y: y as _,
|
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 pt);
|
||||||
|
pt
|
||||||
|
} else {
|
||||||
|
let mut pt = POINT { x: 0, y: 0 };
|
||||||
|
GetCursorPos(&mut pt);
|
||||||
|
pt
|
||||||
};
|
};
|
||||||
ClientToScreen(hwnd, &mut point);
|
TrackPopupMenu(hmenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, std::ptr::null());
|
||||||
TrackPopupMenu(
|
|
||||||
hmenu,
|
|
||||||
TPM_LEFTALIGN,
|
|
||||||
point.x,
|
|
||||||
point.y,
|
|
||||||
0,
|
|
||||||
hwnd,
|
|
||||||
std::ptr::null(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,21 @@
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
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> {
|
pub fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
|
||||||
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
|
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 _ }
|
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