Skip to content

Commit

Permalink
feat(windows): add theme support (#194)
Browse files Browse the repository at this point in the history
* feat(windows): add theme support

closes #170

* clippy
  • Loading branch information
amrbashir authored May 14, 2024
1 parent 19b85f7 commit e758002
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changes/windows-theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"muda": "patch"
---

On Windows, add `Menu::init_for_hwnd_with_theme` and `Menu::set_theme_for_hwnd` to control the window menu bar theme.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ pub use dpi;
pub use error::*;
pub use icon::{BadIcon, Icon, NativeIcon};
pub use items::*;
pub use menu::Menu;
pub use menu::*;
pub use menu_id::MenuId;

/// An enumeration of all available menu types, useful to match against
Expand Down
31 changes: 31 additions & 0 deletions src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,26 @@ impl Menu {
self.inner.borrow_mut().init_for_hwnd(hwnd)
}

/// Adds this menu to a win32 window using the specified theme.
///
/// See [Menu::init_for_hwnd] for more info.
///
/// Note that the theme only affects the menu bar itself and not submenus or context menu.
#[cfg(target_os = "windows")]
pub fn init_for_hwnd_with_theme(&self, hwnd: isize, theme: MenuTheme) -> crate::Result<()> {
self.inner
.borrow_mut()
.init_for_hwnd_with_theme(hwnd, theme)
}

/// Set a theme for the menu bar on this window.
///
/// Note that the theme only affects the menu bar itself and not submenus or context menu.
#[cfg(target_os = "windows")]
pub fn set_theme_for_hwnd(&self, hwnd: isize, theme: MenuTheme) -> crate::Result<()> {
self.inner.borrow().set_theme_for_hwnd(hwnd, theme)
}

/// Returns The [`HACCEL`](windows_sys::Win32::UI::WindowsAndMessaging::HACCEL) associated with this menu
/// It can be used with [`TranslateAcceleratorW`](windows_sys::Win32::UI::WindowsAndMessaging::TranslateAcceleratorW)
/// in the event loop to enable accelerators
Expand Down Expand Up @@ -361,3 +381,14 @@ impl ContextMenu for Menu {
self.inner.borrow().ns_menu()
}
}

/// The window menu bar theme
#[cfg(windows)]
#[repr(usize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MenuTheme {
Dark = 0,
Light = 1,
Auto = 2,
}
82 changes: 57 additions & 25 deletions src/platform_impl/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
icon::{Icon, NativeIcon},
items::PredefinedMenuItemType,
util::{AddOp, Counter},
AboutMetadata, IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType,
AboutMetadata, IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType, MenuTheme,
};
use std::{
cell::{RefCell, RefMut},
Expand Down Expand Up @@ -100,14 +100,14 @@ pub(crate) struct Menu {
internal_id: u32,
hmenu: HMENU,
hpopupmenu: HMENU,
hwnds: Vec<HWND>,
hwnds: HashMap<HWND, MenuTheme>,
haccel_store: Rc<RefCell<AccelWrapper>>,
children: Vec<Rc<RefCell<MenuChild>>>,
}

impl Drop for Menu {
fn drop(&mut self) {
for hwnd in self.hwnds.clone() {
for hwnd in self.hwnds.keys().copied().collect::<Vec<_>>() {
let _ = self.remove_for_hwnd(hwnd);
}

Expand Down Expand Up @@ -137,7 +137,7 @@ impl Drop for Menu {
}

unsafe {
for hwnd in &self.hwnds {
for hwnd in self.hwnds.keys() {
SetMenu(*hwnd, 0);
RemoveWindowSubclass(*hwnd, Some(menu_subclass_proc), MENU_SUBCLASS_ID);
}
Expand All @@ -157,7 +157,7 @@ impl Menu {
hpopupmenu: unsafe { CreatePopupMenu() },
haccel_store: Rc::new(RefCell::new((0, HashMap::new()))),
children: Vec::new(),
hwnds: Vec::new(),
hwnds: HashMap::new(),
}
}

Expand Down Expand Up @@ -244,7 +244,7 @@ impl Menu {
}

// redraw the menu bar
for hwnd in &self.hwnds {
for hwnd in self.hwnds.keys() {
unsafe { DrawMenuBar(*hwnd) };
}

Expand All @@ -271,7 +271,7 @@ impl Menu {
RemoveMenu(self.hpopupmenu, id, MF_BYCOMMAND);

// redraw the menu bar
for hwnd in &self.hwnds {
for hwnd in self.hwnds.keys() {
DrawMenuBar(*hwnd);
}
}
Expand Down Expand Up @@ -323,12 +323,12 @@ impl Menu {
self.hpopupmenu
}

pub fn init_for_hwnd(&mut self, hwnd: isize) -> crate::Result<()> {
if self.hwnds.iter().any(|h| *h == hwnd) {
pub fn init_for_hwnd_with_theme(&mut self, hwnd: isize, theme: MenuTheme) -> crate::Result<()> {
if self.hwnds.contains_key(&hwnd) {
return Err(crate::Error::AlreadyInitialized);
}

self.hwnds.push(hwnd);
self.hwnds.insert(hwnd, theme);

unsafe {
SetMenu(hwnd, self.hmenu);
Expand All @@ -343,16 +343,15 @@ impl Menu {

Ok(())
}
pub fn init_for_hwnd(&mut self, hwnd: isize) -> crate::Result<()> {
self.init_for_hwnd_with_theme(hwnd, MenuTheme::Auto)
}

pub fn remove_for_hwnd(&mut self, hwnd: isize) -> crate::Result<()> {
let index = self
.hwnds
.iter()
.position(|h| *h == hwnd)
self.hwnds
.remove(&hwnd)
.ok_or(crate::Error::NotInitialized)?;

self.hwnds.remove(index);

unsafe {
SetMenu(hwnd, 0);
DrawMenuBar(hwnd);
Expand All @@ -379,7 +378,7 @@ impl Menu {
}

pub fn hide_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
if !self.hwnds.iter().any(|h| *h == hwnd) {
if !self.hwnds.contains_key(&hwnd) {
return Err(crate::Error::NotInitialized);
}

Expand All @@ -392,7 +391,7 @@ impl Menu {
}

pub fn show_for_hwnd(&self, hwnd: isize) -> crate::Result<()> {
if !self.hwnds.iter().any(|h| *h == hwnd) {
if !self.hwnds.contains_key(&hwnd) {
return Err(crate::Error::NotInitialized);
}

Expand All @@ -406,9 +405,8 @@ impl Menu {

pub fn is_visible_on_hwnd(&self, hwnd: isize) -> bool {
self.hwnds
.iter()
.find(|h| **h == hwnd)
.map(|hwnd| unsafe { GetMenu(*hwnd) } != HMENU::default())
.get(&hwnd)
.map(|_| unsafe { GetMenu(hwnd) } != HMENU::default())
.unwrap_or(false)
}

Expand All @@ -424,6 +422,16 @@ impl Menu {
}
show_context_menu(hwnd, hpopupmenu, position)
}

pub fn set_theme_for_hwnd(&self, hwnd: isize, theme: MenuTheme) -> crate::Result<()> {
if !self.hwnds.contains_key(&hwnd) {
return Err(crate::Error::NotInitialized);
}

unsafe { SendMessageW(hwnd, MENU_UPDATE_THEME, 0, theme as _) };

Ok(())
}
}

/// A generic child in a menu
Expand Down Expand Up @@ -1026,7 +1034,8 @@ fn create_icon_item_info(hbitmap: HBITMAP) -> MENUITEMINFOW {
}

const MENU_SUBCLASS_ID: usize = 200;
const SUBMENU_SUBCLASS_ID: usize = 201;
const MENU_UPDATE_THEME: u32 = 201;
const SUBMENU_SUBCLASS_ID: usize = 202;
const CONTEXT_MENU_SUBCLASS_ID: usize = 203;
const CONTEXT_SUBMENU_SUBCLASS_ID: usize = 204;

Expand All @@ -1039,6 +1048,13 @@ unsafe extern "system" fn menu_subclass_proc(
dwrefdata: usize,
) -> LRESULT {
match msg {
MENU_UPDATE_THEME if uidsubclass == MENU_SUBCLASS_ID => {
let menu = dwrefdata as *mut Box<Menu>;
let theme: MenuTheme = std::mem::transmute(wparam);
(*menu).hwnds.insert(hwnd, theme);
0
}

WM_COMMAND => {
let id = util::LOWORD(wparam as _) as u32;

Expand Down Expand Up @@ -1126,8 +1142,10 @@ unsafe extern "system" fn menu_subclass_proc(
}
}

WM_UAHDRAWMENUITEM | WM_UAHDRAWMENU => {
if dark_menu_bar::should_use_dark_mode(hwnd) {
WM_UAHDRAWMENUITEM | WM_UAHDRAWMENU if uidsubclass == MENU_SUBCLASS_ID => {
let menu = dwrefdata as *mut Box<Menu>;
let theme = (*menu).hwnds.get(&hwnd).copied().unwrap_or(MenuTheme::Auto);
if theme.should_use_dark(hwnd) {
dark_menu_bar::draw(hwnd, msg, wparam, lparam);
0
} else {
Expand All @@ -1138,16 +1156,30 @@ unsafe extern "system" fn menu_subclass_proc(
// DefSubclassProc needs to be called before calling the
// custom dark menu redraw
let res = DefSubclassProc(hwnd, msg, wparam, lparam);
if dark_menu_bar::should_use_dark_mode(hwnd) {

let menu = dwrefdata as *mut Box<Menu>;
let theme = (*menu).hwnds.get(&hwnd).copied().unwrap_or(MenuTheme::Auto);
if theme.should_use_dark(hwnd) {
dark_menu_bar::draw(hwnd, msg, wparam, lparam);
}

res
}

_ => DefSubclassProc(hwnd, msg, wparam, lparam),
}
}

impl MenuTheme {
fn should_use_dark(&self, hwnd: isize) -> bool {
match self {
MenuTheme::Dark => true,
MenuTheme::Auto if dark_menu_bar::should_use_dark_mode(hwnd) => true,
_ => false,
}
}
}

enum EditCommand {
Copy,
Cut,
Expand Down

0 comments on commit e758002

Please sign in to comment.