From f4bdd81f90027bd61b6b539fe02355337554e285 Mon Sep 17 00:00:00 2001 From: Emilio Heredia Date: Sat, 7 Nov 2026 10:39:01 -0600 Subject: [PATCH 1/3] Allow more than two columns in the Classic two-column main menu Every column break now starts a column. The last one still starts the second pane, and earlier ones add columns to the first pane, so a menu with one break looks exactly as before. The Windows 7 style keeps using only its first break. A new setting on the Menu Look tab, Second pane look, picks which column gets the skin's second-pane look: the last column (the default, as before), none (all columns alike), or the first column, drawn on the left. For the first column the background is drawn as usual and then mirrored, so the skin's outer borders stay on the outside; emblems, the user picture and the user name are placed in their pane afterwards. What had to change for that, since the layout assumed column 0 and column 1 in many places: each pane's width is the sum of its columns, items are offset from their own pane's content rectangle, every column is aligned to the bottom on its own (COLUMN_PADDING), and the columns inside a pane get the skin's vertical separators like sub-menu columns do. The icon size of an item, which AddStandardItems picks before the layout, follows the same panes: until now every item after the first column break got the second pane's icon size, so a middle column lost its icons with skins that hide them in the second pane. The pager scrolls the first column, where the pinned programs are, and until now it used the whole first pane for that, which was the same thing. With more columns it is not: the arrows were drawn in the middle of the pane, and hovering or clicking at the top or bottom of any column of the pane hit the pager. GetScrollRect gives the pager its own column. With Second pane look = First that column is in the second pane, whose items can draw composited text (their own opacity setting). Composited text ignores the clip rectangle, so the pager also checks those items before it draws its background again over the spilled text. --- Src/StartMenu/StartMenuDLL/MenuCommands.cpp | 5 +- Src/StartMenu/StartMenuDLL/MenuContainer.cpp | 272 ++++++++++++++----- Src/StartMenu/StartMenuDLL/MenuContainer.h | 20 ++ Src/StartMenu/StartMenuDLL/MenuPaint.cpp | 134 ++++++--- Src/StartMenu/StartMenuDLL/SettingsUI.cpp | 4 + Src/StartMenu/StartMenuDLL/SettingsUI.h | 8 + Src/StartMenu/StartMenuDLL/StartMenuDLL.rc | 13 +- Src/StartMenu/StartMenuDLL/resource.h | 8 + 8 files changed, 350 insertions(+), 114 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/MenuCommands.cpp b/Src/StartMenu/StartMenuDLL/MenuCommands.cpp index ac35f04f7..0251b1fc2 100644 --- a/Src/StartMenu/StartMenuDLL/MenuCommands.cpp +++ b/Src/StartMenu/StartMenuDLL/MenuCommands.cpp @@ -1497,9 +1497,10 @@ void CMenuContainer::ActivateItem( int index, TActivateType type, const POINT *p { // scroll the pager to make this item visible RECT rc=item.itemRect; - OffsetRect(&rc,0,-m_rContent.top); + int scrollTop=GetScrollRect().top; + OffsetRect(&rc,0,-scrollTop); int pos=m_ScrollOffset; - int total=m_Items[m_ScrollCount-1].itemRect.bottom-m_rContent.top-m_ScrollHeight; + int total=m_Items[m_ScrollCount-1].itemRect.bottom-scrollTop-m_ScrollHeight; if (pos>rc.top-m_ScrollButtonSize) pos=rc.top-m_ScrollButtonSize; else if (posid==MENU_LAST) @@ -1090,7 +1094,7 @@ void CMenuContainer::AddStandardItems( void ) continue; if (pStdItem->id==MENU_COLUMN_BREAK && !m_bSubMenu && s_Skin.TwoColumns) - bSecondColumn=true; + breaks++; int stdOptions=GetStdOptions(pStdItem->id); if (!(stdOptions&MENU_ENABLED)) continue; @@ -1271,7 +1275,7 @@ void CMenuContainer::AddStandardItems( void ) item.bSplit=item.bFolder && (item.pStdItem->settings&StdMenuItem::MENU_SPLIT_BUTTON)!=0; // get icon - MenuSkin::TIconSize mainIconSize=!bSecondColumn ? s_Skin.Main_icon_size : s_Skin.Main2_icon_size; + MenuSkin::TIconSize mainIconSize=!IsSecondPaneAfterBreaks(breaks,breakCount) ? s_Skin.Main_icon_size : s_Skin.Main2_icon_size; if (item.bInline && mainIconSize==MenuSkin::ICON_SIZE_NONE) mainIconSize=s_Skin.Main_icon_size; @@ -1499,6 +1503,131 @@ void CMenuContainer::AddStandardItems( void ) } } +// The SecondPane setting in effect +TSecondPane CMenuContainer::GetSecondPaneMode( void ) +{ + // the Windows 7 style always uses the last column, and right-to-left menus are mirrored already + if (s_bWin7Style) return SECOND_PANE_LAST; + TSecondPane mode=(TSecondPane)GetSettingInt(L"SecondPane"); + if (mode==SECOND_PANE_FIRST && s_bRTL) return SECOND_PANE_LAST; + return mode; +} + +// The number of column breaks in a menu's items +int CMenuContainer::CountColumnBreaks( const StdMenuItem *pStdItem ) +{ + int count=0; + for (;pStdItem->id!=MENU_LAST;pStdItem++) + if (pStdItem->id==MENU_COLUMN_BREAK) + count++; + return count; +} + +// Whether the items after the given number of column breaks (out of count) get the second-pane look. +// AddStandardItems needs it for their icon size, before the layout knows the columns +bool CMenuContainer::IsSecondPaneAfterBreaks( int breaks, int count ) +{ + if (m_bSubMenu || !s_Skin.TwoColumns || count==0) return false; + if (s_bWin7Style) return breaks>0; // the Windows 7 style uses only the first break + switch (GetSecondPaneMode()) + { + case SECOND_PANE_NONE: return false; + case SECOND_PANE_FIRST: return breaks==0; + default: return breaks==count; + } +} + +// Reads the SecondPane setting for the layout. Returns the index of the last column break, +// or -1 when only the first break starts a column (the Windows 7 style keeps two columns) +int CMenuContainer::InitSecondPane( void ) +{ + m_bSecondPane=true; + m_bSecondPaneFirst=false; + m_Pane2Column=1; // until a column break says otherwise + if (!IsClassicMainMenu()) + return -1; + + int lastBreak=-1; + for (int i=1;i<(int)m_Items.size();i++) + if (m_Items[i].bBreak) + lastBreak=i; + TSecondPane mode=GetSecondPaneMode(); + if (mode==SECOND_PANE_NONE) + m_bSecondPane=false; // every break starts another first-pane column + else if (mode==SECOND_PANE_FIRST && lastBreak>=0) + m_bSecondPaneFirst=true; // the first column has the second-pane look, every break starts a first-pane column + return lastBreak; +} + +// The pane (0 or 1) of the column that a column break starts, given the pane of the column before it. +// Remembers where the panes meet in m_Pane2Column +int CMenuContainer::GetPaneOfNewColumn( int column, int pane, bool bLastBreak ) +{ + if (m_bSecondPaneFirst) + { + if (pane==1) + m_Pane2Column=column; // the first break ends the second pane + return 0; + } + if (m_bSecondPane && bLastBreak) + { + m_Pane2Column=column; + return 1; + } + return pane; +} + +// Sets m_PaneColumns from m_Pane2Column once the number of columns is known +void CMenuContainer::UpdatePaneColumns( void ) +{ + int count=(int)m_ColumnOffsets.size(); + int boundary=m_Pane2Column &columnWidths ) +{ + const PaneColumns &columns=m_PaneColumns[pane]; + if (columns.last<=columns.first) return 0; + return m_ColumnOffsets[columns.last-1]+columnWidths[columns.last-1]-m_ColumnOffsets[columns.first]; +} + +// The left edge of a column, in window coordinates. Each pane starts at its own content rectangle +int CMenuContainer::GetColumnX( int column ) +{ + if (IsSecondPaneColumn(column)) + return m_rContent2.left+m_ColumnOffsets[column]-m_ColumnOffsets[m_PaneColumns[1].first]; + return m_rContent.left+m_ColumnOffsets[column]-m_ColumnOffsets[m_PaneColumns[0].first]; +} + +// Where the pager scrolls: the content of a sub-menu, or the first column of the main menu, where the pinned programs are +RECT CMenuContainer::GetScrollRect( void ) +{ + if (!m_bTwoColumns || m_ColumnOffsets.empty()) + return m_rContent; + int pane=IsSecondPaneColumn(0)?1:0; + RECT rc=pane?m_rContent2:m_rContent; + if (m_PaneColumns[pane].last>1) + rc.right=GetColumnX(1)-s_Skin.Main_separatorWidth; // more columns share the pane: stop at the gap before the next one + return rc; +} + void CMenuContainer::UpdateAccelerators( int first, int last ) { TRecentKeys recentKeys=(TRecentKeys)GetSettingInt(L"RecentProgKeys"); @@ -3233,16 +3362,21 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, bool bMultiColumn=s_ScrollMenus!=0 && (m_Options&CONTAINER_MULTICOLUMN); int shutdownIndex=-1; + // in the Classic two-column menu the last column break starts the second pane, and any earlier break adds a column to the first pane. + // the Windows 7 style keeps using only the first break + int lastBreak=InitSecondPane(); + { int row=0, column=0, subColumn=0; int y=0; int maxw=0; - int index=0; + int index=m_bSecondPaneFirst?1:0; for (int i=0;i<(int)m_Items.size();i++) { MenuItem &item=m_Items[i]; - if (m_bTwoColumns && column==0 && i>0 && item.bBreak) + bool bColumnBreak=item.bBreak && (index==0 || m_bSecondPaneFirst) && (column==0 || lastBreak>=0); + if (m_bTwoColumns && i>0 && bColumnBreak) { // start a new column column++; @@ -3256,7 +3390,7 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, else y=s_Skin.User_frame_position.x+s_Skin.User_image_padding.y-menuPadding[0].top; } - index=1; + index=GetPaneOfNewColumn(column,index,lastBreak<0 || i==lastBreak); } item.bNew=false; if (m_bTwoColumns && im_SearchIndex) && item.id!=MENU_SHUTDOWN_BUTTON) @@ -3455,7 +3589,7 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, y=0; } } - else if (item.id==MENU_SEPARATOR && !item.bBlankSeparator && !item.bInline && m_bTwoColumns && column==0 && i+1<(int)m_Items.size() && m_Items[i+1].bBreak) + else if (item.id==MENU_SEPARATOR && !item.bBlankSeparator && !item.bInline && m_bTwoColumns && (index==0 || m_bSecondPaneFirst) && i+1<(int)m_Items.size() && m_Items[i+1].bBreak) h=0; item.row=row; item.column=column; @@ -3490,6 +3624,8 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, row++; } } + if (!m_bSecondPane) + m_Pane2Column=(int)columnWidths.size(); // past the last column: every column is in the first pane SelectObject(hdc,font0); DeleteDC(hdc); @@ -3561,7 +3697,8 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, // calculate the horizontal position of each item int maxw=0; - int maxh[2]={0,0}; + int maxh[2]={0,0}; // height of the tallest column in each pane + int scrollHeight=0; // the pager scrolls only the first column of the main menu, where the pinned programs are int scrollWidth=GetSystemMetrics(SM_CXVSCROLL); { m_ColumnOffsets.resize(columnWidths.size()); @@ -3571,6 +3708,7 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, m_ColumnOffsets[i]=maxw; maxw+=columnWidths[i]; } + UpdatePaneColumns(); columnWidths.push_back(maxw); bool bInline=false; for (int i=0;i<(int)m_Items.size();i++) @@ -3606,16 +3744,11 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, item.itemRect.right-=scrollWidth; } } - if (m_bTwoColumns) - { - if (maxh[item.column]2) { - w1=columnWidths[0]; - w2=columnWidths[1]; + w1=GetPaneWidth(0,columnWidths); + w2=GetPaneWidth(1,columnWidths); if (s_bWin7Style) { @@ -3794,10 +3927,16 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, MenuItem &item=m_Items[i]; int dx=m_rContent.left; int dy=m_rContent.top; - if (m_bTwoColumns && item.column==1) + if (m_bTwoColumns) { - dx=m_rContent2.left-m_ColumnOffsets[1]; - dy=m_rContent2.top; + // each pane starts at its own content rectangle + if (IsSecondPaneColumn(item.column)) + { + dx=m_rContent2.left-m_ColumnOffsets[m_PaneColumns[1].first]; + dy=m_rContent2.top; + } + else if (m_PaneColumns[0].first<(int)m_ColumnOffsets.size()) + dx=m_rContent.left-m_ColumnOffsets[m_PaneColumns[0].first]; } if (item.id==MENU_SHUTDOWN_BUTTON) dx=0; @@ -3806,49 +3945,34 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, if (m_bTwoColumns && columnWidths.size()>2) { - int dh1=0, dh2=0; + // each column is aligned to the bottom separately (from its first COLUMN_PADDING on) + std::vector columnDH(m_ColumnOffsets.size(),0); // how far down the column's aligned items move for (int i=0;i bAlign(columnDH.size(),false); for (int i=0;imaxHeight[0] && m_ScrollCount>0 && (m_bSubMenu || !s_bWin7Style)) + int scrollPane=IsSecondPaneColumn(0)?1:0; // the first column can have the second-pane look (SecondPane=First) + int scrollTop=GetScrollRect().top; + if (!bMultiColumn && scrollHeight>maxHeight[scrollPane] && m_ScrollCount>0 && (m_bSubMenu || !s_bWin7Style)) { - int d=maxh[0]-maxHeight[0]; - m_ScrollHeight=m_Items[m_ScrollCount-1].itemRect.bottom-d-m_rContent.top; + int d=scrollHeight-maxHeight[scrollPane]; + m_ScrollHeight=m_Items[m_ScrollCount-1].itemRect.bottom-d-scrollTop; if (m_ScrollHeight<=0) { if (m_ScrollCount<(int)m_Items.size()) - d=m_Items[m_ScrollCount].itemRect.bottom-m_rContent.top; + d=m_Items[m_ScrollCount].itemRect.bottom-scrollTop; m_ScrollOffset=m_ScrollHeight=0; for (int i=0;ibottom) m_Items[i].itemRect.bottom=m_Items[i].itemRect.top; } @@ -4120,8 +4246,8 @@ void CMenuContainer::InitWindowInternal( bool bDontShrink, const POINT &corner, { m_SearchBox.Create(L"EDIT",m_hWnd,itemRect,NULL,WS_CHILD|(s_Skin.Search_frame?WS_BORDER:0)|ES_AUTOHSCROLL|ES_WANTRETURN); SetWindowSubclass(m_SearchBox,SubclassSearchBox,(UINT_PTR)this,0); - int index=(m_bTwoColumns && m_Items[m_SearchIndex].column==1)?1:0; - if (index==1 && (m_SearchIndex==0 || m_Items[m_SearchIndex-1].column==0)) + int index=IsSecondPaneColumn(m_Items[m_SearchIndex].column)?1:0; + if (index==1 && (m_SearchIndex==0 || !IsSecondPaneColumn(m_Items[m_SearchIndex-1].column))) m_SearchBox.SetFont(s_Skin.ItemSettings[MenuSkin::COLUMN2_ITEM].font); else m_SearchBox.SetFont(s_Skin.ItemSettings[MenuSkin::COLUMN1_ITEM].font); @@ -4355,7 +4481,7 @@ void CMenuContainer::UpdateScroll( void ) else { m_bScrollUp=(m_ScrollOffset>0); - m_bScrollDown=(m_ScrollOffset+m_ScrollHeight0 && index=m_rContent.top+m_ScrollHeight-m_ScrollButtonSize) + if (m_bScrollDown && rc.top>=scrollTop+m_ScrollHeight-m_ScrollButtonSize) return false; } if (m_SearchScrollCount>m_SearchScrollHeight && index>=m_OriginalCount) @@ -4529,10 +4656,13 @@ bool CMenuContainer::GetItemRect( int index, RECT &rc ) int CMenuContainer::HitTest( const POINT &pt, bool *bArrow, bool bDrop ) { - if (m_bScrollUp && pt.y=rScroll.left && pt.x=m_rContent.top+m_ScrollHeight-m_ScrollButtonSize) + if (m_bScrollDown && bInPager && pt.y>=rScroll.top+m_ScrollHeight-m_ScrollButtonSize) start=m_ScrollCount; int n=(int)m_Items.size(); for (int i=start;im_SearchIndex>=0 && GetSettingInt(L"SearchBox")==SEARCHBOX_NORMAL && GetSettingBool(L"SearchSelect")) { pStartMenu->ActivateItem(pStartMenu->m_SearchIndex,ACTIVATE_SELECT,NULL); - if (pStartMenu->m_bTwoColumns && pStartMenu->m_Items[pStartMenu->m_SearchIndex].column==0 && pStartMenu->m_SearchIndex+1<(int)pStartMenu->m_Items.size() && pStartMenu->m_Items[pStartMenu->m_SearchIndex+1].column==1) + if (pStartMenu->m_bTwoColumns && pStartMenu->m_Items[pStartMenu->m_SearchIndex].column==0 && pStartMenu->m_SearchIndex+1<(int)pStartMenu->m_Items.size() && pStartMenu->m_Items[pStartMenu->m_SearchIndex+1].column!=0) s_bOverrideFirstDown=true; } else if (!bTreeSelected) diff --git a/Src/StartMenu/StartMenuDLL/MenuContainer.h b/Src/StartMenu/StartMenuDLL/MenuContainer.h index 057982104..99a856d0a 100644 --- a/Src/StartMenu/StartMenuDLL/MenuContainer.h +++ b/Src/StartMenu/StartMenuDLL/MenuContainer.h @@ -25,6 +25,7 @@ #endif enum TRecentPrograms; +enum TSecondPane; enum TMenuID { @@ -661,6 +662,14 @@ class CMenuContainer: public IDropTarget, public IFrameworkInputPaneHandler, pub POINT m_PaintOffset; int m_MaxWidth; bool m_bTwoColumns; + bool m_bSecondPane; // false when the SecondPane setting gives no column the second-pane look + bool m_bSecondPaneFirst; // SecondPane=First: the second pane is the columns before m_Pane2Column, on the left + int m_Pane2Column; // where the panes meet: the second pane starts here (or ends here with m_bSecondPaneFirst) + struct PaneColumns + { + int first, last; // [first,last) + }; + PaneColumns m_PaneColumns[2]; // the columns of the first and the second pane, set by InitWindowInternal RECT m_rMenu; RECT m_rContent; RECT m_rContent2; @@ -701,6 +710,17 @@ class CMenuContainer: public IDropTarget, public IFrameworkInputPaneHandler, pub int AddSearchItems( const std::vector &items, const CString &categoryName, unsigned int categoryHash, int originalCount ); void GetRecentPrograms( std::vector &items, int maxCount ); + static TSecondPane GetSecondPaneMode( void ); + static int CountColumnBreaks( const StdMenuItem *pStdItem ); + bool IsSecondPaneAfterBreaks( int breaks, int count ); + int InitSecondPane( void ); + int GetPaneOfNewColumn( int column, int pane, bool bLastBreak ); + void UpdatePaneColumns( void ); + int GetPaneWidth( int pane, const std::vector &columnWidths ); + int GetColumnX( int column ); + RECT GetScrollRect( void ); + bool IsClassicMainMenu( void ) { return m_bTwoColumns && !m_bSubMenu && !s_bWin7Style; } + bool IsSecondPaneColumn( int column ) { return column>=m_PaneColumns[1].first && column0 && !s_bRTL; + int pane1[2]={0,totalWidth1}, pane2[2]={totalWidth1,totalWidth}; // [left,right) of each pane, to align emblems and the user + if (bPane2First) + { + pane2[0]=textHeight; + pane2[1]=textHeight+totalWidth2; + pane1[0]=pane2[1]; + pane1[1]=totalWidth; + } totalHeight=height1+menuPadding[0].top+menuPadding[0].bottom; - if (m_bTwoColumns) + if (m_bTwoColumns && m_bSecondPane) { int totalHeight2=height2+menuPadding[1].top+menuPadding[1].bottom; if (totalHeight0) - pos.x=totalWidth1+(totalWidth2-frameSize.cx)/2; + pos.x=(pane2[0]+pane2[1]-frameSize.cx)/2; else pos.x=(totalWidth-frameSize.cx)/2; } @@ -1228,10 +1274,7 @@ void CMenuContainer::CreateBackground( int width1, int width2, int height1, int { RECT rc0; int x0=0, x1=totalWidth; - if (s_Skin.User_name_align==MenuSkin::HALIGN_CENTER1 || s_Skin.User_name_align==MenuSkin::HALIGN_LEFT1 || s_Skin.User_name_align==MenuSkin::HALIGN_RIGHT1) - x1=totalWidth1; - else if (s_Skin.User_name_align==MenuSkin::HALIGN_CENTER2 || s_Skin.User_name_align==MenuSkin::HALIGN_LEFT2 || s_Skin.User_name_align==MenuSkin::HALIGN_RIGHT2) - x0=totalWidth1; + AlignToPane(s_Skin.User_name_align,pane1,pane2,x0,x1); if (s_Skin.User_name_position.left<0) rc0.left=x1+s_Skin.User_name_position.left; @@ -1606,8 +1649,13 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) MenuBitmap bmpPagerArrows; SIZE pagArrowSize; MenuSkin::TOpacity opacity=m_bSubMenu?s_Skin.Submenu_opacity:s_Skin.Main_opacity; - int glow=s_Skin.ItemSettings[m_bSubMenu?MenuSkin::SUBMENU_ITEM:MenuSkin::COLUMN1_ITEM].glowSize; + // the scrolling items can be in the second pane (SecondPane=First) + const MenuSkin::ItemDrawSettings &scrollSettings=s_Skin.ItemSettings[m_bSubMenu?MenuSkin::SUBMENU_ITEM:(IsSecondPaneColumn(0)?MenuSkin::COLUMN2_ITEM:MenuSkin::COLUMN1_ITEM)]; + int glow=scrollSettings.glowSize; if (!s_Theme) glow=0; + // composited text ignores the clip rectangle, so the pager buttons need their background drawn again (see DrawThemeTextEx below) + bool bTextSpills=(glow || opacity==MenuSkin::OPACITY_FULLALPHA || opacity==MenuSkin::OPACITY_FULLGLASS || + scrollSettings.opacity==MenuSkin::OPACITY_FULLALPHA || scrollSettings.opacity==MenuSkin::OPACITY_FULLGLASS); TRecentKeys recentType=(TRecentKeys)GetSettingInt(L"RecentProgKeys"); if (m_bSubMenu) @@ -1638,13 +1686,14 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) HGDIOBJ font0=GetCurrentObject(hdc,OBJ_FONT); SetBkMode(hdc,TRANSPARENT); - // set clip rectangle for the scrollable items - int clipTop=m_rContent.top; - int clipBottom=m_rContent.bottom; + // set clip rectangle for the scrollable items (the pager covers only the column that scrolls, see GetScrollRect) + RECT rScroll=GetScrollRect(); + int clipTop=rScroll.top; + int clipBottom=rScroll.bottom; if (m_bScrollUp) - clipTop=m_rContent.top+m_ScrollButtonSize; + clipTop=rScroll.top+m_ScrollButtonSize; if (m_bScrollDown) - clipBottom=m_rContent.top+m_ScrollHeight-m_ScrollButtonSize; + clipBottom=rScroll.top+m_ScrollHeight-m_ScrollButtonSize; if (m_ScrollHeight>0) IntersectClipRect(hdc,0,clipTop,m_rMenu.right,clipBottom); @@ -1665,10 +1714,10 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) SelectClipRgn(hdc,NULL); if (m_bScrollUp) { - if (glow || opacity==MenuSkin::OPACITY_FULLALPHA || opacity==MenuSkin::OPACITY_FULLGLASS) + if (bTextSpills) { // fix background behind the up button (DrawThemeTextEx may spill onto the tablecloth) - RECT rc={m_rContent.left,0,m_rContent.right,clipTop}; + RECT rc={rScroll.left,0,rScroll.right,clipTop}; if (bAlphaMain || !bmpMain) { SetDCBrushColor(hdc,m_bSubMenu?s_Skin.Submenu_background:s_Skin.Main_background); @@ -1677,7 +1726,7 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) if (bmpMain) { HGDIOBJ bmp0=SelectObject(hdc2,bmpMain); - IntersectClipRect(hdc,m_rContent.left,0,m_rContent.right,clipTop); + IntersectClipRect(hdc,rScroll.left,0,rScroll.right,clipTop); MarginsBlit(hdc2,hdc,rSrcMain,m_rMenu,rMarginsMain,bAlphaMain); SelectObject(hdc2,bmp0); SelectClipRgn(hdc,NULL); @@ -1685,7 +1734,7 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) } // draw up button - RECT rc=m_rContent; + RECT rc=rScroll; rc.bottom=clipTop; if (bmpPager.GetBitmap() && bmpPagerArrows.GetBitmap()) { @@ -1722,10 +1771,10 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) { int bottom=clipBottom+s_Skin.ItemSettings[m_bSubMenu?MenuSkin::SUBMENU_ITEM:MenuSkin::COLUMN1_ITEM].itemHeight; if (bottom>=m_rMenu.bottom) bottom=m_rMenu.bottom; - if (bottom>clipBottom && (glow || opacity==MenuSkin::OPACITY_FULLALPHA || opacity==MenuSkin::OPACITY_FULLGLASS)) + if (bottom>clipBottom && bTextSpills) { // fix background behind the up button (DrawThemeTextEx may spill onto the tablecloth) - RECT rc={m_rContent.left,clipBottom,m_rContent.right,bottom}; + RECT rc={rScroll.left,clipBottom,rScroll.right,bottom}; if (bAlphaMain || !bmpMain) { SetDCBrushColor(hdc,m_bSubMenu?s_Skin.Submenu_background:s_Skin.Main_background); @@ -1734,7 +1783,7 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) if (bmpMain) { HGDIOBJ bmp0=SelectObject(hdc2,bmpMain); - IntersectClipRect(hdc,m_rContent.left,clipBottom,m_rContent.right,bottom); + IntersectClipRect(hdc,rScroll.left,clipBottom,rScroll.right,bottom); MarginsBlit(hdc2,hdc,rSrcMain,m_rMenu,rMarginsMain,bAlphaMain); SelectObject(hdc2,bmp0); SelectClipRgn(hdc,NULL); @@ -1742,8 +1791,8 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) } // draw down button - RECT rc=m_rContent; - rc.bottom=m_rContent.top+m_ScrollHeight; + RECT rc=rScroll; + rc.bottom=rScroll.top+m_ScrollHeight; rc.top=clipBottom; if (bmpPager.GetBitmap() && bmpPagerArrows.GetBitmap()) { @@ -2451,18 +2500,23 @@ void CMenuContainer::DrawBackground( HDC hdc, const RECT &drawRect ) } } - // draw vertical separators - if (m_bSubMenu && m_ColumnOffsets.size()>1) + // draw vertical separators between the columns of a sub-menu, or within each pane of the main menu + // (the separator between the two panes is part of the background) + for (int pane=0;pane<2;pane++) { + const RECT &content=pane?m_rContent2:m_rContent; + int sepFirst=m_PaneColumns[pane].first+1, sepLast=m_PaneColumns[pane].last; // separators go before these columns + if (sepFirst>=sepLast) + continue; if (bmpSeparatorV.GetBitmap()) { HGDIOBJ bmp0=SelectObject(hdc2,bmpSeparatorV.GetBitmap()); RECT rSrc={0,0,sepWidth,sepSlicesY[0]+sepSlicesY[1]+sepSlicesY[2]}; RECT rMargins={0,sepSlicesY[0],0,sepSlicesY[2]}; - for (size_t i=1;i Date: Sat, 7 Nov 2026 15:42:32 -0600 Subject: [PATCH 2/3] Use the pager's column for its scroll range too The scroll range for touch, hover, keyboard and wheel scrolling was still measured from the top of the first pane (m_rContent). With Second pane look = First column the pinned programs are in the second pane, whose top can be at another height, depending on the skin's padding, so the last pinned program could be cut off or followed by a gap. It now comes from GetScrollRect, like the rest of the pager. With Immersive both panes start at the same height, so the screenshots in this PR don't change. --- Src/StartMenu/StartMenuDLL/MenuContainer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/MenuContainer.cpp b/Src/StartMenu/StartMenuDLL/MenuContainer.cpp index 588f34c80..42f8e9ddd 100644 --- a/Src/StartMenu/StartMenuDLL/MenuContainer.cpp +++ b/Src/StartMenu/StartMenuDLL/MenuContainer.cpp @@ -4934,7 +4934,7 @@ LRESULT CMenuContainer::OnGesture( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL scroll=0; bOverpan=true; } - int total=m_Items[m_ScrollCount-1].itemRect.bottom-m_rContent.top-m_ScrollHeight; + int total=m_Items[m_ScrollCount-1].itemRect.bottom-GetScrollRect().top-m_ScrollHeight; if (scroll>total) { scroll=total; @@ -5406,7 +5406,7 @@ LRESULT CMenuContainer::OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& else if (m_bScrollDown && m_bScrollDownHot) { scroll+=s_Skin.ItemSettings[m_bSubMenu?MenuSkin::SUBMENU_ITEM:MenuSkin::COLUMN1_ITEM].itemHeight*speed/6; - int total=m_Items[m_ScrollCount-1].itemRect.bottom-m_rContent.top-m_ScrollHeight; + int total=m_Items[m_ScrollCount-1].itemRect.bottom-GetScrollRect().top-m_ScrollHeight; if (scroll>total) scroll=total; } if (m_ScrollOffset!=scroll) @@ -5903,7 +5903,7 @@ LRESULT CMenuContainer::OnKeyDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL int y0=m_Items[index].itemRect.top; int scrollOffset=0; if (m_ScrollCount>0) - scrollOffset=m_Items[m_ScrollCount-1].itemRect.bottom-m_rContent.top-m_ScrollHeight; + scrollOffset=m_Items[m_ScrollCount-1].itemRect.bottom-GetScrollRect().top-m_ScrollHeight; if (index0) - scrollOffset=m_Items[m_ScrollCount-1].itemRect.bottom-m_rContent.top-m_ScrollHeight; + scrollOffset=m_Items[m_ScrollCount-1].itemRect.bottom-GetScrollRect().top-m_ScrollHeight; if (indextotal) scroll=total; if (m_ScrollOffset!=scroll) { From 9ac6caa8ba85491850000d5bbdd7d7127401f8ca Mon Sep 17 00:00:00 2001 From: Emilio Heredia Date: Sat, 7 Nov 2026 20:22:41 -0600 Subject: [PATCH 3/3] Pin a drop on empty space only in the pinned programs' column A drop on empty space adds the program to the pinned programs. It was measured against the first pane (m_rContent), which until now was the same as their column. With more columns, a drop on empty space in another column of the first pane pinned the program too, and with Second pane look = First column a drop in the pinned programs' own column did nothing. It now uses GetScrollRect, like the pager. The pager's scroll range was the same expression in six places; GetScrollRange now gives it once. --- Src/StartMenu/StartMenuDLL/DragDrop.cpp | 3 ++- Src/StartMenu/StartMenuDLL/MenuCommands.cpp | 5 ++--- Src/StartMenu/StartMenuDLL/MenuContainer.cpp | 18 ++++++++++++------ Src/StartMenu/StartMenuDLL/MenuContainer.h | 1 + 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Src/StartMenu/StartMenuDLL/DragDrop.cpp b/Src/StartMenu/StartMenuDLL/DragDrop.cpp index 7b3262db8..f272f75f0 100644 --- a/Src/StartMenu/StartMenuDLL/DragDrop.cpp +++ b/Src/StartMenu/StartMenuDLL/DragDrop.cpp @@ -609,7 +609,8 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::DragOver( DWORD grfKeyState, POINTL pt } else { - if (!m_bSubMenu && indexrc.top-m_ScrollButtonSize) pos=rc.top-m_ScrollButtonSize; else if (pos0); - m_bScrollDown=(m_ScrollOffset+m_ScrollHeighttotal) { scroll=total; @@ -5406,7 +5412,7 @@ LRESULT CMenuContainer::OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& else if (m_bScrollDown && m_bScrollDownHot) { scroll+=s_Skin.ItemSettings[m_bSubMenu?MenuSkin::SUBMENU_ITEM:MenuSkin::COLUMN1_ITEM].itemHeight*speed/6; - int total=m_Items[m_ScrollCount-1].itemRect.bottom-GetScrollRect().top-m_ScrollHeight; + int total=GetScrollRange(); if (scroll>total) scroll=total; } if (m_ScrollOffset!=scroll) @@ -5903,7 +5909,7 @@ LRESULT CMenuContainer::OnKeyDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL int y0=m_Items[index].itemRect.top; int scrollOffset=0; if (m_ScrollCount>0) - scrollOffset=m_Items[m_ScrollCount-1].itemRect.bottom-GetScrollRect().top-m_ScrollHeight; + scrollOffset=GetScrollRange(); if (index0) - scrollOffset=m_Items[m_ScrollCount-1].itemRect.bottom-GetScrollRect().top-m_ScrollHeight; + scrollOffset=GetScrollRange(); if (indextotal) scroll=total; if (m_ScrollOffset!=scroll) { diff --git a/Src/StartMenu/StartMenuDLL/MenuContainer.h b/Src/StartMenu/StartMenuDLL/MenuContainer.h index 99a856d0a..f8107635c 100644 --- a/Src/StartMenu/StartMenuDLL/MenuContainer.h +++ b/Src/StartMenu/StartMenuDLL/MenuContainer.h @@ -719,6 +719,7 @@ class CMenuContainer: public IDropTarget, public IFrameworkInputPaneHandler, pub int GetPaneWidth( int pane, const std::vector &columnWidths ); int GetColumnX( int column ); RECT GetScrollRect( void ); + int GetScrollRange( void ); bool IsClassicMainMenu( void ) { return m_bTwoColumns && !m_bSubMenu && !s_bWin7Style; } bool IsSecondPaneColumn( int column ) { return column>=m_PaneColumns[1].first && column