mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 12:01:31 +11:00
* feat: add `IconMenuItem` * Linux * macOS
This commit is contained in:
parent
1c4587efe5
commit
7fc1b02cac
5
.changes/icon-menu-item.md
Normal file
5
.changes/icon-menu-item.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"muda": "minor"
|
||||
---
|
||||
|
||||
Add `IconMenuItem`
|
10
Cargo.toml
10
Cargo.toml
|
@ -14,7 +14,7 @@ categories = [ "gui" ]
|
|||
crossbeam-channel = "0.5"
|
||||
keyboard-types = "0.6"
|
||||
once_cell = "1"
|
||||
thiserror = "1.0.38"
|
||||
thiserror = "1"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
|
||||
version = "0.42"
|
||||
|
@ -24,18 +24,22 @@ features = [
|
|||
"Win32_Graphics_Gdi",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_Globalization",
|
||||
"Win32_UI_Input_KeyboardAndMouse"
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_System_SystemServices",
|
||||
]
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
gdk = "0.15"
|
||||
gtk = { version = "0.15", features = ["v3_22"] }
|
||||
gdk = { version = "0.15", features = ["v3_22"] }
|
||||
gdk-pixbuf = { version = "0.15", features = ["v2_36_8"] }
|
||||
libxdo = "0.6.0"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
cocoa = "0.24"
|
||||
objc = "0.2"
|
||||
png = "0.17"
|
||||
|
||||
[dev-dependencies]
|
||||
winit = "0.27"
|
||||
tao = { git = "https://github.com/tauri-apps/tao", branch = "muda" }
|
||||
image = "0.24"
|
||||
|
|
BIN
examples/icon.png
Normal file
BIN
examples/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -5,7 +5,7 @@
|
|||
#![allow(unused)]
|
||||
use muda::{
|
||||
accelerator::{Accelerator, Code, Modifiers},
|
||||
menu_event_receiver, AboutMetadata, CheckMenuItem, ContextMenu, Menu, MenuItem,
|
||||
menu_event_receiver, AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuItem,
|
||||
PredefinedMenuItem, Submenu,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -77,7 +77,16 @@ fn main() {
|
|||
true,
|
||||
Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)),
|
||||
);
|
||||
let custom_i_2 = MenuItem::new("Custom 2", false, None);
|
||||
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
|
||||
let icon = load_icon(std::path::Path::new(path));
|
||||
let image_item = IconMenuItem::new(
|
||||
"Image custom 1",
|
||||
true,
|
||||
Some(icon),
|
||||
Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyC)),
|
||||
);
|
||||
|
||||
let check_custom_i_1 = CheckMenuItem::new("Check Custom 1", true, true, None);
|
||||
let check_custom_i_2 = CheckMenuItem::new("Check Custom 2", false, true, None);
|
||||
let check_custom_i_3 = CheckMenuItem::new(
|
||||
|
@ -93,7 +102,7 @@ fn main() {
|
|||
|
||||
file_m.append_items(&[
|
||||
&custom_i_1,
|
||||
&custom_i_2,
|
||||
&image_item,
|
||||
&window_m,
|
||||
&PredefinedMenuItem::separator(),
|
||||
&check_custom_i_1,
|
||||
|
@ -113,11 +122,11 @@ fn main() {
|
|||
}),
|
||||
),
|
||||
&check_custom_i_3,
|
||||
&custom_i_2,
|
||||
&image_item,
|
||||
&custom_i_1,
|
||||
]);
|
||||
|
||||
edit_m.append_items(&[©_i, &paste_i, &PredefinedMenuItem::separator()]);
|
||||
edit_m.append_items(&[©_i, &PredefinedMenuItem::separator(), &paste_i]);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
|
@ -194,3 +203,15 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, x: f64, y: f64) {
|
|||
#[cfg(target_os = "macos")]
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, x, y);
|
||||
}
|
||||
|
||||
fn load_icon(path: &std::path::Path) -> muda::icon::Icon {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let image = image::open(path)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
muda::icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
||||
}
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
#![allow(unused)]
|
||||
use muda::{
|
||||
accelerator::{Accelerator, Code, Modifiers},
|
||||
menu_event_receiver, AboutMetadata, CheckMenuItem, ContextMenu, Menu, MenuItem,
|
||||
menu_event_receiver, AboutMetadata, CheckMenuItem, ContextMenu, IconMenuItem, Menu, MenuItem,
|
||||
PredefinedMenuItem, Submenu,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::{EventLoopBuilderExtMacOS, WindowExtMacOS};
|
||||
#[cfg(target_os = "linux")]
|
||||
use winit::platform::unix::WindowExtUnix;
|
||||
#[cfg(target_os = "windows")]
|
||||
use winit::platform::windows::{EventLoopBuilderExtWindows, WindowExtWindows};
|
||||
use winit::{
|
||||
|
@ -77,7 +79,11 @@ fn main() {
|
|||
true,
|
||||
Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)),
|
||||
);
|
||||
let custom_i_2 = MenuItem::new("Custom 2", false, None);
|
||||
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
|
||||
let icon = load_icon(std::path::Path::new(path));
|
||||
let image_item = IconMenuItem::new("Image Custom 1", true, Some(icon), None);
|
||||
|
||||
let check_custom_i_1 = CheckMenuItem::new("Check Custom 1", true, true, None);
|
||||
let check_custom_i_2 = CheckMenuItem::new("Check Custom 2", false, true, None);
|
||||
let check_custom_i_3 = CheckMenuItem::new(
|
||||
|
@ -93,7 +99,7 @@ fn main() {
|
|||
|
||||
file_m.append_items(&[
|
||||
&custom_i_1,
|
||||
&custom_i_2,
|
||||
&image_item,
|
||||
&window_m,
|
||||
&PredefinedMenuItem::separator(),
|
||||
&check_custom_i_1,
|
||||
|
@ -113,11 +119,11 @@ fn main() {
|
|||
}),
|
||||
),
|
||||
&check_custom_i_3,
|
||||
&custom_i_2,
|
||||
&image_item,
|
||||
&custom_i_1,
|
||||
]);
|
||||
|
||||
edit_m.append_items(&[©_i, &paste_i, &PredefinedMenuItem::separator()]);
|
||||
edit_m.append_items(&[©_i, &PredefinedMenuItem::separator(), &paste_i]);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
|
@ -163,7 +169,7 @@ fn main() {
|
|||
..
|
||||
} => {
|
||||
if window_id == window2.id() {
|
||||
show_context_menu(&window2, &window_m, x, y);
|
||||
show_context_menu(&window2, &file_m, x, y);
|
||||
}
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
|
@ -174,7 +180,7 @@ fn main() {
|
|||
|
||||
if let Ok(event) = menu_channel.try_recv() {
|
||||
if event.id == custom_i_1.id() {
|
||||
file_m.insert(&MenuItem::new("New Menu Item", false, None), 2);
|
||||
file_m.insert(&MenuItem::new("New Menu Item", true, None), 2);
|
||||
}
|
||||
println!("{:?}", event);
|
||||
}
|
||||
|
@ -187,3 +193,15 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, x: f64, y: f64) {
|
|||
#[cfg(target_os = "macos")]
|
||||
menu.show_context_menu_for_nsview(window.ns_view() as _, x, y);
|
||||
}
|
||||
|
||||
fn load_icon(path: &std::path::Path) -> muda::icon::Icon {
|
||||
let (icon_rgba, icon_width, icon_height) = {
|
||||
let image = image::open(path)
|
||||
.expect("Failed to open icon path")
|
||||
.into_rgba8();
|
||||
let (width, height) = image.dimensions();
|
||||
let rgba = image.into_raw();
|
||||
(rgba, width, height)
|
||||
};
|
||||
muda::icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
||||
}
|
||||
|
|
166
src/icon.rs
Normal file
166
src/icon.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// taken from https://github.com/rust-windowing/winit/blob/92fdf5ba85f920262a61cee4590f4a11ad5738d1/src/icon.rs
|
||||
|
||||
use crate::platform_impl::PlatformIcon;
|
||||
use std::{error::Error, fmt, io, mem};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pixel {
|
||||
pub(crate) r: u8,
|
||||
pub(crate) g: u8,
|
||||
pub(crate) b: u8,
|
||||
pub(crate) a: u8,
|
||||
}
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
#[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
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
ByteCountNotDivisibleBy4 { byte_count: usize },
|
||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
||||
/// At least one of your arguments is incorrect.
|
||||
DimensionsVsPixelCount {
|
||||
width: u32,
|
||||
height: u32,
|
||||
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 {
|
||||
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 {
|
||||
width,
|
||||
height,
|
||||
width_x_height,
|
||||
pixel_count,
|
||||
} => 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,
|
||||
),
|
||||
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BadIcon {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
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 {
|
||||
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> {
|
||||
Ok(Icon {
|
||||
inner: PlatformIcon::from_rgba(rgba, width, height)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[cfg(windows)]
|
||||
pub fn from_path<P: AsRef<std::path::Path>>(
|
||||
path: P,
|
||||
size: Option<(u32, u32)>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let win_icon = PlatformIcon::from_path(path, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[cfg(windows)]
|
||||
pub fn from_resource(ordinal: u16, size: Option<(u32, u32)>) -> Result<Self, BadIcon> {
|
||||
let win_icon = PlatformIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
79
src/icon_menu_item.rs
Normal file
79
src/icon_menu_item.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{accelerator::Accelerator, icon::Icon, MenuItemExt, MenuItemType};
|
||||
|
||||
/// A check menu item inside a [`Menu`] or [`Submenu`]
|
||||
/// and usually contains a text and a check mark or a similar toggle
|
||||
/// that corresponds to a checked and unchecked states.
|
||||
///
|
||||
/// [`Menu`]: crate::Menu
|
||||
/// [`Submenu`]: crate::Submenu
|
||||
#[derive(Clone)]
|
||||
pub struct IconMenuItem(pub(crate) crate::platform_impl::IconMenuItem);
|
||||
|
||||
unsafe impl MenuItemExt for IconMenuItem {
|
||||
fn type_(&self) -> MenuItemType {
|
||||
MenuItemType::Icon
|
||||
}
|
||||
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
|
||||
self
|
||||
}
|
||||
|
||||
fn id(&self) -> u32 {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl IconMenuItem {
|
||||
/// Create a new check menu item.
|
||||
///
|
||||
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
|
||||
/// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`
|
||||
pub fn new<S: AsRef<str>>(
|
||||
text: S,
|
||||
enabled: bool,
|
||||
icon: Option<Icon>,
|
||||
acccelerator: Option<Accelerator>,
|
||||
) -> Self {
|
||||
Self(crate::platform_impl::IconMenuItem::new(
|
||||
text.as_ref(),
|
||||
enabled,
|
||||
icon,
|
||||
acccelerator,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns a unique identifier associated with this submenu.
|
||||
pub fn id(&self) -> u32 {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
/// Get the text for this check menu item.
|
||||
pub fn text(&self) -> String {
|
||||
self.0.text()
|
||||
}
|
||||
|
||||
/// Get the text for this check menu item. `text` could optionally contain
|
||||
/// an `&` before a character to assign this character as the mnemonic
|
||||
/// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`
|
||||
pub fn set_text<S: AsRef<str>>(&self, text: S) {
|
||||
self.0.set_text(text.as_ref())
|
||||
}
|
||||
|
||||
/// Get whether this check menu item is enabled or not.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.0.is_enabled()
|
||||
}
|
||||
|
||||
/// Enable or disable this check menu item.
|
||||
pub fn set_enabled(&self, enabled: bool) {
|
||||
self.0.set_enabled(enabled)
|
||||
}
|
||||
|
||||
/// Change this menu item icon or remove it.
|
||||
pub fn set_icon(&self, icon: Option<Icon>) {
|
||||
self.0.set_icon(icon)
|
||||
}
|
||||
}
|
|
@ -108,6 +108,7 @@ use once_cell::sync::Lazy;
|
|||
pub mod accelerator;
|
||||
mod check_menu_item;
|
||||
mod error;
|
||||
mod icon_menu_item;
|
||||
mod menu;
|
||||
mod menu_item;
|
||||
mod platform_impl;
|
||||
|
@ -121,7 +122,9 @@ extern crate objc;
|
|||
|
||||
pub use self::error::*;
|
||||
pub use check_menu_item::CheckMenuItem;
|
||||
pub use icon_menu_item::IconMenuItem;
|
||||
pub use menu::Menu;
|
||||
pub mod icon;
|
||||
pub use menu_item::MenuItem;
|
||||
pub use predefined::{AboutMetadata, PredefinedMenuItem};
|
||||
pub use submenu::Submenu;
|
||||
|
@ -132,6 +135,7 @@ pub enum MenuItemType {
|
|||
Normal,
|
||||
Predefined,
|
||||
Check,
|
||||
Icon,
|
||||
}
|
||||
|
||||
impl Default for MenuItemType {
|
||||
|
|
|
@ -90,9 +90,12 @@ pub fn register_accelerator<M: IsA<gtk::Widget>>(
|
|||
item: &M,
|
||||
accel_group: &AccelGroup,
|
||||
accelerator: &Accelerator,
|
||||
) {
|
||||
) -> Option<(gdk::ModifierType, u32)> {
|
||||
if let Ok((mods, key)) = parse_accelerator(accelerator) {
|
||||
item.add_accelerator("activate", accel_group, key, mods, gtk::AccelFlags::VISIBLE);
|
||||
Some((mods, key))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
61
src/platform_impl/gtk/icon.rs
Normal file
61
src/platform_impl/gtk/icon.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2014-2021 The winit contributors
|
||||
// Copyright 2021-2022 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use gdk_pixbuf::{Colorspace, Pixbuf};
|
||||
|
||||
use crate::icon::BadIcon;
|
||||
|
||||
/// An icon used for the window titlebar, taskbar, etc.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlatformIcon {
|
||||
raw: Vec<u8>,
|
||||
width: i32,
|
||||
height: i32,
|
||||
row_stride: i32,
|
||||
}
|
||||
|
||||
impl From<PlatformIcon> for Pixbuf {
|
||||
fn from(icon: PlatformIcon) -> Self {
|
||||
Pixbuf::from_mut_slice(
|
||||
icon.raw,
|
||||
gdk_pixbuf::Colorspace::Rgb,
|
||||
true,
|
||||
8,
|
||||
icon.width,
|
||||
icon.height,
|
||||
icon.row_stride,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformIcon {
|
||||
/// 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> {
|
||||
let row_stride =
|
||||
Pixbuf::calculate_rowstride(Colorspace::Rgb, true, 8, width as i32, height as i32);
|
||||
Ok(Self {
|
||||
raw: rgba,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
row_stride,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_pixbuf(&self, w: i32, h: i32) -> Pixbuf {
|
||||
Pixbuf::from_mut_slice(
|
||||
self.raw.clone(),
|
||||
gdk_pixbuf::Colorspace::Rgb,
|
||||
true,
|
||||
8,
|
||||
self.width,
|
||||
self.height,
|
||||
self.row_stride,
|
||||
)
|
||||
.scale_simple(w, h, gdk_pixbuf::InterpType::Bilinear)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
|
@ -3,9 +3,13 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod accelerator;
|
||||
mod icon;
|
||||
|
||||
pub(crate) use icon::PlatformIcon;
|
||||
|
||||
use crate::{
|
||||
accelerator::Accelerator,
|
||||
icon::Icon,
|
||||
predefined::PredfinedMenuItemType,
|
||||
util::{AddOp, Counter},
|
||||
MenuItemType,
|
||||
|
@ -38,7 +42,8 @@ macro_rules! return_if_predefined_item_not_supported {
|
|||
(
|
||||
crate::MenuItemType::Submenu
|
||||
| crate::MenuItemType::Normal
|
||||
| crate::MenuItemType::Check,
|
||||
| crate::MenuItemType::Check
|
||||
| crate::MenuItemType::Icon,
|
||||
_,
|
||||
) => {}
|
||||
_ => return,
|
||||
|
@ -71,6 +76,9 @@ struct MenuChild {
|
|||
checked: bool,
|
||||
is_syncing_checked_state: Rc<AtomicBool>,
|
||||
|
||||
// icon menu item fields
|
||||
icon: Option<Icon>,
|
||||
|
||||
// submenu fields
|
||||
children: Option<Vec<Rc<RefCell<MenuChild>>>>,
|
||||
gtk_menus: HashMap<u32, Vec<(u32, gtk::Menu)>>,
|
||||
|
@ -157,6 +165,21 @@ impl MenuChild {
|
|||
self.is_syncing_checked_state
|
||||
.store(false, Ordering::Release);
|
||||
}
|
||||
|
||||
fn set_icon(&mut self, icon: Option<Icon>) {
|
||||
self.icon = icon.clone();
|
||||
|
||||
let pixbuf = icon.map(|i| i.inner.to_pixbuf(16, 16));
|
||||
for items in self.gtk_menu_items.values() {
|
||||
for i in items {
|
||||
let box_container = i.child().unwrap().downcast::<gtk::Box>().unwrap();
|
||||
box_container.children()[0]
|
||||
.downcast_ref::<gtk::Image>()
|
||||
.unwrap()
|
||||
.set_pixbuf(pixbuf.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerMenu {
|
||||
|
@ -316,6 +339,7 @@ impl Menu {
|
|||
Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone())))
|
||||
}
|
||||
MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))),
|
||||
MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -648,6 +672,7 @@ impl Submenu {
|
|||
Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone())))
|
||||
}
|
||||
MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))),
|
||||
MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -1089,31 +1114,157 @@ impl CheckMenuItem {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IconMenuItem(Rc<RefCell<MenuChild>>);
|
||||
|
||||
impl IconMenuItem {
|
||||
pub fn new(
|
||||
text: &str,
|
||||
enabled: bool,
|
||||
icon: Option<Icon>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> Self {
|
||||
let child = Rc::new(RefCell::new(MenuChild {
|
||||
text: text.to_string(),
|
||||
enabled,
|
||||
icon,
|
||||
accelerator,
|
||||
id: COUNTER.next(),
|
||||
type_: MenuItemType::Icon,
|
||||
gtk_menu_items: HashMap::new(),
|
||||
is_syncing_checked_state: Rc::new(AtomicBool::new(false)),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
Self(child)
|
||||
}
|
||||
|
||||
fn make_gtk_menu_item(
|
||||
&self,
|
||||
menu_id: u32,
|
||||
accel_group: Option<>k::AccelGroup>,
|
||||
add_to_cache: bool,
|
||||
) -> gtk::MenuItem {
|
||||
let mut self_ = self.0.borrow_mut();
|
||||
|
||||
let image = self_
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|i| gtk::Image::from_pixbuf(Some(&i.inner.to_pixbuf(16, 16))))
|
||||
.unwrap_or_else(gtk::Image::default);
|
||||
|
||||
let label = gtk::AccelLabel::builder()
|
||||
.label(&to_gtk_mnemonic(&self_.text))
|
||||
.use_underline(true)
|
||||
.xalign(0.0)
|
||||
.build();
|
||||
|
||||
let box_container = gtk::Box::new(Orientation::Horizontal, 6);
|
||||
let style_context = box_container.style_context();
|
||||
let css_provider = gtk::CssProvider::new();
|
||||
let theme = r#"
|
||||
box {
|
||||
margin-left: -22px;
|
||||
}
|
||||
"#;
|
||||
let _ = css_provider.load_from_data(theme.as_bytes());
|
||||
style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
box_container.pack_start(&image, false, false, 0);
|
||||
box_container.pack_start(&label, true, true, 0);
|
||||
box_container.show_all();
|
||||
|
||||
let item = gtk::MenuItem::builder()
|
||||
.child(&box_container)
|
||||
.sensitive(self_.enabled)
|
||||
.build();
|
||||
|
||||
if let Some(accelerator) = &self_.accelerator {
|
||||
if let Some(accel_group) = accel_group {
|
||||
if let Some((mods, key)) = register_accelerator(&item, accel_group, accelerator) {
|
||||
label.set_accel(key, mods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id = self_.id;
|
||||
item.connect_activate(move |_| {
|
||||
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||
});
|
||||
|
||||
if add_to_cache {
|
||||
self_
|
||||
.gtk_menu_items
|
||||
.entry(menu_id)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(item.clone());
|
||||
}
|
||||
|
||||
item
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
self.0.borrow().id()
|
||||
}
|
||||
pub fn text(&self) -> String {
|
||||
self.0.borrow().text()
|
||||
}
|
||||
|
||||
pub fn set_text(&self, text: &str) {
|
||||
self.0.borrow_mut().set_text(text)
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.0.borrow().is_enabled()
|
||||
}
|
||||
|
||||
pub fn set_enabled(&self, enabled: bool) {
|
||||
self.0.borrow_mut().set_enabled(enabled)
|
||||
}
|
||||
|
||||
pub fn set_icon(&self, icon: Option<Icon>) {
|
||||
self.0.borrow_mut().set_icon(icon)
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn crate::MenuItemExt + '_ {
|
||||
fn get_child(&self) -> Rc<RefCell<MenuChild>> {
|
||||
match self.type_() {
|
||||
MenuItemType::Submenu => {
|
||||
let submenu = self.as_any().downcast_ref::<crate::Submenu>().unwrap();
|
||||
Rc::clone(&submenu.0 .0)
|
||||
}
|
||||
MenuItemType::Normal => {
|
||||
let menuitem = self.as_any().downcast_ref::<crate::MenuItem>().unwrap();
|
||||
Rc::clone(&menuitem.0 .0)
|
||||
}
|
||||
MenuItemType::Check => {
|
||||
let menuitem = self
|
||||
MenuItemType::Submenu => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::CheckMenuItem>()
|
||||
.unwrap();
|
||||
Rc::clone(&menuitem.0 .0)
|
||||
}
|
||||
MenuItemType::Predefined => {
|
||||
let menuitem = self
|
||||
.downcast_ref::<crate::Submenu>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
MenuItemType::Normal => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::MenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
|
||||
MenuItemType::Predefined => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::PredefinedMenuItem>()
|
||||
.unwrap();
|
||||
Rc::clone(&menuitem.0 .0)
|
||||
}
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
MenuItemType::Check => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::CheckMenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
MenuItemType::Icon => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::IconMenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1124,36 +1275,36 @@ impl dyn crate::MenuItemExt + '_ {
|
|||
add_to_cache: bool,
|
||||
) -> gtk::MenuItem {
|
||||
match self.type_() {
|
||||
MenuItemType::Submenu => {
|
||||
let submenu = self.as_any().downcast_ref::<crate::Submenu>().unwrap();
|
||||
submenu
|
||||
MenuItemType::Submenu => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::Submenu>()
|
||||
.unwrap()
|
||||
.0
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache)
|
||||
}
|
||||
MenuItemType::Normal => {
|
||||
let menuitem = self.as_any().downcast_ref::<crate::MenuItem>().unwrap();
|
||||
menuitem
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache),
|
||||
MenuItemType::Normal => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::MenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache)
|
||||
}
|
||||
MenuItemType::Predefined => {
|
||||
let menuitem = self
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache),
|
||||
MenuItemType::Predefined => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::PredefinedMenuItem>()
|
||||
.unwrap();
|
||||
menuitem
|
||||
.unwrap()
|
||||
.0
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache)
|
||||
}
|
||||
MenuItemType::Check => {
|
||||
let menuitem = self
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache),
|
||||
MenuItemType::Check => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::CheckMenuItem>()
|
||||
.unwrap();
|
||||
menuitem
|
||||
.unwrap()
|
||||
.0
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache)
|
||||
}
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache),
|
||||
MenuItemType::Icon => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::IconMenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.make_gtk_menu_item(menu_id, accel_group, add_to_cache),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
src/platform_impl/macos/icon.rs
Normal file
35
src/platform_impl/macos/icon.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::icon::{BadIcon, RgbaIcon};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlatformIcon(RgbaIcon);
|
||||
|
||||
impl PlatformIcon {
|
||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
||||
Ok(PlatformIcon(RgbaIcon::from_rgba(rgba, width, height)?))
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> (u32, u32) {
|
||||
(self.0.width, self.0.height)
|
||||
}
|
||||
|
||||
pub fn to_png(&self) -> Vec<u8> {
|
||||
let mut png = Vec::new();
|
||||
|
||||
{
|
||||
let mut encoder =
|
||||
png::Encoder::new(Cursor::new(&mut png), self.0.width as _, self.0.height as _);
|
||||
encoder.set_color(png::ColorType::Rgba);
|
||||
encoder.set_depth(png::BitDepth::Eight);
|
||||
|
||||
let mut writer = encoder.write_header().unwrap();
|
||||
writer.write_image_data(&self.0.rgba).unwrap();
|
||||
}
|
||||
|
||||
png
|
||||
}
|
||||
}
|
|
@ -3,14 +3,17 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod accelerator;
|
||||
mod icon;
|
||||
mod util;
|
||||
|
||||
pub(crate) use icon::PlatformIcon;
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Once};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{CGFloat, NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem},
|
||||
appkit::{CGFloat, NSApp, NSApplication, NSEventModifierFlags, NSImage, NSMenu, NSMenuItem},
|
||||
base::{id, nil, selector, NO, YES},
|
||||
foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSString},
|
||||
foundation::{NSAutoreleasePool, NSData, NSInteger, NSPoint, NSRect, NSSize, NSString},
|
||||
};
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
|
@ -20,6 +23,7 @@ use objc::{
|
|||
use self::util::{app_name_string, strip_mnemonic};
|
||||
use crate::{
|
||||
accelerator::Accelerator,
|
||||
icon::Icon,
|
||||
predefined::PredfinedMenuItemType,
|
||||
util::{AddOp, Counter},
|
||||
MenuItemExt, MenuItemType,
|
||||
|
@ -31,7 +35,7 @@ static BLOCK_PTR: &str = "mudaMenuItemBlockPtr";
|
|||
/// A generic child in a menu
|
||||
///
|
||||
/// Be careful when cloning this item and treat it as read-only
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct MenuChild {
|
||||
// shared fields between submenus and menu items
|
||||
|
@ -51,6 +55,9 @@ struct MenuChild {
|
|||
// check menu item fields
|
||||
checked: bool,
|
||||
|
||||
// icon menu item fields
|
||||
icon: Option<Icon>,
|
||||
|
||||
// submenu fields
|
||||
children: Option<Vec<Rc<RefCell<MenuChild>>>>,
|
||||
ns_menus: HashMap<u32, Vec<id>>,
|
||||
|
@ -68,6 +75,7 @@ impl Default for MenuChild {
|
|||
accelerator: Default::default(),
|
||||
predefined_item_type: Default::default(),
|
||||
checked: Default::default(),
|
||||
icon: Default::default(),
|
||||
children: Default::default(),
|
||||
ns_menus: Default::default(),
|
||||
ns_menu: (0, 0 as _),
|
||||
|
@ -129,6 +137,15 @@ impl MenuChild {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_icon(&mut self, icon: Option<Icon>) {
|
||||
self.icon = icon.clone();
|
||||
for ns_items in self.ns_menu_items.values() {
|
||||
for &ns_item in ns_items {
|
||||
menuitem_set_icon(ns_item, icon.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -180,6 +197,13 @@ impl Menu {
|
|||
let menuitem = item.as_any().downcast_ref::<crate::MenuItem>().unwrap();
|
||||
menuitem.0 .0.borrow_mut()
|
||||
}
|
||||
MenuItemType::Predefined => {
|
||||
let menuitem = item
|
||||
.as_any()
|
||||
.downcast_ref::<crate::PredefinedMenuItem>()
|
||||
.unwrap();
|
||||
menuitem.0 .0.borrow_mut()
|
||||
}
|
||||
MenuItemType::Check => {
|
||||
let menuitem = item
|
||||
.as_any()
|
||||
|
@ -187,11 +211,8 @@ impl Menu {
|
|||
.unwrap();
|
||||
menuitem.0 .0.borrow_mut()
|
||||
}
|
||||
MenuItemType::Predefined => {
|
||||
let menuitem = item
|
||||
.as_any()
|
||||
.downcast_ref::<crate::PredefinedMenuItem>()
|
||||
.unwrap();
|
||||
MenuItemType::Icon => {
|
||||
let menuitem = item.as_any().downcast_ref::<crate::IconMenuItem>().unwrap();
|
||||
menuitem.0 .0.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +251,7 @@ impl Menu {
|
|||
Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone())))
|
||||
}
|
||||
MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))),
|
||||
MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -306,6 +328,7 @@ impl Submenu {
|
|||
PredefinedMenuItem(item.clone()).make_ns_item_for_menu(menu_id)
|
||||
}
|
||||
MenuItemType::Check => CheckMenuItem(item.clone()).make_ns_item_for_menu(menu_id),
|
||||
MenuItemType::Icon => IconMenuItem(item.clone()).make_ns_item_for_menu(menu_id),
|
||||
};
|
||||
unsafe { ns_submenu.addItem_(ns_item) };
|
||||
}
|
||||
|
@ -399,7 +422,7 @@ impl Submenu {
|
|||
let children = self_.children.as_mut().unwrap();
|
||||
let index = children
|
||||
.iter()
|
||||
.position(|e| e == &child)
|
||||
.position(|e| e.borrow().id == item.id())
|
||||
.ok_or(crate::Error::NotAChildOfThisMenu)?;
|
||||
children.remove(index);
|
||||
|
||||
|
@ -422,6 +445,7 @@ impl Submenu {
|
|||
Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone())))
|
||||
}
|
||||
MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))),
|
||||
MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -697,6 +721,85 @@ impl CheckMenuItem {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct IconMenuItem(Rc<RefCell<MenuChild>>);
|
||||
|
||||
impl IconMenuItem {
|
||||
pub fn new(
|
||||
text: &str,
|
||||
enabled: bool,
|
||||
icon: Option<Icon>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> Self {
|
||||
Self(Rc::new(RefCell::new(MenuChild {
|
||||
type_: MenuItemType::Icon,
|
||||
text: text.to_string(),
|
||||
enabled,
|
||||
id: COUNTER.next(),
|
||||
icon,
|
||||
accelerator,
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn make_ns_item_for_menu(&self, menu_id: u32) -> id {
|
||||
let mut child = self.0.borrow_mut();
|
||||
|
||||
let ns_menu_item = create_ns_menu_item(
|
||||
&child.text,
|
||||
Some(sel!(fireMenuItemAction:)),
|
||||
&child.accelerator,
|
||||
);
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item];
|
||||
let _: () = msg_send![ns_menu_item, setTag:child.id()];
|
||||
|
||||
// Store a raw pointer to the `MenuChild` as an instance variable on the native menu item
|
||||
let ptr = Box::into_raw(Box::new(&*child));
|
||||
(*ns_menu_item).set_ivar(BLOCK_PTR, ptr as usize);
|
||||
|
||||
if !child.enabled {
|
||||
let () = msg_send![ns_menu_item, setEnabled: NO];
|
||||
}
|
||||
|
||||
menuitem_set_icon(ns_menu_item, child.icon.as_ref());
|
||||
}
|
||||
|
||||
child
|
||||
.ns_menu_items
|
||||
.entry(menu_id)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(ns_menu_item);
|
||||
|
||||
ns_menu_item
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
self.0.borrow().id()
|
||||
}
|
||||
|
||||
pub fn text(&self) -> String {
|
||||
self.0.borrow().text()
|
||||
}
|
||||
|
||||
pub fn set_text(&self, text: &str) {
|
||||
self.0.borrow_mut().set_text(text)
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.0.borrow().is_enabled()
|
||||
}
|
||||
|
||||
pub fn set_enabled(&self, enabled: bool) {
|
||||
self.0.borrow_mut().set_enabled(enabled)
|
||||
}
|
||||
|
||||
pub fn set_icon(&self, icon: Option<Icon>) {
|
||||
self.0.borrow_mut().set_icon(icon)
|
||||
}
|
||||
}
|
||||
|
||||
impl PredfinedMenuItemType {
|
||||
pub(crate) fn selector(&self) -> Option<Sel> {
|
||||
match self {
|
||||
|
@ -725,55 +828,76 @@ impl PredfinedMenuItemType {
|
|||
impl dyn MenuItemExt + '_ {
|
||||
fn get_child(&self) -> Rc<RefCell<MenuChild>> {
|
||||
match self.type_() {
|
||||
MenuItemType::Submenu => {
|
||||
let submenu = self.as_any().downcast_ref::<crate::Submenu>().unwrap();
|
||||
Rc::clone(&submenu.0 .0)
|
||||
}
|
||||
MenuItemType::Normal => {
|
||||
let menuitem = self.as_any().downcast_ref::<crate::MenuItem>().unwrap();
|
||||
Rc::clone(&menuitem.0 .0)
|
||||
}
|
||||
MenuItemType::Check => {
|
||||
let menuitem = self
|
||||
MenuItemType::Submenu => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::CheckMenuItem>()
|
||||
.unwrap();
|
||||
Rc::clone(&menuitem.0 .0)
|
||||
}
|
||||
MenuItemType::Predefined => {
|
||||
let menuitem = self
|
||||
.downcast_ref::<crate::Submenu>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
MenuItemType::Normal => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::MenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
MenuItemType::Predefined => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::PredefinedMenuItem>()
|
||||
.unwrap();
|
||||
Rc::clone(&menuitem.0 .0)
|
||||
}
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
MenuItemType::Check => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::CheckMenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
MenuItemType::Icon => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::IconMenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.0
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_ns_item_for_menu(&self, menu_id: u32) -> *mut Object {
|
||||
match self.type_() {
|
||||
MenuItemType::Submenu => {
|
||||
let submenu = self.as_any().downcast_ref::<crate::Submenu>().unwrap();
|
||||
submenu.0.make_ns_item_for_menu(menu_id)
|
||||
}
|
||||
MenuItemType::Normal => {
|
||||
let menuitem = self.as_any().downcast_ref::<crate::MenuItem>().unwrap();
|
||||
menuitem.0.make_ns_item_for_menu(menu_id)
|
||||
}
|
||||
MenuItemType::Check => {
|
||||
let menuitem = self
|
||||
MenuItemType::Submenu => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::CheckMenuItem>()
|
||||
.unwrap();
|
||||
menuitem.0.make_ns_item_for_menu(menu_id)
|
||||
}
|
||||
MenuItemType::Predefined => {
|
||||
let menuitem = self
|
||||
.downcast_ref::<crate::Submenu>()
|
||||
.unwrap()
|
||||
.0
|
||||
.make_ns_item_for_menu(menu_id),
|
||||
MenuItemType::Normal => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::MenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.make_ns_item_for_menu(menu_id),
|
||||
MenuItemType::Predefined => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::PredefinedMenuItem>()
|
||||
.unwrap();
|
||||
menuitem.0.make_ns_item_for_menu(menu_id)
|
||||
}
|
||||
.unwrap()
|
||||
.0
|
||||
.make_ns_item_for_menu(menu_id),
|
||||
MenuItemType::Check => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::CheckMenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.make_ns_item_for_menu(menu_id),
|
||||
MenuItemType::Icon => self
|
||||
.as_any()
|
||||
.downcast_ref::<crate::IconMenuItem>()
|
||||
.unwrap()
|
||||
.0
|
||||
.make_ns_item_for_menu(menu_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -842,16 +966,14 @@ fn create_ns_menu_item(
|
|||
|
||||
let selector = selector.unwrap_or_else(|| Sel::from_ptr(std::ptr::null()));
|
||||
|
||||
let key_equivalent = accelerator
|
||||
.clone()
|
||||
let key_equivalent = (*accelerator)
|
||||
.map(|accel| accel.key_equivalent())
|
||||
.unwrap_or_default();
|
||||
let key_equivalent = NSString::alloc(nil)
|
||||
.init_str(key_equivalent.as_str())
|
||||
.autorelease();
|
||||
|
||||
let modifier_mask = accelerator
|
||||
.clone()
|
||||
let modifier_mask = (*accelerator)
|
||||
.map(|accel| accel.key_modifier_mask())
|
||||
.unwrap_or_else(NSEventModifierFlags::empty);
|
||||
|
||||
|
@ -863,3 +985,30 @@ fn create_ns_menu_item(
|
|||
ns_menu_item.autorelease()
|
||||
}
|
||||
}
|
||||
|
||||
fn menuitem_set_icon(menuitem: id, icon: Option<&Icon>) {
|
||||
if let Some(icon) = icon {
|
||||
let (width, height) = icon.inner.get_size();
|
||||
let icon = icon.inner.to_png();
|
||||
|
||||
let icon_height: f64 = 18.0;
|
||||
let icon_width: f64 = (width as f64) / (height as f64 / icon_height);
|
||||
|
||||
unsafe {
|
||||
let nsdata = NSData::dataWithBytes_length_(
|
||||
nil,
|
||||
icon.as_ptr() as *const std::os::raw::c_void,
|
||||
icon.len() as u64,
|
||||
);
|
||||
|
||||
let nsimage = NSImage::initWithData_(NSImage::alloc(nil), nsdata);
|
||||
let new_size = NSSize::new(icon_width, icon_height);
|
||||
let _: () = msg_send![nsimage, setSize: new_size];
|
||||
let _: () = msg_send![menuitem, setImage: nsimage];
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
let _: () = msg_send![menuitem, setImage: nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,8 @@ impl Accelerator {
|
|||
let raw_key = vk_code & 0x00ff;
|
||||
|
||||
ACCEL {
|
||||
fVirt: virt_key as u8,
|
||||
key: raw_key as u16,
|
||||
fVirt: virt_key,
|
||||
key: raw_key,
|
||||
cmd: menu_id,
|
||||
}
|
||||
}
|
||||
|
|
193
src/platform_impl/windows/icon.rs
Normal file
193
src/platform_impl/windows/icon.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// taken from https://github.com/rust-windowing/winit/blob/92fdf5ba85f920262a61cee4590f4a11ad5738d1/src/platform_impl/windows/icon.rs
|
||||
|
||||
use std::{fmt, io, mem, path::Path, sync::Arc};
|
||||
|
||||
use windows_sys::{
|
||||
core::PCWSTR,
|
||||
Win32::{
|
||||
Foundation::RECT,
|
||||
Graphics::Gdi::{
|
||||
CreateCompatibleDC, CreateDIBSection, DeleteDC, GetDC, ReleaseDC, SelectObject,
|
||||
BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, HBITMAP,
|
||||
},
|
||||
UI::WindowsAndMessaging::{
|
||||
CreateIcon, DestroyIcon, DrawIconEx, LoadImageW, DI_NORMAL, HICON, IMAGE_ICON,
|
||||
LR_DEFAULTSIZE, LR_LOADFROMFILE,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::icon::*;
|
||||
|
||||
use super::util;
|
||||
|
||||
impl Pixel {
|
||||
fn convert_to_bgra(&mut self) {
|
||||
mem::swap(&mut self.r, &mut self.b);
|
||||
}
|
||||
}
|
||||
|
||||
impl RgbaIcon {
|
||||
fn into_windows_icon(self) -> Result<WinIcon, BadIcon> {
|
||||
let 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_ptr() as *mut Pixel, pixel_count) };
|
||||
for pixel in pixels {
|
||||
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
|
||||
pixel.convert_to_bgra();
|
||||
}
|
||||
assert_eq!(and_mask.len(), pixel_count);
|
||||
let handle = unsafe {
|
||||
CreateIcon(
|
||||
0,
|
||||
self.width as i32,
|
||||
self.height as i32,
|
||||
1,
|
||||
(PIXEL_SIZE * 8) as u8,
|
||||
and_mask.as_ptr(),
|
||||
rgba.as_ptr(),
|
||||
)
|
||||
};
|
||||
if handle != 0 {
|
||||
Ok(WinIcon::from_handle(handle))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RaiiIcon {
|
||||
handle: HICON,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WinIcon {
|
||||
inner: Arc<RaiiIcon>,
|
||||
}
|
||||
|
||||
unsafe impl Send for WinIcon {}
|
||||
|
||||
impl WinIcon {
|
||||
pub unsafe fn to_hbitmap(&self) -> HBITMAP {
|
||||
let hdc = CreateCompatibleDC(0);
|
||||
|
||||
let rc = RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
};
|
||||
|
||||
let mut bitmap_info: BITMAPINFO = std::mem::zeroed();
|
||||
bitmap_info.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as _;
|
||||
bitmap_info.bmiHeader.biWidth = rc.right;
|
||||
bitmap_info.bmiHeader.biHeight = rc.bottom;
|
||||
bitmap_info.bmiHeader.biPlanes = 1;
|
||||
bitmap_info.bmiHeader.biBitCount = 32;
|
||||
bitmap_info.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
let h_dc_bitmap = GetDC(0);
|
||||
|
||||
let hbitmap = CreateDIBSection(h_dc_bitmap, &bitmap_info, DIB_RGB_COLORS, 0 as _, 0, 0);
|
||||
|
||||
ReleaseDC(0, h_dc_bitmap);
|
||||
|
||||
let h_bitmap_old = SelectObject(hdc, hbitmap);
|
||||
|
||||
DrawIconEx(
|
||||
hdc,
|
||||
0,
|
||||
0,
|
||||
self.inner.handle,
|
||||
rc.right,
|
||||
rc.bottom,
|
||||
0,
|
||||
0,
|
||||
DI_NORMAL,
|
||||
);
|
||||
|
||||
SelectObject(hdc, h_bitmap_old);
|
||||
DeleteDC(hdc);
|
||||
|
||||
hbitmap
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn from_handle(handle: HICON) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RaiiIcon { handle }),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<(u32, u32)>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
|
||||
let (width, height) = size.unwrap_or((0, 0));
|
||||
|
||||
let wide_path = util::encode_wide(path.as_ref());
|
||||
|
||||
let handle = unsafe {
|
||||
LoadImageW(
|
||||
0,
|
||||
wide_path.as_ptr(),
|
||||
IMAGE_ICON,
|
||||
width as i32,
|
||||
height as i32,
|
||||
LR_DEFAULTSIZE | LR_LOADFROMFILE,
|
||||
)
|
||||
};
|
||||
if handle != 0 {
|
||||
Ok(WinIcon::from_handle(handle as HICON))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_resource(
|
||||
resource_id: u16,
|
||||
size: Option<(u32, u32)>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
|
||||
let (width, height) = size.unwrap_or((0, 0));
|
||||
let handle = unsafe {
|
||||
LoadImageW(
|
||||
util::get_instance_handle(),
|
||||
resource_id as PCWSTR,
|
||||
IMAGE_ICON,
|
||||
width as i32,
|
||||
height as i32,
|
||||
LR_DEFAULTSIZE,
|
||||
)
|
||||
};
|
||||
if handle != 0 {
|
||||
Ok(WinIcon::from_handle(handle as HICON))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RaiiIcon {
|
||||
fn drop(&mut self) {
|
||||
unsafe { DestroyIcon(self.handle) };
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WinIcon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
(*self.inner).fmt(formatter)
|
||||
}
|
||||
}
|
|
@ -3,10 +3,14 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod accelerator;
|
||||
mod icon;
|
||||
mod util;
|
||||
|
||||
pub(crate) use self::icon::WinIcon as PlatformIcon;
|
||||
|
||||
use crate::{
|
||||
accelerator::Accelerator,
|
||||
icon::Icon,
|
||||
predefined::PredfinedMenuItemType,
|
||||
util::{AddOp, Counter},
|
||||
MenuItemType,
|
||||
|
@ -15,7 +19,7 @@ use std::{cell::RefCell, fmt::Debug, rc::Rc};
|
|||
use util::{decode_wide, encode_wide, Accel};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{HWND, LPARAM, LRESULT, POINT, WPARAM},
|
||||
Graphics::Gdi::ClientToScreen,
|
||||
Graphics::Gdi::{ClientToScreen, HBITMAP},
|
||||
UI::{
|
||||
Input::KeyboardAndMouse::{SendInput, INPUT, INPUT_KEYBOARD, KEYEVENTF_KEYUP, VK_CONTROL},
|
||||
Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass},
|
||||
|
@ -26,7 +30,8 @@ use windows_sys::Win32::{
|
|||
SetMenuItemInfoW, ShowWindow, TrackPopupMenu, HACCEL, HMENU, MB_ICONINFORMATION,
|
||||
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_STATE, MIIM_STRING, SW_MINIMIZE, TPM_LEFTALIGN, WM_COMMAND, WM_DESTROY,
|
||||
MIIM_BITMAP, MIIM_STATE, MIIM_STRING, SW_MINIMIZE, TPM_LEFTALIGN, WM_COMMAND,
|
||||
WM_DESTROY,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -57,6 +62,9 @@ struct MenuChild {
|
|||
// check menu item fields
|
||||
checked: bool,
|
||||
|
||||
// icon menu item fields
|
||||
icon: Option<Icon>,
|
||||
|
||||
// submenu fields
|
||||
hmenu: HMENU,
|
||||
hpopupmenu: HMENU,
|
||||
|
@ -164,6 +172,16 @@ impl MenuChild {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn set_icon(&mut self, icon: Option<Icon>) {
|
||||
self.icon = icon.clone();
|
||||
|
||||
let hbitmap = icon.map(|i| unsafe { i.inner.to_hbitmap() }).unwrap_or(0);
|
||||
let info = create_icon_item_info(hbitmap);
|
||||
for parent in &self.parents_hemnu {
|
||||
unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -241,6 +259,14 @@ impl Menu {
|
|||
flags |= MF_CHECKED;
|
||||
}
|
||||
|
||||
child
|
||||
}
|
||||
MenuItemType::Icon => {
|
||||
let item = item.as_any().downcast_ref::<crate::IconMenuItem>().unwrap();
|
||||
let child = &item.0 .0;
|
||||
|
||||
flags |= MF_STRING;
|
||||
|
||||
child
|
||||
}
|
||||
}
|
||||
|
@ -295,11 +321,31 @@ impl Menu {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let child_ = child.borrow();
|
||||
|
||||
if child_.type_ == MenuItemType::Icon {
|
||||
let hbitmap = child_
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|i| unsafe { i.inner.to_hbitmap() })
|
||||
.unwrap_or(0);
|
||||
let info = create_icon_item_info(hbitmap);
|
||||
|
||||
unsafe {
|
||||
SetMenuItemInfoW(self.hmenu, child_.id, false.into(), &info);
|
||||
SetMenuItemInfoW(self.hpopupmenu, child_.id, false.into(), &info);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut child_ = child.borrow_mut();
|
||||
child_.parents_hemnu.push(self.hmenu);
|
||||
child_.parents_hemnu.push(self.hpopupmenu);
|
||||
}
|
||||
|
||||
{
|
||||
let mut children = self.children.borrow_mut();
|
||||
match op {
|
||||
|
@ -342,6 +388,10 @@ impl Menu {
|
|||
.unwrap();
|
||||
&item.0 .0
|
||||
}
|
||||
MenuItemType::Icon => {
|
||||
let item = item.as_any().downcast_ref::<crate::IconMenuItem>().unwrap();
|
||||
&item.0 .0
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
|
@ -383,6 +433,7 @@ impl Menu {
|
|||
Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone())))
|
||||
}
|
||||
MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))),
|
||||
MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -586,6 +637,14 @@ impl Submenu {
|
|||
flags |= MF_CHECKED;
|
||||
}
|
||||
|
||||
child
|
||||
}
|
||||
MenuItemType::Icon => {
|
||||
let item = item.as_any().downcast_ref::<crate::IconMenuItem>().unwrap();
|
||||
let child = &item.0 .0;
|
||||
|
||||
flags |= MF_STRING;
|
||||
|
||||
child
|
||||
}
|
||||
}
|
||||
|
@ -643,12 +702,33 @@ impl Submenu {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let self_ = self.0.borrow();
|
||||
let child_ = child.borrow();
|
||||
|
||||
if child_.type_ == MenuItemType::Icon {
|
||||
let hbitmap = child_
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|i| unsafe { i.inner.to_hbitmap() })
|
||||
.unwrap_or(0);
|
||||
let info = create_icon_item_info(hbitmap);
|
||||
|
||||
unsafe {
|
||||
SetMenuItemInfoW(self_.hmenu, child_.id, false.into(), &info);
|
||||
SetMenuItemInfoW(self_.hpopupmenu, child_.id, false.into(), &info);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let self_ = self.0.borrow();
|
||||
let mut child_ = child.borrow_mut();
|
||||
child_.parents_hemnu.push(self_.hmenu);
|
||||
child_.parents_hemnu.push(self_.hpopupmenu);
|
||||
}
|
||||
|
||||
{
|
||||
let mut self_ = self.0.borrow_mut();
|
||||
let children = self_.children.as_mut().unwrap();
|
||||
|
@ -688,6 +768,10 @@ impl Submenu {
|
|||
.unwrap();
|
||||
&item.0 .0
|
||||
}
|
||||
MenuItemType::Icon => {
|
||||
let item = item.as_any().downcast_ref::<crate::IconMenuItem>().unwrap();
|
||||
&item.0 .0
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
|
@ -733,6 +817,7 @@ impl Submenu {
|
|||
Box::new(crate::PredefinedMenuItem(PredefinedMenuItem(c.clone())))
|
||||
}
|
||||
MenuItemType::Check => Box::new(crate::CheckMenuItem(CheckMenuItem(c.clone()))),
|
||||
MenuItemType::Icon => Box::new(crate::IconMenuItem(IconMenuItem(c.clone()))),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -908,6 +993,53 @@ impl CheckMenuItem {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct IconMenuItem(Rc<RefCell<MenuChild>>);
|
||||
|
||||
impl IconMenuItem {
|
||||
pub fn new(
|
||||
text: &str,
|
||||
enabled: bool,
|
||||
icon: Option<Icon>,
|
||||
accelerator: Option<Accelerator>,
|
||||
) -> Self {
|
||||
Self(Rc::new(RefCell::new(MenuChild {
|
||||
type_: MenuItemType::Icon,
|
||||
text: text.to_string(),
|
||||
enabled,
|
||||
parents_hemnu: Vec::new(),
|
||||
id: COUNTER.next(),
|
||||
accelerator,
|
||||
icon,
|
||||
..Default::default()
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
self.0.borrow().id()
|
||||
}
|
||||
|
||||
pub fn text(&self) -> String {
|
||||
self.0.borrow().text()
|
||||
}
|
||||
|
||||
pub fn set_text(&self, text: &str) {
|
||||
self.0.borrow_mut().set_text(text)
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.0.borrow().is_enabled()
|
||||
}
|
||||
|
||||
pub fn set_enabled(&self, enabled: bool) {
|
||||
self.0.borrow_mut().set_enabled(enabled)
|
||||
}
|
||||
|
||||
pub fn set_icon(&self, icon: Option<Icon>) {
|
||||
self.0.borrow_mut().set_icon(icon)
|
||||
}
|
||||
}
|
||||
|
||||
const MENU_SUBCLASS_ID: usize = 200;
|
||||
const SUBMENU_SUBCLASS_ID: usize = 201;
|
||||
const WM_CLEAR_MENU_DATA: u32 = 600;
|
||||
|
@ -1089,3 +1221,11 @@ fn execute_edit_command(command: EditCommand) {
|
|||
SendInput(4, &inputs as *const _, std::mem::size_of::<INPUT>() as _);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_icon_item_info(hbitmap: HBITMAP) -> MENUITEMINFOW {
|
||||
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
|
||||
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
|
||||
info.fMask = MIIM_BITMAP;
|
||||
info.hbmpItem = hbitmap;
|
||||
info
|
||||
}
|
||||
|
|
|
@ -51,3 +51,20 @@ impl DerefMut for Accel {
|
|||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
// taken from winit's code base
|
||||
// https://github.com/rust-windowing/winit/blob/ee88e38f13fbc86a7aafae1d17ad3cd4a1e761df/src/platform_impl/windows/util.rs#L138
|
||||
pub fn get_instance_handle() -> windows_sys::Win32::Foundation::HINSTANCE {
|
||||
// Gets the instance handle by taking the address of the
|
||||
// pseudo-variable created by the microsoft linker:
|
||||
// https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483
|
||||
|
||||
// This is preferred over GetModuleHandle(NULL) because it also works in DLLs:
|
||||
// https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance
|
||||
|
||||
extern "C" {
|
||||
static __ImageBase: windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER;
|
||||
}
|
||||
|
||||
unsafe { &__ImageBase as *const _ as _ }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue