In the code for drawing the widget, we will use a lot of constant values for variables like horizontal and vertical spacing between two objects, the height of the bar, etc. We will define these values as constants in the private section of the ProTutorialWidget
class in the plugin.h file, to distinguish them from other variables we will use the prefix "k". The names of the constants should be descriptive enough for other developers to know what they mean and what they are used for when they see them.
const int kBarHeight = 18;
const int kYSpacing = 30;
const int kXSpacing = 30;
const int kTitleFontSize = 10;
const int kValueFontSize = 8;
const double kValidatorMaxLimit = 1000;
const double kValidatorMinLimit = -1000;
The main drawCanvasData()
function for our example will look like this:
void
ProTutorialWidget
::drawCanvasData(DrawDataParams& drawParams)
{
Canvas& canvas = drawParams.canvas;
Rect& controlRect = drawParams.rect;
int yScreenCoordinate = kYSpacing;
Rect titleRect(0, 0, controlRect.width, yScreenCoordinate);
drawGraphTitle(drawParams, titleRect, L"Widget example");
yScreenCoordinate += kYSpacing;
size_t maxChannelsOnVisibleArea = (size_t)round(controlRect.height / kYSpacing);
int channelIndex = 0;
ChannelList inputChannels = getInputChannels();
while ((channelIndex < inputChannels.getCount()) && (maxChannelsOnVisibleArea > 0))
{
Channel channel = inputChannels[channelIndex];
drawChannelName(channel, drawParams, kXSpacing, yScreenCoordinate);
yScreenCoordinate += canvas.getTextHeight(channel.getName());
drawChannelValue(channel, drawParams, controlRect.width, yScreenCoordinate, maxChannelsOnVisibleArea);
yScreenCoordinate += channel.getArraySize() * kYSpacing;
channelIndex++;
}
}
As we can see we will create two local variables one to remember the index of the last displayed channel and the other to keep track of how much horizontal space on the widget we already used. We will also create a title rectangular for displaying the global title of the widget and display it with the drawGraphTitle()
function that we implemented ourselves. We will also implement a function for drawing the current channel's name, value and unit. Our functions for drawing are defined in the private section of the ProTutorialWidget
class in the plugin.h file. We will also need a lot of additional helper functions, you can see their definitions in the code below.
class ProTutorialWidget : public Dewesoft::Widgets::Api::Widget
{
public:
private:
double getChannelMinValue(Channel& ch);
double getChannelMaxValue(Channel& ch);
int getCenterTextCoordinate(DrawDataParams& drawParams, Rect& rect, const std::wstring& text);
int getBarWidth(double currentValue, double minValue, double maxValue, Rect& barRect);
void drawBar(Channel& channel, size_t arrayIndex, DrawDataParams& drawParams, Rect& barRect);
void drawGraphTitle(DrawDataParams& drawParams, Rect& titleRect, const std::wstring& text);
void drawChannelName(Channel& channel, DrawDataParams& drawParams, int x, int y);
void drawChannelValue(Channel& channel, DrawDataParams& drawParams, int controlRectWidth, int y, size_t& maxChannelsOnVisibleArea);
std::wstring buildBarText(Channel& channel, size_t arrayIndex, double currentValue);
void drawUnit(Channel& channel, DrawDataParams& drawParams, Rect& barRect);
std::wstring getChannelAxisValue(Channel& ch, int axisIndex, int index);
}
What each of these functions does and how to use them will be explained later on in this section.
void ProTutorialWidget::drawGraphTitle(DrawDataParams& drawParams, Rect& titleRect, const std::wstring& text)
{
Canvas& canvas = drawParams.canvas;
ColorPalette& colorPalette = drawParams.colorPalette;
CanvasTextFormats textFormat = { ctfCenter };
int textCenterXY = getCenterTextCoordinate(drawParams, titleRect, text);
CanvasFont& canvasFont = canvas.getFont();
canvasFont.setSize(kTitleFontSize);
canvasFont.setColor(colorPalette.fontColor);
canvas.getBrush().setStyle(cbsClear);
canvas.textRect(titleRect.x, textCenterXY, titleRect.width, titleRect.height, text, textFormat);
}
The drawGraphTitle()
function accepts drawParams, title rectangle, and title text as parameters and it draws the text inside a textRect()
. We want to output the text in the center on the top of the rectangle. The center of the rectangular is calculated with the getCenterTextCoordinate()
function whose implementation looks like this.
int ProTutorialWidget::getCenterTextCoordinate(DrawDataParams& drawParams, Rect& rect, const std::wstring& text)
{
return int(round(rect.y + (rect.height - rect.y) / 2 - (drawParams.canvas.getTextHeight(text) / 2)));
}
void ProTutorialWidget::drawChannelName(Channel& channel, DrawDataParams& drawParams, int x, int y)
{
Canvas& canvas = drawParams.canvas;
CanvasFont& canvasFont = canvas.getFont();
canvasFont.setSize(userFontSize);
canvasFont.setColor(channel.getMainDisplayColor());
canvas.getBrush().setStyle(cbsClear);
canvas.textOut(x, y, channel.getName());
}
The drawChannelName()
is a very simple function that sets the font size and color of the text we want to display and then displays the name of the current channel. The position of the text is calculated beforehand in the drawCanvasData()
function.
void ProTutorialWidget::drawChannelValue(Channel& channel, DrawDataParams& drawParams, int controlRectWidth, int y, size_t& maxChannelsOnVisibleArea
)
{
Rect barRect(kXSpacing, y, controlRectWidth - 4 * kYSpacing, kBarHeight);
drawUnit(channel, drawParams, barRect);
size_t arrayIndex = 0;
while ((arrayIndex < static_cast<size_t>(channel.getArraySize())) && (maxChannelsOnVisibleArea > 0))
{
drawBar(channel, arrayIndex, drawParams, barRect);
maxChannelsOnVisibleArea--;
arrayIndex++;
}
}
Now we just need to draw the channel value inside a bar. The bar will be represented as a rectangular and inside of it we first draw the value of the channel and then fill the rectangular to match the channel value. We will create an additional function drawBar()
for drawing the actual bar. We also add additional helper functions getBarWidth()
for calculating how much of the bar we have to fill, buildBarText()
for reading the correct text from the channel and getChannelAxisValue()
if the input channel is vector or matrix to read the values from the axis. The drawChannelValue()
function gets called for every channel that will be displayed on our widget. If the channel is of vector or matrix type then we will display every axis value as its own bar with name and unit. We also need to keep track of how many channels we been already drawn on widget because we do not want to draw a channel on an area of the widget that can not be seen by the user.
void ProTutorialWidget::drawUnit(Channel& channel, DrawDataParams& drawParams, Rect& barRect)
{
Canvas& canvas = drawParams.canvas;
CanvasFont canvasFont = canvas.getFont();
ColorPalette& colorPalette = drawParams.colorPalette;
canvasFont.setSize(kValueFontSize);
canvasFont.setColor(colorPalette.fontColor);
canvas.getBrush().setStyle(cbsClear);
canvas.textOut(barRect.x + barRect.width + 10, barRect.y, channel.getUnit());
}
The drawUnit()
is again a very simple function that sets the font size and color and displays the unit of the channel at the end of the bar.
void ProTutorialWidget::drawBar(Channel& channel, size_t arrayIndex, DrawDataParams& drawParams, Rect& barRect)
{
Canvas& canvas = drawParams.canvas;
CanvasFont canvasFont = canvas.getFont();
CanvasBrush canvasBrush = canvas.getBrush();
CanvasPen canvasPen = canvas.getPen();
ColorPalette& colorPalette = drawParams.colorPalette;
double maxValue = getChannelMaxValue(channel);
double minValue = getChannelMinValue(channel);
double currentValue = channel.getCurrentValue(static_cast<int>(arrayIndex));
canvasBrush.setColor(colorPalette.backColor);
canvasPen.setColor(colorPalette.fontColor);
canvas.rectangle(barRect.x, barRect.y, barRect.x + barRect.width, barRect.y + barRect.height);
canvasBrush.setColor(channel.getMainDisplayColor());
canvasPen.setColor(colorPalette.fontColor);
canvas.fillRect(barRect.x + 1, barRect.y + 1, getBarWidth(currentValue, minValue, maxValue, barRect), barRect.y + barRect.height);
canvasFont.setSize(kValueFontSize);
canvasFont.setColor(dcom::Color::White);
canvasBrush.setStyle(cbsClear);
canvas.textOut(barRect.x + 2, barRect.y + 2, buildBarText(channel, arrayIndex, currentValue));
barRect.y += kYSpacing;
}
The drawBar()
function draws the value of the channel and fills the bar in the ratio to the value and the minimal and maximal value of the channel. To draw a the container of the bar we use the rectangle()
function with the specified rectangle dimensions. To fill the bar (rectangle) we use the fillRect()
function and with the helper function getBarWidth()
we calculate the point to which the bar needs to be filled. We will also write a getter function for reading the minimum and maximum values of the channel whether they are user-defined or default.
double ProTutorialWidget::
getChannelMinValue(Channel& ch)
{
if (useManualLimits)
return minChannelValue;
else
return ch.getTypicalMinValue();
}
double ProTutorialWidget::
getChannelMaxValue(Channel& ch)
{
if (useManualLimits)
return maxChannelValue;
else
return ch.getTypicalMaxValue();
}
int ProTutorialWidget::getBarWidth(double currentValue, double minValue, double maxValue, Rect& barRect)
{
currentValue = std::clamp(currentValue, minValue, maxValue);
int currentValueInPixels = (maxValue - minValue == 0.0) ? 0 : static_cast<int>(round((currentValue - minValue) / (maxValue - minValue) * barRect.width));
return barRect.x + currentValueInPixels - 1;
}
std::wstring ProTutorialWidget::buildBarText(Channel& channel, size_t arrayIndex, double currentValue)
{
if (channel.getArrayChannel())
return getChannelAxisValue(channel, 0, arrayIndex) + L": " + std::to_wstring(currentValue);
else
return std::to_wstring(currentValue);
}
Because array channels have axis values we also need to add a function for reading the values. Array channels can have multiple axes so we need to specify the index of which axis information we would like to access in the getAxisDefinition()
function. The axis values can be of three types: string, float or a linear function but we don't need to worry about that because the getAxisValue()
function will handle all that for us and return the correct axis value based on its type.
std::wstring ProTutorialWidget::getChannelAxisValue(Channel& ch, int axisIndex, int index)
{
AxisDefiniton axisDef = ch.getArrayInfo().getAxisDefinition(axisIndex);
return axisDef.getAxisValue(index);
}
Our widget will now show the data like this:
Image 16: The widget now shows the data
The image shows two input channels both of them are the output channels of the FFT Analyser with one begin scalar channel (FFT block count) and the other one being a vector channel (AI 1/AmplFFT).