// Border.cpp: implementation of the Border class. // // Author: Darren Sessions // // // Description: // Border Draws or Fills rounded rectangles for GDI+. It was implemented // to overcome the asymmetric issues associated with GDI+ round rectangles // // History // Version 1.0 - 2008 June 24 // - Initial public release // // License: // This software is released under the Code Project Open License (CPOL), // which may be found here: http://www.codeproject.com/info/eula.aspx // You are free to use this software in any way you like, except that you // may not sell this source code. // // This software is provided "as is" with no expressed or implied warranty. // I accept no liability for any damage or loss of business that this // software may cause. // /////////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "Border.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// Border::Border() { } Border::~Border() { } //============================================================================= // // GetPath() // // Purpose: Defines a the appropriate path and places it in the GraphicsPath // // Parameters: pPath - [out] pointer to GraphicsPath that will recieve the // path data // r - [in] Rect that defines the round rectangle boundaries // // Returns: None // void Border::GetPath(GraphicsPath *pPath, Rect r) { if(shape == RECTANGLE) { GetRectPath(pPath, r); } else { GetRoundRectPath(pPath, r); } } //============================================================================= // // GetRectPath() // // Purpose: Defines a Rectangle and places it in the GraphicsPath // // Parameters: pPath - [out] pointer to GraphicsPath that will recieve the // path data // r - [in] Rect that defines the round rectangle boundaries // // Returns: None // void Border::GetRectPath(GraphicsPath *pPath, Rect r) { pPath->Reset(); pPath->AddRectangle(r); pPath->CloseFigure(); } //============================================================================= // // GetRoundRectPath() // // Purpose: Defines a Rounded Rectangle and places it in the GraphicsPath // // Parameters: pPath - [out] pointer to GraphicsPath that will recieve the // path data // r - [in] Rect that defines the round rectangle boundaries // // Returns: None // void Border::GetRoundRectPath(GraphicsPath *pPath, Rect r) { // set the diameter int dia = 2*radius; // this isn't a round rect anymore but it // can occur with progress bars if( (dia > r.Width) || (dia > r.Height) ) { r.Inflate(0, -(dia/r.Width)); // add other case pPath->AddEllipse(r); return; } // define a corner Rect Corner(r.X, r.Y, dia, dia); // begin path pPath->Reset(); // top left pPath->AddArc(Corner, 180, 90); // tweak needed for radius of 10 (dia of 20) if(dia == 20) { Corner.Width += 1; Corner.Height += 1; r.Width -=1; r.Height -= 1; } // top right Corner.X += (r.Width - dia - 1); pPath->AddArc(Corner, 270, 90); // bottom right Corner.Y += (r.Height - dia - 1); pPath->AddArc(Corner, 0, 90); // bottom left Corner.X -= (r.Width - dia - 1); pPath->AddArc(Corner, 90, 90); // end path pPath->CloseFigure(); } //============================================================================= // // Draw() // // Purpose: Draws a border with a solid pen // // Parameters: pGraphics - [in] pointer to the Graphics device // r - [in] Rect that defines the round rectangle boundaries // ulclr - [in] Color value for the pen // brclr - [in] Bottom right pen color (SQUARE border only) // width - [in] width of the border // // Returns: None // void Border::Draw(Graphics* pGraphics, Rect r, Color ulclr, Color brclr, int width) { if(shape == RECTANGLE) { DrawRect(pGraphics, r, ulclr, brclr, width); } if(shape == ELLIPSE) { Pen pen(ulclr, 1); pen.SetAlignment(PenAlignmentCenter); RectF rf((float)r.X-1, (float)r.Y-1, (float)r.Width+1, (float)r.Height+1); pGraphics->DrawEllipse(&pen, rf); } else if(shape == TRANSITION) { // disable smoothing for the clip fill SmoothingMode oldMode = pGraphics->GetSmoothingMode(); pGraphics->SetSmoothingMode(SmoothingModeNone); // fill the transition zone using a clip region pGraphics->SetClip(r, CombineModeReplace); // fill one pixel width into the round rect if width > 1 // this supresses any transition antialiasing Rect cr(r); if(width > 1) cr.Inflate(-1, -1); // use local clip function for round rect SetBorderClip(pGraphics, cr, CombineModeExclude); // fill with the border color SolidBrush sbr(ulclr); pGraphics->FillRectangle(&sbr, r); // clear the clipping region pGraphics->ResetClip(); // restore any smoothing mode pGraphics->SetSmoothingMode(oldMode); // now draw the round border DrawRoundRect(pGraphics, r, ulclr, width); } else { DrawRoundRect(pGraphics, r, ulclr, width); } } //============================================================================= // // DrawRect() // // Purpose: Draws a rectangle with a solid pen // // Parameters: pGraphics - [in] pointer to the Graphics device // r - [in] Rect that defines the round rectangle boundaries // color - [in] Color value for the brush // width - [in] width of the border // // Returns: None // void Border::DrawRect(Graphics* pGraphics, Rect r, Color ulclr, Color brclr, int width) { int i, left, top, bottom, right; // set to pixel mode Unit oldPageUnit = pGraphics->GetPageUnit(); pGraphics->SetPageUnit(UnitPixel); // define the upper left pen Pen ulpen(ulclr, 1); ulpen.SetAlignment(PenAlignmentCenter); // define the bottom right pen Pen brpen(brclr, 1); brpen.SetAlignment(PenAlignmentCenter); // not sure why Rect rc(r); for(i = 0; i < width; i++) { left = rc.X; top = rc.Y; bottom = rc.GetBottom()-1; right = rc.GetRight()-1; // left pGraphics->DrawLine(&ulpen, left, top, left, bottom); // top pGraphics->DrawLine(&ulpen, left, top, right, top); // right pGraphics->DrawLine(&brpen, right, top+1, right, bottom); // bottom pGraphics->DrawLine(&brpen, left+1, bottom, right, bottom); rc.Inflate(-1, -1); } // restore page unit pGraphics->SetPageUnit(oldPageUnit); } //============================================================================= // // DrawRoundRect() // // Purpose: Draws a rounded rectangle with a solid pen // // Parameters: pGraphics - [in] pointer to the Graphics device // r - [in] Rect that defines the round rectangle boundaries // color - [in] Color value for the brush // width - [in] width of the border // // Returns: None // void Border::DrawRoundRect(Graphics* pGraphics, Rect r, Color color, int width) { // set to pixel mode Unit oldPageUnit = pGraphics->GetPageUnit(); pGraphics->SetPageUnit(UnitPixel); // define the pen Pen pen(color, 1); pen.SetAlignment(PenAlignmentCenter); // get the corner path GraphicsPath path; // get path GetRoundRectPath(&path, r); // draw the round rect pGraphics->DrawPath(&pen, &path); // save smoothing mode SmoothingMode oldMode = pGraphics->GetSmoothingMode(); // if width > 1 for(int i=1; iSetSmoothingMode(SmoothingModeNone); // re enable for last line if(i == (width - 1)) pGraphics->SetSmoothingMode(oldMode); // left stroke r.Inflate(-1, 0); // get the path GetRoundRectPath(&path, r); // draw the round rect pGraphics->DrawPath(&pen, &path); // up stroke r.Inflate(0, -1); // get the path GetRoundRectPath(&path, r); // draw the round rect pGraphics->DrawPath(&pen, &path); } // restore page unit pGraphics->SetPageUnit(oldPageUnit); } //============================================================================= // // FillRoundRect() // // Purpose: Fills a rounded rectangle with a solid brush. Draws the border // first then fills in the rectangle. // // Parameters: pGraphics - [in] pointer to the Graphics device // r - [in] Rect that defines the round rectangle boundaries // color - [in] Color value for the brush // radius - [in] radius of the rounded corner // // Returns: None // void Border::FillRoundRect(Graphics* pGraphics, Rect r, Color color, int radius) { SolidBrush sbr(color); FillRoundRect(pGraphics, &sbr, r, color, radius); } //============================================================================= // // FillRoundRect() // // Purpose: Fills a rounded rectangle with the passed brush. Fills the // rectangle then drawst the border. // // Parameters: pGraphics - [in] pointer to the Graphics device // pBrush - [in] pointer to a Brush // r - [in] Rect that defines the round rectangle boundaries // color - [in] Color value for the border (needed in case the // brush is a type other than solid) // radius - [in] radius of the rounded corner // // Returns: None // void Border::FillRoundRect(Graphics* pGraphics, Brush* pBrush, Rect r, Color border, int radius) { int dia = 2*radius; // set to pixel mode Unit oldPageUnit = pGraphics->GetPageUnit(); pGraphics->SetPageUnit(UnitPixel); // define the pen Pen pen(border, 1); pen.SetAlignment(PenAlignmentCenter); // get the corner path GraphicsPath path; // get path GetRoundRectPath(&path, r); // fill pGraphics->FillPath(pBrush, &path); // draw the border last so it will be on top in case the color is different pGraphics->DrawPath(&pen, &path); // restore page unit pGraphics->SetPageUnit(oldPageUnit); } //============================================================================= // // SetBorderClip() // // Purpose: Sets the clipping region to the inside of and including this border. // The SetClip(path) of GDI+ has the same problem that a fill path does // on a round rectangle, so use GDI's function instead // // Parameters: pGraphics - [in] pointer to the Graphics device // // Returns: None // void Border::SetBorderClip(Graphics* pGraphics, Rect rc, CombineMode mode) { CRgn frgn; int dia; dia = 2*radius; if(shape == UNDEFINED) { // the border needs a shape ASSERT(FALSE); } if(shape == RECTANGLE) { frgn.CreateRectRgn(rc.X, rc.Y, rc.GetRight()+1, rc.GetBottom()+1); } else if(shape == ELLIPSE) { frgn.CreateEllipticRgn(rc.X-1, rc.Y-1, rc.GetRight()+2, rc.GetBottom()+2); } // handle this special case - TODO: add for Height < dia else if(rc.Width < dia) { rc.Width++; rc.Height++; Rect lrc(rc); lrc.Width = 2*dia; Rect rrc(rc); rrc.Width = 2*dia; rrc.X = rrc.X - (rrc.Width - rc.Width); CRgn lrgn, rrgn; lrgn.CreateRoundRectRgn(lrc.X, lrc.Y, lrc.GetRight()+1, lrc.GetBottom()+1, dia+1, dia+1); rrgn.CreateRoundRectRgn(rrc.X, rrc.Y, rrc.GetRight()+1, rrc.GetBottom()+1, dia+1, dia+1); frgn.CreateRectRgn(0,0,0,0); frgn.CombineRgn(&lrgn, &rrgn, RGN_AND); } else { frgn.CreateRoundRectRgn(rc.X, rc.Y, rc.GetRight()+1, rc.GetBottom()+1, dia+1, dia+1); } Region grgn(frgn); pGraphics->SetClip(&grgn, mode); }