void pMenu::append(Action &action) { action.p.parentMenu = &menu; if(parentWindow) parentWindow->p.updateMenu(); } void pMenu::remove(Action &action) { if(parentWindow) parentWindow->p.updateMenu(); action.p.parentMenu = 0; } void pMenu::setImage(const image &image) { createBitmap(); if(parentWindow) parentWindow->p.updateMenu(); } void pMenu::setText(const string &text) { if(parentWindow) parentWindow->p.updateMenu(); } void pMenu::constructor() { hmenu = 0; createBitmap(); } void pMenu::destructor() { if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } if(parentMenu) { parentMenu->remove(menu); } else if(parentWindow) { //belongs to window's main menubar parentWindow->remove(menu); } } void pMenu::createBitmap() { if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } if(menu.state.image.width && menu.state.image.height) { nall::image nallImage = menu.state.image; nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); nallImage.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) nallImage.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); hbitmap = CreateBitmap(nallImage); } } //Windows actions lack the ability to toggle visibility. //To support this, menus must be destroyed and recreated when toggling any action's visibility. void pMenu::update(Window &parentWindow, Menu *parentMenu) { this->parentMenu = parentMenu; this->parentWindow = &parentWindow; if(hmenu) DestroyMenu(hmenu); hmenu = CreatePopupMenu(); for(auto &action : menu.state.action) { action.p.parentMenu = &menu; action.p.parentWindow = &parentWindow; unsigned enabled = action.state.enabled ? 0 : MF_GRAYED; if(dynamic_cast(&action)) { Menu &item = (Menu&)action; if(action.state.visible) { item.p.update(parentWindow, &menu); AppendMenu(hmenu, MF_STRING | MF_POPUP | enabled, (UINT_PTR)item.p.hmenu, utf16_t(item.state.text)); if(item.state.image.width && item.state.image.height) { MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) //this causes too much spacing, so use a custom checkmark image instead mii.fMask = MIIM_CHECKMARKS; mii.hbmpUnchecked = item.p.hbitmap; SetMenuItemInfo(hmenu, (UINT_PTR)item.p.hmenu, FALSE, &mii); } } } else if(dynamic_cast(&action)) { Separator &item = (Separator&)action; if(action.state.visible) { AppendMenu(hmenu, MF_SEPARATOR | enabled, item.p.id, L""); } } else if(dynamic_cast(&action)) { Item &item = (Item&)action; if(action.state.visible) { AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); if(item.state.image.width && item.state.image.height) { MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) //this causes too much spacing, so use a custom checkmark image instead mii.fMask = MIIM_CHECKMARKS; mii.hbmpUnchecked = item.p.hbitmap; SetMenuItemInfo(hmenu, item.p.id, FALSE, &mii); } } } else if(dynamic_cast(&action)) { CheckItem &item = (CheckItem&)action; if(action.state.visible) { AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); } if(item.state.checked) item.setChecked(); } else if(dynamic_cast(&action)) { RadioItem &item = (RadioItem&)action; if(action.state.visible) { AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); } if(item.state.checked) item.setChecked(); } } }