559 lines
19 KiB
C++

/******************************************************************************
* Copyright (c) 2018(-2023) STMicroelectronics.
* All rights reserved.
*
* This file is part of the TouchGFX 4.21.2 distribution.
*
* This software is licensed under terms that can be found in the LICENSE file in
* the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
*******************************************************************************/
/**
* @file touchgfx/widgets/canvas/Circle.hpp
*
* Declares the touchgfx::Circle class.
*/
#ifndef TOUCHGFX_CIRCLE_HPP
#define TOUCHGFX_CIRCLE_HPP
#include <touchgfx/hal/Types.hpp>
#include <touchgfx/widgets/canvas/CWRUtil.hpp>
#include <touchgfx/widgets/canvas/Canvas.hpp>
#include <touchgfx/widgets/canvas/CanvasWidget.hpp>
namespace touchgfx
{
/**
* Simple widget capable of drawing a circle, or part of a circle (an arc). The Circle can be
* filled or be drawn as a simple line along the circumference of the circle. Several
* parameters of the circle can be changed: Center, radius, line width, line cap, start
* angle and end angle.
*
* @note Since the underlying CanwasWidgetRenderer only supports straight lines, the circle is
* drawn using many small straight lines segments. The granularity can be adjusted to
* match the requirements - large circles need more line segments, small circles need
* fewer line segments, to look smooth and round.
* @note All circle parameters are internally handled as CWRUtil::Q5 which means that floating
* point values are rounded down to a fixed number of binary digits, for example:
* @code
* Circle circle;
* circle.setCircle(1.1f, 1.1f, 0.9); // Will use (35/32, 35/32, 28/32) = (1.09375f, 1.09375f, 0.875f)
* int x, y, r;
* circle.getCenter(&x, &y); // Will return (1, 1)
* circle.getRadius(&r); // Will return 0
* @endcode.
*/
class Circle : public CanvasWidget
{
public:
Circle();
/**
* Sets the center and radius of the Circle.
*
* @tparam T Generic type parameter, either int or float.
* @param x The x coordinate of center.
* @param y The y coordinate of center.
* @param r The radius.
*
* @see setCenter, setRadius
*
* @note The area containing the Circle is not invalidated.
*/
template <typename T>
void setCircle(const T x, const T y, const T r)
{
setCenter<T>(x, y);
setRadius<T>(r);
}
/**
* Sets the center and radius of the Circle.
*
* @param x The x coordinate of center.
* @param y The y coordinate of center.
* @param r The radius.
*
* @see setCenter, setRadius
*
* @note The area containing the Circle is not invalidated.
*/
void setCircle(const int16_t x, const int16_t y, const int16_t r)
{
setCircle<int>(x, y, r);
}
/**
* Sets the center of the Circle.
*
* @tparam T Generic type parameter, either int or float.
* @param x The x coordinate of center.
* @param y The y coordinate of center.
*
* @see setRadius, setCircle, getCenter
*
* @note The area containing the Circle is not invalidated.
*/
template <typename T>
void setCenter(const T x, const T y)
{
this->circleCenterX = CWRUtil::toQ5(x);
this->circleCenterY = CWRUtil::toQ5(y);
}
/**
* Sets the center of the Circle.
*
* @param x The x coordinate of center.
* @param y The y coordinate of center.
*
* @see setRadius, setCircle, getCenter
*
* @note The area containing the Circle is not invalidated.
*/
void setCenter(const int16_t x, const int16_t y)
{
setCenter<int>(x, y);
}
/**
* Sets the center of the circle / arc in the middle of a pixel. Normally the coordinate is
* between pixel number x and x+1 horizontally and between pixel y and y+1 vertically. This
* function will set the center in the middle of the pixel by adding 0.5 to both x and y.
*
* @param x The x coordinate of the center of the circle.
* @param y The y coordinate of the center of the circle.
*/
void setPixelCenter(int x, int y)
{
int32_t half = (int32_t)CWRUtil::toQ5(1) / 2;
setCenter<CWRUtil::Q5>(CWRUtil::Q5((int32_t)CWRUtil::toQ5(x) + half), CWRUtil::Q5((int32_t)CWRUtil::toQ5(y) + half));
}
/**
* Gets the center coordinates of the Circle.
*
* @tparam T Generic type parameter, either int or float.
* @param [out] x The x coordinate of the center rounded down to the precision of T.
* @param [out] y The y coordinate of the center rounded down to the precision of T.
*
* @see setCenter
*/
template <typename T>
void getCenter(T& x, T& y) const
{
x = circleCenterX.to<T>();
y = circleCenterY.to<T>();
}
/**
* Sets the radius of the Circle.
*
* @tparam T Generic type parameter, either int or float.
* @param r The radius.
*
* @see setCircle, setCenter, getRadius
*
* @note The area containing the Circle is not invalidated.
*/
template <typename T>
void setRadius(const T r)
{
this->circleRadius = CWRUtil::toQ5(r);
this->circleRadius = MAX(CWRUtil::toQ5(0), this->circleRadius);
}
/**
* Gets the radius of the Circle.
*
* @tparam T Generic type parameter, either int or float.
* @param [out] r The radius rounded down to the precision of T.
*/
template <typename T>
void getRadius(T& r) const
{
r = circleRadius.to<T>();
}
/**
* Sets the start and end angles in degrees of the Circle arc. 0 degrees is straight up (12
* o'clock) and 90 degrees is to the left (3 o'clock). Any positive or negative degrees can be
* used to specify the part of the Circle to draw.
*
* @tparam T Generic type parameter, either int or float.
* @param startAngle The start degrees.
* @param endAngle The end degrees.
*
* @see getArc, updateArcStart, updateArcEnd, updateArc
*
* @note The area containing the Circle is not invalidated.
* @note Angles are given in degrees, so a full circle is 360.
*/
template <typename T>
void setArc(const T startAngle, const T endAngle)
{
circleArcAngleStart = CWRUtil::toQ5(startAngle);
circleArcAngleEnd = CWRUtil::toQ5(endAngle);
}
/**
* Sets the start and end angles in degrees of the Circle arc. 0 degrees is straight up (12
* o'clock) and 90 degrees is to the left (3 o'clock). Any positive or negative degrees can be
* used to specify the part of the Circle to draw.
*
* @param startAngle The start degrees.
* @param endAngle The end degrees.
*
* @see getArc, updateArcStart, updateArcEnd, updateArc
*
* @note The area containing the Circle is not invalidated.
* @note Angles are given in degrees, so a full circle is 360.
*/
void setArc(const int16_t startAngle, const int16_t endAngle)
{
setArc<int>(startAngle, endAngle);
}
/**
* Gets the start and end angles in degrees for the circle arc.
*
* @tparam T Generic type parameter, either int or float.
* @param [out] startAngle The start angle rounded down to the precision of T.
* @param [out] endAngle The end angle rounded down to the precision of T.
*
* @see setArc
*
* @note Angles are given in degrees, so a full circle is 360.
*/
template <typename T>
void getArc(T& startAngle, T& endAngle) const
{
startAngle = circleArcAngleStart.to<T>();
endAngle = circleArcAngleEnd.to<T>();
}
/**
* Gets the start angle in degrees for the arc.
*
* @return The starting angle for the arc rounded down to an integer.
*
* @see getArc, setArc
*
* @note Angles are given in degrees, so a full circle is 360.
*/
int16_t getArcStart() const
{
return circleArcAngleStart.to<int>();
}
/**
* Gets the start angle in degrees for the arc.
*
* @tparam T Generic type parameter, either int or float.
* @param [out] angle The starting angle rounded down to the precision of T.
*
* @see getArc, setArc
*
* @note Angles are given in degrees, so a full circle is 360.
*/
template <typename T>
void getArcStart(T& angle) const
{
angle = circleArcAngleStart.to<T>();
}
/**
* Gets the end angle in degrees for the arc.
*
* @return The end angle for the arc rounded down to an integer.
*
* @see getArc, setArc
*
* @note Angles are given in degrees, so a full circle is 360.
*/
int16_t getArcEnd() const
{
return circleArcAngleEnd.to<int>();
}
/**
* Gets the end angle in degrees for the arc.
*
* @tparam T Generic type parameter, either int or float.
* @param [out] angle The end angle rounded down to the precision of T.
*
* @note Angles are given in degrees, so a full circle is 360.
*/
template <typename T>
void getArcEnd(T& angle) const
{
angle = circleArcAngleEnd.to<T>();
}
/**
* Updates the start angle in degrees for this Circle arc.
*
* @tparam T Generic type parameter, either int or float.
* @param startAngle The start angle in degrees.
*
* @see setArc, updateArcEnd, updateArc
*
* @note The area containing the updated Circle arc is invalidated.
* @note Angles are given in degrees, so a full circle is 360.
*/
template <typename T>
void updateArcStart(const T startAngle)
{
CWRUtil::Q5 startAngleQ5 = CWRUtil::toQ5(startAngle);
if (circleArcAngleStart == startAngleQ5)
{
return;
}
Rect minimalRect = getMinimalRectForUpdatedStartAngle(startAngleQ5);
circleArcAngleStart = startAngleQ5;
invalidateRect(minimalRect);
}
/**
* Updates the end angle in degrees for this Circle arc.
*
* @tparam T Generic type parameter, either int or float.
* @param endAngle The end angle in degrees.
*
* @see setArc, updateArcStart, updateArc
*
* @note The area containing the updated Circle arc is invalidated.
* @note Angles are given in degrees, so a full circle is 360.
*/
template <typename T>
void updateArcEnd(const T endAngle)
{
CWRUtil::Q5 endAngleQ5 = CWRUtil::toQ5(endAngle);
if (circleArcAngleEnd == endAngleQ5)
{
return;
}
Rect minimalRect = getMinimalRectForUpdatedEndAngle(endAngleQ5);
circleArcAngleEnd = endAngleQ5;
invalidateRect(minimalRect);
}
/**
* Updates the start and end angle in degrees for this Circle arc.
*
* @tparam T Generic type parameter, either int or float.
* @param startAngle The new start angle in degrees.
* @param endAngle The new end angle in degrees.
*
* @see setArc, getArc, updateArcStart, updateArcEnd
*
* @note The areas containing the updated Circle arcs are invalidated. As little as possible
* will be invalidated for best performance.
* @note Angles are given in degrees, so a full circle is 360.
*/
template <typename T>
void updateArc(const T startAngle, const T endAngle)
{
updateArc(CWRUtil::toQ5(startAngle), CWRUtil::toQ5(endAngle));
}
/**
* Sets the line width for this Circle. If the line width is set to zero, the circle
* will be filled.
*
* @tparam T Generic type parameter, either int or float.
* @param width The width of the line measured in pixels.
*
* @note The area containing the Circle is not invalidated.
* @note if the new line with is smaller than the old width, the circle should be invalidated
* before updating the width to ensure that the old circle is completely erased.
*/
template <typename T>
void setLineWidth(const T width)
{
this->circleLineWidth = CWRUtil::toQ5(width);
this->circleLineWidth = MAX(CWRUtil::toQ5(0), this->circleLineWidth);
}
/**
* Gets line width.
*
* @tparam T Generic type parameter, either int or float.
* @param [out] width The line width rounded down to the precision of T.
*
* @see setLineWidth
*/
template <typename T>
void getLineWidth(T& width) const
{
width = circleLineWidth.to<T>();
}
/**
* Sets precision of the Circle drawing function. The number given as precision is the
* number of degrees used as step counter when drawing the line fragments around the
* circumference of the circle, five being a reasonable value. Higher values results in
* less nice circles but faster rendering and possibly sufficient for very small
* circles. Large circles might require a precision smaller than five to make the edge
* of the circle look nice and smooth.
*
* @param precision The precision measured in degrees.
*
* @note The circle is not invalidated.
*/
void setPrecision(const int precision);
/**
* Gets the precision of the circle drawing function. The precision is the number of
* degrees used as step counter when drawing smaller line fragments around the
* circumference of the circle, the default being 5.
*
* @return The precision.
*
* @see setPrecision
*/
int getPrecision() const;
/**
* Sets the precision of the ends of the Circle arc. The precision is given in degrees
* where 180 is the default which results in a square ended arc (aka "butt cap"). 90
* will draw "an arrow head" and smaller values gives a round cap. Larger values of
* precision results in faster rendering of the circle.
*
* @param precision The new cap precision.
*
* @note The circle is not invalidated.
* @note The cap precision is not used if the circle is filled (if line width is zero) or when
* a full circle is drawn.
*/
void setCapPrecision(const int precision);
/**
* Gets the precision of the ends of the Circle arc.
*
* @return The cap precision in degrees.
*
* @see getCapPrecision
*/
int getCapPrecision() const;
virtual bool drawCanvasWidget(const Rect& invalidatedArea) const;
virtual Rect getMinimalRect() const
{
return getMinimalRect(circleArcAngleStart, circleArcAngleEnd);
}
/**
* Gets minimal rectangle containing a given circle arc using the set line width.
*
* @param arcStart The arc start.
* @param arcEnd The arc end.
*
* @return The minimal rectangle.
*/
Rect getMinimalRect(int16_t arcStart, int16_t arcEnd) const
{
return getMinimalRect(CWRUtil::toQ5<int>(arcStart), CWRUtil::toQ5<int>(arcEnd));
}
/**
* Gets minimal rectangle containing a given circle arc using the set line width.
*
* @param arcStart The arc start.
* @param arcEnd The arc end.
*
* @return The minimal rectangle.
*/
Rect getMinimalRect(CWRUtil::Q5 arcStart, CWRUtil::Q5 arcEnd) const;
protected:
/**
* Updates the start and end angle in degrees for this Circle arc.
*
* @param setStartAngleQ5 The new start angle in degrees.
* @param setEndAngleQ5 The new end angle in degrees.
*
* @see setArc, getArc, updateArcStart, updateArcEnd
*
* @note The areas containing the updated Circle arcs are invalidated. As little as possible
* will be invalidated for best performance.
*/
void updateArc(const CWRUtil::Q5 setStartAngleQ5, const CWRUtil::Q5 setEndAngleQ5);
private:
CWRUtil::Q5 circleCenterX;
CWRUtil::Q5 circleCenterY;
CWRUtil::Q5 circleRadius;
CWRUtil::Q5 circleArcAngleStart;
CWRUtil::Q5 circleArcAngleEnd;
CWRUtil::Q5 circleLineWidth;
uint8_t circleArcIncrement;
uint8_t circleCapArcIncrement;
FORCE_INLINE_FUNCTION void moveToAngleRadius2(Canvas& canvas, const CWRUtil::Q5& angle, const CWRUtil::Q5& r2) const
{
canvas.moveTo(circleCenterX + ((r2 * CWRUtil::sine(angle)) / 2), circleCenterY - ((r2 * CWRUtil::cosine(angle)) / 2));
}
FORCE_INLINE_FUNCTION void lineToAngleRadius2(Canvas& canvas, const CWRUtil::Q5& angle, const CWRUtil::Q5& r2) const
{
lineToXYAngleRadius2(canvas, circleCenterX, circleCenterY, angle, r2);
}
FORCE_INLINE_FUNCTION void lineToXYAngleRadius2(Canvas& canvas, const CWRUtil::Q5& x, const CWRUtil::Q5& y, const CWRUtil::Q5& angle, const CWRUtil::Q5& r2) const
{
canvas.lineTo(x + ((r2 * CWRUtil::sine(angle)) / 2), y - ((r2 * CWRUtil::cosine(angle)) / 2));
}
FORCE_INLINE_FUNCTION void updateMinMaxAngleLine(const CWRUtil::Q5& angle, const CWRUtil::Q5& halfLineWidth, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const
{
const CWRUtil::Q5 xNew = circleCenterX + (circleRadius * CWRUtil::sine(angle));
updateMinMaxX(xNew - halfLineWidth, xMin, xMax);
updateMinMaxX(xNew + halfLineWidth, xMin, xMax);
const CWRUtil::Q5 yNew = circleCenterY - (circleRadius * CWRUtil::cosine(angle));
updateMinMaxY(yNew - halfLineWidth, yMin, yMax);
updateMinMaxY(yNew + halfLineWidth, yMin, yMax);
}
FORCE_INLINE_FUNCTION void updateMinMaxAngleSolid(const CWRUtil::Q5& angle, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const
{
updateMinMaxX(circleCenterX + (circleRadius * CWRUtil::sine(angle)), xMin, xMax);
updateMinMaxY(circleCenterY - (circleRadius * CWRUtil::cosine(angle)), yMin, yMax);
}
FORCE_INLINE_FUNCTION void updateMinMaxX(const CWRUtil::Q5& xNew, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax) const
{
if (xNew < xMin)
{
xMin = xNew;
}
if (xNew > xMax)
{
xMax = xNew;
}
}
FORCE_INLINE_FUNCTION void updateMinMaxY(const CWRUtil::Q5& yNew, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const
{
if (yNew < yMin)
{
yMin = yNew;
}
if (yNew > yMax)
{
yMax = yNew;
}
}
void calculateMinimalRect(CWRUtil::Q5 arcStart, CWRUtil::Q5 arcEnd, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const;
Rect getMinimalRectForUpdatedStartAngle(const CWRUtil::Q5& startAngleQ5) const;
Rect getMinimalRectForUpdatedEndAngle(const CWRUtil::Q5& endAngleQ5) const;
};
} // namespace touchgfx
#endif // TOUCHGFX_CIRCLE_HPP