// Classic Shell (c) 2009-2017, Ivo Beltchev // Open-Shell (c) 2017-2018, The Open-Shell Team // Confidential information of Ivo Beltchev. Not for disclosure or distribution without prior written consent from the author #include "stdafx.h" #include "resource.h" #include "Translations.h" #include "Settings.h" #include "SettingsUI.h" #include "ResourceHelper.h" #include "ItemManager.h" #include "StartMenuDLL.h" #include "StartButton.h" #include "MenuContainer.h" #include "TouchHelper.h" #include "dllmain.h" #include #include #include #include static int START_ICON_SIZE=0; const int START_BUTTON_PADDING=3; const int START_BUTTON_OFFSET=2; const int START_TEXT_PADDING=2; const int BLEND_PRECISION=1000; bool g_bAllowMoveButton; // CStartButton - implementation of a start button (for Windows 8) class CStartButton: public CWindowImpl { public: DECLARE_WND_CLASS_EX(L"OpenShell.CStartButton",CS_DBLCLKS,COLOR_MENU) CStartButton( void ); // message handlers BEGIN_MSG_MAP( CStartButton ) MESSAGE_HANDLER( WM_CREATE, OnCreate ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) MESSAGE_HANDLER( WM_CLOSE, OnClose ) MESSAGE_HANDLER( WM_MOUSEACTIVATE, OnMouseActivate ) MESSAGE_HANDLER( WM_MOUSEMOVE, OnMouseMove ) MESSAGE_HANDLER( WM_ERASEBKGND, OnEraseBkgnd ) MESSAGE_HANDLER( WM_TIMER, OnTimer ) MESSAGE_HANDLER( WM_SETTINGCHANGE, OnSettingChange ) MESSAGE_HANDLER( WM_THEMECHANGED, OnThemeChanged ) MESSAGE_HANDLER( WM_POINTERDOWN, OnPointer ) MESSAGE_HANDLER( WM_POINTERUPDATE, OnPointer ) MESSAGE_HANDLER( WM_POINTERUP, OnPointer ) END_MSG_MAP() void SetPressed( bool bPressed ); void UpdateButton( void ); void TaskBarMouseMove( void ); SIZE GetSize( void ) const { return m_Size; } bool GetSmallIcons( void ) const { return m_bSmallIcons; } protected: LRESULT OnCreate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnClose( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { return 0; } LRESULT OnEraseBkgnd( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { return 1; } LRESULT OnMouseActivate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { return MA_NOACTIVATE; } LRESULT OnMouseMove( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnSettingChange( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnThemeChanged( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnPointer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); private: enum { TIMER_BLEND=1, TIMER_LEAVE=2 }; size_t m_TaskbarId; SIZE m_Size; HBITMAP m_Bitmap, m_Blendmap; unsigned int *m_Bits, *m_BlendBits; HICON m_Icon; HFONT m_Font; bool m_bHot, m_bPressed; bool m_bTrackMouse; bool m_bClassic; bool m_bRTL; bool m_bSmallIcons; int m_HotBlend; // 0..BLEND_PRECISION CWindow m_Tooltip; HTHEME m_Theme; // animations int m_YOffset; int m_Frames[3]; struct Animation { std::vector frames; int duration; // in ms bool bBlend; }; Animation m_Animations[2]; void ParseAnimation( Animation &animation, const std::vector &pixels, int &index, int totalFrames ); void LoadBitmap( void ); void SetHot( bool bHot ); }; CStartButton::CStartButton( void ) { m_Size.cx=m_Size.cy=0; m_TaskbarId=-1; m_Bitmap=m_Blendmap=NULL; m_Bits=m_BlendBits=NULL; m_Icon=NULL; m_Font=NULL; m_bHot=m_bPressed=false; m_bTrackMouse=false; m_bClassic=m_bSmallIcons=false; m_bRTL=false; m_HotBlend=0; m_Theme=NULL; } LRESULT CStartButton::OnCreate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { size_t params=(intptr_t)(((CREATESTRUCT*)lParam)->lpCreateParams); m_bRTL=(params&1)!=0; m_TaskbarId=params>>1; m_bSmallIcons=IsTaskbarSmallIcons(); std::vector modules; m_Icon=NULL; START_ICON_SIZE=0; CString iconPath=GetSettingString(L"StartButtonIcon"); if (_wcsicmp(iconPath,L"none")!=0) { START_ICON_SIZE=GetSettingInt(L"StartButtonIconSize"); if (START_ICON_SIZE==0) START_ICON_SIZE=GetSystemMetrics(m_bSmallIcons?SM_CXSMICON:SM_CXICON); if (START_ICON_SIZE<8) START_ICON_SIZE=8; if (START_ICON_SIZE>64) START_ICON_SIZE=64; m_Icon=LoadIcon(START_ICON_SIZE,iconPath,modules); for (std::vector::const_iterator it=modules.begin();it!=modules.end();++it) FreeLibrary(*it); if (!m_Icon) m_Icon=(HICON)LoadImage(g_Instance,MAKEINTRESOURCE(IDI_APPICON),IMAGE_ICON,START_ICON_SIZE,START_ICON_SIZE,LR_DEFAULTCOLOR); } int dpi=CItemManager::GetDPI(false); m_Font=CreateFont(10*dpi/72,0,0,0,FW_BOLD,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,L"Tahoma"); int val=1; DwmSetWindowAttribute(m_hWnd,DWMWA_EXCLUDED_FROM_PEEK,&val,sizeof(val)); val=DWMFLIP3D_EXCLUDEABOVE; DwmSetWindowAttribute(m_hWnd,DWMWA_FLIP3D_POLICY,&val,sizeof(val)); LoadBitmap(); m_Tooltip=CreateWindowEx(WS_EX_TOPMOST|WS_EX_TOOLWINDOW|WS_EX_TRANSPARENT|(m_bRTL?WS_EX_LAYOUTRTL:0),TOOLTIPS_CLASS,NULL,WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP,0,0,0,0,NULL,NULL,g_Instance,NULL); OnThemeChanged(WM_THEMECHANGED,0,0,bHandled); m_bPressed=true; SetPressed(false); ResizeClient(m_Size.cx,m_Size.cy); bHandled=FALSE; return 0; } LRESULT CStartButton::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if (m_Bitmap) DeleteObject(m_Bitmap); if (m_Blendmap) DeleteObject(m_Blendmap); if (m_Icon) DestroyIcon(m_Icon); if (m_Font) DeleteObject(m_Font); if (m_Theme) CloseThemeData(m_Theme); m_Tooltip.DestroyWindow(); KillTimer(TIMER_BLEND); bHandled=FALSE; return 0; } void CStartButton::UpdateButton( void ) { BLENDFUNCTION func={AC_SRC_OVER,0,255,AC_SRC_ALPHA}; HDC hSrc=CreateCompatibleDC(NULL); RECT rc; GetWindowRect(&rc); SIZE size={rc.right-rc.left,rc.bottom-rc.top}; if (m_bClassic) { if (m_bRTL) SetLayout(hSrc,LAYOUT_RTL); HGDIOBJ bmp0=SelectObject(hSrc,m_Blendmap); RECT rc={0,0,m_Size.cx,m_Size.cy}; FillRect(hSrc,&rc,(HBRUSH)GetStockObject(BLACK_BRUSH)); InflateRect(&rc,-START_BUTTON_OFFSET,-START_BUTTON_OFFSET); int offset=0; if (m_Theme) { int state=m_bPressed?PBS_PRESSED:(m_bHot?PBS_HOT:PBS_NORMAL); DrawThemeBackground(m_Theme,hSrc,BP_PUSHBUTTON,state,&rc,NULL); } else { DrawFrameControl(hSrc,&rc,DFC_BUTTON,DFCS_BUTTONPUSH|(m_bPressed?DFCS_PUSHED:0)); offset=m_bPressed?1:0; } if (m_Icon) DrawIconEx(hSrc,START_BUTTON_PADDING+START_BUTTON_OFFSET+offset,(m_Size.cy-START_ICON_SIZE)/2+offset,m_Icon,0,0,0,NULL,DI_NORMAL|DI_NOMIRROR); rc.left+=START_BUTTON_PADDING+START_ICON_SIZE+START_TEXT_PADDING+offset; rc.top+=START_BUTTON_PADDING+offset; rc.right-=START_BUTTON_PADDING+START_TEXT_PADDING-offset; rc.bottom-=START_BUTTON_PADDING-offset; HFONT font0=(HFONT)SelectObject(hSrc,m_Font); COLORREF color=GetSysColor(COLOR_BTNTEXT); if (m_Theme) { int state=m_bPressed?PBS_PRESSED:(m_bHot?PBS_HOT:PBS_NORMAL); if (FAILED(GetThemeColor(m_Theme,BP_PUSHBUTTON,state,TMT_TEXTCOLOR,&color))) color=GetSysColor(COLOR_BTNTEXT); } SetTextColor(hSrc,color); SetBkMode(hSrc,TRANSPARENT); CString startStr=GetSettingString(L"StartButtonText"); const wchar_t *startText=startStr; if (startText[0]=='$') startText=FindTranslation(startText+1,L"Start"); DrawText(hSrc,startText,-1,&rc,DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER); SelectObject(hSrc,bmp0); // mark the button pixels as opaque for (int y=START_BUTTON_OFFSET;y>24); int r1=(pixel1>>16)&255; int g1=(pixel1>>8)&255; int b1=(pixel1)&255; int a2=(pixel2>>24); int r2=(pixel2>>16)&255; int g2=(pixel2>>8)&255; int b2=(pixel2)&255; int a=a1+(a2-a1)*blend/BLEND_PRECISION; int r=r1+(r2-r1)*blend/BLEND_PRECISION; int g=g1+(g2-g1)*blend/BLEND_PRECISION; int b=b1+(b2-b1)*blend/BLEND_PRECISION; m_BlendBits[i]=(a<<24)|(r<<16)|(g<<8)|b; } HGDIOBJ bmp0=SelectObject(hSrc,m_Blendmap); POINT pos={0,0}; UpdateLayeredWindow(m_hWnd,NULL,NULL,&size,hSrc,&pos,0,&func,ULW_ALPHA); SelectObject(hSrc,bmp0); } } DeleteDC(hSrc); } void CStartButton::SetHot( bool bHot ) { if (m_bHot!=bHot) { m_bHot=bHot; if (!m_bPressed) { if (bHot) CMenuContainer::PlayMenuSound(SOUND_BUTTON_HOVER); SetTimer(TIMER_BLEND,30); } } } LRESULT CStartButton::OnMouseMove( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { SetTimer(TIMER_LEAVE,30); SetHot(true); return 0; } void CStartButton::TaskBarMouseMove( void ) { SetHot(true); SetTimer(CStartButton::TIMER_LEAVE,30); } LRESULT CStartButton::OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if (wParam==TIMER_BLEND) { int duration=m_Animations[m_bHot?0:1].duration; int blend; if (duration>0) { int dp=(30*BLEND_PRECISION)/duration; blend=m_HotBlend+(m_bHot?dp:-dp); if (blend<0) blend=0; if (blend>BLEND_PRECISION) blend=BLEND_PRECISION; } else { blend=m_bHot?BLEND_PRECISION:0; } if (blend!=m_HotBlend) { m_HotBlend=blend; UpdateButton(); } else KillTimer(TIMER_BLEND); } else if (wParam==TIMER_LEAVE) { CPoint pt(GetMessagePos()); if (WindowFromPoint(pt)!=m_hWnd && !PointAroundStartButton(m_TaskbarId)) { KillTimer(TIMER_LEAVE); SetHot(false); } } else bHandled=FALSE; return 0; } LRESULT CStartButton::OnSettingChange( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { UpdateButton(); bHandled=FALSE; return 0; } LRESULT CStartButton::OnThemeChanged( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if (m_Theme) CloseThemeData(m_Theme); m_Theme=NULL; HIGHCONTRAST contrast={sizeof(contrast)}; if (GetWinVersion()>=WIN_VER_WIN8 && SystemParametersInfo(SPI_GETHIGHCONTRAST,sizeof(contrast),&contrast,0) && (contrast.dwFlags&HCF_HIGHCONTRASTON)) { // only use themes on Win8 with high contrast m_Theme=OpenThemeData(m_hWnd,L"button"); UpdateButton(); } return 0; } LRESULT CStartButton::OnPointer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { POINTER_INPUT_TYPE type; GetPointerType2(GET_POINTERID_WPARAM(wParam),&type); if (type==PT_TOUCH) return GetParent().SendMessage(uMsg,wParam,lParam); bHandled=FALSE; return 0; } void CStartButton::SetPressed( bool bPressed ) { if (m_bPressed!=bPressed) { m_bPressed=bPressed; m_HotBlend=m_bHot?BLEND_PRECISION:0; KillTimer(TIMER_BLEND); TOOLINFO tool={sizeof(tool),TTF_CENTERTIP|TTF_SUBCLASS|TTF_IDISHWND|TTF_TRANSPARENT|(m_bRTL?TTF_RTLREADING:0U),m_hWnd}; tool.uId=(UINT_PTR)m_hWnd; CString startStr=GetSettingString(L"StartButtonTip"); const wchar_t *startText=startStr; if (startText[0]=='$') startText=FindTranslation(startText+1,L"Start"); wchar_t buf[256]; Strcpy(buf,_countof(buf),startText); DoEnvironmentSubst(buf,_countof(buf)); tool.lpszText=buf; m_Tooltip.SendMessage(bPressed?TTM_DELTOOL:TTM_ADDTOOL,0,(LPARAM)&tool); UpdateButton(); } } TStartButtonType GetStartButtonType( void ) { bool bDef; TStartButtonType buttonType=(TStartButtonType)GetSettingInt(L"StartButtonType",bDef); if (bDef) { bool bClassic; if (GetWinVersion() &pixels, int &index, int totalFrames ) { animation.duration=0; animation.bBlend=true; if (index>=(int)pixels.size()) { animation.frames.clear(); return; } animation.duration=((pixels[index]&255)*1000)/60; animation.bBlend=((pixels[index]>>16)&255)==1; int ranges=(pixels[index]>>8)&255; if (ranges>0 && index+ranges<(int)pixels.size()) { animation.frames.clear(); for (int r=1;r<=ranges;r++) { int from=pixels[index+r]&255; if (from>totalFrames-1) from=totalFrames-1; int to=(pixels[index+r]>>16)&255; if (to>totalFrames-1) to=totalFrames-1; if (fromto) { for (int i=from;i>=to;i--) animation.frames.push_back(i); } else { animation.frames.push_back(from); } } } index+=ranges+1; } void CStartButton::LoadBitmap( void ) { m_Size.cx=m_Size.cy=0; if (m_Bitmap) DeleteObject(m_Bitmap); if (m_Blendmap) DeleteObject(m_Blendmap); m_Bitmap=m_Blendmap=NULL; m_Bits=m_BlendBits=NULL; TStartButtonType buttonType=GetStartButtonType(); m_bClassic=(buttonType==START_BUTTON_CLASSIC); wchar_t path[_MAX_PATH]; SIZE size={0,0}; if (buttonType==START_BUTTON_CUSTOM) { Strcpy(path,_countof(path),GetSettingString(L"StartButtonPath")); DoEnvironmentSubst(path,_countof(path)); size.cx=GetSettingInt(L"StartButtonSize"); } m_YOffset=0; m_Frames[0]=0; // Normal m_Frames[1]=1; // Hot m_Frames[2]=2; // Pressed m_Animations[0].frames.resize(2); m_Animations[0].frames[0]=0; m_Animations[0].frames[1]=1; m_Animations[0].duration=300; m_Animations[0].bBlend=true; // NH m_Animations[1].frames.resize(2); m_Animations[1].frames[0]=1; m_Animations[1].frames[1]=0; m_Animations[1].duration=300; m_Animations[1].bBlend=true; // HN if (m_bClassic) { // classic theme HDC hdc=CreateCompatibleDC(NULL); HFONT font0=(HFONT)SelectObject(hdc,m_Font); RECT rc={0,0,0,0}; CString startStr=GetSettingString(L"StartButtonText"); const wchar_t *startText=startStr; if (startText[0]=='$') startText=FindTranslation(startText+1,L"Start"); DrawText(hdc,startText,-1,&rc,DT_NOPREFIX|DT_SINGLELINE|DT_CALCRECT); m_Size.cx=rc.right+START_ICON_SIZE+2*START_TEXT_PADDING+2*START_BUTTON_PADDING+2*START_BUTTON_OFFSET; m_Size.cy=rc.bottom; if (m_Size.cy buttonAnim; if (*path) { m_Bitmap=LoadImageFile(path,&size,true,true,&buttonAnim); } if (!m_Bitmap) { int id; int dpi=GetDpi(GetParent()); if (dpi<120) id=IDB_BUTTON96; else if (dpi<144) id=IDB_BUTTON120; else if (dpi<168) id=IDB_BUTTON144; else id=IDB_BUTTON180; m_Bitmap=LoadImageResource(g_Instance,MAKEINTRESOURCE(id),true,true); bResource=true; } BITMAP info; GetObject(m_Bitmap,sizeof(info),&info); m_Size.cx=info.bmWidth; m_Size.cy=info.bmHeight; m_Bits=(unsigned int*)info.bmBits; if (buttonAnim.empty() && info.bmWidth>=10 && (m_Bits[0]&0xFFFFFF)==ANIM_BUTTON_TAG1 && (m_Bits[1]&0xFFFFFF)==ANIM_BUTTON_TAG2) { m_YOffset=(m_Bits[2]>>16)&255; if (m_YOffset>info.bmHeight) m_YOffset=info.bmHeight; if (m_YOffset>0) { int size=info.bmWidth*m_YOffset; buttonAnim.resize(size); memcpy(&buttonAnim[0],m_Bits,size*4); m_Bits+=size; m_Size.cy=info.bmHeight-m_YOffset; } } if (!buttonAnim.empty()) { int total=buttonAnim[2]&255; if (total<1) total=1; if (total>info.bmHeight-1) total=info.bmHeight-1; m_Size.cy/=total; m_Frames[0]=buttonAnim[3]&255; // Normal if (m_Frames[0]>total-1) m_Frames[0]=total-1; m_Frames[1]=buttonAnim[4]&255; // Hot if (m_Frames[1]>total-1) m_Frames[1]=total-1; m_Frames[2]=buttonAnim[5]&255; // Pressed if (m_Frames[2]>total-1) m_Frames[2]=total-1; m_Animations[0].frames[0]=m_Animations[1].frames[1]=m_Frames[0]; m_Animations[0].frames[1]=m_Animations[1].frames[0]=m_Frames[1]; int index=6; ParseAnimation(m_Animations[0],buttonAnim,index,total); ParseAnimation(m_Animations[1],buttonAnim,index,total); } else { m_Size.cy/=3; } } if (m_Size.cx>0) { BITMAPINFO bi={0}; bi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); bi.bmiHeader.biWidth=m_Size.cx; bi.bmiHeader.biHeight=-m_Size.cy; bi.bmiHeader.biPlanes=1; bi.bmiHeader.biBitCount=32; HDC hdc=CreateCompatibleDC(NULL); m_Blendmap=CreateDIBSection(hdc,&bi,DIB_RGB_COLORS,(void**)&m_BlendBits,NULL,0); DeleteDC(hdc); } } static std::map g_StartButtons; HWND CreateStartButton( int taskbarId, HWND taskBar, HWND rebar ) { bool bRTL=(GetWindowLongPtr(rebar,GWL_EXSTYLE)&WS_EX_LAYOUTRTL)!=0; DWORD styleTopmost=GetWindowLongPtr(taskBar,GWL_EXSTYLE)&WS_EX_TOPMOST; CStartButton &button=g_StartButtons[taskbarId]; button.Create(taskBar,NULL,NULL,WS_POPUP,styleTopmost|WS_EX_TOOLWINDOW|WS_EX_LAYERED,0U,(void*)(intptr_t)(taskbarId*2+(bRTL?1:0))); return button.m_hWnd; } void DestroyStartButton( int taskbarId ) { std::map::iterator it=g_StartButtons.find(taskbarId); if (it!=g_StartButtons.end()) { if (it->second.m_hWnd) it->second.DestroyWindow(); g_StartButtons.erase(it); } } void UpdateStartButton( int taskbarId ) { std::map::iterator it=g_StartButtons.find(taskbarId); if (it!=g_StartButtons.end()) it->second.UpdateButton(); } void PressStartButton( int taskbarId, bool bPressed ) { std::map::iterator it=g_StartButtons.find(taskbarId); if (it!=g_StartButtons.end()) it->second.SetPressed(bPressed); } SIZE GetStartButtonSize( int taskbarId ) { std::map::iterator it=g_StartButtons.find(taskbarId); if (it!=g_StartButtons.end()) return it->second.GetSize(); SIZE size={0,0}; return size; } bool IsStartButtonSmallIcons( int taskbarId ) { std::map::iterator it=g_StartButtons.find(taskbarId); if (it!=g_StartButtons.end()) return it->second.GetSmallIcons(); return false; } bool IsTaskbarSmallIcons( void ) { CRegKey regKey; if (regKey.Open(HKEY_CURRENT_USER,L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced")!=ERROR_SUCCESS) return true; DWORD val; return regKey.QueryDWORDValue(L"TaskbarSmallIcons",val)!=ERROR_SUCCESS || val; } void TaskBarMouseMove( int taskbarId ) { std::map::iterator it=g_StartButtons.find(taskbarId); if (it!=g_StartButtons.end()) it->second.TaskBarMouseMove(); }