// SandInterface.h #ifndef SandInterfaceh #define SandInterfaceh #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "PixelBlock.h" /* CSandInterface makes the contents of a window collapse as if it were turned to sand. To use it, either draw first(and only once!) and call the Draw function afterwards or derive a class from it which implements the InitDC and InitPixelBlock functions and put your drawing code in there. This example uses an Owner-draw Button in a Dialog: class CSand : public CSandInterface { public: bool InitDC(HDC hDC, const CRect& Rect) { FillRect(hDC, &Rect, CreateSolidBrush(GetSysColor(COLOR_3DFACE))); //Draw in dialogs background colour DrawText(hDC, "Hello",-1, (LPRECT)&Rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER); return true; //Always } void InitPixelBlock() { Rectangle(10,20,10,20,0xCAFE69); Rectangle(20,10,50,10,0x69CAFE); } }; Then in the Dialog Header have: CSand Sand; and in the .cpp file use ClassWizard to add an OnDrawItem handler like this: void CMyDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { if(nIDCtl==IDC_Pic) Sand.Draw(lpDrawItemStruct->hDC, lpDrawItemStruct->rcItem, 1000); //Delay 1 second (1000ms) before collapsing CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct); } You can save all the frames as bitmaps using the Draw function with the SaveFiles parameter set to true. they get saved in the current Directory as t000.bmp upwards. If its too fast (for small buttons etc), call SetSlow(); Bear in mind that this algorithm needs to know what the background colour is (ie. what colour Pixel is _not_ sand... By default it uses the System Dialog Background Colour, if you're using something else use the SetBGColor function. */ class CSandInterface : public CPixelBlock { bool FirstPass; //true only for the first Draw bool Finished; //true when no sand fell during the last redraw. bool Slow; int Frame; //Which frame nuber we're Drawing/Saving DWORD Time; //Delay before collapse DWORD Bg; //Background colour For Dialogs this is GetSysColor(COLOR_3DFACE); public: CSandInterface() : FirstPass(true), Slow(false), Bg(GetSysColor(COLOR_3DFACE)) {} void SetBgColor(DWORD c) {Bg=c;} DWORD GetBgColor() const {return Bg;} void Restart() {FirstPass=true;} void Draw(HDC hDC, const CRect& Rect, DWORD Delay=0, bool SaveFiles=false) { if(!(Rect.Width()|Rect.Height())) return; if(FirstPass) { FirstPass=false; Set(Rect.Width(), Rect.Height()); First(hDC, Rect, SaveFiles ? "t0000.bmp" : ""); Time=GetTickCount()+Delay; Frame=1; Finished=false; }else if(Finished || (Time>GetTickCount())) { CPixelBlock::Paint(hDC, Rect.left, Rect.top); if(!Finished) Redraw(hDC); }else{ CString S; if(SaveFiles) S.Format("t%04i.bmp",Frame++); Next(hDC, Rect, S); } } void SetSlow(bool _Slow=true) {Slow=_Slow;} protected: void First(HDC hDC, const CRect& Rect, CString Path) { if(InitDC(hDC, Rect)) Set(hDC, Rect.Width(), Rect.Height()); // Draw what you want on the DC then Copy DC to PixelBlock InitPixelBlock(); // Draw what you want on the PixelBlock CPixelBlock::Paint(hDC, Rect.left, Rect.top, Path); Redraw(hDC); } void Next(HDC hDC, const CRect& Rect, CString Path) { if(Slow) { // Slow things down a little: DWORD Time= GetTickCount(); while(Time==GetTickCount()); } bool DoneOne=false; DWORD a,aa,b,bb,c,d; for(int y=Rect.bottom-1; y>=Rect.top+1; --y) { a=GetPixel((WORD)Rect.left, y-1, Bg); b=GetPixel((WORD)Rect.left, y , Bg); for(int x=Rect.left+1; x Origin is top-left b=bb=GetPixel(x, y , Bg); // | d a switch(((((((d!=Bg) <<1) // V c b |(c!=Bg))<<1) |(b!=Bg))<<1) // (x,y) is b |(a!=Bg)) { case 1: case 5: case 13: b=a; a=Bg; break; // a falls to b case 8: case 10: case 11: SetPixel(x-1,y-1, Bg); SetPixel(x-1,y, d); DoneOne=true; break; // d falls to c case 9: b=a; a=Bg; SetPixel(x-1,y-1, Bg); SetPixel(x-1,y, d); break; // a falls to b AND d falls to c case 3: if(y&4) {SetPixel(x-1,y , a ); a=Bg; break;}// a falls to c else if(y&2) {SetPixel(x-1,y , b ); b=Bg; break;}// b moves to c else {SetPixel(x-1,y-1, b ); b=Bg; break;}// b moves to d case 12: if(y&4) {SetPixel(x-1,y-1, Bg); b=d; break;}// d falls to b else if(y&2) {SetPixel(x-1,y , Bg); b=c; break;}// c moves to b else {SetPixel(x-1,y , Bg); a=c; break;}// c moves to a } if(a!=aa) {SetPixel(x,y-1, a); DoneOne=true;} if(b!=bb) {SetPixel(x,y, b); DoneOne=true;} } } CPixelBlock::Paint(hDC, Rect.left, Rect.top, Path); if(DoneOne) Redraw(hDC); else { Finished=true; OnFinish(); } } void Redraw(HDC hDC) {(CDC::FromHandle(hDC)->GetWindow())->RedrawWindow(0,0, RDW_INVALIDATE);} //Non-Windows Platforms will need to change this! virtual bool InitDC(HDC hDC, const CRect& Rect) {return false;} // Draw what you want on the DC and then return true; virtual void InitPixelBlock() {}; // Draw what you want on the PixelBlock virtual void OnStart() {}; // Called when the animation starts virtual void OnFinish() {}; // Called when the animation finishes }; #endif //ndef SandInterfaceh