Files
Upsilon/apps/shared/curve_view.cpp
Léa Saviot 6e9021ed35 [apps/stats] Fix display one axis only in Box tab
The axis view is now a separate view, so there is no problem of
computing the height of the "axed" box view.
2018-05-29 15:34:49 +02:00

595 lines
24 KiB
C++

#include "curve_view.h"
#include "../constant.h"
#include <assert.h>
#include <string.h>
#include <cmath>
#include <float.h>
using namespace Poincare;
namespace Shared {
CurveView::CurveView(CurveViewRange * curveViewRange, CurveViewCursor * curveViewCursor, BannerView * bannerView,
View * cursorView, View * okView, bool displayBanner) :
View(),
m_bannerView(bannerView),
m_curveViewCursor(curveViewCursor),
m_curveViewRange(curveViewRange),
m_cursorView(cursorView),
m_okView(okView),
m_forceOkDisplay(false),
m_mainViewSelected(false),
m_drawnRangeVersion(0),
m_displayBanner(displayBanner)
{
}
void CurveView::reload() {
uint32_t rangeVersion = m_curveViewRange->rangeChecksum();
if (m_drawnRangeVersion != rangeVersion) {
// FIXME: This should also be called if the *curve* changed
m_drawnRangeVersion = rangeVersion;
KDCoordinate bannerHeight = (m_bannerView != nullptr && m_displayBanner) ? m_bannerView->bounds().height() : 0;
markRectAsDirty(KDRect(0, 0, bounds().width(), bounds().height() - bannerHeight));
if (label(Axis::Horizontal, 0) != nullptr) {
computeLabels(Axis::Horizontal);
}
if (label(Axis::Vertical, 0) != nullptr) {
computeLabels(Axis::Vertical);
}
}
layoutSubviews();
}
bool CurveView::isMainViewSelected() const {
return m_mainViewSelected;
}
void CurveView::selectMainView(bool mainViewSelected) {
if (m_mainViewSelected != mainViewSelected) {
m_mainViewSelected = mainViewSelected;
reload();
}
}
void CurveView::setCurveViewRange(CurveViewRange * curveViewRange) {
m_curveViewRange = curveViewRange;
}
/* When setting cursor, banner or ok view we first dirty the former element
* frame (in case we set the new element to be nullptr or the new element frame
* does not recover the former element frame) and then we dirty the new element
* frame (most of the time it is automatically done by the layout but the frame
* might be identical to the previous one and in that case layoutSubviews will
* do nothing). */
void CurveView::setCursorView(View * cursorView) {
markRectAsDirty(cursorFrame());
m_cursorView = cursorView;
markRectAsDirty(cursorFrame());
layoutSubviews();
}
void CurveView::setBannerView(View * bannerView) {
markRectAsDirty(bannerFrame());
m_bannerView = bannerView;
layoutSubviews();
}
void CurveView::setOkView(View * okView) {
markRectAsDirty(okFrame());
m_okView = okView;
layoutSubviews();
}
float CurveView::resolution() const {
return bounds().width()*samplingRatio();
}
float CurveView::samplingRatio() const {
return 1.1f;
}
float CurveView::min(Axis axis) const {
assert(axis == Axis::Horizontal || axis == Axis::Vertical);
return (axis == Axis::Horizontal ? m_curveViewRange->xMin(): m_curveViewRange->yMin());
}
float CurveView::max(Axis axis) const {
assert(axis == Axis::Horizontal || axis == Axis::Vertical);
return (axis == Axis::Horizontal ? m_curveViewRange->xMax() : m_curveViewRange->yMax());
}
float CurveView::gridUnit(Axis axis) const {
return (axis == Axis::Horizontal ? m_curveViewRange->xGridUnit() : m_curveViewRange->yGridUnit());
}
KDCoordinate CurveView::pixelLength(Axis axis) const {
assert(axis == Axis::Horizontal || axis == Axis::Vertical);
return (axis == Axis::Horizontal ? m_frame.width() : m_frame.height());
}
float CurveView::pixelToFloat(Axis axis, KDCoordinate p) const {
KDCoordinate pixels = axis == Axis::Horizontal ? p : pixelLength(axis)-p;
return min(axis) + pixels*((max(axis)-min(axis))/pixelLength(axis));
}
float CurveView::floatToPixel(Axis axis, float f) const {
float fraction = (f-min(axis))/(max(axis)-min(axis));
fraction = axis == Axis::Horizontal ? fraction : 1.0f - fraction;
/* Fraction is a float that translates the relative position of f on the axis.
* When fraction is between 0 and 1, f is visible. Otherwise, f is out of the
* visible window. We need to clip fraction to avoid big float issue (often
* due to float to int transformation). However, we cannot clip fraction
* between 0 and 1 because drawing a sized stamp on the extern boarder of the
* window should still be visible. We thus arbitrarily clip fraction between
* -10 and 10. */
fraction = fraction < -10.0f ? -10.0f : fraction;
fraction = fraction > 10.0f ? 10.0f : fraction;
return pixelLength(axis)*fraction;
}
void CurveView::computeLabels(Axis axis) {
char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)];
float step = gridUnit(axis);
for (int index = 0; index < numberOfLabels(axis); index++) {
float labelValue = 2.0f*step*(std::ceil(min(axis)/(2.0f*step)))+index*2.0f*step;
if (labelValue < step && labelValue > -step) {
labelValue = 0.0f;
}
PrintFloat::convertFloatToText<float>(labelValue, buffer,
PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits),
Constant::ShortNumberOfSignificantDigits, PrintFloat::Mode::Decimal);
//TODO: check for size of label?
strlcpy(label(axis, index), buffer, strlen(buffer)+1);
}
}
void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly, bool fixCoordinate, KDCoordinate fixedCoordinate) const {
float step = gridUnit(axis);
float start = 2.0f*step*(std::ceil(min(axis)/(2.0f*step)));
float end = max(axis);
float verticalCoordinate = fixCoordinate ? fixedCoordinate : std::round(floatToPixel(Axis::Vertical, 0.0f));
float horizontalCoordinate = fixCoordinate ? fixedCoordinate : std::round(floatToPixel(Axis::Horizontal, 0.0f));
int i = 0;
for (float x = start; x < end; x += 2.0f*step) {
/* When |start| >> step, start + step = start. In that case, quit the
* infinite loop. */
if (x == x-step || x == x+step) {
return;
}
KDRect graduation(std::round(floatToPixel(Axis::Horizontal, x)), verticalCoordinate -(k_labelGraduationLength-2)/2, 1, k_labelGraduationLength);
if (axis == Axis::Vertical) {
graduation = KDRect(horizontalCoordinate-(k_labelGraduationLength-2)/2, std::round(floatToPixel(Axis::Vertical, x)), k_labelGraduationLength, 1);
}
if (!graduationOnly) {
KDSize textSize = KDText::stringSize(label(axis, i), KDText::FontSize::Small);
KDPoint origin(std::round(floatToPixel(Axis::Horizontal, x)) - textSize.width()/2, verticalCoordinate + k_labelMargin);
if (axis == Axis::Vertical) {
origin = KDPoint(horizontalCoordinate + k_labelMargin, std::round(floatToPixel(Axis::Vertical, x)) - textSize.height()/2);
}
if (-step < x && x < step && shiftOrigin) {
origin = KDPoint(horizontalCoordinate + k_labelMargin, verticalCoordinate + k_labelMargin);
}
if (rect.intersects(KDRect(origin, KDText::stringSize(label(axis, i), KDText::FontSize::Small)))) {
ctx->blendString(label(axis, i), origin, KDText::FontSize::Small, KDColorBlack);
}
}
ctx->fillRect(graduation, KDColorBlack);
i++;
}
}
void CurveView::drawLine(KDContext * ctx, KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness) const {
KDRect lineRect = KDRectZero;
switch(axis) {
case Axis::Horizontal:
lineRect = KDRect(
rect.x(), std::round(floatToPixel(Axis::Vertical, coordinate)),
rect.width(), thickness
);
break;
case Axis::Vertical:
lineRect = KDRect(
std::round(floatToPixel(Axis::Horizontal, coordinate)), rect.y(),
thickness, rect.height()
);
break;
}
if (rect.intersects(lineRect)) {
ctx->fillRect(lineRect, color);
}
}
void CurveView::drawSegment(KDContext * ctx, KDRect rect, Axis axis, float coordinate, float lowerBound, float upperBound, KDColor color, KDCoordinate thickness) const {
KDRect lineRect = KDRectZero;
switch(axis) {
case Axis::Horizontal:
lineRect = KDRect(
std::round(floatToPixel(Axis::Horizontal, lowerBound)), std::round(floatToPixel(Axis::Vertical, coordinate)),
std::round(floatToPixel(Axis::Horizontal, upperBound) - floatToPixel(Axis::Horizontal, lowerBound)), thickness
);
break;
case Axis::Vertical:
lineRect = KDRect(
std::round(floatToPixel(Axis::Horizontal, coordinate)), std::round(floatToPixel(Axis::Vertical, upperBound)),
thickness, std::round(floatToPixel(Axis::Vertical, lowerBound) - floatToPixel(Axis::Vertical, upperBound))
);
break;
}
if (rect.intersects(lineRect)) {
ctx->fillRect(lineRect, color);
}
}
constexpr KDCoordinate dotDiameter = 5;
const uint8_t dotMask[dotDiameter][dotDiameter] = {
{0xE1, 0x45, 0x0C, 0x45, 0xE1},
{0x45, 0x00, 0x00, 0x00, 0x45},
{0x00, 0x00, 0x00, 0x00, 0x00},
{0x45, 0x00, 0x00, 0x00, 0x45},
{0xE1, 0x45, 0x0C, 0x45, 0xE1},
};
constexpr KDCoordinate oversizeDotDiameter = 7;
const uint8_t oversizeDotMask[oversizeDotDiameter][oversizeDotDiameter] = {
{0xE1, 0x45, 0x0C, 0x00, 0x0C, 0x45, 0xE1},
{0x45, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x45},
{0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C},
{0x45, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x45},
{0xE1, 0x45, 0x0C, 0x00, 0x0C, 0x45, 0xE1},
};
KDColor s_dotWorkingBuffer[dotDiameter*dotDiameter];
KDColor s_oversizeDotWorkingBuffer[oversizeDotDiameter*oversizeDotDiameter];
void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, bool oversize) const {
KDCoordinate px = std::round(floatToPixel(Axis::Horizontal, x));
KDCoordinate py = std::round(floatToPixel(Axis::Vertical, y));
if ((px + dotDiameter < rect.left() - k_externRectMargin || px - dotDiameter > rect.right() + k_externRectMargin) ||
(py + dotDiameter < rect.top() - k_externRectMargin || py - dotDiameter > rect.bottom() + k_externRectMargin)) {
return;
}
KDRect dotRect = KDRect(px - dotDiameter/2, py-dotDiameter/2, dotDiameter, dotDiameter);
ctx->blendRectWithMask(dotRect, color, (const uint8_t *)dotMask, s_dotWorkingBuffer);
if (oversize) {
KDRect oversizeDotRect = KDRect(px - oversizeDotDiameter/2, py-oversizeDotDiameter/2, oversizeDotDiameter, oversizeDotDiameter);
ctx->blendRectWithMask(oversizeDotRect, color, (const uint8_t *)oversizeDotMask, s_oversizeDotWorkingBuffer);
}
}
void CurveView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor color) const {
float rectMin = pixelToFloat(Axis::Horizontal, rect.left());
float rectMax = pixelToFloat(Axis::Horizontal, rect.right());
if (axis == Axis::Vertical) {
rectMax = pixelToFloat(Axis::Vertical, rect.top());
rectMin = pixelToFloat(Axis::Vertical, rect.bottom());
}
float start = step*((int)(min(axis)/step));
Axis otherAxis = (axis == Axis::Horizontal) ? Axis::Vertical : Axis::Horizontal;
for (float x =start; x < max(axis); x += step) {
/* When |start| >> step, start + step = start. In that case, quit the
* infinite loop. */
if (x == x-step || x == x+step) {
return;
}
if (rectMin <= x && x <= rectMax) {
drawLine(ctx, rect, otherAxis, x, color);
}
}
}
void CurveView::drawGrid(KDContext * ctx, KDRect rect) const {
drawGridLines(ctx, rect, Axis::Horizontal, m_curveViewRange->xGridUnit(), Palette::GreyWhite);
drawGridLines(ctx, rect, Axis::Vertical, m_curveViewRange->yGridUnit(), Palette::GreyWhite);
}
void CurveView::drawAxes(KDContext * ctx, KDRect rect, Axis axis) const {
drawLine(ctx, rect, axis, 0.0f, KDColorBlack, 1);
}
#define LINE_THICKNESS 2
#if LINE_THICKNESS == 1
constexpr KDCoordinate circleDiameter = 1;
constexpr KDCoordinate stampSize = circleDiameter+1;
const uint8_t stampMask[stampSize+1][stampSize+1] = {
{0xFF, 0xE1, 0xFF},
{0xE1, 0x00, 0xE1},
{0xFF, 0xE1, 0xFF},
};
#elif LINE_THICKNESS == 2
constexpr KDCoordinate circleDiameter = 2;
constexpr KDCoordinate stampSize = circleDiameter+1;
const uint8_t stampMask[stampSize+1][stampSize+1] = {
{0xFF, 0xE6, 0xE6, 0xFF},
{0xE6, 0x33, 0x33, 0xE6},
{0xE6, 0x33, 0x33, 0xE6},
{0xFF, 0xE6, 0xE6, 0xFF},
};
#elif LINE_THICKNESS == 3
constexpr KDCoordinate circleDiameter = 3;
constexpr KDCoordinate stampSize = circleDiameter+1;
const uint8_t stampMask[stampSize+1][stampSize+1] = {
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0x7A, 0x0C, 0x7A, 0xFF},
{0xFF, 0x0C, 0x00, 0x0C, 0xFF},
{0xFF, 0x7A, 0x0C, 0x7A, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
};
#elif LINE_THICKNESS == 5
constexpr KDCoordinate circleDiameter = 5;
constexpr KDCoordinate stampSize = circleDiameter+1;
const uint8_t stampMask[stampSize+1][stampSize+1] = {
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF},
{0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF},
{0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0xFF},
{0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF},
{0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
};
#endif
constexpr static int k_maxNumberOfIterations = 10;
void CurveView::drawCurve(KDContext * ctx, KDRect rect, EvaluateModelWithParameter evaluation, void * model, void * context, KDColor color, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, bool continuously) const {
float xMin = min(Axis::Horizontal);
float xMax = max(Axis::Horizontal);
float xStep = (xMax-xMin)/resolution();
float rectMin = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin);
float rectMax = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin);
float pixelColorLowerBound = std::round(floatToPixel(Axis::Horizontal, colorLowerBound));
float pixelColorUpperBound = std::round(floatToPixel(Axis::Horizontal, colorUpperBound));
for (float x = rectMin; x < rectMax; x += xStep) {
/* When |rectMin| >> xStep, rectMin + xStep = rectMin. In that case, quit
* the infinite loop. */
if (x == x-xStep || x == x+xStep) {
return;
}
float y = evaluation(x, model, context);
if (std::isnan(y)|| std::isinf(y)) {
continue;
}
float pxf = floatToPixel(Axis::Horizontal, x);
float pyf = floatToPixel(Axis::Vertical, y);
if (colorUnderCurve && pxf > pixelColorLowerBound && pxf < pixelColorUpperBound) {
KDRect colorRect((int)pxf, std::round(pyf), 1, std::round(floatToPixel(Axis::Vertical, 0.0f)) - std::round(pyf));
if (floatToPixel(Axis::Vertical, 0.0f) < std::round(pyf)) {
colorRect = KDRect((int)pxf, std::round(floatToPixel(Axis::Vertical, 0.0f)), 1, std::round(pyf) - std::round(floatToPixel(Axis::Vertical, 0.0f)));
}
ctx->fillRect(colorRect, color);
}
stampAtLocation(ctx, rect, pxf, pyf, color);
if (x <= rectMin || std::isnan(evaluation(x-xStep, model, context))) {
continue;
}
if (continuously) {
float puf = floatToPixel(Axis::Horizontal, x - xStep);
float pvf = floatToPixel(Axis::Vertical, evaluation(x-xStep, model, context));
straightJoinDots(ctx, rect, puf, pvf, pxf, pyf, color);
} else {
jointDots(ctx, rect, evaluation, model, context, x - xStep, evaluation(x-xStep, model, context), x, y, color, k_maxNumberOfIterations);
}
}
}
void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateModelWithParameter evaluation, void * model, void * context, float firstBarAbscissa, float barWidth,
bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound, float highlightUpperBound) const {
float rectMin = pixelToFloat(Axis::Horizontal, rect.left());
float rectMinBinNumber = std::floor((rectMin - firstBarAbscissa)/barWidth);
float rectMinLowerBound = firstBarAbscissa + rectMinBinNumber*barWidth;
float rectMax = pixelToFloat(Axis::Horizontal, rect.right());
float rectMaxBinNumber = std::floor((rectMax - firstBarAbscissa)/barWidth);
float rectMaxUpperBound = firstBarAbscissa + (rectMaxBinNumber+1)*barWidth + barWidth;
float pHighlightLowerBound = floatToPixel(Axis::Horizontal, highlightLowerBound);
float pHighlightUpperBound = floatToPixel(Axis::Horizontal, highlightUpperBound);
float step = barWidth;
if ((rectMaxUpperBound-rectMinLowerBound)/step > resolution()) {
step = (rectMaxUpperBound-rectMinLowerBound)/resolution();
}
for (float x = rectMinLowerBound; x < rectMaxUpperBound; x += step) {
/* When |rectMinLowerBound| >> step, rectMinLowerBound + step = rectMinLowerBound.
* In that case, quit the infinite loop. */
if (x == x-step || x == x+step) {
return;
}
float centerX = fillBar ? x+barWidth/2.0f : x;
float y = evaluation(centerX, model, context);
if (std::isnan(y)) {
continue;
}
KDCoordinate pxf = std::round(floatToPixel(Axis::Horizontal, x));
KDCoordinate pyf = std::round(floatToPixel(Axis::Vertical, y));
KDCoordinate pixelBarWidth = fillBar ? std::round(floatToPixel(Axis::Horizontal, x+barWidth)) - std::round(floatToPixel(Axis::Horizontal, x))-1 : 2;
KDRect binRect(pxf, pyf, pixelBarWidth, std::round(floatToPixel(Axis::Vertical, 0.0f)) - pyf);
if (floatToPixel(Axis::Vertical, 0.0f) < pyf) {
binRect = KDRect(pxf, std::round(floatToPixel(Axis::Vertical, 0.0f)), pixelBarWidth+1, pyf - std::round(floatToPixel(Axis::Vertical, 0.0f)));
}
KDColor binColor = defaultColor;
bool shouldColorBin = fillBar ? centerX >= highlightLowerBound && centerX <= highlightUpperBound : pxf >= floorf(pHighlightLowerBound) && pxf <= floorf(pHighlightUpperBound);
if (shouldColorBin) {
binColor = highlightColor;
}
ctx->fillRect(binRect, binColor);
}
}
int CurveView::numberOfLabels(Axis axis) const {
Axis otherAxis = axis == Axis::Horizontal ? Axis::Vertical : Axis::Horizontal;
if (min(otherAxis) > 0.0f || max(otherAxis) < 0.0f) {
return 0;
}
return std::ceil((max(axis) - min(axis))/(2*gridUnit(axis)));
}
void CurveView::jointDots(KDContext * ctx, KDRect rect, EvaluateModelWithParameter evaluation, void * model, void * context, float x, float y, float u, float v, KDColor color, int maxNumberOfRecursion) const {
float pyf = floatToPixel(Axis::Vertical, y);
float pvf = floatToPixel(Axis::Vertical, v);
if (std::isnan(pyf) || std::isnan(pvf)) {
return;
}
// No need to draw if both dots are outside visible area
if ((pyf < -stampSize && pvf < -stampSize) || (pyf > pixelLength(Axis::Vertical)+stampSize && pvf > pixelLength(Axis::Vertical)+stampSize)) {
return;
}
// If one of the dot is infinite, we cap it with a dot outside area
if (std::isinf(pyf)) {
pyf = pyf > 0 ? pixelLength(Axis::Vertical)+stampSize : -stampSize;
}
if (std::isinf(pvf)) {
pvf = pvf > 0 ? pixelLength(Axis::Vertical)+stampSize : -stampSize;
}
if (pyf - (float)circleDiameter/2.0f < pvf && pvf < pyf + (float)circleDiameter/2.0f) {
// the dots are already joined
return;
}
// C is the dot whose abscissa is between x and u
float cx = (x + u)/2.0f;
float cy = evaluation(cx, model, context);
if ((y <= cy && cy <= v) || (v <= cy && cy <= y)) {
/* As the middle dot is vertically between the two dots, we assume that we
* can draw a 'straight' line between the two */
float pxf = floatToPixel(Axis::Horizontal, x);
float puf = floatToPixel(Axis::Horizontal, u);
if (std::isnan(pxf) || std::isnan(puf)) {
return;
}
straightJoinDots(ctx, rect, pxf, pyf, puf, pvf, color);
return;
}
float pcxf = floatToPixel(Axis::Horizontal, cx);
float pcyf = floatToPixel(Axis::Vertical, cy);
if (maxNumberOfRecursion > 0) {
stampAtLocation(ctx, rect, pcxf, pcyf, color);
jointDots(ctx, rect, evaluation, model, context, x, y, cx, cy, color, maxNumberOfRecursion-1);
jointDots(ctx, rect, evaluation, model, context, cx, cy, u, v, color, maxNumberOfRecursion-1);
}
}
void CurveView::straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float pyf, float puf, float pvf, KDColor color) const {
if (pyf <= pvf) {
for (float pnf = pyf; pnf<pvf; pnf+= 1.0f) {
float pmf = pxf + (pnf - pyf)*(puf - pxf)/(pvf - pyf);
stampAtLocation(ctx, rect, pmf, pnf, color);
}
return;
}
straightJoinDots(ctx, rect, puf, pvf, pxf, pyf, color);
}
void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color) const {
// We avoid drawing when no part of the stamp is visible
if (pyf < -stampSize || pyf > pixelLength(Axis::Vertical)+stampSize) {
return;
}
KDCoordinate px = pxf;
KDCoordinate py = pyf;
KDRect stampRect(px-(circleDiameter-2)/2, py-(circleDiameter-2)/2, stampSize, stampSize);
if (!rect.intersects(stampRect)) {
return;
}
uint8_t shiftedMask[stampSize][stampSize];
KDColor workingBuffer[stampSize*stampSize];
float dx = pxf - std::floor(pxf);
float dy = pyf - std::floor(pyf);
/* TODO: this could be optimized by precomputing 10 or 100 shifted masks. The
* dx and dy would be rounded to one tenth or one hundredth to choose the
* right shifted mask. */
for (int i=0; i<stampSize; i++) {
for (int j=0; j<stampSize; j++) {
shiftedMask[i][j] = dx * (stampMask[i][j]*dy+stampMask[i+1][j]*(1.0f-dy))
+ (1.0f-dx) * (stampMask[i][j+1]*dy + stampMask[i+1][j+1]*(1.0f-dy));
}
}
ctx->blendRectWithMask(stampRect, color, (const uint8_t *)shiftedMask, workingBuffer);
}
void CurveView::layoutSubviews() {
if (m_curveViewCursor != nullptr && m_cursorView != nullptr) {
m_cursorView->setFrame(cursorFrame());
}
if (m_bannerView != nullptr && m_displayBanner) {
m_bannerView->setFrame(bannerFrame());
}
if (m_okView != nullptr) {
m_okView->setFrame(okFrame());
}
}
KDRect CurveView::cursorFrame() {
KDRect cursorFrame = KDRectZero;
if (m_cursorView && m_mainViewSelected && !std::isnan(m_curveViewCursor->x()) && !std::isnan(m_curveViewCursor->y())) {
KDSize cursorSize = m_cursorView->minimalSizeForOptimalDisplay();
KDCoordinate xCursorPixelPosition = std::round(floatToPixel(Axis::Horizontal, m_curveViewCursor->x()));
KDCoordinate yCursorPixelPosition = std::round(floatToPixel(Axis::Vertical, m_curveViewCursor->y()));
cursorFrame = KDRect(xCursorPixelPosition - (cursorSize.width()-1)/2, yCursorPixelPosition - (cursorSize.height()-1)/2, cursorSize.width(), cursorSize.height());
if (cursorSize.height() == 0) {
KDCoordinate bannerHeight = (m_bannerView != nullptr && m_displayBanner) ? m_bannerView->minimalSizeForOptimalDisplay().height() : 0;
cursorFrame = KDRect(xCursorPixelPosition - (cursorSize.width()-1)/2, 0, cursorSize.width(),bounds().height()-bannerHeight);
}
}
return cursorFrame;
}
KDRect CurveView::bannerFrame() {
KDRect bannerFrame = KDRectZero;
if (m_bannerView && m_displayBanner && m_mainViewSelected) {
KDCoordinate bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height();
bannerFrame = KDRect(0, bounds().height()- bannerHeight, bounds().width(), bannerHeight);
}
return bannerFrame;
}
KDRect CurveView::okFrame() {
KDRect okFrame = KDRectZero;
if (m_okView && (m_mainViewSelected || m_forceOkDisplay)) {
KDCoordinate bannerHeight = 0;
if (m_bannerView != nullptr && m_displayBanner) {
bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height();
}
KDSize okSize = m_okView->minimalSizeForOptimalDisplay();
okFrame = KDRect(bounds().width()- okSize.width()-k_okHorizontalMargin, bounds().height()- bannerHeight-okSize.height()-k_okVerticalMargin, okSize);
}
return okFrame;
}
int CurveView::numberOfSubviews() const {
return (m_bannerView != nullptr && m_displayBanner) + (m_cursorView != nullptr) + (m_okView != nullptr);
};
View * CurveView::subviewAtIndex(int index) {
assert(index >= 0 && index < 3);
/* If all subviews exist, we want Ok view to be the first child to avoid
* redrawing it because it falls in the union of dirty rectangles linked to
* the banner view and curve view */
if (index == 0) {
if (m_okView != nullptr) {
return m_okView;
} else {
if (m_bannerView != nullptr && m_displayBanner) {
return m_bannerView;
}
}
}
if (index == 1 && m_bannerView != nullptr && m_displayBanner && m_okView != nullptr) {
return m_bannerView;
}
return m_cursorView;
}
}