mirror of
https://github.com/italicsjenga/muda.git
synced 2024-12-23 20:11:29 +11:00
implement windows
This commit is contained in:
parent
bac4dbd1ab
commit
832e4964e7
|
@ -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]
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
19
src/util.rs
19
src/util.rs
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue