Actions

Please note that as of October 24, 2014, the Nokia Developer Wiki will no longer be accepting user contributions, including new entries, edits and comments, as we begin transitioning to our new home, in the Windows Phone Development Wiki. We plan to move over the majority of the existing entries over the next few weeks. Thanks for all your past and future contributions.

How to draw complicated rounded rectangles in Symbian C++

This article provides a solution for drawing rectangles that can have different rounding properties for each of the corners. It provides a more flexible solution than CWindowGc::DrawRoundRect(), which allows you to draw rounded rectangles with four symmetric corners.

Tested with
Devices(s): Nokia 5800 XpressMusic, Nokia E90
Compatibility
Platform(s): S60 3rd Edition, S60 5th Edition
Platform Security
Signing Required: Self-Signed
Capabilities: None
Article
Keywords: CWindowGc::UseBrushPattern(), CWindowGc::DrawPolygon()
Created: isalento (07 May 2009)
Last edited: hamishwillee (10 Feb 2012)

How it is done

Since drawing of advanced rounded rectangles is not directly supported, one must do some hacking. You can think rounded rectangle to consist of four ellipses (or less if there is sharp corners) as presented in the picture below.

The outermost points of each ellipse are part of the border of the rounded rectangle. There is different ways to calculate ellipse points, but I used one that can be found from the end of the ellipse article in the wikipedia.

CWindowGc:: DrawPolygon() is used to draw the rectangle. It is quite handy as you can use gc.UseBrushPattern() to set background image and gc.SetPenXXX() properties to define how the border will look. Please note that it would be more efficient to calculate and store the rectangle vertice values before entering the draw function. In this example, calculation is done "on the fly".

Source code

`/** Draws a rectangle. Each corner radius is limited to be half of rectangle height or width* * gc graphics context* aX  upper left corner x coordinate* aY  upper left corner y coordinate* width rectangle width* height rectangle height* aBl bottom left corner properties TSize(ellipse horizontal radius, ellipse vertical radius)* aTl top left corner properties* aTr top right corner properties* aBr bottom right corner properties* aBodrderThickness thickness of the border* aBorderColor border color* aBackgroundColor color of the background (fill color) * */void drawRoundedRectangle(CWindowGc &gc,TInt aX, TInt aY, TInt width, TInt height, TSize aBl, TSize aTl, TSize aTr, TSize aBr, TSize aBodrerThickness, TRgb aBorderColor, TRgb aBackgroundColor) const; /** gc graphics context* aX  upper left corner x coordinate* aY  upper left corner y coordinate* width rectangle width* height rectangle height* aBl bottom left corner properties TSize(ellipse horizontal radius, ellipse vertical radius)* aTl top left corner properties* aTr top right corner properties* aBr bottom right corner properties* aBodrderThickness thickness of the border* aBorderColor border color* aBackgroundImage Pointer to the background image*/void drawRoundedRectangle(CWindowGc &gc,TInt aX, TInt aY, TInt width, TInt height,TSize aBl, TSize aTl, TSize aTr, TSize aBr, TSize aBodrerThickness, TRgb aBorderColor, CFbsBitmap* aBackgroundImage) const;  CArrayFix<TPoint>*  calculateCornerPixels(TInt aX, TInt aY, TInt width, TInt height, TSize aBl, TSize aTl, TSize aTr, TSize aBr) const;  /** From CCoeControl, Draw* Draw this CRoundedRectangleAppView to the screen.* @param aRect the rectangle of this view that needs updating*/void Draw(const TRect& aRect) const;`

Source file

`void CRoundedRectangleAppView::drawRoundedRectangle(CWindowGc &gc, TInt aX, TInt aY, TInt aWidth, TInt aHeight, TSize aBl, TSize aTl, TSize aTr, TSize aBr, TSize aBorderThickness, TRgb aBorderColor, TRgb aBackgroundColor) const{	gc.SetBrushStyle(CGraphicsContext::ESolidBrush);	gc.SetPenStyle(CGraphicsContext::ESolidPen); 	gc.SetPenSize(aBorderThickness);	gc.SetPenColor(aBorderColor); 	gc.SetBrushColor(aBackgroundColor); 	CArrayFix<TPoint>* points = calculateCornerPixels(aX,aY,aWidth,aHeight, aBl,aTl,aTr,aBr);	gc.DrawPolygon(points);	delete points; 	} void CRoundedRectangleAppView::drawRoundedRectangle(CWindowGc &gc, TInt aX, TInt aY, TInt aWidth, TInt aHeight,TSize aBl, TSize aTl, TSize aTr, TSize aBr, TSize aBorderThickness, TRgb aBorderColor, CFbsBitmap* aBackgroundImage) const	{	gc.SetPenStyle(CGraphicsContext::ESolidPen);	gc.SetPenSize(aBorderThickness);	gc.SetPenColor(aBorderColor); 	gc.UseBrushPattern(aBackgroundImage);	gc.SetBrushStyle(CGraphicsContext::EPatternedBrush); 	CArrayFix<TPoint>* points = calculateCornerPixels(aX,aY,aWidth,aHeight, aBl,aTl,aTr,aBr); 	gc.DrawPolygon(points); 	gc.SetBrushStyle(CGraphicsContext::ESolidBrush);	gc.DiscardBrushPattern(); 	delete points;	}  CArrayFix<TPoint>* CRoundedRectangleAppView::calculateCornerPixels(TInt aX, TInt aY, TInt width, TInt height, TSize aBl, TSize aTl, TSize aTr, TSize aBr) const 	{	TReal sinalpha=0;	TReal cosalpha=0;	TReal alpha=0;	TReal X=0,Y=0;        const TReal piRad = KPi/180;         //simple sanity checks        TReal widthPerTwo = width /2;        TReal heightPerTwo = height /2;         aBl.iWidth = Min(aBl.iWidth, widthPerTwo);        aTl.iWidth = Min(aTl.iWidth, widthPerTwo);        aTr.iWidth = Min(aTr.iWidth, widthPerTwo);        aBr.iWidth = Min(aBr.iWidth, widthPerTwo);         aBl.iHeight = Min(aBl.iHeight, heightPerTwo);        aTl.iHeight = Min(aTl.iHeight, heightPerTwo);        aTr.iHeight = Min(aTr.iHeight, heightPerTwo);        aBr.iHeight = Min(aBr.iHeight, heightPerTwo);          //calculate ellipse middle points        TPoint bottomLeft(aX+aBl.iWidth, aY+height-aBl.iHeight);        TPoint topLeft(aX+aTl.iWidth, aY+aTl.iHeight);        TPoint topRight(aX+width-aTr.iWidth, aY+aTr.iHeight);        TPoint bottomRight(aX+width-aBr.iWidth, aY+height-aBr.iHeight);	 	//container for the calculated points	CArrayFix<TPoint>* rectanglePoints = new CArrayFixFlat<TPoint>(12);	CleanupStack::PushL(rectanglePoints); 	//bottom left corner	if(aBl.iHeight==0 || aBl.iWidth==0)		 {		 rectanglePoints->AppendL(TPoint(aX,aY+height)); //sharp corner 		 }	 	 else	 	 {	 	 //let's reduce points needed based on the corner properties	 	 //no need for calculating fixed amount of points for every corner e.g. 2x2 corner	 	 TReal stepBl=0;	 	 Math::Sqrt(stepBl, aBl.iWidth*aBl.iWidth+aBl.iHeight*aBl.iHeight);	 	 stepBl = ((90/stepBl)<1 ? 1: 90/stepBl); //limit the minium to be 1 --> max 90 points per corner 		 for (TReal i = 90; i <= 180 ; i += stepBl) 			  {				alpha = i * piRad ;				Math::Sin(sinalpha,alpha);				Math::Cos(cosalpha,alpha); 				Math::Round(X,bottomLeft.iX + (aBl.iWidth  * cosalpha),0);				Math::Round(Y,bottomLeft.iY + (aBl.iHeight * sinalpha),0); 				rectanglePoints->AppendL(TPoint(X,Y));			  }	 	 }  	 // top left corner	 if(aTl.iHeight==0 || aTl.iWidth==0)		 {		 rectanglePoints->AppendL(TPoint(aX,aY));		 }		 else		 {	 	 TReal stepTl=0;	 	 Math::Sqrt(stepTl, aBl.iWidth*aTl.iWidth+aTl.iHeight*aTl.iHeight);	 	 stepTl = ((90/stepTl)<1 ? 1: 90/stepTl);  	 	 for (TReal i = 180; i <= 270; i += stepTl) 			  {			  alpha = i * piRad ;			  Math::Sin(sinalpha,alpha);			  Math::Cos(cosalpha,alpha); 			  Math::Round(X,topLeft.iX + (aTl.iWidth  * cosalpha),0);			  Math::Round(Y,topLeft.iY + (aTl.iHeight * sinalpha),0); 			  rectanglePoints->AppendL(TPoint(X,Y));			  }		 }  	 // top right corner	 if(aTr.iHeight==0 || aTr.iWidth==0)		 {		 rectanglePoints->AppendL(TPoint(aX+width,aY));		 }	 	 else	 	 {	 	 TReal stepTr=0;	 	 Math::Sqrt(stepTr, aTr.iWidth*aTr.iWidth+aTr.iHeight*aTr.iHeight);	 	 stepTr = ((90/stepTr)<1 ? 1: 90/stepTr);  		 for (TReal i = 270; i <= 360; i += stepTr) 			  {			  alpha = i * piRad ;			  Math::Sin(sinalpha,alpha);			  Math::Cos(cosalpha,alpha); 			  Math::Round(X,topRight.iX + (aTr.iWidth  * cosalpha),0);			  Math::Round(Y,topRight.iY + (aTr.iHeight * sinalpha),0); 			  rectanglePoints->AppendL(TPoint(X,Y));			  }	 	 }   	 // bottom right corner	 if(aBr.iHeight==0 || aBr.iWidth==0)		 {		 rectanglePoints->AppendL(TPoint(aX+width,aY+height));		 }	 else{		 TReal stepBr=0;		 Math::Sqrt(stepBr, aBr.iWidth*aBr.iWidth+aBr.iHeight*aBr.iHeight);		 stepBr = ((90/stepBr)<1 ? 1: 90/stepBr);  		 for (TReal i = 0; i <= 90; i += stepBr) 			  {				alpha = i * piRad ;				Math::Sin(sinalpha,alpha);				Math::Cos(cosalpha,alpha); 				Math::Round(X,bottomRight.iX + (aBr.iWidth  * cosalpha),0);				Math::Round(Y,bottomRight.iY + (aBr.iHeight * sinalpha),0); 				rectanglePoints->AppendL(TPoint(X,Y));			  }		 } 	CleanupStack::Pop(); // rectanglePoints	return rectanglePoints; 	} // -----------------------------------------------------------------------------// CRoundedRectangleAppView::Draw()// Draws the display.// -----------------------------------------------------------------------------//void CRoundedRectangleAppView::Draw(const TRect& /*aRect*/) const	{		// Get the standard graphics context	CWindowGc& gc = SystemGc(); 	TRect drawRect(Rect());	gc.Clear(drawRect); 	drawRoundedRectangle(gc,5,5,100,30,TSize(10,10),TSize(10,10),TSize(10,10),TSize(10,10),TSize(2,2),TRgb(0x00000),TRgb(0xffffff));	drawRoundedRectangle(gc,5,40,100,30,TSize(10,10),TSize(10,10),TSize(10,10),TSize(10,10),TSize(1,1),TRgb(0xD97821),TRgb(0x2148D9)); 	drawRoundedRectangle(gc,5,75,100,30,TSize(0,0),TSize(16,21),TSize(0,0),TSize(16,21),TSize(1,1),TRgb(0xD62AC4),TRgb(0xD62AC4));	drawRoundedRectangle(gc,5,110,100,30,TSize(14,19),TSize(0,0),TSize(14,19),TSize(0,0),TSize(1,1),TRgb(0xD62AC4),TRgb(0xD62AC4)); 	drawRoundedRectangle(gc,5,145,100,30,TSize(0,0),TSize(15,20),TSize(0,0),TSize(15,20),TSize(1,1),TRgb(0xD62AC4),TRgb(0x75AD3F));	drawRoundedRectangle(gc,5,180,100,30,TSize(15,20),TSize(0,0),TSize(15,20),TSize(0,0),TSize(1,1),TRgb(0xD62AC4),TRgb(0x75AD3F)); 	drawRoundedRectangle(gc,5,215,100,30,TSize(0,0),TSize(0,0),TSize(15,15),TSize(15,15),TSize(1,1),TRgb(0xD62AC4),TRgb(0x30D087));	drawRoundedRectangle(gc,5,250,100,30,TSize(15,15),TSize(15,15),TSize(0,0),TSize(0,0),TSize(1,1),TRgb(0xD62AC4),TRgb(0x30D087));         //commented out as these use images that are not loaded in this example	//drawRoundedRectangle(gc,110,5,150,150,TSize(300,200),TSize(400,200),TSize(400,300),TSize(400,300),TSize(0,0),TRgb(0xD62AC4), iBg1); 	//drawRoundedRectangle(gc,110,200,200,200,TSize(30,20),TSize(150,150),TSize(40,30),TSize(40,30),TSize(2,2),TRgb(0xD62AC4),iBg2); }`

Considerations

• Code is not fully tested.
• Converting from calculated TReal values to TInt may cause some graphic bugs.
• Since we are talking about graphics there is always room for optimization