Improve and simplify drawing of tabs in TabBarPlus
Closes #1075 - All positions are dynamically calculated relative to the tab rectangle now (i.e. no hardcoded pixel values are used to position icons/text anymore) - Match positioning of icons/text in active and inactive tabs (i.e. elements are not "jumping around" anymore upon selection) Some specific fixes: - Most issues with vertical TabBar are resolved now (it was basically unusable before, for example labels were cut) - Darkened background of inactive tabs fills the whole tab now (fixes #1011) - Close button is centered correctly now (fixes #1010)
This commit is contained in:
parent
da144f4688
commit
5b7f900ce8
@ -447,7 +447,7 @@ LRESULT TabBarPlus::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lPara
|
||||
int xPos = LOWORD(lParam);
|
||||
int yPos = HIWORD(lParam);
|
||||
|
||||
if (_closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect))
|
||||
if (_closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect, _isVertical))
|
||||
{
|
||||
_whichCloseClickDown = getTabIndexAt(xPos, yPos);
|
||||
::SendMessage(_hParent, WM_COMMAND, IDM_VIEW_REFRESHTABAR, 0);
|
||||
@ -524,7 +524,7 @@ LRESULT TabBarPlus::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lPara
|
||||
if (_currentHoverTabItem != -1)
|
||||
{
|
||||
::SendMessage(_hSelf, TCM_GETITEMRECT, _currentHoverTabItem, (LPARAM)&_currentHoverTabRect);
|
||||
_isCloseHover = _closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect);
|
||||
_isCloseHover = _closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect, _isVertical);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -588,7 +588,7 @@ LRESULT TabBarPlus::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lPara
|
||||
|
||||
if (_drawTabCloseButton)
|
||||
{
|
||||
if ((_whichCloseClickDown == currentTabOn) && _closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect))
|
||||
if ((_whichCloseClickDown == currentTabOn) && _closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect, _isVertical))
|
||||
{
|
||||
TBHDR nmhdr;
|
||||
nmhdr.hdr.hwndFrom = _hSelf;
|
||||
@ -699,22 +699,62 @@ void TabBarPlus::drawItem(DRAWITEMSTRUCT *pDrawItemStruct)
|
||||
::FillRect(hDC, &rect, hBrush);
|
||||
::DeleteObject((HGDIOBJ)hBrush);
|
||||
|
||||
// equalize drawing areas of active and inactive tabs
|
||||
int paddingDynamicTwoX = NppParameters::getInstance()->_dpiManager.scaleX(2);
|
||||
int paddingDynamicTwoY = NppParameters::getInstance()->_dpiManager.scaleY(2);
|
||||
if (isSelected)
|
||||
{
|
||||
// the drawing area of the active tab extends on all borders by default
|
||||
rect.top += ::GetSystemMetrics(SM_CYEDGE);
|
||||
rect.bottom -= ::GetSystemMetrics(SM_CYEDGE);
|
||||
rect.left += ::GetSystemMetrics(SM_CXEDGE);
|
||||
rect.right -= ::GetSystemMetrics(SM_CXEDGE);
|
||||
// the active tab is also slightly higher by default (use this to shift the tab cotent up bx two pixels if tobBar is not drawn)
|
||||
if (_isVertical)
|
||||
{
|
||||
rect.left += _drawTopBar ? paddingDynamicTwoX : 0;
|
||||
rect.right -= _drawTopBar ? 0 : paddingDynamicTwoX;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.top += _drawTopBar ? paddingDynamicTwoY : 0;
|
||||
rect.bottom -= _drawTopBar ? 0 : paddingDynamicTwoY;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isVertical)
|
||||
{
|
||||
rect.left += paddingDynamicTwoX;
|
||||
rect.right += paddingDynamicTwoX;
|
||||
rect.top -= paddingDynamicTwoY;
|
||||
rect.bottom += paddingDynamicTwoY;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.left -= paddingDynamicTwoX;
|
||||
rect.right += paddingDynamicTwoX;
|
||||
rect.top += paddingDynamicTwoY;
|
||||
rect.bottom += paddingDynamicTwoY;
|
||||
}
|
||||
}
|
||||
|
||||
// draw highlights on tabs (top bar for active tab / darkened background for inactive tab)
|
||||
RECT barRect = rect;
|
||||
if (isSelected)
|
||||
{
|
||||
if (_drawTopBar)
|
||||
{
|
||||
RECT barRect = rect;
|
||||
int paddingDynamicSix = NppParameters::getInstance()->_dpiManager.scaleX(6);
|
||||
int paddingDynamicTwo = NppParameters::getInstance()->_dpiManager.scaleX(2);
|
||||
int topBarHeight = NppParameters::getInstance()->_dpiManager.scaleX(4);
|
||||
if (_isVertical)
|
||||
{
|
||||
barRect.right = barRect.left + paddingDynamicSix;
|
||||
rect.left += paddingDynamicTwo;
|
||||
barRect.left -= NppParameters::getInstance()->_dpiManager.scaleX(2);
|
||||
barRect.right = barRect.left + topBarHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
barRect.bottom = barRect.top + paddingDynamicSix;
|
||||
rect.top += paddingDynamicTwo;
|
||||
barRect.top -= NppParameters::getInstance()->_dpiManager.scaleY(2);
|
||||
barRect.bottom = barRect.top + topBarHeight;
|
||||
}
|
||||
|
||||
if (::SendMessage(_hParent, NPPM_INTERNAL_ISFOCUSEDTAB, 0, (LPARAM)_hSelf))
|
||||
@ -730,33 +770,15 @@ void TabBarPlus::drawItem(DRAWITEMSTRUCT *pDrawItemStruct)
|
||||
{
|
||||
if (_drawInactiveTab)
|
||||
{
|
||||
RECT barRect = rect;
|
||||
|
||||
hBrush = ::CreateSolidBrush(_inactiveBgColour);
|
||||
::FillRect(hDC, &barRect, hBrush);
|
||||
::DeleteObject((HGDIOBJ)hBrush);
|
||||
}
|
||||
}
|
||||
|
||||
// draw close button
|
||||
if (_drawTabCloseButton)
|
||||
{
|
||||
RECT closeButtonRect = _closeButtonZone.getButtonRectFrom(rect);
|
||||
if (isSelected)
|
||||
{
|
||||
if (!_isVertical)
|
||||
{
|
||||
closeButtonRect.left -= 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isVertical)
|
||||
closeButtonRect.left += 2;
|
||||
else
|
||||
closeButtonRect.left += NppParameters::getInstance()->_dpiManager.scaleX(2);
|
||||
}
|
||||
|
||||
|
||||
// 3 status for each inactive tab and selected tab close item :
|
||||
// normal / hover / pushed
|
||||
int idCloseImg;
|
||||
@ -768,7 +790,6 @@ void TabBarPlus::drawItem(DRAWITEMSTRUCT *pDrawItemStruct)
|
||||
else
|
||||
idCloseImg = isSelected?IDR_CLOSETAB:IDR_CLOSETAB_INACT;
|
||||
|
||||
|
||||
HDC hdcMemory;
|
||||
hdcMemory = ::CreateCompatibleDC(hDC);
|
||||
HBITMAP hBmp = ::LoadBitmap(_hInst, MAKEINTRESOURCE(idCloseImg));
|
||||
@ -778,25 +799,17 @@ void TabBarPlus::drawItem(DRAWITEMSTRUCT *pDrawItemStruct)
|
||||
int bmDpiDynamicalWidth = NppParameters::getInstance()->_dpiManager.scaleX(bmp.bmWidth);
|
||||
int bmDpiDynamicalHeight = NppParameters::getInstance()->_dpiManager.scaleY(bmp.bmHeight);
|
||||
|
||||
if (_isVertical)
|
||||
rect.top = closeButtonRect.top + bmDpiDynamicalHeight;
|
||||
else
|
||||
rect.right = closeButtonRect.left;
|
||||
|
||||
RECT buttonRect = _closeButtonZone.getButtonRectFrom(rect, _isVertical);
|
||||
|
||||
::SelectObject(hdcMemory, hBmp);
|
||||
::StretchBlt(hDC, closeButtonRect.left, closeButtonRect.top, bmDpiDynamicalWidth, bmDpiDynamicalHeight, hdcMemory, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
|
||||
::StretchBlt(hDC, buttonRect.left, buttonRect.top, bmDpiDynamicalWidth, bmDpiDynamicalHeight, hdcMemory, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
|
||||
::DeleteDC(hdcMemory);
|
||||
::DeleteObject(hBmp);
|
||||
}
|
||||
|
||||
// Draw image
|
||||
// draw image
|
||||
HIMAGELIST hImgLst = (HIMAGELIST)::SendMessage(_hSelf, TCM_GETIMAGELIST, 0, 0);
|
||||
|
||||
SIZE charPixel;
|
||||
::GetTextExtentPoint(hDC, TEXT(" "), 1, &charPixel);
|
||||
int spaceUnit = charPixel.cx;
|
||||
|
||||
if (hImgLst && tci.iImage >= 0)
|
||||
{
|
||||
IMAGEINFO info;
|
||||
@ -804,29 +817,26 @@ void TabBarPlus::drawItem(DRAWITEMSTRUCT *pDrawItemStruct)
|
||||
|
||||
RECT& imageRect = info.rcImage;
|
||||
|
||||
int yPos = 0;
|
||||
int xPos = 0;
|
||||
if (_isVertical)
|
||||
xPos = (rect.left + (rect.right - rect.left) / 2 + NppParameters::getInstance()->_dpiManager.scaleX(2)) - (imageRect.right - imageRect.left) / 2;
|
||||
else
|
||||
yPos = (rect.top + (rect.bottom - rect.top) / 2 + (isSelected ? 0 : NppParameters::getInstance()->_dpiManager.scaleX(2))) - (imageRect.bottom - imageRect.top) / 2;
|
||||
|
||||
int marge = spaceUnit;
|
||||
|
||||
int fromBorder;
|
||||
int xPos, yPos;
|
||||
if (_isVertical)
|
||||
{
|
||||
rect.bottom -= imageRect.bottom - imageRect.top;
|
||||
ImageList_Draw(hImgLst, tci.iImage, hDC, xPos, rect.bottom - marge, isSelected?ILD_TRANSPARENT:ILD_SELECTED);
|
||||
rect.bottom += marge;
|
||||
fromBorder = (rect.right - rect.left - (imageRect.right - imageRect.left) + 1) / 2;
|
||||
xPos = rect.left + fromBorder;
|
||||
yPos = rect.bottom - fromBorder - (imageRect.bottom - imageRect.top);
|
||||
rect.bottom -= fromBorder + (imageRect.bottom - imageRect.top);
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.left += marge;
|
||||
ImageList_Draw(hImgLst, tci.iImage, hDC, rect.left, yPos, isSelected?ILD_TRANSPARENT:ILD_SELECTED);
|
||||
rect.left += imageRect.right - imageRect.left;
|
||||
fromBorder = (rect.bottom - rect.top - (imageRect.bottom - imageRect.top) + 1) / 2;
|
||||
yPos = rect.top + fromBorder;
|
||||
xPos = rect.left + fromBorder;
|
||||
rect.left += fromBorder + (imageRect.right - imageRect.left);
|
||||
}
|
||||
ImageList_Draw(hImgLst, tci.iImage, hDC, xPos, yPos, isSelected ? ILD_TRANSPARENT : ILD_SELECTED);
|
||||
}
|
||||
|
||||
// draw text
|
||||
bool isStandardSize = (::SendMessage(_hParent, NPPM_INTERNAL_ISTABBARREDUCED, 0, 0) == TRUE);
|
||||
|
||||
if (isStandardSize)
|
||||
@ -843,78 +853,62 @@ void TabBarPlus::drawItem(DRAWITEMSTRUCT *pDrawItemStruct)
|
||||
else
|
||||
SelectObject(hDC, _hLargeFont);
|
||||
}
|
||||
SIZE charPixel;
|
||||
::GetTextExtentPoint(hDC, TEXT(" "), 1, &charPixel);
|
||||
int spaceUnit = charPixel.cx;
|
||||
|
||||
TEXTMETRIC textMetrics;
|
||||
GetTextMetrics(hDC, &textMetrics);
|
||||
int textHeight = textMetrics.tmHeight;
|
||||
int textDescent = textMetrics.tmDescent;
|
||||
|
||||
int Flags = DT_SINGLELINE | DT_NOPREFIX;
|
||||
|
||||
// This code will read in one character at a time and remove every first ampersand (&).
|
||||
// ex. If input "test && test &&& test &&&&" then output will be "test & test && test &&&".
|
||||
// Tab's caption must be encoded like this because otherwise tab control would make tab too small or too big for the text.
|
||||
TCHAR decodedLabel[MAX_PATH];
|
||||
|
||||
{
|
||||
const TCHAR* in = label;
|
||||
TCHAR* out = decodedLabel;
|
||||
|
||||
//This code will read in one character at a time and remove every first ampersand(&).
|
||||
//ex. If input "test && test &&& test &&&&" then output will be "test & test && test &&&".
|
||||
//Tab's caption must be encoded like this because otherwise tab control would make tab too small or too big for the text.
|
||||
|
||||
while (*in != 0)
|
||||
const TCHAR* in = label;
|
||||
TCHAR* out = decodedLabel;
|
||||
while (*in != 0)
|
||||
if (*in == '&')
|
||||
while (*(++in) == '&')
|
||||
*out++ = *in;
|
||||
while (*(++in) == '&')
|
||||
*out++ = *in;
|
||||
else
|
||||
*out++ = *in++;
|
||||
*out = '\0';
|
||||
}
|
||||
*out = '\0';
|
||||
|
||||
if (_drawTabCloseButton)
|
||||
if (_isVertical)
|
||||
{
|
||||
// center text horizontally (rotated text is positioned as if it were unrotated, therefore manual positioning is necessary)
|
||||
Flags |= DT_LEFT;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_isVertical)
|
||||
Flags |= DT_CENTER;
|
||||
}
|
||||
|
||||
// the following uses pixel values the fix alignments issues with DrawText
|
||||
// and font's that are rotated 90 degrees
|
||||
if (isSelected)
|
||||
{
|
||||
//COLORREF selectedColor = RGB(0, 0, 255);
|
||||
::SetTextColor(hDC, _activeTextColour);
|
||||
|
||||
if (_isVertical)
|
||||
{
|
||||
rect.bottom -= NppParameters::getInstance()->_dpiManager.scaleY(2);
|
||||
rect.left += ::GetSystemMetrics(SM_CXEDGE) + NppParameters::getInstance()->_dpiManager.scaleX(4);
|
||||
rect.top += (_drawTabCloseButton)?spaceUnit:0;
|
||||
|
||||
Flags |= DT_BOTTOM;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.top -= ::GetSystemMetrics(SM_CYEDGE);
|
||||
rect.top += NppParameters::getInstance()->_dpiManager.scaleY(3);
|
||||
rect.left += _drawTabCloseButton?spaceUnit:0;
|
||||
|
||||
Flags |= DT_VCENTER;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
::SetTextColor(hDC, _inactiveTextColour);
|
||||
if (_isVertical)
|
||||
{
|
||||
rect.top += NppParameters::getInstance()->_dpiManager.scaleY(2);
|
||||
rect.bottom += NppParameters::getInstance()->_dpiManager.scaleY(4);
|
||||
rect.left += ::GetSystemMetrics(SM_CXEDGE) + NppParameters::getInstance()->_dpiManager.scaleX(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.left += (_drawTabCloseButton)?spaceUnit:0;
|
||||
}
|
||||
|
||||
Flags |= DT_BOTTOM;
|
||||
rect.left += (rect.right - rect.left - textHeight) / 2;
|
||||
rect.bottom += textHeight;
|
||||
|
||||
// ignoring the descent when centering (text elements below the base line) is more pleasing to the eye
|
||||
rect.left += textDescent / 2;
|
||||
rect.right += textDescent / 2;
|
||||
|
||||
// 1 space distance to save icon
|
||||
rect.bottom -= spaceUnit;
|
||||
}
|
||||
else
|
||||
{
|
||||
// center text vertically
|
||||
Flags |= DT_LEFT;
|
||||
Flags |= DT_VCENTER;
|
||||
|
||||
// ignoring the descent when centering (text elements below the base line) is more pleasing to the eye
|
||||
rect.top += textDescent / 2;
|
||||
rect.bottom += textDescent / 2;
|
||||
|
||||
// 1 space distance to save icon
|
||||
rect.left += spaceUnit;
|
||||
}
|
||||
|
||||
::SetTextColor(hDC, isSelected ? _activeTextColour : _inactiveTextColour);
|
||||
|
||||
::DrawText(hDC, decodedLabel, lstrlen(decodedLabel), &rect, Flags);
|
||||
::RestoreDC(hDC, nSavedDC);
|
||||
}
|
||||
@ -1010,32 +1004,39 @@ void TabBarPlus::exchangeItemData(POINT point)
|
||||
|
||||
CloseButtonZone::CloseButtonZone()
|
||||
{
|
||||
// TODO: get width/height of close button dynamically
|
||||
_width = NppParameters::getInstance()->_dpiManager.scaleX(11);
|
||||
_hight = NppParameters::getInstance()->_dpiManager.scaleY(11);
|
||||
_fromTop = NppParameters::getInstance()->_dpiManager.scaleY(5);
|
||||
_fromRight = NppParameters::getInstance()->_dpiManager.scaleX(3);
|
||||
_height = NppParameters::getInstance()->_dpiManager.scaleY(11);
|
||||
}
|
||||
|
||||
|
||||
bool CloseButtonZone::isHit(int x, int y, const RECT & testZone) const
|
||||
bool CloseButtonZone::isHit(int x, int y, const RECT & tabRect, bool isVertical) const
|
||||
{
|
||||
if (((x + _width + _fromRight) < testZone.right) || (x > (testZone.right - _fromRight)))
|
||||
return false;
|
||||
RECT buttonRect = getButtonRectFrom(tabRect, isVertical);
|
||||
|
||||
if (((y - _hight - _fromTop) > testZone.top) || (y < (testZone.top + _fromTop)))
|
||||
return false;
|
||||
if (x >= buttonRect.left && x <= buttonRect.right && y >= buttonRect.top && y <= buttonRect.bottom)
|
||||
return true;
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
RECT CloseButtonZone::getButtonRectFrom(const RECT & tabItemRect) const
|
||||
RECT CloseButtonZone::getButtonRectFrom(const RECT & tabRect, bool isVertical) const
|
||||
{
|
||||
RECT rect;
|
||||
rect.right = tabItemRect.right - _fromRight;
|
||||
rect.left = rect.right - _width;
|
||||
rect.top = tabItemRect.top + _fromTop;
|
||||
rect.bottom = rect.top + _hight;
|
||||
RECT buttonRect;
|
||||
|
||||
return rect;
|
||||
int fromBorder;
|
||||
if (isVertical)
|
||||
{
|
||||
fromBorder = (tabRect.right - tabRect.left - _width + 1) / 2;
|
||||
buttonRect.left = tabRect.left + fromBorder;
|
||||
}
|
||||
else
|
||||
{
|
||||
fromBorder = (tabRect.bottom - tabRect.top - _height + 1) / 2;
|
||||
buttonRect.left = tabRect.right - fromBorder - _width;
|
||||
}
|
||||
buttonRect.top = tabRect.top + fromBorder;
|
||||
buttonRect.bottom = buttonRect.top + _height;
|
||||
buttonRect.right = buttonRect.left + _width;
|
||||
|
||||
return buttonRect;
|
||||
}
|
@ -135,13 +135,11 @@ protected:
|
||||
struct CloseButtonZone
|
||||
{
|
||||
CloseButtonZone();
|
||||
bool isHit(int x, int y, const RECT & testZone) const;
|
||||
RECT getButtonRectFrom(const RECT & tabItemRect) const;
|
||||
bool isHit(int x, int y, const RECT & tabRect, bool isVertical) const;
|
||||
RECT getButtonRectFrom(const RECT & tabRect, bool isVertical) const;
|
||||
|
||||
int _width;
|
||||
int _hight;
|
||||
int _fromTop; // distance from top in pixzl
|
||||
int _fromRight; // distance from right in pixzl
|
||||
int _height;
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user