where i = indent in pixels
// Paragraph:
// Horizontal line:
// Icon: where x = icon id and (optional) y = size (16/32/50)
// where x = icon resource name
// Color: where x = RGB in hex
// where x = color id
//
// In addition the standard string resource formats apply:
//
// %{n} where n = resource-string-id
//
if( ::IsRectEmpty(&rc) ) return;
bool bDraw = (uStyle & DT_CALCRECT) == 0;
RECT rcClip = { 0 };
::GetClipBox(hDC, &rcClip);
HRGN hOldRgn = ::CreateRectRgnIndirect(&rcClip);
HRGN hRgn = ::CreateRectRgnIndirect(&rc);
if( bDraw ) ::ExtSelectClipRgn(hDC, hRgn, RGN_AND);
CStdString sText = pstrText;
sText.ProcessResourceTokens();
pstrText = sText;
HFONT hOldFont = (HFONT) ::SelectObject(hDC, pManager->GetThemeFont(UIFONT_NORMAL));
::SetBkMode(hDC, TRANSPARENT);
::SetTextColor(hDC, pManager->GetThemeColor(iTextColor));
// If the drawstyle includes an alignment, we'll need to first determine the text-size so
// we can draw it at the correct position...
if( (uStyle & DT_SINGLELINE) != 0 && (uStyle & DT_VCENTER) != 0 && (uStyle & DT_CALCRECT) == 0 ) {
RECT rcText = { 0, 0, 9999, 100 };
int nLinks = 0;
DoPaintPrettyText(hDC, pManager, rcText, pstrText, iTextColor, iBackColor, NULL, nLinks, uStyle | DT_CALCRECT);
rc.top = rc.top + ((rc.bottom - rc.top) / 2) - ((rcText.bottom - rcText.top) / 2);
rc.bottom = rc.top + (rcText.bottom - rcText.top);
}
if( (uStyle & DT_SINGLELINE) != 0 && (uStyle & DT_CENTER) != 0 && (uStyle & DT_CALCRECT) == 0 ) {
RECT rcText = { 0, 0, 9999, 100 };
int nLinks = 0;
DoPaintPrettyText(hDC, pManager, rcText, pstrText, iTextColor, iBackColor, NULL, nLinks, uStyle | DT_CALCRECT);
::OffsetRect(&rc, (rc.right - rc.left) / 2 - (rcText.right - rcText.left) / 2, 0);
}
if( (uStyle & DT_SINGLELINE) != 0 && (uStyle & DT_RIGHT) != 0 && (uStyle & DT_CALCRECT) == 0 ) {
RECT rcText = { 0, 0, 9999, 100 };
int nLinks = 0;
DoPaintPrettyText(hDC, pManager, rcText, pstrText, iTextColor, iBackColor, NULL, nLinks, uStyle | DT_CALCRECT);
rc.left = rc.right - (rcText.right - rcText.left);
}
// Paint backhground
if( iBackColor != UICOLOR__INVALID ) DoFillRect(hDC, pManager, rc, iBackColor);
// Determine if we're hovering over a link, because we would like to
// indicate it by coloring the link text.
// BUG: This assumes that the prcLink has already been filled once with
// link coordinates! That is usually not the case at first repaint. We'll clear
// the remanining entries at exit.
int i;
bool bHoverLink = false;
POINT ptMouse = pManager->GetMousePos();
for( i = 0; !bHoverLink && i < nLinkRects; i++ ) {
if( ::PtInRect(prcLinks + i, ptMouse) ) bHoverLink = true;
}
TEXTMETRIC tm = pManager->GetThemeFontInfo(UIFONT_NORMAL);
POINT pt = { rc.left, rc.top };
int iLineIndent = 0;
int iLinkIndex = 0;
int cyLine = tm.tmHeight + tm.tmExternalLeading;
int cyMinHeight = 0;
POINT ptLinkStart = { 0 };
bool bInLink = false;
while( *pstrText != '\0' )
{
if( pt.x >= rc.right || *pstrText == '\n' )
{
// A new link was detected/requested. We'll adjust the line height
// for the next line and expand the link hitbox (if any)
if( bInLink && iLinkIndex < nLinkRects) ::SetRect(&prcLinks[iLinkIndex++], ptLinkStart.x, ptLinkStart.y, pt.x, pt.y + tm.tmHeight);
if( (uStyle & DT_SINGLELINE) != 0 ) break;
if( *pstrText == '\n' ) pstrText++;
pt.x = rc.left + iLineIndent;
pt.y += cyLine - tm.tmDescent;
ptLinkStart = pt;
cyLine = tm.tmHeight + tm.tmExternalLeading;
if( pt.x >= rc.right ) break;
while( *pstrText == ' ' ) pstrText++;
}
else if( *pstrText == '<'
&& (pstrText[1] >= 'a' && pstrText[1] <= 'z')
&& (pstrText[2] == ' ' || pstrText[2] == '>') )
{
pstrText++;
switch( *pstrText++ )
{
case 'a': // Link
{
::SetTextColor(hDC, pManager->GetThemeColor(bHoverLink ? UICOLOR_LINK_TEXT_HOVER : UICOLOR_LINK_TEXT_NORMAL));
::SelectObject(hDC, pManager->GetThemeFont(UIFONT_LINK));
tm = pManager->GetThemeFontInfo(UIFONT_LINK);
cyLine = MAX(cyLine, tm.tmHeight + tm.tmExternalLeading);
ptLinkStart = pt;
bInLink = true;
}
break;
case 'f': // Font
{
UITYPE_FONT iFont = (UITYPE_FONT) _tcstol(pstrText, const_cast(&pstrText), 10);
::SelectObject(hDC, pManager->GetThemeFont(iFont));
tm = pManager->GetThemeFontInfo(iFont);
cyLine = MAX(cyLine, tm.tmHeight + tm.tmExternalLeading);
}
break;
case 'b': // Bold text
{
::SelectObject(hDC, pManager->GetThemeFont(UIFONT_BOLD));
tm = pManager->GetThemeFontInfo(UIFONT_BOLD);
cyLine = MAX(cyLine, tm.tmHeight + tm.tmExternalLeading);
}
break;
case 'x': // Indent
{
iLineIndent = (int) _tcstol(pstrText, const_cast(&pstrText), 10);
if( pt.x < rc.left + iLineIndent ) pt.x = rc.left + iLineIndent;
}
break;
case 'p': // Paragraph
{
pt.x = rc.right;
cyLine = MAX(cyLine, tm.tmHeight + tm.tmExternalLeading) + 5;
iLineIndent = 0;
::SelectObject(hDC, pManager->GetThemeFont(UIFONT_NORMAL));
::SetTextColor(hDC, pManager->GetThemeColor(iTextColor));
tm = pManager->GetThemeFontInfo(UIFONT_NORMAL);
}
break;
case 'h': // Horizontal line
{
::SelectObject(hDC, pManager->GetThemePen(UICOLOR_STANDARD_GREY));
if( bDraw ) {
POINT ptTemp = { 0 };
::MoveToEx(hDC, pt.x, pt.y + 5, &ptTemp);
::LineTo(hDC, rc.right - iLineIndent, pt.y + 5);
}
cyLine = 12;
}
break;
case 'i': // Icon
{
int iSize = 16;
if( *pstrText == ' ' ) pstrText++;
if( isdigit(*pstrText) ) {
int iIndex = (int) _tcstol(pstrText, const_cast(&pstrText), 10);
iSize = MAX(16, _ttoi(pstrText));
if( bDraw ) {
HICON hIcon = pManager->GetThemeIcon(iIndex, iSize);
ASSERT(hIcon!=NULL);
::DrawIconEx(hDC, pt.x, pt.y, hIcon, iSize, iSize, 0, NULL, DI_NORMAL);
::DestroyIcon(hIcon);
}
}
else {
if( *pstrText == ' ' ) pstrText++;
CStdString sRes;
while( _istalnum(*pstrText) || *pstrText == '.' || *pstrText == '_' ) sRes += *pstrText++;
HICON hIcon = (HICON) ::LoadImage(pManager->GetResourceInstance(), sRes, IMAGE_ICON, 0, 0, LR_LOADTRANSPARENT | LR_SHARED);
ASSERT(hIcon!=NULL);
ICONINFO ii = { 0 };
::GetIconInfo(hIcon, &ii);
BITMAP bi = { 0 };
::GetObject(ii.hbmColor, sizeof(BITMAP), &bi);
iSize = bi.bmWidth;
if( bDraw ) ::DrawIconEx(hDC, pt.x, pt.y, hIcon, iSize, iSize, 0, NULL, DI_NORMAL);
::DestroyIcon(hIcon);
}
// A special feature with an icon at the left edge is that it also sets
// the paragraph indent.
if( pt.x == rc.left ) iLineIndent = iSize + (iSize / 8); else cyLine = MAX(iSize, cyLine);
pt.x += iSize + (iSize / 8);
cyMinHeight = pt.y + iSize;
}
break;
case 'c': // Color
{
if( *pstrText == ' ' ) pstrText++;
if( *pstrText == '#') {
pstrText++;
COLORREF clrColor = _tcstol(pstrText, const_cast(&pstrText), 16);
clrColor = RGB(GetBValue(clrColor), GetGValue(clrColor), GetRValue(clrColor));
::SetTextColor(hDC, clrColor);
}
else {
UITYPE_COLOR Color = (UITYPE_COLOR) _tcstol(pstrText, const_cast(&pstrText), 10);
::SetTextColor(hDC, pManager->GetThemeColor(Color));
}
}
break;
}
while( *pstrText != '\0' && *pstrText != '>' ) pstrText++;
pstrText++;
}
else if( *pstrText == '<' && pstrText[1] == '/' )
{
pstrText += 2;
switch( *pstrText++ )
{
case 'a':
if( iLinkIndex < nLinkRects ) ::SetRect(&prcLinks[iLinkIndex++], ptLinkStart.x, ptLinkStart.y, pt.x, pt.y + tm.tmHeight + tm.tmExternalLeading);
::SetTextColor(hDC, pManager->GetThemeColor(iTextColor));
::SelectObject(hDC, pManager->GetThemeFont(UIFONT_NORMAL));
tm = pManager->GetThemeFontInfo(UIFONT_NORMAL);
bInLink = false;
break;
case 'f':
case 'b':
// TODO: Use a context stack instead
::SelectObject(hDC, pManager->GetThemeFont(UIFONT_NORMAL));
tm = pManager->GetThemeFontInfo(UIFONT_NORMAL);
break;
case 'c':
::SetTextColor(hDC, pManager->GetThemeColor(iTextColor));
break;
}
while( *pstrText != '\0' && *pstrText != '>' ) pstrText++;
pstrText++;
}
else if( *pstrText == '&' )
{
if( (uStyle & DT_NOPREFIX) == 0 ) {
if( bDraw && pManager->GetSystemSettings().bShowKeyboardCues ) ::TextOut(hDC, pt.x, pt.y, _T("_"), 1);
}
else {
SIZE szChar = { 0 };
::GetTextExtentPoint32(hDC, _T("&"), 1, &szChar);
if( bDraw ) ::TextOut(hDC, pt.x, pt.y, _T("&"), 1);
pt.x += szChar.cx;
}
pstrText++;
}
else if( *pstrText == ' ' )
{
SIZE szSpace = { 0 };
::GetTextExtentPoint32(hDC, _T(" "), 1, &szSpace);
// Still need to paint the space because the font might have
// underline formatting.
if( bDraw ) ::TextOut(hDC, pt.x, pt.y, _T(" "), 1);
pt.x += szSpace.cx;
pstrText++;
}
else
{
POINT ptPos = pt;
int cchChars = 0;
int cchLastGoodWord = 0;
LPCTSTR p = pstrText;
SIZE szText = { 0 };
if( *p == '<' ) p++, cchChars++;
while( *p != '\0' && *p != '<' && *p != '\n' && *p != '&' ) {
// This part makes sure that we're word-wrapping if needed or providing support
// for DT_END_ELLIPSIS. Unfortunately the GetTextExtentPoint32() call is pretty
// slow when repeated so often.
// TODO: Rewrite and use GetTextExtentExPoint() instead!
cchChars++;
szText.cx = cchChars * tm.tmMaxCharWidth;
if( pt.x + szText.cx >= rc.right ) {
::GetTextExtentPoint32(hDC, pstrText, cchChars, &szText);
}
if( pt.x + szText.cx >= rc.right ) {
if( (uStyle & DT_WORDBREAK) != 0 && cchLastGoodWord > 0 ) {
cchChars = cchLastGoodWord;
pt.x = rc.right;
}
if( (uStyle & DT_END_ELLIPSIS) != 0 && cchChars > 2 ) {
cchChars -= 2;
pt.x = rc.right;
}
break;
}
if( *p == ' ' ) cchLastGoodWord = cchChars;
p = ::CharNext(p);
}
if( cchChars > 0 ) {
::GetTextExtentPoint32(hDC, pstrText, cchChars, &szText);
if( bDraw ) {
::TextOut(hDC, ptPos.x, ptPos.y, pstrText, cchChars);
if( pt.x == rc.right && (uStyle & DT_END_ELLIPSIS) != 0 ) ::TextOut(hDC, rc.right - 10, ptPos.y, _T("..."), 3);
}
pt.x += szText.cx;
pstrText += cchChars;
}
}
ASSERT(iLinkIndex<=nLinkRects);
}
// Clear remaining link rects and return number of used rects
for( i = iLinkIndex; i < nLinkRects; i++ ) ::ZeroMemory(prcLinks + i, sizeof(RECT));
nLinkRects = iLinkIndex;
// Return size of text when requested
if( (uStyle & DT_CALCRECT) != 0 ) {
rc.bottom = MAX(cyMinHeight, pt.y + cyLine);
if( rc.right >= 9999 ) {
if( _tcslen(pstrText) > 0 ) pt.x += 3;
rc.right = pt.x;
}
}
if( bDraw ) ::SelectClipRgn(hDC, hOldRgn);
::DeleteObject(hOldRgn);
::DeleteObject(hRgn);
::SelectObject(hDC, hOldFont);
}
void CBlueRenderEngineUI::DoPaintGradient(HDC hDC, CPaintManagerUI* pManager, RECT rc, COLORREF clrFirst, COLORREF clrSecond, bool bVertical, int nSteps)
{
typedef BOOL (WINAPI *PGradientFill)(HDC, PTRIVERTEX, ULONG, PVOID, ULONG, ULONG);
static PGradientFill lpGradientFill = (PGradientFill) ::GetProcAddress(::GetModuleHandle("msimg32.dll"), "GradientFill");
if( lpGradientFill != NULL )
{
// Use Windows gradient function from msimg32.dll
// It may be slower than the code below but makes really pretty gradients on 16bit colors.
TRIVERTEX triv[2] =
{
{ rc.left, rc.top, GetRValue(clrFirst) << 8, GetGValue(clrFirst) << 8, GetBValue(clrFirst) << 8, 0xFF00 },
{ rc.right, rc.bottom, GetRValue(clrSecond) << 8, GetGValue(clrSecond) << 8, GetBValue(clrSecond) << 8, 0xFF00 }
};
GRADIENT_RECT grc = { 0, 1 };
lpGradientFill(hDC, triv, 2, &grc, 1, bVertical ? GRADIENT_FILL_RECT_V : GRADIENT_FILL_RECT_H);
}
else
{
// Determine how many shades
int nShift = 1;
if( nSteps >= 64 ) nShift = 6;
else if( nSteps >= 32 ) nShift = 5;
else if( nSteps >= 16 ) nShift = 4;
else if( nSteps >= 8 ) nShift = 3;
else if( nSteps >= 4 ) nShift = 2;
int nLines = 1 << nShift;
for( int i = 0; i < nLines; i++ ) {
// Do a little alpha blending
BYTE bR = (BYTE) ((GetRValue(clrSecond) * (nLines - i) + GetRValue(clrFirst) * i) >> nShift);
BYTE bG = (BYTE) ((GetGValue(clrSecond) * (nLines - i) + GetGValue(clrFirst) * i) >> nShift);
BYTE bB = (BYTE) ((GetBValue(clrSecond) * (nLines - i) + GetBValue(clrFirst) * i) >> nShift);
// ... then paint with the resulting color
HBRUSH hBrush = ::CreateSolidBrush(RGB(bR,bG,bB));
RECT r2 = rc;
if( bVertical ) {
r2.bottom = rc.bottom - ((i * (rc.bottom - rc.top)) >> nShift);
r2.top = rc.bottom - (((i + 1) * (rc.bottom - rc.top)) >> nShift);
if( (r2.bottom - r2.top) > 0 ) ::FillRect(hDC, &r2, hBrush);
}
else {
r2.left = rc.right - (((i + 1) * (rc.right - rc.left)) >> nShift);
r2.right = rc.right - ((i * (rc.right - rc.left)) >> nShift);
if( (r2.right - r2.left) > 0 ) ::FillRect(hDC, &r2, hBrush);
}
::DeleteObject(hBrush);
}
}
}
void CBlueRenderEngineUI::DoPaintAlphaBitmap(HDC hDC, CPaintManagerUI* pManager, HBITMAP hBitmap, RECT rc, BYTE iAlpha)
{
// Alpha blitting is only supported of the msimg32.dll library is located on the machine.
typedef BOOL (WINAPI *LPALPHABLEND)(HDC, int, int, int, int,HDC, int, int, int, int, BLENDFUNCTION);
static LPALPHABLEND lpAlphaBlend = (LPALPHABLEND) ::GetProcAddress(::GetModuleHandle("msimg32.dll"), "AlphaBlend");
if( lpAlphaBlend == NULL ) return;
if( hBitmap == NULL ) return;
HDC hCloneDC = ::CreateCompatibleDC(pManager->GetPaintDC());
HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(hCloneDC, hBitmap);
int cx = rc.right - rc.left;
int cy = rc.bottom - rc.top;
::SetStretchBltMode(hDC, COLORONCOLOR);
BLENDFUNCTION bf = { 0 };
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.AlphaFormat = AC_SRC_ALPHA;
bf.SourceConstantAlpha = iAlpha;
lpAlphaBlend(hDC, rc.left, rc.top, cx, cy, hCloneDC, 0, 0, cx, cy, bf);
::SelectObject(hCloneDC, hOldBitmap);
::DeleteDC(hCloneDC);
}
void CBlueRenderEngineUI::DoAnimateWindow(HWND hWnd, UINT uStyle, DWORD dwTime /*= 200*/)
{
typedef BOOL (CALLBACK* PFNANIMATEWINDOW)(HWND, DWORD, DWORD);
#ifndef AW_HIDE
const DWORD AW_HIDE = 0x00010000;
const DWORD AW_BLEND = 0x00080000;
#endif
// Mix flags
DWORD dwFlags = 0;
if( (uStyle & UIANIM_HIDE) != 0 ) dwFlags |= AW_HIDE;
if( (uStyle & UIANIM_FADE) != 0 ) dwFlags |= AW_BLEND;
PFNANIMATEWINDOW pfnAnimateWindow = (PFNANIMATEWINDOW) ::GetProcAddress(::GetModuleHandle(_T("user32.dll")), "AnimateWindow");
if( pfnAnimateWindow != NULL ) pfnAnimateWindow(hWnd, dwTime, dwFlags);
}
HBITMAP CBlueRenderEngineUI::GenerateAlphaBitmap(CPaintManagerUI* pManager, CControlUI* pControl, RECT rc, UITYPE_COLOR Background)
{
typedef BOOL (WINAPI *LPALPHABLEND)(HDC, int, int, int, int,HDC, int, int, int, int, BLENDFUNCTION);
static FARPROC lpAlphaBlend = ::GetProcAddress(::GetModuleHandle("msimg32.dll"), "AlphaBlend");
if( lpAlphaBlend == NULL ) return NULL;
int cx = rc.right - rc.left;
int cy = rc.bottom - rc.top;
// Let the control paint itself onto an offscreen bitmap
HDC hPaintDC = ::CreateCompatibleDC(pManager->GetPaintDC());
HBITMAP hPaintBitmap = ::CreateCompatibleBitmap(pManager->GetPaintDC(), rc.right, rc.bottom);
ASSERT(hPaintDC);
ASSERT(hPaintBitmap);
HBITMAP hOldPaintBitmap = (HBITMAP) ::SelectObject(hPaintDC, hPaintBitmap);
DoFillRect(hPaintDC, pManager, rc, Background);
pControl->DoPaint(hPaintDC, rc);
// Create a new 32bpp bitmap with room for an alpha channel
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = cx;
bmi.bmiHeader.biHeight = cy;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = cx * cy * sizeof(DWORD);
LPDWORD pDest = NULL;
HDC hCloneDC = ::CreateCompatibleDC(pManager->GetPaintDC());
HBITMAP hBitmap = ::CreateDIBSection(pManager->GetPaintDC(), &bmi, DIB_RGB_COLORS, (LPVOID*) &pDest, NULL, 0);
ASSERT(hCloneDC);
ASSERT(hBitmap);
if( hBitmap != NULL )
{
// Copy offscreen bitmap to our new 32bpp bitmap
HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(hCloneDC, hBitmap);
::BitBlt(hCloneDC, 0, 0, cx, cy, hPaintDC, rc.left, rc.top, SRCCOPY);
::SelectObject(hCloneDC, hOldBitmap);
::DeleteDC(hCloneDC);
::GdiFlush();
// Make the background color transparent
COLORREF clrBack = pManager->GetThemeColor(Background);
DWORD dwKey = RGB(GetBValue(clrBack), GetGValue(clrBack), GetRValue(clrBack));
DWORD dwShowColor = 0xFF000000;
for( int y = 0; y < abs(bmi.bmiHeader.biHeight); y++ ) {
for( int x = 0; x < bmi.bmiHeader.biWidth; x++ ) {
if( *pDest != dwKey ) *pDest = *pDest | dwShowColor;
else *pDest = 0x00000000;
pDest++;
}
}
}
// Cleanup
::SelectObject(hPaintDC, hOldPaintBitmap);
::DeleteObject(hPaintBitmap);
::DeleteDC(hPaintDC);
return hBitmap;
}