/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ #include "stdafx.h" #include "FloatingText.h" #include "MyMemoryDC.h" #include "MyLog.h" #include "MyApp.h" #include "MyRegistry.h" #include "Generic.h" #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Defines #define TIMERID_ANIMATION 1 #define TIMERID_FINALE 2 #define TIMERID_DURATION 3 ///////////////////////////////////////////////////////////////////////////// // Floater // Constructor. Floater::Floater(FloatingText* pFloatingText, int nFloater, const CString& sText) : m_pFloatingText(pFloatingText) , m_nFloater(nFloater) , m_sText(sText) , m_cr(NULL) , m_nDX(0) , m_nDY(0) , m_nRColorDrift(0) , m_nGColorDrift(0) , m_nBColorDrift(0) , m_bFreezing(false) , m_bFrozen(false) { ASSERT_VALID(pFloatingText); _ASSERTE(! sText.IsEmpty()); _ASSERTE(m_pFloatingText->m_ftd.nMinPointSize <= m_pFloatingText->m_ftd.nMaxPointSize); // Initialize. Isn't one purpose of a class to provide guaranteed // initialization, Microsoft? m_rcFullSlot.SetRectEmpty(); m_ptTarget.x = 0; m_ptTarget.y = 0; m_sizExtent.cx = 0; m_sizExtent.cy = 0; // Get the next true-type font name. We just rotate around when we've used // them all up. int nCount(m_pFloatingText->m_olFloaters.GetCount()); CString sFaceName(m_pFloatingText->m_saFaceNames [nCount % m_pFloatingText->m_saFaceNames.GetSize()]); // Set the point size. int nPointSize(m_pFloatingText->m_ftd.nMinPointSize + (rand() % (m_pFloatingText->m_ftd.nMaxPointSize - m_pFloatingText->m_ftd.nMinPointSize + 1))); _ASSERTE(0 < nPointSize); // Create the DC. CDC* pDCParent = m_pFloatingText->m_ftd.pParent->GetDC(); ASSERT_VALID(pDCParent); // Create the font semi-randomly. LOGFONT lf; ::ZeroMemory(&lf, sizeof lf); lstrcpy(lf.lfFaceName, sFaceName); _ASSERTE(lstrlen(lf.lfFaceName) < sizeof lf.lfFaceName && "Buffer overrun"); lf.lfHeight = -MulDiv(nPointSize, pDCParent->GetDeviceCaps(LOGPIXELSY), /* nPointsPerInch */ 72); // We don't want to italicize small fonts because they often look bad. if (12 <= nPointSize) { lf.lfItalic = static_cast (rand() % 2); } // This creates the font anywhere from 100 (FW_THIN) to 400 (FW_NORMAL). // Bold tends to look ugly in this control. lf.lfWeight = 100 * (1 + (rand() % 4)); VERIFY(m_font.CreateFontIndirect(&lf)); // Now get the text extent - we have to have this font selected into the // DC to do this. CFont* pFontOld = pDCParent->SelectObject(&m_font); m_sizExtent = pDCParent->GetTextExtent(m_sText); pDCParent->SelectObject(pFontOld); // Release the DC. m_pFloatingText->m_ftd.pParent->ReleaseDC(pDCParent); pDCParent = NULL; // Set the color drift randomly. m_nRColorDrift = (rand() % 2) ? 1 : -1; m_nGColorDrift = (rand() % 2) ? 1 : -1; m_nBColorDrift = (rand() % 2) ? 1 : -1; // Set location randomly. Note that we must have the ability to start wholly // or partially offscreen, otherwise it is unnaturally crowded at the startup. CRect rcFloatingText; m_pFloatingText->GetClientRect(rcFloatingText); m_pt.x = rand() % (rcFloatingText.Width() * 3 / 2); m_pt.y = rand() % (rcFloatingText.Height() * 3 / 2); // Set speed randomly, but larger point sizes move faster. (We don't let // either speed be zero, to avoid unsightly exactly horizontal or exactly // vertical movement.) nSpeedmax needs to be even to provide an equal // population of positive and negative dX and dY values. int nSpeedMax(3 + (nPointSize / 2)); if (nSpeedMax % 2) nSpeedMax++; do { m_nDX = (nSpeedMax / 2) - (rand() % nSpeedMax); m_nDY = (nSpeedMax / 2) - (rand() % nSpeedMax); } while (0 == (m_nDX * m_nDY)); // Choose color. Avoid an infinite loop, but try to get the colors spaced // randomly, but fairly evenly thoughout the color cube. for (int nAttempt = 0; nAttempt < 100; nAttempt++) { m_cr = RGB(rand() % 256, rand() % 256, rand() % 256); // If it is not too close to white (our starting background color). if (GetRValue(m_cr) + GetGValue(m_cr) + GetBValue(m_cr) < (192 * 3)) { // It's acceptable - quit looking. break; } } } // Destructor. /* virtual */ Floater::~Floater() { } // Called by MFC. #ifdef _DEBUG void Floater::AssertValid() const { _ASSERTE(! m_sText.IsEmpty()); ASSERT_VALID(&m_font); //_ASSERTE(0 <= m_pt.x); //_ASSERTE(0 <= m_pt.y); _ASSERTE(NULL != m_cr); //_ASSERTE(0 != m_nDX); //_ASSERTE(0 != m_nDY); _ASSERTE(0 != m_nRColorDrift); _ASSERTE(0 != m_nGColorDrift); _ASSERTE(0 != m_nBColorDrift); CObject::AssertValid(); } #endif // Draw assuming one "time-unit" has passed. We don't care how long... void Floater::Step(CDC* pDC, const CRect& rcParent) { VALIDATE; ASSERT_VALID(pDC); if (pDC) { // If we're not frozen, we need to know where our slot is, if we're // doing the puzzle finale. CRect rcCurrentSlot; if (m_pFloatingText->m_ftd.bPuzzleFinale && ! m_bFrozen) { CRect rcControl; m_pFloatingText->GetClientRect(rcControl); int nSlots(m_pFloatingText->m_olFloaters.GetCount()); int nCols(2); int nRows((nSlots + 1) / nCols); int nRow(m_nFloater / nCols); int nCol(m_nFloater % nCols); int nColW(rcControl.Width() / nCols); int nRowH(rcControl.Height() / nRows); rcCurrentSlot.left = (nCol * nColW); rcCurrentSlot.top = (nRow * nRowH); rcCurrentSlot.right = ((nCol + 1) * nColW); rcCurrentSlot.bottom = ((nRow + 1) * nRowH); m_rcFullSlot.CopyRect(rcCurrentSlot); // Now make the current slot, and also make the columns of text a // little jagged. This is what provides the jumpy affect as it // gets closer to the target rect. int nGapSlotX(4 + (rand() % (rcCurrentSlot.Width() / 3))); int nGapSlotY(4 + (rand() % (rcCurrentSlot.Height() / 3))); rcCurrentSlot.left += nGapSlotX; rcCurrentSlot.top += nGapSlotY; // If not already set, create the target point. if (0 == m_ptTarget.x && 0 == m_ptTarget.y) { // The target is how we know when to stop. m_ptTarget.x = m_rcFullSlot.left; m_ptTarget.y = m_rcFullSlot.top; int nUnusedX(m_rcFullSlot.Width() - m_sizExtent.cx - 8); int nUnusedY(m_rcFullSlot.Height() - m_sizExtent.cy - 8); int nGapTargetX(4); int nGapTargetY(4); if (0 < nUnusedX) { nGapTargetX = 4 + (rand() % nUnusedX); } if (0 < nUnusedY) { nGapTargetY = 4 + (rand() % nUnusedY); } m_ptTarget.x += nGapTargetX; m_ptTarget.y += nGapTargetY; _ASSERTE(m_ptTarget.x >= m_rcFullSlot.left + 4); _ASSERTE(m_ptTarget.y >= m_rcFullSlot.top + 4); } } // Figure out our new position. If not freezing, just move to the next // location on the screen. if (m_bFrozen) { ; // Do nothing. } else if (m_bFreezing) { // If freezing, we need to move to the target point. _ASSERTE(m_pFloatingText->m_ftd.bPuzzleFinale && "Must be doing this if we're freezing"); //DEBUG_ONLY(pDC->Rectangle(m_rcFullSlot)); //DEBUG_ONLY(pDC->SetPixel(m_ptTarget, RGB_RED)); // Now move toward our slot's target point. int nDiffX(m_ptTarget.x - m_pt.x); if (nDiffX) { m_nDX = abs(nDiffX) / nDiffX; } else { m_nDX = 0; } int nDiffY(m_ptTarget.y - m_pt.y); if (nDiffY) { m_nDY = abs(nDiffY) / nDiffY; } else { m_nDY = 0; } _ASSERTE(2 > abs(m_nDX * m_nDY) && "Both must be -1, 0, or 1"); // Make sure the new position is inside the current slot. The fact // that rcCurrentSlot changes size randomly each time is what // causes the jigginess as we approach the target. Note that if the // nDiff?" value is zero, then this *isn't* a new position in that // direction. int nNewX(m_pt.x + m_nDX); int nNewY(m_pt.y + m_nDY); bool bTextIsTooBigForSlot(true); if (nDiffX && nNewX >= rcCurrentSlot.left && nNewX <= rcCurrentSlot.right) { m_pt.x = nNewX; bTextIsTooBigForSlot = false; } if (nDiffY && nNewY >= rcCurrentSlot.top && nNewY <= rcCurrentSlot.bottom) { m_pt.y = nNewY; bTextIsTooBigForSlot = false; } // Make it do something interesting while it's freezing. m_cr = Generic::FlipRGB(m_cr); // Are we frozen - have we finally reached the top left corner? if (m_ptTarget.x == m_pt.x && m_ptTarget.y == m_pt.y || bTextIsTooBigForSlot) { m_bFrozen = true; // Choose color when it's finally frozen. for (int nAttempt = 0; nAttempt < 100; nAttempt++) { m_cr = Generic::HLStoRGB(rand() % HLSMAX, HLSMAX / 4, HLSMAX); // If it is not too close to white (our starting background color). if (GetRValue(m_cr) + GetGValue(m_cr) + GetBValue(m_cr) < (192 * 3)) { // It's acceptable - quit looking. break; } } } } else { // Neither frozen nor freezing; default case. //DEBUG_ONLY(pDC->Rectangle(m_rcFullSlot)); //DEBUG_ONLY(pDC->SetPixel(m_ptTarget, RGB_RED)); m_pt.x += m_nDX; m_pt.y += m_nDY; // If we're doing the puzzle finale, see if this one is in its slot. if (m_pFloatingText->m_ftd.bPuzzleFinale) { if (rcCurrentSlot.PtInRect(m_pt)) { m_bFreezing = true; } else { // Still not in its slot. If we don't add some randomness, // there's a chance it can be endlessly stuck in this pattern. if (0 == rand() % 10) { // Now move toward our slot's target point. int nDiffX(m_ptTarget.x - m_pt.x); if (nDiffX) { m_nDX = abs(m_nDX) * (abs(nDiffX) / nDiffX); } else { m_nDX = 0; } int nDiffY(m_ptTarget.y - m_pt.y); if (nDiffY) { m_nDY = abs(m_nDY) * (abs(nDiffY) / nDiffY); } else { m_nDY = 0; } m_pt.x += m_nDX; m_pt.y += m_nDY; // Check it again. if (rcCurrentSlot.PtInRect(m_pt)) { m_bFreezing = true; } else if (m_ptTarget.x == m_pt.x && m_ptTarget.y == m_pt.y) { m_bFrozen = true; } else { //m_nDX += m_nDX ? -abs(m_nDX) / m_nDX : (rand() % 3) - 1; //m_nDY += m_nDY ? -abs(m_nDY) / m_nDY : (rand() % 3) - 1; } } } } // Get a slightly new color. m_pFloatingText->ChangeColor(m_pFloatingText->m_bEnoughColors, m_cr, /* bLightColors */ false, m_nRColorDrift, m_nGColorDrift, m_nBColorDrift); } // Select the GDI object into the DC. pDC->SelectObject(&m_font); pDC->SetTextColor(m_cr); // Wrap around edges. We must call GetTextExtent() each time, because // if the text is outside the clipping area, the extent will be smaller. CSize siz(pDC->GetOutputTextExtent(m_sText)); if (m_pt.x > rcParent.right && 0 < m_nDX) { m_pt.x = rcParent.left - (1 * siz.cx); } else if (m_pt.x + siz.cx < rcParent.left && 0 > m_nDX) { m_pt.x = rcParent.right + (1 * siz.cx); } if (m_pt.y > rcParent.bottom && 0 < m_nDY) { m_pt.y = rcParent.top - (1 * siz.cy); } else if (m_pt.y + siz.cy < rcParent.top && 0 > m_nDY) { m_pt.y = rcParent.bottom + (1 * siz.cy); } // Draw our text. pDC->TextOut(m_pt.x, m_pt.y, m_sText); } } ///////////////////////////////////////////////////////////////////////////// // FloatingText // Constructor. FloatingText::FloatingText() : m_nRColorDrift(0) , m_nGColorDrift(0) , m_nBColorDrift(0) , m_nAnimationCount(0) , m_bEnoughColors(false) { ::ZeroMemory(&m_ftd, sizeof(m_ftd)); } // Destructor. /* virtual */ FloatingText::~FloatingText() { // Clean out list. POSITION pos(m_olFloaters.GetHeadPosition()); while (pos) { delete m_olFloaters.GetNext(pos); } m_olFloaters.RemoveAll(); } BEGIN_MESSAGE_MAP(FloatingText, CButton) //{{AFX_MSG_MAP(FloatingText) ON_WM_TIMER() ON_WM_ERASEBKGND() ON_CONTROL_REFLECT(BN_CLICKED, OnClicked) ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // FloatingText message handlers // Initialize. bool FloatingText::Start(PFLOATINGTEXTDATA pftd) { VALIDATE; ASSERT_NULL_OR_POINTER(pftd, FLOATINGTEXTDATA); // Set the color drift randomly. m_nRColorDrift = (rand() % 2) ? 1 : -1; m_nGColorDrift = (rand() % 2) ? 1 : -1; m_nBColorDrift = (rand() % 2) ? 1 : -1; // Set defaults. We start the color out close to light blue. if (pftd) { m_ftd.pParent = AfxGetMainWnd(); m_ftd.uiFirstStringID = 0U; lstrcpy(m_ftd.szRegistryKey, ""); m_ftd.bParseToWords = false; m_ftd.bPuzzleFinale = false; m_ftd.bBackgroundColorDrift = false; lstrcpy(m_ftd.szFinale, ""); m_ftd.nMinPointSize = 10; m_ftd.nMaxPointSize = 64; m_ftd.nAnimationMS = 50; m_ftd.nFinaleMS = 10 * 1000; m_ftd.nDurationMS = 0; m_ftd.crBackground = RGB( 192 + (rand() % (256 - 192)), 222 + (rand() % (256 - 222)), 244 + (rand() % (256 - 244))); // Set any user-sent values. if (pftd->pParent) m_ftd.pParent = pftd->pParent; if (pftd->uiFirstStringID) m_ftd.uiFirstStringID = pftd->uiFirstStringID; if (pftd->szRegistryKey[0]) lstrcpy(m_ftd.szRegistryKey, pftd->szRegistryKey); if (pftd->bParseToWords) m_ftd.bParseToWords = pftd->bParseToWords; if (pftd->bPuzzleFinale) m_ftd.bPuzzleFinale = pftd->bPuzzleFinale; if (pftd->bBackgroundColorDrift) m_ftd.bBackgroundColorDrift = pftd->bBackgroundColorDrift; if (pftd->szFinale[0]) lstrcpy(m_ftd.szFinale, pftd->szFinale); if (pftd->nMinPointSize) m_ftd.nMinPointSize = pftd->nMinPointSize; if (pftd->nMaxPointSize) m_ftd.nMaxPointSize = pftd->nMaxPointSize; if (pftd->nAnimationMS) m_ftd.nAnimationMS = pftd->nAnimationMS; if (pftd->nFinaleMS) m_ftd.nFinaleMS = pftd->nFinaleMS; if (pftd->nDurationMS) m_ftd.nDurationMS = pftd->nDurationMS; if (pftd->crBackground) m_ftd.crBackground = pftd->crBackground; if (pftd->pfAddString) m_ftd.pfAddString = pftd->pfAddString; } // Create the DC. CDC* pDCParent = m_ftd.pParent->GetDC(); ASSERT_VALID(pDCParent); // If there are LE 256 colors, some special effects won't work. int nBitsPerPlane(pDCParent->GetDeviceCaps(BITSPIXEL)); int nColorPlanes(pDCParent->GetDeviceCaps(PLANES)); int nBitsPerPixel(nBitsPerPlane * nColorPlanes); m_bEnoughColors = (nBitsPerPixel > 8); // Start the timers. We do it as soon as we have set the background color // so that painting can begin, even before we have fully populated the // floaters collection. pDCParent->SetBkColor(m_ftd.crBackground); SetTimer(TIMERID_ANIMATION, m_ftd.nAnimationMS, NULL); if (0 < m_ftd.nFinaleMS) { SetTimer(TIMERID_FINALE, m_ftd.nFinaleMS, NULL); } if (0 < m_ftd.nDurationMS) { SetTimer(TIMERID_DURATION, m_ftd.nDurationMS, NULL); } // First, let's get an array of all the TrueType font names. If for some // weird reason there are no TrueType fonts, we default to a common one. LOGFONT lf = { 0 }; ::EnumFontFamiliesEx(pDCParent->m_hDC, &lf, reinterpret_cast (FloatingText::MyEnumFontCallback), reinterpret_cast (this), /* dwUnused */ 0); if (FloatingText::m_saFaceNames.GetSize() == 0) { FloatingText::m_saFaceNames.Add("Courier"); } // Parse the first (and only) string into words. if (m_ftd.bParseToWords) { CString sData; CStringList slWords; VERIFY(sData.LoadString(m_ftd.uiFirstStringID)); Generic::ParseToWords(sData, slWords); POSITION pos(slWords.GetHeadPosition()); _ASSERTE(pos); for (int nFloater = 0; pos; ++nFloater) { sData = slWords.GetNext(pos); if (! sData.IsEmpty()) { Floater* pFloater = new Floater(this, nFloater, sData); ASSERT_VALID(pFloater); m_olFloaters.AddTail(pFloater); } } } else { // Clean out list. POSITION pos(m_olFloaters.GetHeadPosition()); while (pos) { delete m_olFloaters.GetNext(pos); } m_olFloaters.RemoveAll(); // Load up the floaters text string from the string table. Don't // use Generic::LoadString here - we use the inability to read a string // as the signal that there are no more strings, not as an error. CString sData; int nFloater(0); for (UINT uiID = m_ftd.uiFirstStringID; sData.LoadString(uiID); ++nFloater, ++uiID) { Floater* pFloater = new Floater(this, nFloater, sData); ASSERT_VALID(pFloater); m_olFloaters.AddTail(pFloater); } // Now load any strings from the Registry path, if passed. if (m_ftd.szRegistryKey[0]) { MyRegistry reg(HKEY_CURRENT_USER, m_ftd.szRegistryKey); if (reg.IsOpen()) { for (int nFloater = 0; true; ++nFloater) { CString sKey; sKey.Format("Floater%d", nFloater); if (reg.ReadValueString(sKey, sData)) { Floater* pFloater = new Floater(this, nFloater, sData); ASSERT_VALID(pFloater); m_olFloaters.AddTail(pFloater); } else { break; } } } } // Make sure there is at least one floater. if (0 == m_olFloaters.GetCount()) { Floater* pFloater = new Floater(this, nFloater, "No text has been configured for this window!"); ASSERT_VALID(pFloater); m_olFloaters.AddTail(pFloater); } } return true; } // Pause the animation. void FloatingText::Pause() { VALIDATE; VERIFY(KillTimer(TIMERID_ANIMATION)); } // Un-pause the animation. void FloatingText::Resume() { VALIDATE; SetTimer(TIMERID_ANIMATION, m_ftd.nAnimationMS, NULL); } // Draw the button. Called because of the Invalidate() on OnTimer(). void FloatingText::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { VALIDATE; // Set up the DC. CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); ASSERT_VALID(pDC); // Create the memory bitmap. CRect rc; GetClientRect(rc); MyMemoryDC dcMemory(pDC, rc); if (pDC) { CBrush* pBrush = CBrush::FromHandle(::CreateSolidBrush(m_ftd.crBackground)); ASSERT_VALID(pBrush); if (pBrush) { dcMemory.SetBkMode(TRANSPARENT); dcMemory.SetBkColor(m_ftd.crBackground); dcMemory.FillRect(rc, pBrush); // Tell each floater to draw itself. POSITION pos(m_olFloaters.GetHeadPosition()); while (pos) { Floater* pFloater = static_cast (m_olFloaters.GetNext(pos)); ASSERT_VALID(pFloater); pFloater->Step(&dcMemory, rc); } // Done with brush. if (pBrush->DeleteObject()) { ; } else { DOMYLOGST(API_FAILURE), "DeleteObject", ::GetLastError(), Generic::GetSysErrorString()); } pBrush = NULL; } else { DOMYLOGST(API_FAILURE), "CreateSolidBrush", ::GetLastError(), Generic::GetSysErrorString()); } } } // Handle WM_TIMER. void FloatingText::OnTimer(UINT nIDEvent) { VALIDATE; switch (nIDEvent) { // Repaint the screen. Only change the color very slowly. case TIMERID_ANIMATION: ++m_nAnimationCount; // Change the color. if (m_ftd.bBackgroundColorDrift && 0 == (m_nAnimationCount % 100)) { ChangeColor(m_bEnoughColors, m_ftd.crBackground, /* bLightColors */ true, m_nRColorDrift, m_nGColorDrift, m_nBColorDrift); } // Add the floater. if (0 == (m_nAnimationCount % 20)) { if (m_ftd.pfAddString) { CString sAdd((*m_ftd.pfAddString)()); if (! FindFloater(sAdd)) { int nFloater(m_olFloaters.GetCount() - 1); Floater* pFloater = new Floater(this, nFloater, sAdd); ASSERT_VALID(pFloater); m_olFloaters.AddTail(pFloater); DOMYLOGD ("Added floater <%d>.\n", nFloater); } } } Invalidate( /* bEraseBackground */ false); break; // The finale timer. case TIMERID_FINALE: KillTimer(TIMERID_FINALE); DoFinale(); break; // The duration timer. case TIMERID_DURATION: KillTimer(TIMERID_ANIMATION); KillTimer(TIMERID_FINALE); KillTimer(TIMERID_DURATION); break; } CButton::OnTimer(nIDEvent); } // Change the sent color slightly and slowly. /* static */ void FloatingText::ChangeColor(bool bEnoughColors, COLORREF& cr, bool bLightColors, int& nRColorDrift, int& nGColorDrift, int& nBColorDrift) { if (bEnoughColors) { int nR(GetRValue(cr)); int nG(GetGValue(cr)); int nB(GetBValue(cr)); switch (rand() % 3) { case 0: FloatingText::ChangeColorPart(nR, nRColorDrift, bLightColors); break; case 1: FloatingText::ChangeColorPart(nG, nGColorDrift, bLightColors); break; case 2: FloatingText::ChangeColorPart(nB, nBColorDrift, bLightColors); break; } if ((bLightColors && ((nR + nG + nB) > (3 * 192))) || (! bLightColors && ((nR + nG + nB) < (3 * 192)))) { cr = RGB(nR, nG, nB); } } } // Tendency for the colors to drift toward one end of the block. /* static */ void FloatingText::ChangeColorPart(int& nX, int& nDriftX, bool bLightColors) { _ASSERTE(0 <= nX && 256 > nX && "Color out of range"); _ASSERTE(-1 == nDriftX || 1 == nDriftX && "Illegal drift value"); int nDelta(1 - (rand() % 3)); nDelta = (0 == nDelta) ? nDriftX : nDelta; // Light color getting too dark. if (bLightColors && (nDelta + nX < 192)) { nDriftX = 1; nDelta = -nDelta; } else if (! bLightColors && (nDelta + nX >= 192)) { // Dark color getting too light. nDriftX = -1; nDelta = -nDelta; } // Any color rolled past white! if (nDelta + nX > 255) { nDriftX = -1; nDelta = -nDelta; } else if (nDelta + nX < 0) { // Any color rolled past black! nDriftX = 1; nDelta = -nDelta; } nX += nDelta; _ASSERTE(0 <= nX && 256 > nX && "Color out of range"); } // The grand finale. Every "n" seconds, we change one of the floaters text to // our phrase. void FloatingText::DoFinale() { VALIDATE; // Make sure there even is any finale text. if (0 < lstrlen(m_ftd.szFinale)) { POSITION pos(m_olFloaters.GetHeadPosition()); while (pos) { Floater* pFloater = static_cast (m_olFloaters.GetNext(pos)); ASSERT_VALID(pFloater); if (pFloater) { CString sOldText(pFloater->m_sText); if (sOldText.Compare(m_ftd.szFinale)) { pFloater->m_sText = m_ftd.szFinale; SetTimer(TIMERID_FINALE, 500, NULL); break; } } } } } // When this control is partially obscured, or sized by the user, we tell it to // not repaint the background. This eliminates one source of flicker. (We are // already using FillRect() to paint it anyway.) BOOL FloatingText::OnEraseBkgnd(CDC* pDC) { VALIDATE; UNUSED_ALWAYS(pDC); return false; // CButton::OnEraseBkgnd(pDC); } // User clicked on the floaters. void FloatingText::OnClicked() { VALIDATE; // Flip their colors. POSITION pos(m_olFloaters.GetHeadPosition()); for (int nFloater = 0; pos; nFloater++) { Floater* pFloater = static_cast (m_olFloaters.GetNext(pos)); ASSERT_VALID(pFloater); pFloater->m_cr = Generic::FlipRGB(pFloater->m_cr); } m_ftd.crBackground = Generic::FlipRGB(m_ftd.crBackground); // Start the timers again (double the duration). if (0 < m_ftd.nAnimationMS) { SetTimer(TIMERID_ANIMATION, m_ftd.nAnimationMS, NULL); } if (0 < m_ftd.nFinaleMS) { SetTimer(TIMERID_FINALE, m_ftd.nFinaleMS, NULL); } if (0 < m_ftd.nDurationMS) { m_ftd.nDurationMS *= 2; SetTimer(TIMERID_DURATION, m_ftd.nDurationMS, NULL); } } // Build the array of True Type face names available on this system. /* static */ int CALLBACK FloatingText::MyEnumFontCallback( ENUMLOGFONTEX * lpelfe, NEWTEXTMETRICEX *lpntme, int FontType, LPARAM lParam) { _ASSERTE(lpelfe); _ASSERTE(lpntme); _ASSERTE(FontType); _ASSERTE(lParam); UNUSED_ALWAYS(lpntme); // Retrive the pointer we sent. FloatingText* pFloatingText = reinterpret_cast (lParam); ASSERT_VALID(pFloatingText); // We only want non-decorative TrueType fonts. if (TRUETYPE_FONTTYPE == FontType && ! (lpelfe->elfLogFont.lfPitchAndFamily & FF_DECORATIVE)) { CString sFaceName(lpelfe->elfLogFont.lfFaceName); bool bAlreadyStored(false); for (int nFace = 0; nFace < pFloatingText->m_saFaceNames.GetSize(); nFace++) { if (0 == pFloatingText->m_saFaceNames.GetAt(nFace).Compare(sFaceName)) { bAlreadyStored = true; break; } } if (! bAlreadyStored) { pFloatingText->m_saFaceNames.Add(sFaceName); //DOMYLOGD ("Added font facename <%s>.\n", sFaceName); } } return 1; } // Return floater for text, or NULL if not found. Floater* FloatingText::FindFloater(const CString& sText) { VALIDATE; Floater* pFloaterReturn = NULL; POSITION pos(m_olFloaters.GetHeadPosition()); while (pos) { Floater* pFloater = static_cast (m_olFloaters.GetNext(pos)); ASSERT_VALID(pFloater); if (0 == pFloater->m_sText.CompareNoCase(sText)) { pFloaterReturn = pFloater; break; } } return pFloaterReturn; } // WM_DESTROY void FloatingText::OnDestroy() { VALIDATE; CButton::OnDestroy(); // These may correctly fail if the timer wasn't started. KillTimer(TIMERID_ANIMATION); KillTimer(TIMERID_FINALE); KillTimer(TIMERID_DURATION); }