From 832e4964e711a75b8a60986813714b48cfe08183 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 6 May 2022 18:38:39 +0200 Subject: [PATCH] implement windows --- Cargo.toml | 1 + examples/tao.rs | 5 +- examples/winit.rs | 9 +- src/lib.rs | 5 + src/platform_impl/linux.rs | 4 +- src/platform_impl/windows.rs | 237 ++++++++++++++++++++++------------- src/util.rs | 19 ++- 7 files changed, 180 insertions(+), 100 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a6bbee..3412635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ features = [ "Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_Graphics_Gdi", + "Win32_UI_Shell" ] [target.'cfg(target_os = "linux")'.dependencies] diff --git a/examples/tao.rs b/examples/tao.rs index 8857019..b27dfe2 100644 --- a/examples/tao.rs +++ b/examples/tao.rs @@ -40,7 +40,7 @@ fn main() { let menu_channel = menu_event_receiver(); let mut open_item_disabled = false; - let counter = 0; + let mut counter = 0; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -49,8 +49,9 @@ fn main() { match event.id { _ if event.id == save_item.id() => { println!("Save menu item triggered"); - + counter += 1; save_item.set_label(format!("Save triggered {counter} times")); + if !open_item_disabled { println!("Open item disabled!"); open_item.set_enabled(false); diff --git a/examples/winit.rs b/examples/winit.rs index 8e96eaa..33d049c 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -11,7 +11,7 @@ fn main() { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - let window2 = 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); @@ -28,12 +28,12 @@ fn main() { #[cfg(target_os = "windows")] { menu_bar.init_for_hwnd(window.hwnd() as _); - menu_bar.init_for_hwnd(window2.hwnd() as _); + menu_bar.init_for_hwnd(_window2.hwnd() as _); } let menu_channel = menu_event_receiver(); let mut open_item_disabled = false; - let counter = 0; + let mut counter = 0; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -42,8 +42,9 @@ fn main() { match event.id { _ if event.id == save_item.id() => { println!("Save menu item triggered"); - + counter += 1; save_item.set_label(format!("Save triggered {counter} times")); + if !open_item_disabled { println!("Open item disabled!"); open_item.set_enabled(false); diff --git a/src/lib.rs b/src/lib.rs index 731b02d..0a7e02b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,11 @@ impl Menu { { self.0.init_for_gtk_window(w) } + + #[cfg(target_os = "windows")] + pub fn init_for_hwnd(&self, hwnd: isize) { + self.0.init_for_hwnd(hwnd) + } } #[derive(Clone)] diff --git a/src/platform_impl/linux.rs b/src/platform_impl/linux.rs index 760766a..6d203d1 100644 --- a/src/platform_impl/linux.rs +++ b/src/platform_impl/linux.rs @@ -1,9 +1,11 @@ +#![cfg(target_os = "linux")] + use crate::util::Counter; use gtk::{prelude::*, Orientation}; use parking_lot::Mutex; use std::sync::Arc; -const COUNTER: Counter = Counter::new(); +static COUNTER: Counter = Counter::new(); enum MenuEntryType { Submenu, diff --git a/src/platform_impl/windows.rs b/src/platform_impl/windows.rs index 942e241..e799c5e 100644 --- a/src/platform_impl/windows.rs +++ b/src/platform_impl/windows.rs @@ -1,137 +1,196 @@ -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, +#![cfg(target_os = "windows")] + +use crate::util::{encode_wide, Counter, LOWORD}; +use windows_sys::Win32::{ + Foundation::{HWND, LPARAM, LRESULT, WPARAM}, + UI::{ + Shell::{DefSubclassProc, SetWindowSubclass}, + WindowsAndMessaging::{ + AppendMenuW, CreateMenu, EnableMenuItem, SetMenu, SetMenuItemInfoW, MENUITEMINFOW, + MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, MIIM_STRING, WM_COMMAND, + }, + }, }; -use crate::{util::encode_wide, MenuEntry, IDS_COUNTER}; +static COUNTER: Counter = Counter::new(); -pub struct MenuBar(HMENU); +pub struct Menu(isize); -impl MenuBar { +impl Menu { pub fn new() -> Self { Self(unsafe { CreateMenu() }) } - pub fn add_entry(&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() { + pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + let hmenu = unsafe { CreateMenu() }; + let mut flags = MF_POPUP; + if !enabled { flags |= MF_GRAYED; } - unsafe { - AppendMenuW(self.0, flags, id, encode_wide(entry.title()).as_mut_ptr()); + AppendMenuW( + self.0, + flags, + hmenu as _, + encode_wide(label.as_ref()).as_ptr(), + ) + }; + Submenu { + label: label.as_ref().to_string(), + enabled, + hmenu, + parent_hmenu: self.0, } } pub fn init_for_hwnd(&self, hwnd: isize) { - unsafe { SetMenu(hwnd, self.0) }; + unsafe { + SetMenu(hwnd, self.0); + SetWindowSubclass(hwnd, Some(menu_subclass_proc), 22, 0); + }; } } -pub struct Menu { - hmenu: HMENU, - parent: HMENU, +#[derive(Clone)] +pub struct Submenu { + label: String, + enabled: bool, + hmenu: isize, + parent_hmenu: isize, } -impl Menu { - pub fn new(_title: impl Into) -> Self { - Self { - hmenu: unsafe { CreateMenu() }, - parent: 0, - } +impl Submenu { + pub fn label(&self) -> String { + self.label.clone() } - pub fn id(&self) -> u64 { - self.hmenu as u64 + pub fn set_label(&mut self, label: impl AsRef) { + self.label = label.as_ref().to_string(); + + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_STRING; + info.dwTypeData = encode_wide(&self.label).as_mut_ptr(); + + unsafe { SetMenuItemInfoW(self.parent_hmenu, self.hmenu as u32, false.into(), &info) }; } - pub fn set_title(&mut self, title: impl Into) { - let mut item_info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; - item_info.cbSize = std::mem::size_of::() 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 enabled(&self) -> bool { + self.enabled } 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) }; + self.enabled = enabled; + unsafe { + EnableMenuItem( + self.parent_hmenu, + self.hmenu as _, + if enabled { MF_ENABLED } else { MF_DISABLED }, + ) + }; } - pub fn add_entry(&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() { + pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + let hmenu = unsafe { CreateMenu() }; + let mut flags = MF_POPUP; + if !enabled { flags |= MF_GRAYED; } - unsafe { AppendMenuW( self.hmenu, flags, - id, - encode_wide(entry.title()).as_mut_ptr(), - ); + hmenu as _, + encode_wide(label.as_ref()).as_ptr(), + ) + }; + Submenu { + label: label.as_ref().to_string(), + enabled, + hmenu, + parent_hmenu: self.hmenu, + } + } + + pub fn add_text_item(&mut self, label: impl AsRef, enabled: bool) -> TextMenuItem { + let id = COUNTER.next(); + let mut flags = MF_POPUP; + if !enabled { + flags |= MF_GRAYED; + } + unsafe { + AppendMenuW( + self.hmenu, + flags, + id as _, + encode_wide(label.as_ref()).as_ptr(), + ) + }; + TextMenuItem { + label: label.as_ref().to_string(), + enabled, + id, + parent_hmenu: self.hmenu, } } } -pub struct MenuItem { +#[derive(Clone)] +pub struct TextMenuItem { + label: String, + enabled: bool, id: u64, - parent: HMENU, + parent_hmenu: isize, } -impl MenuItem { - pub fn new(_title: impl Into) -> Self { - Self { - id: IDS_COUNTER.next(), - parent: 0, - } +impl TextMenuItem { + pub fn label(&self) -> String { + self.label.clone() + } + + pub fn set_label(&mut self, label: impl AsRef) { + self.label = label.as_ref().to_string(); + + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_STRING; + info.dwTypeData = encode_wide(&self.label).as_mut_ptr(); + + unsafe { SetMenuItemInfoW(self.parent_hmenu, self.id as u32, false.into(), &info) }; + } + + pub fn enabled(&self) -> bool { + self.enabled + } + + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + unsafe { + EnableMenuItem( + self.parent_hmenu, + self.id as _, + if enabled { MF_ENABLED } else { MF_DISABLED }, + ) + }; } pub fn id(&self) -> u64 { self.id } - - pub fn set_title(&self, title: impl Into) { - let mut item_info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; - item_info.cbSize = std::mem::size_of::() 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) }; - } +} + +unsafe extern "system" fn menu_subclass_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + _uidsubclass: usize, + _dwrefdata: usize, +) -> LRESULT { + let id = LOWORD(wparam as _); + if msg == WM_COMMAND && 0 < id && (id as u64) < COUNTER.current() { + let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id: id as _ }); + }; + + DefSubclassProc(hwnd, msg, wparam, lparam) } diff --git a/src/util.rs b/src/util.rs index 13a36ac..fa4629b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,14 +1,19 @@ use std::sync::atomic::{AtomicU64, Ordering}; -pub(crate) struct Counter(AtomicU64); +pub struct Counter(AtomicU64); impl Counter { - pub(crate) const fn new() -> Self { + pub const fn new() -> Self { Self(AtomicU64::new(1)) } - pub(crate) fn next(&self) -> u64 { - self.0.fetch_add(1, Ordering::Release) + pub fn next(&self) -> u64 { + self.0.fetch_add(1, Ordering::Relaxed) + } + + #[allow(unused)] + pub fn current(&self) -> u64 { + self.0.load(Ordering::Relaxed) } } @@ -18,3 +23,9 @@ pub fn encode_wide(string: impl AsRef) -> Vec { .chain(std::iter::once(0)) .collect() } + +#[cfg(target_os = "windows")] +#[allow(non_snake_case)] +pub fn LOWORD(dword: u32) -> u16 { + (dword & 0xFFFF) as u16 +}