359 lines
11 KiB
C++
359 lines
11 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.
|
|
*
|
|
*******************************************************************************/
|
|
|
|
#include <touchgfx/Drawable.hpp>
|
|
#include <touchgfx/widgets/canvas/Canvas.hpp>
|
|
#include <touchgfx/widgets/canvas/Line.hpp>
|
|
|
|
namespace touchgfx
|
|
{
|
|
Line::Line()
|
|
: CanvasWidget(),
|
|
startXQ5(0), startYQ5(0), endXQ5(0), endYQ5(0),
|
|
lineWidthQ5(CWRUtil::toQ5<int>(1)),
|
|
lineEnding(BUTT_CAP_ENDING),
|
|
minimalRect(),
|
|
lineCapArcIncrement(18)
|
|
{
|
|
Drawable::setWidthHeight(0, 0);
|
|
}
|
|
|
|
void Line::setStart(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
|
{
|
|
if (startXQ5 == xQ5 && startYQ5 == yQ5)
|
|
{
|
|
return;
|
|
}
|
|
|
|
startXQ5 = xQ5;
|
|
startYQ5 = yQ5;
|
|
|
|
updateCachedShape();
|
|
}
|
|
|
|
void Line::updateStart(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
|
{
|
|
if (startXQ5 == xQ5 && startYQ5 == yQ5)
|
|
{
|
|
return;
|
|
}
|
|
|
|
invalidateContent();
|
|
|
|
startXQ5 = xQ5;
|
|
startYQ5 = yQ5;
|
|
|
|
updateCachedShape();
|
|
|
|
invalidateContent();
|
|
}
|
|
|
|
void Line::setEnd(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
|
{
|
|
if (endXQ5 == xQ5 && endYQ5 == yQ5)
|
|
{
|
|
return;
|
|
}
|
|
|
|
endXQ5 = xQ5;
|
|
endYQ5 = yQ5;
|
|
|
|
updateCachedShape();
|
|
}
|
|
|
|
void Line::updateEnd(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
|
{
|
|
if (endXQ5 == xQ5 && endYQ5 == yQ5)
|
|
{
|
|
return;
|
|
}
|
|
|
|
invalidateContent();
|
|
|
|
endXQ5 = xQ5;
|
|
endYQ5 = yQ5;
|
|
|
|
updateCachedShape();
|
|
|
|
invalidateContent();
|
|
}
|
|
|
|
void Line::setLineEndingStyle(Line::LINE_ENDING_STYLE lineEndingStyle)
|
|
{
|
|
lineEnding = lineEndingStyle;
|
|
updateCachedShape();
|
|
}
|
|
|
|
Line::LINE_ENDING_STYLE Line::getLineEndingStyle() const
|
|
{
|
|
return lineEnding;
|
|
}
|
|
|
|
void Line::setCapPrecision(int precision)
|
|
{
|
|
if (precision < 1)
|
|
{
|
|
precision = 1;
|
|
}
|
|
if (precision > 180)
|
|
{
|
|
precision = 180;
|
|
}
|
|
lineCapArcIncrement = precision;
|
|
}
|
|
|
|
bool Line::drawCanvasWidget(const Rect& invalidatedArea) const
|
|
{
|
|
Canvas canvas(this, invalidatedArea);
|
|
|
|
CWRUtil::Q5 radiusQ5;
|
|
if (lineEnding == ROUND_CAP_ENDING)
|
|
{
|
|
const int angleInDegrees = CWRUtil::angle(cornerXQ5[0] - startXQ5, cornerYQ5[0] - startYQ5, radiusQ5);
|
|
canvas.moveTo(cornerXQ5[0], cornerYQ5[0]);
|
|
canvas.lineTo(cornerXQ5[1], cornerYQ5[1]);
|
|
for (int i = lineCapArcIncrement; i < 180; i += lineCapArcIncrement)
|
|
{
|
|
canvas.lineTo(endXQ5 + radiusQ5 * CWRUtil::sine(angleInDegrees - i), endYQ5 - radiusQ5 * CWRUtil::cosine(angleInDegrees - i));
|
|
}
|
|
canvas.lineTo(cornerXQ5[2], cornerYQ5[2]);
|
|
canvas.lineTo(cornerXQ5[3], cornerYQ5[3]);
|
|
for (int i = 180 - lineCapArcIncrement; i > 0; i -= lineCapArcIncrement)
|
|
{
|
|
canvas.lineTo(startXQ5 + radiusQ5 * CWRUtil::sine(angleInDegrees + i), startYQ5 - radiusQ5 * CWRUtil::cosine(angleInDegrees + i));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
canvas.moveTo(cornerXQ5[0], cornerYQ5[0]);
|
|
canvas.lineTo(cornerXQ5[1], cornerYQ5[1]);
|
|
canvas.lineTo(cornerXQ5[2], cornerYQ5[2]);
|
|
canvas.lineTo(cornerXQ5[3], cornerYQ5[3]);
|
|
}
|
|
return canvas.render();
|
|
}
|
|
|
|
void Line::updateCachedShape()
|
|
{
|
|
CWRUtil::Q5 dxQ5 = endXQ5 - startXQ5;
|
|
CWRUtil::Q5 dyQ5 = endYQ5 - startYQ5;
|
|
CWRUtil::Q5 dQ5 = CWRUtil::toQ5<int>(0);
|
|
// Look for horizontal, vertical or no-line
|
|
if (dxQ5 == 0)
|
|
{
|
|
if (dyQ5 == 0)
|
|
{
|
|
cornerXQ5[0] = cornerXQ5[1] = cornerXQ5[2] = cornerXQ5[3] = startXQ5;
|
|
cornerYQ5[0] = cornerYQ5[1] = cornerYQ5[2] = cornerYQ5[3] = startYQ5;
|
|
return;
|
|
}
|
|
dQ5 = abs(dyQ5);
|
|
}
|
|
else if (dyQ5 == 0)
|
|
{
|
|
dQ5 = abs(dxQ5);
|
|
}
|
|
else
|
|
{
|
|
// We want to hit as close to the limit inside 32bits.
|
|
// sqrt(0x7FFFFFFF / 2) = 46340, which is roughly toQ5(1448)
|
|
static const int32_t MAXVAL = 46340;
|
|
int32_t common_divisor = gcd(abs((int32_t)dxQ5), abs((int32_t)dyQ5));
|
|
// First try to scale down
|
|
if (common_divisor != 1)
|
|
{
|
|
dxQ5 = CWRUtil::Q5((int32_t)dxQ5 / common_divisor);
|
|
dyQ5 = CWRUtil::Q5((int32_t)dyQ5 / common_divisor);
|
|
}
|
|
// Neither dx or dy is zero, find the largest multiplier / smallest divisor to stay within limit
|
|
if (abs((int32_t)dxQ5) <= MAXVAL || abs((int32_t)dyQ5) <= MAXVAL)
|
|
{
|
|
// Look for largest multiplier
|
|
const int32_t mulx = MAXVAL / abs((int32_t)dxQ5);
|
|
const int32_t muly = MAXVAL / abs((int32_t)dyQ5);
|
|
const int32_t mult = MIN(mulx, muly);
|
|
dxQ5 = CWRUtil::Q5(mult * (int32_t)dxQ5);
|
|
dyQ5 = CWRUtil::Q5(mult * (int32_t)dyQ5);
|
|
}
|
|
else
|
|
{
|
|
// Look for smallest divisor
|
|
const int32_t divx = abs((int32_t)dxQ5) / MAXVAL;
|
|
const int32_t divy = abs((int32_t)dyQ5) / MAXVAL;
|
|
const int32_t divi = MAX(divx, divy) + 1;
|
|
dxQ5 = CWRUtil::Q5((int32_t)dxQ5 / divi);
|
|
dyQ5 = CWRUtil::Q5((int32_t)dyQ5 / divi);
|
|
}
|
|
dQ5 = CWRUtil::length(dxQ5, dyQ5);
|
|
}
|
|
|
|
dyQ5 = CWRUtil::muldivQ5(lineWidthQ5, dyQ5, dQ5) / 2;
|
|
dxQ5 = CWRUtil::muldivQ5(lineWidthQ5, dxQ5, dQ5) / 2;
|
|
|
|
switch (lineEnding)
|
|
{
|
|
case BUTT_CAP_ENDING:
|
|
cornerXQ5[0] = startXQ5 - dyQ5;
|
|
cornerYQ5[0] = startYQ5 + dxQ5;
|
|
cornerXQ5[1] = endXQ5 - dyQ5;
|
|
cornerYQ5[1] = endYQ5 + dxQ5;
|
|
cornerXQ5[2] = endXQ5 + dyQ5;
|
|
cornerYQ5[2] = endYQ5 - dxQ5;
|
|
cornerXQ5[3] = startXQ5 + dyQ5;
|
|
cornerYQ5[3] = startYQ5 - dxQ5;
|
|
break;
|
|
case ROUND_CAP_ENDING:
|
|
// Nothing cached, calculated on each draw, but extremes are same as SQUARE_CAP_ENDING, so
|
|
// Fall Through (for calculations)
|
|
case SQUARE_CAP_ENDING:
|
|
cornerXQ5[0] = (startXQ5 - dyQ5) - dxQ5;
|
|
cornerYQ5[0] = (startYQ5 + dxQ5) - dyQ5;
|
|
cornerXQ5[1] = (endXQ5 - dyQ5) + dxQ5;
|
|
cornerYQ5[1] = (endYQ5 + dxQ5) + dyQ5;
|
|
cornerXQ5[2] = (endXQ5 + dyQ5) + dxQ5;
|
|
cornerYQ5[2] = (endYQ5 - dxQ5) + dyQ5;
|
|
cornerXQ5[3] = (startXQ5 + dyQ5) - dxQ5;
|
|
cornerYQ5[3] = (startYQ5 - dxQ5) - dyQ5;
|
|
break;
|
|
}
|
|
|
|
CWRUtil::Q5 xMinQ5 = cornerXQ5[0];
|
|
CWRUtil::Q5 xMaxQ5 = cornerXQ5[0];
|
|
CWRUtil::Q5 yMinQ5 = cornerYQ5[0];
|
|
CWRUtil::Q5 yMaxQ5 = cornerYQ5[0];
|
|
for (int i = 1; i < 4; i++)
|
|
{
|
|
if (cornerXQ5[i] < xMinQ5)
|
|
{
|
|
xMinQ5 = cornerXQ5[i];
|
|
}
|
|
if (cornerXQ5[i] > xMaxQ5)
|
|
{
|
|
xMaxQ5 = cornerXQ5[i];
|
|
}
|
|
if (cornerYQ5[i] < yMinQ5)
|
|
{
|
|
yMinQ5 = cornerYQ5[i];
|
|
}
|
|
if (cornerYQ5[i] > yMaxQ5)
|
|
{
|
|
yMaxQ5 = cornerYQ5[i];
|
|
}
|
|
}
|
|
const int16_t xMin = xMinQ5.floor();
|
|
const int16_t yMin = yMinQ5.floor();
|
|
const int16_t xMax = xMaxQ5.ceil();
|
|
const int16_t yMax = yMaxQ5.ceil();
|
|
minimalRect = Rect(xMin, yMin, xMax - xMin, yMax - yMin);
|
|
|
|
if (lineEnding == ROUND_CAP_ENDING)
|
|
{
|
|
cornerXQ5[0] = startXQ5 - dyQ5;
|
|
cornerYQ5[0] = startYQ5 + dxQ5;
|
|
cornerXQ5[1] = endXQ5 - dyQ5;
|
|
cornerYQ5[1] = endYQ5 + dxQ5;
|
|
cornerXQ5[2] = endXQ5 + dyQ5;
|
|
cornerYQ5[2] = endYQ5 - dxQ5;
|
|
cornerXQ5[3] = startXQ5 + dyQ5;
|
|
cornerYQ5[3] = startYQ5 - dxQ5;
|
|
}
|
|
}
|
|
|
|
touchgfx::Rect Line::rectContainingPoints(const Rect& fullRect, CWRUtil::Q5 x0, CWRUtil::Q5 y0, CWRUtil::Q5 x1, CWRUtil::Q5 y1, CWRUtil::Q5 x2, CWRUtil::Q5 y2) const
|
|
{
|
|
const int16_t xMin = MIN(MIN(x0, x1), x2).floor();
|
|
const int16_t yMin = MIN(MIN(y0, y1), y2).floor();
|
|
const int16_t xMax = MAX(MAX(x0, x1), x2).ceil();
|
|
const int16_t yMax = MAX(MAX(y0, y1), y2).ceil();
|
|
Rect r(xMin, yMin, xMax - xMin, yMax - yMin);
|
|
r &= fullRect;
|
|
return r;
|
|
}
|
|
|
|
Rect Line::getMinimalRect() const
|
|
{
|
|
return minimalRect;
|
|
}
|
|
|
|
void Line::updateLengthAndAngle(CWRUtil::Q5 length, CWRUtil::Q5 angle)
|
|
{
|
|
updateEnd(startXQ5 + length * CWRUtil::sine(angle), startYQ5 - length * CWRUtil::cosine(angle));
|
|
}
|
|
|
|
void Line::invalidateContent() const
|
|
{
|
|
if (alpha == 0)
|
|
{
|
|
return;
|
|
}
|
|
Rect smallRect = getMinimalRect();
|
|
if (abs(startXQ5.to<int>() - endXQ5.to<int>()) < lineWidthQ5.to<int>() * 2 ||
|
|
abs(startYQ5.to<int>() - endYQ5.to<int>()) < lineWidthQ5.to<int>() * 2)
|
|
{
|
|
invalidateRect(smallRect);
|
|
return;
|
|
}
|
|
|
|
int16_t center_x = ((startXQ5 + endXQ5) / 2).to<int16_t>();
|
|
int16_t center_y = ((startYQ5 + endYQ5) / 2).to<int16_t>();
|
|
// The following should be "lineWidth/sqrt(2)" but to speed up we take the slightly larger "lineWidth/1.3333"="(lineWidth*3)/4"
|
|
int16_t extra_width = ((CWRUtil::Q5)(((int)(lineWidthQ5)*3 + 3) >> 2)).ceil();
|
|
#define same_sign(x, y) (((x) < 0 && (y) < 0) || ((x) > 0 && (y) > 0))
|
|
if (smallRect.width < smallRect.height)
|
|
{
|
|
const int16_t left_x = center_x - extra_width;
|
|
const int16_t right_x = center_x + extra_width;
|
|
// "vertical" line
|
|
if (same_sign(endXQ5 - startXQ5, endYQ5 - startYQ5))
|
|
{
|
|
// From top-left to bottom-right
|
|
Rect topLeftRect(smallRect.x, smallRect.y, right_x - smallRect.x, center_y - smallRect.y);
|
|
Rect bottomRightRect(left_x, center_y, smallRect.right() - left_x, smallRect.bottom() - center_y);
|
|
invalidateRect(topLeftRect);
|
|
invalidateRect(bottomRightRect);
|
|
}
|
|
else
|
|
{
|
|
// From top-right to bottom-left
|
|
Rect topRightRect(left_x, smallRect.y, smallRect.right() - left_x, center_y - smallRect.y);
|
|
Rect bottomLeftRect(smallRect.x, center_y, right_x - smallRect.x, smallRect.bottom() - center_y);
|
|
invalidateRect(topRightRect);
|
|
invalidateRect(bottomLeftRect);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int16_t top_y = center_y - extra_width;
|
|
const int16_t bottom_y = center_y + extra_width;
|
|
// "horizontal" line
|
|
if (same_sign(endXQ5 - startXQ5, endYQ5 - startYQ5))
|
|
{
|
|
// From top-left to bottom-right
|
|
Rect topLeftRect(smallRect.x, smallRect.y, center_x - smallRect.x, bottom_y - smallRect.y);
|
|
Rect bottomRightRect(center_x, top_y, smallRect.right() - center_x, smallRect.bottom() - top_y);
|
|
invalidateRect(topLeftRect);
|
|
invalidateRect(bottomRightRect);
|
|
}
|
|
else
|
|
{
|
|
// From top-right to bottom-left
|
|
Rect bottomLeftRect(smallRect.x, top_y, center_x - smallRect.x, smallRect.bottom() - top_y);
|
|
Rect topRightRect(center_x, smallRect.y, smallRect.right() - center_x, bottom_y - smallRect.y);
|
|
invalidateRect(bottomLeftRect);
|
|
invalidateRect(topRightRect);
|
|
}
|
|
}
|
|
#undef same_sign
|
|
}
|
|
|
|
} // namespace touchgfx
|