You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
9.8 KiB
335 lines
9.8 KiB
2 years ago
|
/*
|
||
|
* EEZ Modular Firmware
|
||
|
* Copyright (C) 2020-present, Envox d.o.o.
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include <math.h>
|
||
|
#include <stdio.h>
|
||
|
|
||
|
#include <eez/core/alloc.h>
|
||
|
#include <eez/core/util.h>
|
||
|
|
||
|
#include <eez/gui/gui.h>
|
||
|
#include <eez/gui/data.h>
|
||
|
#include <eez/gui/widgets/gauge.h>
|
||
|
|
||
|
namespace eez {
|
||
|
namespace gui {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void arcBar(Agg2D &graphics, int xCenter, int yCenter, int radOuter, int radInner, float angleDeg) {
|
||
|
auto angle = Agg2D::deg2Rad(angleDeg);
|
||
|
auto cosa = cos(angle);
|
||
|
auto sina = sin(angle);
|
||
|
|
||
|
graphics.moveTo(xCenter - radOuter, yCenter);
|
||
|
|
||
|
graphics.arcTo(
|
||
|
radOuter, // rx
|
||
|
radOuter, // ry
|
||
|
Agg2D::deg2Rad(0), // angle
|
||
|
0, // largeArcFlag
|
||
|
1, // sweepFlag
|
||
|
xCenter + radOuter * cosa, // dx
|
||
|
yCenter - radOuter * sina // dy
|
||
|
);
|
||
|
|
||
|
graphics.lineTo(
|
||
|
xCenter + radInner * cosa,
|
||
|
yCenter - radInner * sina
|
||
|
);
|
||
|
|
||
|
graphics.arcTo(
|
||
|
radInner,
|
||
|
radInner,
|
||
|
Agg2D::deg2Rad(-180),
|
||
|
0,
|
||
|
0,
|
||
|
xCenter - radInner,
|
||
|
yCenter
|
||
|
);
|
||
|
|
||
|
graphics.lineTo(xCenter - radOuter, yCenter);
|
||
|
}
|
||
|
|
||
|
float firstTick(float n) {
|
||
|
auto p = powf(10, floorf(log10f(n / 6.0f)));
|
||
|
auto f = n / 6.0f / p;
|
||
|
int i;
|
||
|
if (f > 5) {
|
||
|
i = 10;
|
||
|
} else if (f > 2) {
|
||
|
i = 5;
|
||
|
} else {
|
||
|
i = 2;
|
||
|
}
|
||
|
return i * p;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
bool GaugeWidgetState::updateState() {
|
||
|
WIDGET_STATE_START(GaugeWidget);
|
||
|
|
||
|
WIDGET_STATE(flags.active, g_isActiveWidget);
|
||
|
WIDGET_STATE(data, get(widgetCursor, widget->data));
|
||
|
WIDGET_STATE(minValue, get(widgetCursor, widget->min));
|
||
|
WIDGET_STATE(maxValue, get(widgetCursor, widget->max));
|
||
|
WIDGET_STATE(thresholdValue, get(widgetCursor, widget->threshold));
|
||
|
WIDGET_STATE(unitValue, get(widgetCursor, widget->unit));
|
||
|
|
||
|
WIDGET_STATE_END()
|
||
|
}
|
||
|
|
||
|
void GaugeWidgetState::render() {
|
||
|
const WidgetCursor &widgetCursor = g_widgetCursor;
|
||
|
|
||
|
auto widget = (const GaugeWidget*)widgetCursor.widget;
|
||
|
|
||
|
using namespace display;
|
||
|
|
||
|
float min = minValue.toFloat();
|
||
|
float max = maxValue.toFloat();
|
||
|
float value = data.toFloat();
|
||
|
float threshold = thresholdValue.toFloat();
|
||
|
|
||
|
auto unit = unitValue.toString(0xa9ddede3).getString();
|
||
|
|
||
|
if (isNaN(min) || isNaN(max) || isNaN(value) || isinf(min) || isinf(max) || isinf(value) || min >= max) {
|
||
|
min = 0.0;
|
||
|
max = 1.0f;
|
||
|
value = 0.0f;
|
||
|
} else {
|
||
|
if (value < min) {
|
||
|
value = min;
|
||
|
} else if (value > max) {
|
||
|
value = max;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const Style* style = getStyle(widget->style);
|
||
|
const Style* barStyle = getStyle(widget->barStyle);
|
||
|
const Style* valueStyle = getStyle(widget->valueStyle);
|
||
|
const Style* ticksStyle = getStyle(widget->ticksStyle);
|
||
|
const Style* thresholdStyle = getStyle(widget->thresholdStyle);
|
||
|
|
||
|
auto isActive = flags.active;
|
||
|
|
||
|
// auto colorBackground = getColor16FromIndex(style->background_color);
|
||
|
auto colorBorder = getColor16FromIndex(isActive ? style->activeColor : style->color);
|
||
|
auto colorBar = getColor16FromIndex(isActive ? barStyle->activeColor : barStyle->color);
|
||
|
|
||
|
auto xCenter = widgetCursor.w / 2;
|
||
|
auto yCenter = widgetCursor.h - 8;
|
||
|
|
||
|
// clear background
|
||
|
setColor(isActive ? style->activeBackgroundColor : style->backgroundColor);
|
||
|
fillRect(widgetCursor.x, widgetCursor.y, widgetCursor.x + widgetCursor.w - 1, widgetCursor.y + widgetCursor.h - 1);
|
||
|
|
||
|
// init AGG
|
||
|
display::AggDrawing aggDrawing;
|
||
|
display::aggInit(aggDrawing);
|
||
|
auto &graphics = aggDrawing.graphics;
|
||
|
|
||
|
graphics.clipBox(widgetCursor.x, widgetCursor.y, widgetCursor.x + widgetCursor.w, widgetCursor.y + widgetCursor.h);
|
||
|
graphics.translate(widgetCursor.x, widgetCursor.y);
|
||
|
|
||
|
// draw frame
|
||
|
if (style->borderSizeLeft > 0) {
|
||
|
graphics.lineWidth(style->borderSizeLeft);
|
||
|
graphics.lineColor(COLOR_TO_R(colorBorder), COLOR_TO_G(colorBorder), COLOR_TO_B(colorBorder));
|
||
|
graphics.noFill();
|
||
|
graphics.roundedRect(
|
||
|
style->borderSizeLeft / 2.0,
|
||
|
style->borderSizeLeft / 2.0,
|
||
|
widgetCursor.w - style->borderSizeLeft,
|
||
|
widgetCursor.h - style->borderSizeLeft,
|
||
|
style->borderRadiusTLX, style->borderRadiusTLY, style->borderRadiusTRX, style->borderRadiusTRY,
|
||
|
style->borderRadiusBRX, style->borderRadiusBRY, style->borderRadiusBLX, style->borderRadiusBLY
|
||
|
);
|
||
|
}
|
||
|
|
||
|
static const int PADDING_HORZ = 56;
|
||
|
static const int TICK_LINE_LENGTH = 5;
|
||
|
static const int TICK_LINE_WIDTH = 1;
|
||
|
static const int TICK_TEXT_GAP = 1;
|
||
|
static const int THRESHOLD_LINE_WIDTH = 2;
|
||
|
|
||
|
// draw border
|
||
|
auto radBorderOuter = (widgetCursor.w - PADDING_HORZ) / 2;
|
||
|
|
||
|
auto BORDER_WIDTH = radBorderOuter / 3;
|
||
|
auto BAR_WIDTH = BORDER_WIDTH / 2;
|
||
|
|
||
|
auto radBorderInner = radBorderOuter - BORDER_WIDTH;
|
||
|
graphics.resetPath();
|
||
|
graphics.noFill();
|
||
|
graphics.lineColor(COLOR_TO_R(colorBorder), COLOR_TO_G(colorBorder), COLOR_TO_B(colorBorder));
|
||
|
graphics.lineWidth(1.5);
|
||
|
arcBar(graphics, xCenter, yCenter, radBorderOuter, radBorderInner, 0);
|
||
|
graphics.drawPath();
|
||
|
|
||
|
// draw bar
|
||
|
auto radBarOuter = (widgetCursor.w - PADDING_HORZ) / 2 - (BORDER_WIDTH - BAR_WIDTH) / 2;
|
||
|
auto radBarInner = radBarOuter - BAR_WIDTH;
|
||
|
auto angle = remap(value, min, 180.0f, max, 0.0f);
|
||
|
graphics.resetPath();
|
||
|
graphics.noLine();
|
||
|
graphics.fillColor(COLOR_TO_R(colorBar), COLOR_TO_G(colorBar), COLOR_TO_B(colorBar));
|
||
|
graphics.lineWidth(1.5);
|
||
|
arcBar(graphics, xCenter, yCenter, radBarOuter, radBarInner, angle);
|
||
|
graphics.drawPath();
|
||
|
|
||
|
// draw threshold
|
||
|
auto thresholdAngleDeg = remap(threshold, min, 180.0f, max, 0);
|
||
|
if (thresholdAngleDeg >= 0 && thresholdAngleDeg <= 180.0f) {
|
||
|
auto thresholdAngle = Agg2D::deg2Rad(thresholdAngleDeg);
|
||
|
float acos = cosf(thresholdAngle);
|
||
|
float asin = sinf(thresholdAngle);
|
||
|
int x1 = floorf(xCenter + radBarInner * acos);
|
||
|
int y1 = floorf(yCenter - radBarInner * asin);
|
||
|
int x2 = floorf(xCenter + radBarOuter * acos);
|
||
|
int y2 = floorf(yCenter - radBarOuter * asin);
|
||
|
|
||
|
graphics.resetPath();
|
||
|
graphics.noFill();
|
||
|
auto thresholdColor = getColor16FromIndex(isActive ? thresholdStyle->activeColor : thresholdStyle->color);
|
||
|
graphics.lineColor(COLOR_TO_R(thresholdColor), COLOR_TO_G(thresholdColor), COLOR_TO_B(thresholdColor));
|
||
|
graphics.lineWidth(THRESHOLD_LINE_WIDTH);
|
||
|
graphics.moveTo(x1, y1);
|
||
|
graphics.lineTo(x2, y2);
|
||
|
graphics.drawPath();
|
||
|
}
|
||
|
|
||
|
// draw ticks
|
||
|
font::Font ticksFont = styleGetFont(ticksStyle);
|
||
|
auto ft = firstTick(max - min);
|
||
|
auto ticksRad = radBorderOuter + 1;
|
||
|
for (auto tickValue = min; tickValue <= max; tickValue += ft) {
|
||
|
auto tickAngleDeg = remap(tickValue, min, 180.0f, max, 0);
|
||
|
if (tickAngleDeg <= 180.0) {
|
||
|
auto tickAngle = Agg2D::deg2Rad(tickAngleDeg);
|
||
|
float acos = cosf(tickAngle);
|
||
|
float asin = sinf(tickAngle);
|
||
|
int x1 = floorf(xCenter + ticksRad * acos);
|
||
|
int y1 = floorf(yCenter - ticksRad * asin);
|
||
|
int x2 = floorf(xCenter + (ticksRad + TICK_LINE_LENGTH) * acos);
|
||
|
int y2 = floorf(yCenter - (ticksRad + TICK_LINE_LENGTH) * asin);
|
||
|
|
||
|
graphics.resetPath();
|
||
|
graphics.noFill();
|
||
|
auto tickColor = getColor16FromIndex(isActive ? ticksStyle->activeColor : ticksStyle->color);
|
||
|
graphics.lineColor(COLOR_TO_R(tickColor), COLOR_TO_G(tickColor), COLOR_TO_B(tickColor));
|
||
|
graphics.lineWidth(TICK_LINE_WIDTH);
|
||
|
graphics.moveTo(x1, y1);
|
||
|
graphics.lineTo(x2, y2);
|
||
|
graphics.drawPath();
|
||
|
|
||
|
char tickText[50];
|
||
|
snprintf(tickText, sizeof(tickText), "%g", tickValue);
|
||
|
if (unit && *unit) {
|
||
|
stringAppendString(tickText, sizeof(tickText), " ");
|
||
|
stringAppendString(tickText, sizeof(tickText), unit);
|
||
|
}
|
||
|
|
||
|
auto tickTextWidth = display::measureStr(tickText, -1, ticksFont);
|
||
|
if (tickAngleDeg == 180.0) {
|
||
|
drawText(
|
||
|
tickText,
|
||
|
-1,
|
||
|
widgetCursor.x + xCenter -
|
||
|
radBorderOuter -
|
||
|
TICK_TEXT_GAP -
|
||
|
tickTextWidth,
|
||
|
widgetCursor.y + y2 - TICK_TEXT_GAP - ticksFont.getAscent(),
|
||
|
tickTextWidth,
|
||
|
ticksFont.getAscent(),
|
||
|
ticksStyle,
|
||
|
isActive
|
||
|
);
|
||
|
} else if (tickAngleDeg > 90.0) {
|
||
|
drawText(
|
||
|
tickText,
|
||
|
-1,
|
||
|
widgetCursor.x + x2 - TICK_TEXT_GAP - tickTextWidth,
|
||
|
widgetCursor.y + y2 - TICK_TEXT_GAP - ticksFont.getAscent(),
|
||
|
tickTextWidth,
|
||
|
ticksFont.getAscent(),
|
||
|
ticksStyle,
|
||
|
isActive
|
||
|
);
|
||
|
} else if (tickAngleDeg == 90.0) {
|
||
|
drawText(
|
||
|
tickText,
|
||
|
-1,
|
||
|
widgetCursor.x + x2 - tickTextWidth / 2,
|
||
|
widgetCursor.y + y2 - TICK_TEXT_GAP - ticksFont.getAscent(),
|
||
|
tickTextWidth,
|
||
|
ticksFont.getAscent(),
|
||
|
ticksStyle,
|
||
|
isActive
|
||
|
);
|
||
|
} else if (tickAngleDeg > 0) {
|
||
|
drawText(
|
||
|
tickText,
|
||
|
-1,
|
||
|
widgetCursor.x + x2 + TICK_TEXT_GAP,
|
||
|
widgetCursor.y + y2 - TICK_TEXT_GAP - ticksFont.getAscent(),
|
||
|
tickTextWidth,
|
||
|
ticksFont.getAscent(),
|
||
|
ticksStyle,
|
||
|
isActive
|
||
|
);
|
||
|
} else {
|
||
|
drawText(
|
||
|
tickText,
|
||
|
-1,
|
||
|
widgetCursor.x + xCenter + radBorderOuter + TICK_TEXT_GAP,
|
||
|
widgetCursor.y + y2 - TICK_TEXT_GAP - ticksFont.getAscent(),
|
||
|
tickTextWidth,
|
||
|
ticksFont.getAscent(),
|
||
|
ticksStyle,
|
||
|
isActive
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// draw value
|
||
|
font::Font valueFont = styleGetFont(valueStyle);
|
||
|
char valueText[50];
|
||
|
snprintf(valueText, sizeof(valueText), "%g", value);
|
||
|
if (unit && *unit) {
|
||
|
stringAppendString(valueText, sizeof(valueText), " ");
|
||
|
stringAppendString(valueText, sizeof(valueText), unit);
|
||
|
}
|
||
|
auto valueTextWidth = display::measureStr(valueText, -1, valueFont);
|
||
|
drawText(
|
||
|
valueText,
|
||
|
-1,
|
||
|
widgetCursor.x + xCenter - valueTextWidth / 2,
|
||
|
widgetCursor.y + yCenter - valueFont.getHeight(),
|
||
|
valueTextWidth,
|
||
|
valueFont.getHeight(),
|
||
|
valueStyle,
|
||
|
isActive
|
||
|
);
|
||
|
}
|
||
|
|
||
|
} // namespace gui
|
||
|
} // namespace eez
|