×
Namespaces

Variants
Actions

How to draw complicated rounded rectangles in Symbian C++

From Nokia Developer Wiki
Jump to: navigation, search

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.

Article Metadata
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)

Application running on 5800xpressMusic

Contents

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.

How rounded rectangle is formed

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

Header file

/*
* 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
This page was last modified on 10 February 2012, at 04:17.
65 page views in the last 30 days.
×