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

/*
* 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