2025-11-27 16:46:48 +09:00

310 lines
8.8 KiB
C++

#include "StdAfx.h"
#include "OXHyperBarRender.h"
#include "OXHyperbar.h"
#include<cmath>
namespace
{
double ColumnDistanceFromDOI(int columnIndex, int columnCount, int barWidth, POINT * mousePos)
{
double mouseProportion = 0.5;
if (mousePos)
{
mouseProportion = mousePos->x / (double)barWidth;
}
double positionProportion = (double)(columnIndex + 0.5) / columnCount;
return mouseProportion - positionProportion;
}
double ValueAtX(double x, double variance)
{
double exponent = (x * x) / (2 * variance * variance);
return exp(-exponent);
}
}
double COXHyperBarRender::m_Focus = .2; // This represents the spacing of the toolbar, which also causes it to zoom
COXHyperBarRender::COXHyperBarRender(CToolBar * pToolbar)
{
m_nCmd = -1;
m_pToolbar = pToolbar;
m_buttonInfo = NULL;
m_buffer = NULL;
}
void COXHyperBarRender::Initialise(SIZE szBtn)
{
// Create array to contain button info, and populate it.
m_buttonInfo = new buttonInfo[m_pToolbar->GetToolBarCtrl().GetButtonCount()];
CImageList * const pImages = m_pToolbar->GetToolBarCtrl().GetImageList();
for(int i=0;i<m_pToolbar->GetToolBarCtrl().GetButtonCount();++i)
{
TBBUTTON btn;
m_pToolbar->GetToolBarCtrl().GetButton(i, &btn);
IMAGEINFO iif;
pImages->GetImageInfo( btn.iBitmap, &iif);
const int imgWidth = iif.rcImage.right - iif.rcImage.left;
const int imgHeight = iif.rcImage.bottom - iif.rcImage.top;
m_buttonInfo[i].defaultWidth = imgWidth;
m_buttonInfo[i].width = szBtn.cx;//imgWidth;
m_buttonInfo[i].defaultHeight = imgHeight;
m_buttonInfo[i].height = szBtn.cy;//imgHeight;
m_buttonInfo[i].bitmapIndex = btn.iBitmap;
m_buttonInfo[i].commandID = btn.idCommand;
}
}
COXHyperBarRender::~COXHyperBarRender(void)
{
if (m_buttonInfo)
{
delete [] m_buttonInfo;
m_buttonInfo = NULL;
}
if (m_buffer)
{
delete m_buffer;
m_buffer = NULL;
}
}
void COXHyperBarRender::RenderToolbar(CDC * pDC, RECT * pRect, SIZE szBtn, POINT * pMouse, CBitmap * pOverlay)
{
// The first time this is run after the toolbar is set up,
// call the method to fill the array of image data
if (!m_buttonInfo)
{
if(m_pToolbar->GetToolBarCtrl().m_hWnd && m_pToolbar->GetToolBarCtrl().GetButtonCount())
{
Initialise(szBtn);
}
else return;
}
CImageList * const pImages = m_pToolbar->GetToolBarCtrl().GetImageList();
const int width = pRect->right - pRect->left;
const int height = pRect->bottom - pRect->top;
// Create the bitmap used as a double buffer
if (!m_buffer)
{
m_buffer = new CBitmap();
// The buffer will be created when the mouse is not over the toolbar
m_buffer->CreateCompatibleBitmap(pDC, width, height+(COXHyperBar::OffsetY * 2));
}
CDC dc;
dc.CreateCompatibleDC(pDC);
CBitmap * pOld = dc.SelectObject(m_buffer);
// offset is the amount of space around each button
const int offset = 4;
const int buttonCount = m_pToolbar->GetToolBarCtrl().GetButtonCount();
RECT rc;
rc.right = width;
rc.bottom = height;
rc.top = rc.left = 0;
// Reset the command, it will be set again if our mouse is over a button in the code below
m_nCmd = -1;
// This variable is used to accumulate the current position as we draw the buttons
POINT ptDraw;
ptDraw.x = ptDraw.y = 0;
// We need to do this in two parts:
// 1 - create a bitmap which has the overlay as a background and then the buttons drawn in the right positions
// 2 - use the AlphaBlend function to blend this toolbar onto our device context.
// The reason for this is that we need to use the Image List to draw the buttons transparently first,
// then we need to do the alpha blend after that.
CDC imageListDC;
imageListDC.CreateCompatibleDC(pDC);
CBitmap imageListBitmap;
imageListBitmap.CreateCompatibleBitmap(pDC, width, height);
imageListDC.SetStretchBltMode(COLORONCOLOR);
dc.SetStretchBltMode(COLORONCOLOR);
CBitmap * pOldImageList = imageListDC.SelectObject(&imageListBitmap);
// If pOverlay != NULL, then draw the pOverlay onto both the buffer bitmap, and the bitmap that will be the source for alpha blending.
if (pOverlay)
{
CDC dcBmp;
dcBmp.CreateCompatibleDC(&dc);
CBitmap * pOld = dcBmp.SelectObject(pOverlay);
imageListDC.BitBlt(0, 0, width, height, &dcBmp, 0, 0, SRCCOPY);
dc.BitBlt(0, 0, width, height, &dcBmp, 0, 0, SRCCOPY);
dcBmp.SelectObject(&pOld);
}
// rcBar becomes the rectangle we use to know the position of the toolbar within our CDC
CRect rcBar = rc;
if (pOverlay)
{
rcBar.InflateRect(0, -(COXHyperBar::OffsetY + 2));
}
((COXHyperBar*)m_pToolbar)->RenderBackground(&dc, &rcBar);
((COXHyperBar*)m_pToolbar)->RenderBackground(&imageListDC, rcBar);
double * hypers = new double[buttonCount];
double hyperTotal = 0.0;
for(int j=0;j<buttonCount;++j)
{
// Skip seperators, which would otherwise kill the spacing, etc.
if (!m_buttonInfo[j].commandID)
{
hypers[j] = 0;
continue;
}
double distanceFromDOI = ColumnDistanceFromDOI(j, buttonCount, width, pMouse);
// Plug the distance from zero (the DOI itself is at zero) into the hyperbolic function.
// .05 -> .4 Focus
hypers[j] = ValueAtX(distanceFromDOI, m_Focus);
hyperTotal += hypers[j];
}
double factor = width/hyperTotal;
for(int n=0;n<buttonCount;++n)
{
double distanceFromDOI = ColumnDistanceFromDOI(n, buttonCount, width, pMouse);
double hyperbolic = hypers[n];
// commandID is 0 for non buttons.
if (!m_buttonInfo[n].commandID) continue;
int alpha = 255;
if (pMouse)
{
// The second param is always 1
alpha = (int) (ValueAtX(distanceFromDOI, m_Focus) * 255);
}
// If we're not drawing the overlay window, we nust draw at the default width
int imgWidth = (pMouse) ? (int)(factor * hyperbolic) - offset * 2 : m_buttonInfo[n].width ;
int imgHeight = (pMouse)?(int)(m_buttonInfo[n].height * factor/m_buttonInfo[n].width * hyperbolic) : m_buttonInfo[n].height ;
ptDraw.x += offset;
ptDraw.y = (height - imgHeight)/2;
POINT pt;
pt.x = pt.y = 0;
// Draw the whole toolbar image
SIZE szDraw;
szDraw.cx = szDraw.cy = 0;
CDC itemDC;
itemDC.CreateCompatibleDC(&dc);
CBitmap itemBitmap;
itemBitmap.CreateCompatibleBitmap(&dc, m_buttonInfo[n].defaultWidth, m_buttonInfo[n].defaultHeight);
CBitmap * pOldItemBitmap = itemDC.SelectObject(&itemBitmap);
itemDC.FillSolidRect(0, 0, m_buttonInfo[n].defaultWidth, m_buttonInfo[n].defaultHeight, RGB(255,0,255));
pImages->Draw(&itemDC, m_buttonInfo[n].bitmapIndex, pt, ILD_TRANSPARENT);
if (pMouse)
{
// Check if this is the active button
CRect rcDraw(ptDraw.x, ptDraw.y, imgWidth + ptDraw.x, ptDraw.y + imgHeight);
if (PtInRect(&rcDraw, *pMouse))
{
m_nCmd = m_buttonInfo[n].commandID;
}
}
// Draw the button transparently onto the image list render DC
TransparentBlt(imageListDC, ptDraw.x, ptDraw.y, imgWidth, imgHeight, itemDC, 0, 0, m_buttonInfo[n].defaultWidth, m_buttonInfo[n].defaultHeight, RGB(255, 0, 255));
// Now blend it back across the main DC
BLENDFUNCTION bf;
bf.AlphaFormat = 0;
bf.BlendFlags = 0;
bf.BlendOp = 0;
bf.SourceConstantAlpha = (BYTE)alpha;
AlphaBlend(dc, ptDraw.x, ptDraw.y, imgWidth, imgHeight, imageListDC, ptDraw.x, ptDraw.y, imgWidth, imgHeight, bf);
itemDC.SelectObject(pOldItemBitmap);
ptDraw.x += offset + imgWidth;
}
pDC->BitBlt(0, 0, width, height, &dc, 0, 0, SRCCOPY);
imageListDC.SelectObject(pOldImageList);
dc.SelectObject(pOld);
delete [] hypers;
// Once the paint has occurred, set the flag to say if we drew over the main toolbar, or the floating window.
((COXHyperBar*)m_pToolbar)->m_bHasRedrawn = (pMouse == NULL);
}
// This method is called whenever the mouse moves, it works out which item is under the mouse, and stores it's ID for use if
// the click occurs, in which case it's used to send the command to the main window.
void COXHyperBarRender::SetActiveItem(POINT pt)
{
CImageList * const pImages = m_pToolbar->GetToolBarCtrl().GetImageList();
RECT rect;
m_pToolbar->GetClientRect(&rect);
RECT * pRect = &rect;
const int width = pRect->right - pRect->left;
const int height = pRect->bottom - pRect->top;
const int imageCount = pImages->GetImageCount();
const int buttonWidth = (int)floor(width/(double)imageCount);
const int buttonCount = m_pToolbar->GetToolBarCtrl().GetButtonCount();
// It's reset every time this method is called.
m_nCmd = -1;
for(int j=0;j<buttonCount;++j)
{
TBBUTTON btn;
m_pToolbar->GetToolBarCtrl().GetButton(j, &btn);
IMAGEINFO iif;
pImages->GetImageInfo(btn.iBitmap, &iif);
const int imgWidth = iif.rcImage.right - iif.rcImage.left;
const int imgHeight = iif.rcImage.bottom - iif.rcImage.top;
POINT ptDraw;
ptDraw.x = buttonWidth * btn.iBitmap + (buttonWidth - imgWidth)/2;
ptDraw.y = (height - imgHeight)/2;
CRect rcDraw(ptDraw.x, ptDraw.y, imgWidth + ptDraw.x, ptDraw.y + imgHeight + 20);
// We've found a match.
if (PtInRect(&rcDraw, pt))
{
m_nCmd = btn.idCommand;
break;
}
}
}