implement windows

This commit is contained in:
amrbashir 2022-05-06 18:38:39 +02:00
parent bac4dbd1ab
commit 832e4964e7
No known key found for this signature in database
GPG key ID: BBD7A47A2003FF33
7 changed files with 180 additions and 100 deletions

View file

@ -13,6 +13,7 @@ features = [
"Win32_UI_WindowsAndMessaging", "Win32_UI_WindowsAndMessaging",
"Win32_Foundation", "Win32_Foundation",
"Win32_Graphics_Gdi", "Win32_Graphics_Gdi",
"Win32_UI_Shell"
] ]
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]

View file

@ -40,7 +40,7 @@ fn main() {
let menu_channel = menu_event_receiver(); let menu_channel = menu_event_receiver();
let mut open_item_disabled = false; let mut open_item_disabled = false;
let counter = 0; let mut counter = 0;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
@ -49,8 +49,9 @@ fn main() {
match event.id { match event.id {
_ if event.id == save_item.id() => { _ if event.id == save_item.id() => {
println!("Save menu item triggered"); println!("Save menu item triggered");
counter += 1;
save_item.set_label(format!("Save triggered {counter} times")); save_item.set_label(format!("Save triggered {counter} times"));
if !open_item_disabled { if !open_item_disabled {
println!("Open item disabled!"); println!("Open item disabled!");
open_item.set_enabled(false); open_item.set_enabled(false);

View file

@ -11,7 +11,7 @@ fn main() {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap(); 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 menu_bar = Menu::new();
let mut file_menu = menu_bar.add_submenu("File", true); let mut file_menu = menu_bar.add_submenu("File", true);
@ -28,12 +28,12 @@ fn main() {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
menu_bar.init_for_hwnd(window.hwnd() as _); 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 menu_channel = menu_event_receiver();
let mut open_item_disabled = false; let mut open_item_disabled = false;
let counter = 0; let mut counter = 0;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
@ -42,8 +42,9 @@ fn main() {
match event.id { match event.id {
_ if event.id == save_item.id() => { _ if event.id == save_item.id() => {
println!("Save menu item triggered"); println!("Save menu item triggered");
counter += 1;
save_item.set_label(format!("Save triggered {counter} times")); save_item.set_label(format!("Save triggered {counter} times"));
if !open_item_disabled { if !open_item_disabled {
println!("Open item disabled!"); println!("Open item disabled!");
open_item.set_enabled(false); open_item.set_enabled(false);

View file

@ -34,6 +34,11 @@ impl Menu {
{ {
self.0.init_for_gtk_window(w) 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)] #[derive(Clone)]

View file

@ -1,9 +1,11 @@
#![cfg(target_os = "linux")]
use crate::util::Counter; use crate::util::Counter;
use gtk::{prelude::*, Orientation}; use gtk::{prelude::*, Orientation};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
const COUNTER: Counter = Counter::new(); static COUNTER: Counter = Counter::new();
enum MenuEntryType { enum MenuEntryType {
Submenu, Submenu,

View file

@ -1,137 +1,196 @@
use windows_sys::Win32::UI::WindowsAndMessaging::{ #![cfg(target_os = "windows")]
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, 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 { pub fn new() -> Self {
Self(unsafe { CreateMenu() }) Self(unsafe { CreateMenu() })
} }
pub fn add_entry<M: MenuEntry>(&mut self, entry: &mut M) { pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
let mut flags = 0; let hmenu = unsafe { CreateMenu() };
let id; let mut flags = MF_POPUP;
if !enabled {
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; flags |= MF_GRAYED;
} }
unsafe { 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) { 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 { #[derive(Clone)]
hmenu: HMENU, pub struct Submenu {
parent: HMENU, label: String,
enabled: bool,
hmenu: isize,
parent_hmenu: isize,
} }
impl Menu { impl Submenu {
pub fn new(_title: impl Into<String>) -> Self { pub fn label(&self) -> String {
Self { self.label.clone()
hmenu: unsafe { CreateMenu() },
parent: 0,
}
} }
pub fn id(&self) -> u64 { pub fn set_label(&mut self, label: impl AsRef<str>) {
self.hmenu as u64 self.label = label.as_ref().to_string();
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() 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<String>) { pub fn enabled(&self) -> bool {
let mut item_info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; self.enabled
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) { pub fn set_enabled(&mut self, enabled: bool) {
let enabled = if enabled { MF_ENABLED } else { MF_DISABLED }; self.enabled = enabled;
unsafe { EnableMenuItem(self.parent, self.hmenu as u32, MF_BYCOMMAND | enabled) }; unsafe {
EnableMenuItem(
self.parent_hmenu,
self.hmenu as _,
if enabled { MF_ENABLED } else { MF_DISABLED },
)
};
} }
pub fn add_entry<M: MenuEntry>(&mut self, entry: &mut M) { pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
let mut flags = 0; let hmenu = unsafe { CreateMenu() };
let id; let mut flags = MF_POPUP;
if !enabled {
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; flags |= MF_GRAYED;
} }
unsafe { unsafe {
AppendMenuW( AppendMenuW(
self.hmenu, self.hmenu,
flags, flags,
id, hmenu as _,
encode_wide(entry.title()).as_mut_ptr(), 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<str>, 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, id: u64,
parent: HMENU, parent_hmenu: isize,
} }
impl MenuItem { impl TextMenuItem {
pub fn new(_title: impl Into<String>) -> Self { pub fn label(&self) -> String {
Self { self.label.clone()
id: IDS_COUNTER.next(), }
parent: 0,
} pub fn set_label(&mut self, label: impl AsRef<str>) {
self.label = label.as_ref().to_string();
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() 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 { pub fn id(&self) -> u64 {
self.id self.id
} }
}
pub fn set_title(&self, title: impl Into<String>) {
let mut item_info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; unsafe extern "system" fn menu_subclass_proc(
item_info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _; hwnd: HWND,
item_info.fMask = MIIM_STRING; msg: u32,
item_info.dwTypeData = encode_wide(title.into()).as_mut_ptr(); wparam: WPARAM,
lparam: LPARAM,
unsafe { SetMenuItemInfoW(self.parent, self.id as u32, false.into(), &item_info) }; _uidsubclass: usize,
} _dwrefdata: usize,
) -> LRESULT {
pub fn set_enabled(&self, enabled: bool) { let id = LOWORD(wparam as _);
let enabled = if enabled { MF_ENABLED } else { MF_DISABLED }; if msg == WM_COMMAND && 0 < id && (id as u64) < COUNTER.current() {
unsafe { EnableMenuItem(self.parent, self.id as u32, MF_BYCOMMAND | enabled) }; let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id: id as _ });
} };
DefSubclassProc(hwnd, msg, wparam, lparam)
} }

View file

@ -1,14 +1,19 @@
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
pub(crate) struct Counter(AtomicU64); pub struct Counter(AtomicU64);
impl Counter { impl Counter {
pub(crate) const fn new() -> Self { pub const fn new() -> Self {
Self(AtomicU64::new(1)) Self(AtomicU64::new(1))
} }
pub(crate) fn next(&self) -> u64 { pub fn next(&self) -> u64 {
self.0.fetch_add(1, Ordering::Release) 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<std::ffi::OsStr>) -> Vec<u16> {
.chain(std::iter::once(0)) .chain(std::iter::once(0))
.collect() .collect()
} }
#[cfg(target_os = "windows")]
#[allow(non_snake_case)]
pub fn LOWORD(dword: u32) -> u16 {
(dword & 0xFFFF) as u16
}