mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 20:11:29 +11:00
init - initial linux support
This commit is contained in:
commit
a46c53dbe9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "menu-rs"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
windows-sys = { version = "0.34", features = [
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_Graphics_Gdi"
|
||||||
|
] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
parking_lot = "0.12.0"
|
||||||
|
gtk = "0.15"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
winit = "0.26"
|
||||||
|
tao = { path = "../tao", default-features = false }
|
51
examples/tao.rs
Normal file
51
examples/tao.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use menu_rs::Menu;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use tao::platform::unix::WindowExtUnix;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use tao::platform::windows::WindowExtWindows;
|
||||||
|
use tao::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::WindowBuilder,
|
||||||
|
};
|
||||||
|
fn main() {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
|
let window2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
|
|
||||||
|
let mut menu_bar = Menu::new();
|
||||||
|
let mut file_menu = menu_bar.add_submenu("File", true);
|
||||||
|
let mut edit_menu = menu_bar.add_submenu("Edit", true);
|
||||||
|
|
||||||
|
let _open_item = file_menu.add_text_item("Open", true);
|
||||||
|
let mut save_item = file_menu.add_text_item("Save", true);
|
||||||
|
let _quit_item = file_menu.add_text_item("Quit", true);
|
||||||
|
|
||||||
|
let _copy_item = edit_menu.add_text_item("Copy", true);
|
||||||
|
let _cut_item = edit_menu.add_text_item("Cut", true);
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
menu_bar.init_for_hwnd(window.hwnd() as _);
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
menu_bar.init_for_gtk_window(window.gtk_window());
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
menu_bar.init_for_gtk_window(window2.gtk_window());
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
save_item.set_enabled(false);
|
||||||
|
save_item.set_label("Save disabled");
|
||||||
|
}
|
||||||
|
Event::MainEventsCleared => {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
43
examples/winit.rs
Normal file
43
examples/winit.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use menu_rs::Menu;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use winit::platform::windows::WindowExtWindows;
|
||||||
|
use winit::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::WindowBuilder,
|
||||||
|
};
|
||||||
|
fn main() {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
|
|
||||||
|
let mut menu_bar = Menu::new();
|
||||||
|
let mut file_menu = menu_bar.add_submenu("File", true);
|
||||||
|
let mut edit_menu = menu_bar.add_submenu("Edit", true);
|
||||||
|
|
||||||
|
let _open_item = file_menu.add_text_item("Open", true);
|
||||||
|
let mut save_item = file_menu.add_text_item("Save", true);
|
||||||
|
let _quit_item = file_menu.add_text_item("Quit", true);
|
||||||
|
|
||||||
|
let _copy_item = edit_menu.add_text_item("Copy", true);
|
||||||
|
let _cut_item = edit_menu.add_text_item("Cut", true);
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
menu_bar.init_for_hwnd(window.hwnd() as _);
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
save_item.set_enabled(false);
|
||||||
|
}
|
||||||
|
Event::MainEventsCleared => {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
63
src/lib.rs
Normal file
63
src/lib.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
mod platform_impl;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub struct Menu(platform_impl::Menu);
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(platform_impl::Menu::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||||
|
Submenu(self.0.add_submenu(label, enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
|
||||||
|
TextMenuItem(self.0.add_text_item(label, enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn init_for_gtk_window<W>(&self, w: &W)
|
||||||
|
where
|
||||||
|
W: gtk::prelude::IsA<gtk::Container>,
|
||||||
|
{
|
||||||
|
self.0.init_for_gtk_window(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Submenu(platform_impl::Submenu);
|
||||||
|
|
||||||
|
impl Submenu {
|
||||||
|
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||||
|
self.0.set_label(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
self.0.set_enabled(enabled)
|
||||||
|
}
|
||||||
|
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||||
|
Submenu(self.0.add_submenu(label, enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
|
||||||
|
TextMenuItem(self.0.add_text_item(label, enabled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TextMenuItem(platform_impl::TextMenuItem);
|
||||||
|
|
||||||
|
impl TextMenuItem {
|
||||||
|
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||||
|
self.0.set_label(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
self.0.set_enabled(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.0.id()
|
||||||
|
}
|
||||||
|
}
|
230
src/platform_impl/linux.rs
Normal file
230
src/platform_impl/linux.rs
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use gtk::{prelude::*, Orientation};
|
||||||
|
|
||||||
|
use crate::util::Counter;
|
||||||
|
|
||||||
|
const COUNTER: Counter = Counter::new();
|
||||||
|
|
||||||
|
enum MenuEntryType {
|
||||||
|
Submenu,
|
||||||
|
Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MenuEntry {
|
||||||
|
label: String,
|
||||||
|
enabled: bool,
|
||||||
|
entries: Option<Vec<Arc<Mutex<MenuEntry>>>>,
|
||||||
|
etype: MenuEntryType,
|
||||||
|
menu_gtk_items: Option<Arc<Mutex<Vec<(gtk::MenuItem, gtk::Menu)>>>>,
|
||||||
|
item_gtk_items: Option<Arc<Mutex<Vec<gtk::MenuItem>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InnerMenu {
|
||||||
|
entries: Vec<Arc<Mutex<MenuEntry>>>,
|
||||||
|
gtk_items: Vec<(gtk::MenuBar, gtk::Box)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Menu(Arc<Mutex<InnerMenu>>);
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(InnerMenu {
|
||||||
|
entries: Vec::new(),
|
||||||
|
gtk_items: Vec::new(),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||||
|
let label = label.as_ref().to_string();
|
||||||
|
let gtk_items = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let entry = Arc::new(Mutex::new(MenuEntry {
|
||||||
|
label: label.clone(),
|
||||||
|
enabled,
|
||||||
|
entries: Some(Vec::new()),
|
||||||
|
etype: MenuEntryType::Submenu,
|
||||||
|
menu_gtk_items: Some(gtk_items.clone()),
|
||||||
|
item_gtk_items: None,
|
||||||
|
}));
|
||||||
|
self.0.lock().entries.push(entry.clone());
|
||||||
|
Submenu {
|
||||||
|
label,
|
||||||
|
enabled,
|
||||||
|
entry,
|
||||||
|
gtk_items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
|
||||||
|
let label = label.as_ref().to_string();
|
||||||
|
let gtk_items = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let entry = Arc::new(Mutex::new(MenuEntry {
|
||||||
|
label: label.clone(),
|
||||||
|
enabled,
|
||||||
|
entries: None,
|
||||||
|
etype: MenuEntryType::Text,
|
||||||
|
menu_gtk_items: None,
|
||||||
|
item_gtk_items: Some(gtk_items.clone()),
|
||||||
|
}));
|
||||||
|
self.0.lock().entries.push(entry.clone());
|
||||||
|
TextMenuItem {
|
||||||
|
label,
|
||||||
|
enabled,
|
||||||
|
entry,
|
||||||
|
gtk_items,
|
||||||
|
id: COUNTER.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_for_gtk_window<W>(&self, w: &W)
|
||||||
|
where
|
||||||
|
W: IsA<gtk::Container>,
|
||||||
|
{
|
||||||
|
let menu_bar = gtk::MenuBar::new();
|
||||||
|
add_entries_to_menu(&menu_bar, &self.0.lock().entries);
|
||||||
|
|
||||||
|
let vbox = gtk::Box::new(Orientation::Vertical, 0);
|
||||||
|
vbox.pack_start(&menu_bar, false, false, 0);
|
||||||
|
w.add(&vbox);
|
||||||
|
vbox.show_all();
|
||||||
|
|
||||||
|
self.0.lock().gtk_items.push((menu_bar, vbox));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(gtk_menu: &M, entries: &Vec<Arc<Mutex<MenuEntry>>>) {
|
||||||
|
for entry in entries {
|
||||||
|
let mut entry = entry.lock();
|
||||||
|
let gtk_item = gtk::MenuItem::with_label(&entry.label);
|
||||||
|
gtk_menu.append(>k_item);
|
||||||
|
gtk_item.set_sensitive(entry.enabled);
|
||||||
|
if let MenuEntryType::Submenu = entry.etype {
|
||||||
|
let gtk_menu = gtk::Menu::new();
|
||||||
|
gtk_item.set_submenu(Some(>k_menu));
|
||||||
|
add_entries_to_menu(>k_menu, entry.entries.as_ref().unwrap());
|
||||||
|
entry
|
||||||
|
.menu_gtk_items
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.push((gtk_item, gtk_menu));
|
||||||
|
} else {
|
||||||
|
entry.item_gtk_items.as_mut().unwrap().lock().push(gtk_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Submenu {
|
||||||
|
label: String,
|
||||||
|
enabled: bool,
|
||||||
|
entry: Arc<Mutex<MenuEntry>>,
|
||||||
|
gtk_items: Arc<Mutex<Vec<(gtk::MenuItem, gtk::Menu)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Submenu {
|
||||||
|
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||||
|
let label = label.as_ref().to_string();
|
||||||
|
for (item, _) in self.gtk_items.lock().iter() {
|
||||||
|
item.set_label(&label);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.label = label.clone();
|
||||||
|
self.entry.lock().label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
for (item, _) in self.gtk_items.lock().iter() {
|
||||||
|
item.set_sensitive(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.enabled = enabled;
|
||||||
|
self.entry.lock().enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||||
|
let label = label.as_ref().to_string();
|
||||||
|
let gtk_items = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let entry = Arc::new(Mutex::new(MenuEntry {
|
||||||
|
label: label.clone(),
|
||||||
|
enabled,
|
||||||
|
entries: Some(Vec::new()),
|
||||||
|
etype: MenuEntryType::Submenu,
|
||||||
|
menu_gtk_items: Some(gtk_items.clone()),
|
||||||
|
item_gtk_items: None,
|
||||||
|
}));
|
||||||
|
self.entry
|
||||||
|
.lock()
|
||||||
|
.entries
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.push(entry.clone());
|
||||||
|
Submenu {
|
||||||
|
label,
|
||||||
|
enabled,
|
||||||
|
entry,
|
||||||
|
gtk_items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
|
||||||
|
let label = label.as_ref().to_string();
|
||||||
|
let gtk_items = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let entry = Arc::new(Mutex::new(MenuEntry {
|
||||||
|
label: label.clone(),
|
||||||
|
enabled,
|
||||||
|
entries: None,
|
||||||
|
etype: MenuEntryType::Text,
|
||||||
|
menu_gtk_items: None,
|
||||||
|
item_gtk_items: Some(gtk_items.clone()),
|
||||||
|
}));
|
||||||
|
self.entry
|
||||||
|
.lock()
|
||||||
|
.entries
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.push(entry.clone());
|
||||||
|
TextMenuItem {
|
||||||
|
label,
|
||||||
|
enabled,
|
||||||
|
entry,
|
||||||
|
gtk_items,
|
||||||
|
id: COUNTER.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TextMenuItem {
|
||||||
|
label: String,
|
||||||
|
enabled: bool,
|
||||||
|
entry: Arc<Mutex<MenuEntry>>,
|
||||||
|
gtk_items: Arc<Mutex<Vec<gtk::MenuItem>>>,
|
||||||
|
id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextMenuItem {
|
||||||
|
pub fn set_label(&mut self, label: impl AsRef<str>) {
|
||||||
|
let label = label.as_ref().to_string();
|
||||||
|
for item in self.gtk_items.lock().iter() {
|
||||||
|
item.set_label(&label);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.label = label.clone();
|
||||||
|
self.entry.lock().label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
for item in self.gtk_items.lock().iter() {
|
||||||
|
item.set_sensitive(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.enabled = enabled;
|
||||||
|
self.entry.lock().enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
8
src/platform_impl/mod.rs
Normal file
8
src/platform_impl/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
pub use self::platform_impl::*;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
#[path = "windows.rs"]
|
||||||
|
mod platform_impl;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[path = "linux.rs"]
|
||||||
|
mod platform_impl;
|
137
src/platform_impl/windows.rs
Normal file
137
src/platform_impl/windows.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||||
|
AppendMenuW, CreateMenu, EnableMenuItem, SetMenu, SetMenuItemInfoW, HMENU, MENUITEMINFOW,
|
||||||
|
MF_BYCOMMAND, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, MF_STRING, MIIM_STRING,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{util::encode_wide, MenuEntry, IDS_COUNTER};
|
||||||
|
|
||||||
|
pub struct MenuBar(HMENU);
|
||||||
|
|
||||||
|
impl MenuBar {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(unsafe { CreateMenu() })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entry<M: MenuEntry>(&mut self, entry: &mut M) {
|
||||||
|
let mut flags = 0;
|
||||||
|
let id;
|
||||||
|
|
||||||
|
if entry.is_menu() {
|
||||||
|
flags |= MF_POPUP;
|
||||||
|
let menu = entry.platform_menu().unwrap();
|
||||||
|
id = menu.hmenu as _;
|
||||||
|
menu.parent = self.0;
|
||||||
|
} else {
|
||||||
|
flags |= MF_STRING;
|
||||||
|
let item = entry.platform_item().unwrap();
|
||||||
|
id = item.id as _;
|
||||||
|
item.parent = self.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !entry.enabled() {
|
||||||
|
flags |= MF_GRAYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
AppendMenuW(self.0, flags, id, encode_wide(entry.title()).as_mut_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_for_hwnd(&self, hwnd: isize) {
|
||||||
|
unsafe { SetMenu(hwnd, self.0) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Menu {
|
||||||
|
hmenu: HMENU,
|
||||||
|
parent: HMENU,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
pub fn new(_title: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
hmenu: unsafe { CreateMenu() },
|
||||||
|
parent: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.hmenu as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title(&mut self, title: impl Into<String>) {
|
||||||
|
let mut item_info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
|
||||||
|
item_info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
|
||||||
|
item_info.fMask = MIIM_STRING;
|
||||||
|
item_info.dwTypeData = encode_wide(title.into()).as_mut_ptr();
|
||||||
|
|
||||||
|
unsafe { SetMenuItemInfoW(self.parent, self.hmenu as u32, false.into(), &item_info) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
let enabled = if enabled { MF_ENABLED } else { MF_DISABLED };
|
||||||
|
unsafe { EnableMenuItem(self.parent, self.hmenu as u32, MF_BYCOMMAND | enabled) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entry<M: MenuEntry>(&mut self, entry: &mut M) {
|
||||||
|
let mut flags = 0;
|
||||||
|
let id;
|
||||||
|
|
||||||
|
if entry.is_menu() {
|
||||||
|
flags |= MF_POPUP;
|
||||||
|
let menu = entry.platform_menu().unwrap();
|
||||||
|
id = menu.hmenu as _;
|
||||||
|
menu.parent = self.hmenu;
|
||||||
|
} else {
|
||||||
|
flags |= MF_STRING;
|
||||||
|
let item = entry.platform_item().unwrap();
|
||||||
|
id = item.id as _;
|
||||||
|
item.parent = self.hmenu;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !entry.enabled() {
|
||||||
|
flags |= MF_GRAYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
AppendMenuW(
|
||||||
|
self.hmenu,
|
||||||
|
flags,
|
||||||
|
id,
|
||||||
|
encode_wide(entry.title()).as_mut_ptr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MenuItem {
|
||||||
|
id: u64,
|
||||||
|
parent: HMENU,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuItem {
|
||||||
|
pub fn new(_title: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: IDS_COUNTER.next(),
|
||||||
|
parent: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title(&self, title: impl Into<String>) {
|
||||||
|
let mut item_info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
|
||||||
|
item_info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
|
||||||
|
item_info.fMask = MIIM_STRING;
|
||||||
|
item_info.dwTypeData = encode_wide(title.into()).as_mut_ptr();
|
||||||
|
|
||||||
|
unsafe { SetMenuItemInfoW(self.parent, self.id as u32, false.into(), &item_info) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enabled(&self, enabled: bool) {
|
||||||
|
let enabled = if enabled { MF_ENABLED } else { MF_DISABLED };
|
||||||
|
unsafe { EnableMenuItem(self.parent, self.id as u32, MF_BYCOMMAND | enabled) };
|
||||||
|
}
|
||||||
|
}
|
20
src/util.rs
Normal file
20
src/util.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
pub(crate) struct Counter(AtomicU64);
|
||||||
|
|
||||||
|
impl Counter {
|
||||||
|
pub(crate) const fn new() -> Self {
|
||||||
|
Self(AtomicU64::new(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn next(&self) -> u64 {
|
||||||
|
self.0.fetch_add(1, Ordering::Release)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub fn encode_wide(string: impl AsRef<std::ffi::OsStr>) -> Vec<u16> {
|
||||||
|
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect()
|
||||||
|
}
|
Loading…
Reference in a new issue