More compact views for inputs, mixers and outputs

This commit is contained in:
Raphael Coeffic
2022-08-28 13:07:03 +02:00
committed by Raphael Coeffic
parent cd211cba59
commit 731d1834e3
21 changed files with 632 additions and 308 deletions
+31
View File
@@ -416,3 +416,34 @@ int applyCurrentCurve(int x)
return applyCustomCurve(x, s_currIdxSubMenu);
}
#endif
#if defined(COLORLCD)
char *getCurveRefString(char *dest, size_t len, const CurveRef& curve)
{
if (!len) return dest;
char *s = dest;
if (curve.value != 0) {
switch (curve.type) {
case CURVE_REF_DIFF:
*(s++) = 'D'; if (--len == 0) return dest;
getValueOrGVarString(s, len, curve.value, -100, 100, 0, "%");
return dest;
case CURVE_REF_EXPO:
*(s++) = 'E'; if (--len == 0) return dest;
getValueOrGVarString(s, len, curve.value, -100, 100, 0, "%");
return dest;
case CURVE_REF_FUNC:
strAppend(dest, STR_VCURVEFUNC[curve.value], len);
return dest;
case CURVE_REF_CUSTOM:
return getCurveString(dest, curve.value);
}
}
return dest;
}
#endif
+2
View File
@@ -47,4 +47,6 @@ int applyCustomCurve(int x, uint8_t idx);
int applyCurve(int x, CurveRef & curve);
int applyCurrentCurve(int x);
char *getCurveRefString(char *dest, size_t len, const CurveRef& curve);
#endif
+1
View File
@@ -207,6 +207,7 @@ add_gui_src(input_mix_group.cpp)
add_gui_src(input_mix_button.cpp)
add_gui_src(channel_bar.cpp)
add_gui_src(list_line_button.cpp)
add_gui_src(lvgl_widgets/input_mix_line.c)
add_gui_src(lvgl_widgets/input_mix_group.c)
+2 -10
View File
@@ -161,18 +161,12 @@ static const uint8_t mask_stats_throttle_graph[] = {
static const uint8_t mask_stats_timers[] = {
#include "mask_stats_timers.lbm"
};
static const uint8_t mask_textline_curve[] = {
#include "mask_textline_curve.lbm"
};
static const uint8_t mask_textline_delay[] = {
#include "mask_textline_delay.lbm"
};
static const uint8_t mask_textline_delayslow[] = {
#include "mask_textline_delayslow.lbm"
};
static const uint8_t mask_textline_fm[] = {
#include "mask_textline_fm.lbm"
};
static const uint8_t mask_textline_label[] = {
#include "mask_textline_label.lbm"
};
@@ -241,13 +235,12 @@ BitmapBuffer * chanMonInvertedBitmap = nullptr;
BitmapBuffer * mixerSetupMixerBitmap = nullptr;
BitmapBuffer * mixerSetupToBitmap = nullptr;
BitmapBuffer * mixerSetupOutputBitmap = nullptr;
BitmapBuffer * mixerSetupCurveIcon = nullptr;
// BitmapBuffer * mixerSetupCurveIcon = nullptr;
BitmapBuffer * mixerSetupSwitchIcon = nullptr;
BitmapBuffer * mixerSetupLabelIcon = nullptr;
BitmapBuffer * mixerSetupDelayIcon = nullptr;
BitmapBuffer * mixerSetupSlowIcon = nullptr;
BitmapBuffer * mixerSetupDelaySlowIcon = nullptr;
BitmapBuffer * mixerSetupFlightmodeIcon = nullptr;
struct _BuiltinBitmap {
@@ -277,9 +270,8 @@ static const _BuiltinBitmap _builtinBitmaps[] = {
{BMP_8BIT, mask_sbar_output, &mixerSetupOutputBitmap},
{BMP_8BIT, mask_textline_label, &mixerSetupLabelIcon},
{BMP_8BIT, mask_textline_curve, &mixerSetupCurveIcon},
// {BMP_8BIT, mask_textline_curve, &mixerSetupCurveIcon},
{BMP_8BIT, mask_textline_switch, &mixerSetupSwitchIcon},
{BMP_8BIT, mask_textline_fm, &mixerSetupFlightmodeIcon},
{BMP_8BIT, mask_textline_slow, &mixerSetupSlowIcon},
{BMP_8BIT, mask_textline_delay, &mixerSetupDelayIcon},
{BMP_8BIT, mask_textline_delayslow, &mixerSetupDelaySlowIcon},
-1
View File
@@ -51,7 +51,6 @@ extern BitmapBuffer * mixerSetupAddBitmap;
extern BitmapBuffer * mixerSetupMultiBitmap;
extern BitmapBuffer * mixerSetupReplaceBitmap;
extern BitmapBuffer * mixerSetupLabelIcon;
extern BitmapBuffer * mixerSetupFlightmodeIcon;
extern BitmapBuffer * mixerSetupCurveIcon;
extern BitmapBuffer * mixerSetupSwitchIcon;
extern BitmapBuffer * mixerSetupDelayIcon;
+2 -2
View File
@@ -259,12 +259,12 @@ void drawCurveRef(BitmapBuffer * dc, coord_t x, coord_t y, const CurveRef & curv
switch (curve.type) {
case CURVE_REF_DIFF:
x = dc->drawText(x, y, "D", flags);
drawValueOrGVar(dc, x, y + 2, curve.value, -100, 100, LEFT | FONT(XS) | flags);
drawValueOrGVar(dc, x, y, curve.value, -100, 100, LEFT | flags);
break;
case CURVE_REF_EXPO:
x = dc->drawText(x, y, "E", flags);
drawValueOrGVar(dc, x, y + 2, curve.value, -100, 100, LEFT | FONT(XS) | flags);
drawValueOrGVar(dc, x, y, curve.value, -100, 100, LEFT | flags);
break;
case CURVE_REF_FUNC:
+117 -46
View File
@@ -22,72 +22,143 @@
#include "input_mix_button.h"
#include "opentx.h"
#include "lvgl_widgets/input_mix_line.h"
// icon: 17 x 17
static const uint8_t _mask_textline_fm[] = {
#include "mask_textline_fm.lbm"
};
STATIC_LZ4_BITMAP(mask_textline_fm);
void InputMixButton::self_size(lv_event_t* e)
{
auto obj = lv_event_get_target(e);
auto p = (lv_point_t*)lv_event_get_param(e);
// total: 92 x 17
#define FM_CANVAS_HEIGHT 17
#define FM_CANVAS_WIDTH 92
auto btn = (InputMixButton*)lv_obj_get_user_data(obj);
if (!btn) return;
#if LCD_W > LCD_H // Landscape
static const lv_coord_t col_dsc[] = {
LV_GRID_FR(1), // weigth
LV_GRID_FR(1), // source
LV_GRID_FR(4), // opts
FM_CANVAS_WIDTH, // flight modes
LV_GRID_TEMPLATE_LAST
};
p->y = btn->calcHeight();
}
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT,
LV_GRID_TEMPLATE_LAST};
#else // Portrait
static const lv_coord_t col_dsc[] = {
LV_GRID_FR(1), // weigth
LV_GRID_FR(1), // source
LV_GRID_FR(2), // opts
LV_GRID_TEMPLATE_LAST
};
void InputMixButton::value_changed(lv_event_t* e)
{
auto obj = lv_event_get_target(e);
lv_obj_refresh_self_size(obj);
}
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT,
LV_GRID_CONTENT,
LV_GRID_TEMPLATE_LAST};
#endif
static const char _empty_txt[] = "";
InputMixButton::InputMixButton(Window* parent, uint8_t index) :
Button(parent, rect_t{}, nullptr, 0, 0/*COLOR_THEME_PRIMARY1*/,
input_mix_line_create),
index(index)
ListLineButton(parent, index)
{
lv_obj_add_event_cb(lvobj, InputMixButton::self_size, LV_EVENT_GET_SELF_SIZE, nullptr);
lv_obj_add_event_cb(lvobj, InputMixButton::value_changed, LV_EVENT_VALUE_CHANGED, nullptr);
lv_obj_set_layout(lvobj, LV_LAYOUT_GRID);
lv_obj_set_grid_dsc_array(lvobj, col_dsc, row_dsc);
weight = lv_label_create(lvobj);
lv_obj_set_grid_cell(weight, LV_GRID_ALIGN_END, 0, 1, LV_GRID_ALIGN_START, 0, 1);
source = lv_label_create(lvobj);
lv_obj_set_grid_cell(source, LV_GRID_ALIGN_START, 1, 1, LV_GRID_ALIGN_START, 0, 1);
opts = lv_label_create(lvobj);
#if LCD_W > LCD_H // Landscape
lv_obj_set_grid_cell(opts, LV_GRID_ALIGN_START, 2, 1, LV_GRID_ALIGN_START, 0, 1);
#else
lv_obj_set_grid_cell(opts, LV_GRID_ALIGN_STRETCH, 2, 1, LV_GRID_ALIGN_START, 0, 2);
#endif
}
void InputMixButton::checkEvents()
InputMixButton::~InputMixButton()
{
check(isActive());
Button::checkEvents();
if (fm_buffer) free(fm_buffer);
}
void InputMixButton::drawFlightModes(BitmapBuffer *dc, FlightModesType value,
LcdFlags textColor, coord_t x, coord_t y)
void InputMixButton::setWeight(gvar_t value, gvar_t min, gvar_t max)
{
dc->drawMask(x, y + 2, mixerSetupFlightmodeIcon, textColor);
char s[32];
getValueOrGVarString(s, sizeof(s), value, min, max, 0, "%");
lv_label_set_text(weight, s);
}
void InputMixButton::setSource(mixsrc_t idx)
{
char* s = getSourceString(idx);
lv_label_set_text(source, s);
}
void InputMixButton::setFlightModes(uint16_t modes)
{
if (modes == fm_modes) return;
fm_modes = modes;
if (!fm_modes) {
if (!fm_canvas) return;
lv_obj_del(fm_canvas);
free(fm_buffer);
fm_canvas = nullptr;
fm_buffer = nullptr;
return;
}
if (!fm_canvas) {
fm_canvas = lv_canvas_create(lvobj);
fm_buffer = malloc(FM_CANVAS_WIDTH * FM_CANVAS_HEIGHT);
lv_canvas_set_buffer(fm_canvas, fm_buffer, FM_CANVAS_WIDTH,
FM_CANVAS_HEIGHT, LV_IMG_CF_ALPHA_8BIT);
#if LCD_W > LCD_H // Landscape
lv_obj_set_grid_cell(fm_canvas, LV_GRID_ALIGN_START, 3, 1,
LV_GRID_ALIGN_CENTER, 0, 1);
#else
lv_obj_set_grid_cell(fm_canvas, LV_GRID_ALIGN_CENTER, 0, 2,
LV_GRID_ALIGN_CENTER, 1, 1);
#endif
lv_obj_set_style_img_recolor(fm_canvas, makeLvColor(COLOR_THEME_SECONDARY1), 0);
lv_obj_set_style_img_recolor_opa(fm_canvas, LV_OPA_COVER, 0);
}
lv_canvas_fill_bg(fm_canvas, lv_color_black(), LV_OPA_TRANSP);
auto mask = (const uint8_t*)mask_textline_fm;
auto mask_hdr = (const uint16_t*)mask;
lv_coord_t w = mask_hdr[0];
lv_coord_t h = mask_hdr[1];
coord_t x = 0;
lv_canvas_copy_buf(fm_canvas, mask + 4, x, 0, w, h);
x += 20;
lv_draw_label_dsc_t label_dsc;
lv_draw_label_dsc_init(&label_dsc);
lv_draw_rect_dsc_t rect_dsc;
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.bg_opa = LV_OPA_COVER;
const lv_font_t* font = getFont(FONT(XS));
label_dsc.font = font;
for (int i = 0; i < MAX_FLIGHT_MODES; i++) {
char s[] = " ";
s[0] = '0' + i;
if (value & (1 << i)) {
dc->drawText(x, y + 2, s, FONT(XS) | COLOR_THEME_DISABLED);
if (fm_modes & (1 << i)) {
label_dsc.color = lv_color_make(0x7f, 0x7f, 0x7f);
} else {
dc->drawSolidFilledRect(x, y + 2, 8, 3, COLOR_THEME_FOCUS);
dc->drawText(x, y + 2, s, FONT(XS) | textColor);
lv_canvas_draw_rect(fm_canvas, x, 0, 8, 3, &rect_dsc);
label_dsc.color = lv_color_white();
}
lv_canvas_draw_text(fm_canvas, x, 0, 8, &label_dsc, s);
x += 8;
}
}
lv_coord_t InputMixButton::calcHeight() const
{
auto h = getFontHeight(FONT(STD));
// h += 2 * lv_obj_get_style_border_width(lvobj, LV_PART_MAIN);
// h += lv_obj_get_style_pad_top(lvobj, LV_PART_MAIN);
// h += lv_obj_get_style_pad_bottom(lvobj, LV_PART_MAIN);
size_t lines = getLines();
if (lines > 1) {
lines -= 1;
h += lines * lv_obj_get_style_text_line_space(lvobj, LV_PART_MAIN);
h += lines * getFontHeight(FONT(STD));
}
return h;
}
+14 -17
View File
@@ -21,28 +21,25 @@
#pragma once
#include "button.h"
#include "list_line_button.h"
#include "opentx_types.h"
class InputMixButton : public Button
class InputMixButton : public ListLineButton
{
lv_obj_t* fm_canvas = nullptr;
void* fm_buffer = nullptr;
uint16_t fm_modes = 0;
public:
InputMixButton(Window* parent, uint8_t index);
uint8_t getIndex() const { return index; }
void setIndex(uint8_t i) { index = i; }
void checkEvents() override;
void drawFlightModes(BitmapBuffer *dc, FlightModesType value,
LcdFlags textColor, coord_t x, coord_t y);
~InputMixButton();
protected:
uint8_t index;
lv_obj_t* weight;
lv_obj_t* source;
lv_obj_t* opts;
static void self_size(lv_event_t* e);
static void value_changed(lv_event_t* e);
lv_coord_t calcHeight() const;
virtual size_t getLines() const = 0;
virtual bool isActive() const = 0;
void setWeight(gvar_t value, gvar_t min, gvar_t max);
void setSource(mixsrc_t idx);
void setFlightModes(uint16_t modes);
};
+16 -6
View File
@@ -69,20 +69,30 @@ InputMixGroup::InputMixGroup(Window* parent, mixsrc_t idx) :
lv_obj_set_size(line_container, lv_pct(100), LV_SIZE_CONTENT);
lv_obj_set_flex_flow(line_container, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_flex_cross_place(line_container, LV_FLEX_ALIGN_END, 0);
lv_obj_set_style_pad_row(line_container, lv_dpx(8), LV_PART_MAIN);
lv_obj_set_style_pad_row(line_container, lv_dpx(4), LV_PART_MAIN);
}
void InputMixGroup::addMixerMonitor(uint8_t channel)
void InputMixGroup::enableMixerMonitor(uint8_t channel)
{
rect_t r{ 0, 0, 100, 14 };
auto mon = new MixerChannelBar(this, r, channel);
mon->setDrawMiddleBar(false);
if (monitor != nullptr) return;
lv_obj_t* mon_obj = mon->getLvObj();
rect_t r{ 0, 0, 100, 14 };
monitor = new MixerChannelBar(this, r, channel);
monitor->setDrawMiddleBar(false);
lv_obj_t* mon_obj = monitor->getLvObj();
lv_obj_set_parent(mon_obj, line_container);
lv_obj_move_to_index(mon_obj, 0);
lv_obj_set_style_translate_x(mon_obj, -lv_dpx(8), 0);
}
void InputMixGroup::disableMixerMonitor()
{
if (!monitor) return;
monitor->deleteLater();
monitor = nullptr;
}
void InputMixGroup::addLine(Window* line, const uint8_t* symbol)
{
lines.emplace_back(line, symbol);
+8 -1
View File
@@ -26,6 +26,8 @@
#include <list>
class MixerChannelBar;
class InputMixGroup : public Window
{
struct Line {
@@ -43,12 +45,17 @@ class InputMixGroup : public Window
lv_obj_t* label;
lv_obj_t* line_container;
MixerChannelBar* monitor = nullptr;
static void value_changed(lv_event_t* e);
public:
InputMixGroup(Window* parent, mixsrc_t idx);
void addMixerMonitor(uint8_t channel);
void enableMixerMonitor(uint8_t channel);
void disableMixerMonitor();
bool mixerMonitorEnabled() { return monitor != nullptr; }
mixsrc_t getMixSrc() { return idx; }
size_t getLineCount() { return lines.size(); }
@@ -0,0 +1,45 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "list_line_button.h"
#include "opentx.h"
#include "lvgl_widgets/input_mix_line.h"
void ListLineButton::value_changed(lv_event_t* e)
{
auto obj = lv_event_get_target(e);
auto btn = (ListLineButton*)lv_obj_get_user_data(obj);
if (btn) btn->refresh();
}
ListLineButton::ListLineButton(Window* parent, uint8_t index) :
Button(parent, rect_t{}, nullptr, 0, 0, input_mix_line_create),
index(index)
{
lv_obj_add_event_cb(lvobj, ListLineButton::value_changed, LV_EVENT_VALUE_CHANGED, nullptr);
}
void ListLineButton::checkEvents()
{
check(isActive());
Button::checkEvents();
}
+44
View File
@@ -0,0 +1,44 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#pragma once
#include "button.h"
#include "opentx_types.h"
class ListLineButton : public Button
{
public:
ListLineButton(Window* parent, uint8_t index);
uint8_t getIndex() const { return index; }
void setIndex(uint8_t i) { index = i; }
void checkEvents() override;
protected:
uint8_t index;
static void value_changed(lv_event_t* e);
virtual bool isActive() const = 0;
virtual void refresh() = 0;
};
+27 -56
View File
@@ -131,76 +131,45 @@ class InputLineButton : public InputMixButton
public:
using InputMixButton::InputMixButton;
void paint(BitmapBuffer *dc) override
void refresh() override
{
const ExpoData &line = g_model.expoData[index];
LcdFlags textColor = COLOR_THEME_SECONDARY1;
setWeight(line.weight, -100, 100);
setSource(line.srcRaw);
coord_t border = lv_obj_get_style_border_width(lvobj, LV_PART_MAIN);
coord_t pad_left = lv_obj_get_style_pad_left(lvobj, LV_PART_MAIN);
char tmp_str[64];
size_t maxlen = sizeof(tmp_str);
coord_t left = pad_left + border;
coord_t line_h = lv_obj_get_style_text_line_space(lvobj, LV_PART_MAIN)
+ getFontHeight(FONT(STD));
// first line ...
coord_t y = 0;
y += border;
y += lv_obj_get_style_pad_top(lvobj, LV_PART_MAIN);
coord_t x = left;
drawValueOrGVar(dc, x, y, line.weight, -100, 100, textColor);
drawSource(dc, x + 65, y, line.srcRaw, textColor);
char *s = tmp_str;
*s = '\0';
if (line.name[0]) {
dc->drawMask(146, y, mixerSetupLabelIcon, textColor);
dc->drawSizedText(166, y, line.name, sizeof(line.name), textColor);
int cnt = lv_snprintf(s, maxlen, "%.*s ", (int)sizeof(line.name), line.name);
if (cnt >= maxlen) maxlen = 0;
else { maxlen -= cnt; s += cnt; }
}
// second line ...
y += line_h;
if (line.swtch || line.curve.value) {
if (line.swtch) {
dc->drawMask(x, y, mixerSetupSwitchIcon, textColor);
drawSwitch(dc, x + 21, y, line.swtch, textColor);
}
if (line.curve.value != 0) {
dc->drawMask(x + 65, y, mixerSetupCurveIcon, textColor);
drawCurveRef(dc, x + 85, y, line.curve, textColor);
}
if (line.swtch) {
char* sw_pos = getSwitchPositionName(line.swtch);
int cnt = lv_snprintf(s, maxlen, "%s ", sw_pos);
if (cnt >= maxlen) maxlen = 0;
else { maxlen -= cnt; s += cnt; }
}
if (line.curve.value != 0) {
getCurveRefString(s, maxlen, line.curve);
int cnt = strnlen(s, maxlen);
if (cnt >= maxlen) maxlen = 0;
else { maxlen -= cnt; s += cnt; }
}
}
lv_label_set_text_fmt(opts, "%.*s", (int)sizeof(tmp_str), tmp_str);
if (line.flightModes) {
#if LCD_H > LCD_W
// third line ...
y += line_h;
x = left;
#else
x = 146;
#endif
drawFlightModes(dc, line.flightModes, textColor, x, y);
}
setFlightModes(line.flightModes);
}
protected:
bool isActive() const override { return isExpoActive(index); }
size_t getLines() const override {
const ExpoData* line = expoAddress(index);
size_t lines = 1;
#if LCD_W > LCD_H
if (line->swtch || line->curve.value != 0 || line->flightModes) {
lines += 1;
}
#else
if (line->swtch || line->curve.value != 0) {
lines += 1;
}
if (line->flightModes) {
lines += 1;
}
#endif
return lines;
}
};
ModelInputsPage::ModelInputsPage():
@@ -273,6 +242,8 @@ InputMixButton* ModelInputsPage::createLineButton(InputMixGroup *group,
uint8_t index)
{
auto button = new InputLineButton(group, index);
button->refresh();
lines.emplace_back(button);
group->addLine(button);
@@ -467,7 +438,7 @@ void ModelInputsPage::build(FormWindow *window)
form = new FormGroup(window, rect_t{});
form->setFlexLayout();
form->padRow(lv_dpx(8));
form->padRow(lv_dpx(4));
auto form_obj = form->getLvObj();
lv_obj_set_width(form_obj, lv_pct(100));
+88 -83
View File
@@ -174,18 +174,22 @@ class MixLineButton : public InputMixButton
{
public:
MixLineButton(Window* parent, uint8_t index);
void paint(BitmapBuffer* dc) override;
void deleteLater(bool detach = true, bool trash = true) override;
void refresh() override;
protected:
size_t getLines() const override;
bool isActive() const override { return isMixActive(index); }
};
static void mix_draw_mplex(lv_event_t* e)
{
auto target = (lv_obj_t*)lv_event_get_target(e);
auto group = (InputMixGroup*)lv_obj_get_user_data(target);
uint32_t offset = group->mixerMonitorEnabled() ? 1 : 0;
auto obj = (lv_obj_t*)lv_event_get_user_data(e);
if (!obj || (lv_obj_get_index(obj) <= 1)) return;
if (!obj || (lv_obj_get_index(obj) <= offset)) return;
auto btn = (MixLineButton*)lv_obj_get_user_data(obj);
if (!btn) return;
@@ -249,94 +253,56 @@ void MixLineButton::deleteLater(bool detach, bool trash)
InputMixButton::deleteLater(detach, trash);
}
size_t MixLineButton::getLines() const
{
size_t lines = 1;
const MixData* mix = mixAddress(index);
uint8_t delayslow = 0;
if (mix->speedDown || mix->speedUp) delayslow = 1;
if (mix->delayUp || mix->delayDown) delayslow += 2;
if (mix->flightModes || mix->name[0] || delayslow) {
lines += 1;
}
return lines;
}
void MixLineButton::paint(BitmapBuffer* dc)
void MixLineButton::refresh()
{
const MixData& line = g_model.mixData[index];
LcdFlags textColor = COLOR_THEME_SECONDARY1;
setWeight(line.weight, MIX_WEIGHT_MIN, MIX_WEIGHT_MAX);
setSource(line.srcRaw);
coord_t border = lv_obj_get_style_border_width(lvobj, LV_PART_MAIN);
coord_t pad_left = lv_obj_get_style_pad_left(lvobj, LV_PART_MAIN);
coord_t pad_right = lv_obj_get_style_pad_right(lvobj, LV_PART_MAIN);
char tmp_str[64];
size_t maxlen = sizeof(tmp_str);
coord_t left = pad_left + border;
coord_t line_h = lv_obj_get_style_text_line_space(lvobj, LV_PART_MAIN)
+ getFontHeight(FONT(STD));
char *s = tmp_str;
*s = '\0';
#if LCD_W > LCD_H
const coord_t pad = 42;
#else
const coord_t pad = 0;
#endif
// first line ...
coord_t y = 0;
y += border;
y += lv_obj_get_style_pad_top(lvobj, LV_PART_MAIN);
if (line.name[0]) {
int cnt = lv_snprintf(s, maxlen, "%.*s ", (int)sizeof(line.name), line.name);
if (cnt >= maxlen) maxlen = 0;
else { maxlen -= cnt; s += cnt; }
}
coord_t x = left;
drawValueOrGVar(dc, x, y, line.weight, MIX_WEIGHT_MIN, MIX_WEIGHT_MAX, textColor);
x += 46 + pad;
drawSource(dc, x, y, line.srcRaw, textColor);
x += 60 + pad;
// second line ...
if (line.swtch || line.curve.value) {
if (line.swtch) {
if (pad) dc->drawMask(x - 20, y, mixerSetupSwitchIcon, textColor);
drawSwitch(dc, x, y, line.swtch, textColor);
char* sw_pos = getSwitchPositionName(line.swtch);
int cnt = lv_snprintf(s, maxlen, "%s ", sw_pos);
if (cnt >= maxlen) maxlen = 0;
else { maxlen -= cnt; s += cnt; }
}
x += 44 + pad;
if (line.curve.value) {
if (pad) dc->drawMask(x - 20, y, mixerSetupCurveIcon, textColor);
drawCurveRef(dc, x, y, line.curve, textColor);
}
// x += 48 + pad;
}
uint8_t delayslow = 0;
if (line.speedDown || line.speedUp) delayslow = 1;
if (line.delayUp || line.delayDown) delayslow += 2;
if (line.flightModes || line.name[0] || delayslow) {
y += line_h;
x = left;
if (line.flightModes) {
drawFlightModes(dc, line.flightModes, textColor, x, y);
}
x += 104 + 3*pad/2;
if (line.name[0]) {
dc->drawMask(x, y, mixerSetupLabelIcon, textColor);
dc->drawSizedText(x + 20, y, line.name, sizeof(line.name), textColor);
}
if (delayslow) {
BitmapBuffer* delayslowbmp[] = {mixerSetupSlowIcon, mixerSetupDelayIcon,
mixerSetupDelaySlowIcon};
const BitmapBuffer* mask = delayslowbmp[delayslow - 1];
coord_t w = lv_obj_get_width(lvobj);
w -= mask->width();
dc->drawMask(w - border - pad_right, y, mask, textColor);
if (line.curve.value != 0) {
getCurveRefString(s, maxlen, line.curve);
int cnt = strnlen(s, maxlen);
if (cnt >= maxlen) maxlen = 0;
else { maxlen -= cnt; s += cnt; }
}
}
lv_label_set_text_fmt(opts, "%.*s", (int)sizeof(tmp_str), tmp_str);
setFlightModes(line.flightModes);
// TODO: should we add these? then it would be best to have them in the font...
//
// uint8_t delayslow = 0;
// if (line.speedDown || line.speedUp) delayslow = 1;
// if (line.delayUp || line.delayDown) delayslow += 2;
// if (delayslow) {
// BitmapBuffer* delayslowbmp[] = {mixerSetupSlowIcon, mixerSetupDelayIcon,
// mixerSetupDelaySlowIcon};
// const BitmapBuffer* mask = delayslowbmp[delayslow - 1];
// coord_t w = lv_obj_get_width(lvobj);
// w -= mask->width();
// dc->drawMask(w - border - pad_right, y, mask, textColor);
// }
}
ModelMixesPage::ModelMixesPage() :
@@ -358,13 +324,16 @@ InputMixGroup* ModelMixesPage::getGroupByIndex(uint8_t index)
InputMixGroup* ModelMixesPage::createGroup(FormGroup* form, mixsrc_t src)
{
auto group = new InputMixGroup(form, src);
group->addMixerMonitor(src - MIXSRC_CH1);
// TODO: make optional
// group->addMixerMonitor(src - MIXSRC_CH1);
return group;
}
InputMixButton* ModelMixesPage::createLineButton(InputMixGroup *group, uint8_t index)
{
auto button = new MixLineButton(group, index);
button->refresh();
lines.emplace_back(button);
group->addLine(button);
@@ -537,22 +506,37 @@ void ModelMixesPage::pasteMixAfter(uint8_t dst_idx)
void ModelMixesPage::build(FormWindow * window)
{
scroll_win = window->getParent();
window->setFlexLayout();
window->padRow(lv_dpx(8));
form = new FormGroup(window, rect_t{});
form->setFlexLayout();
form->padRow(lv_dpx(8));
form->padRow(lv_dpx(4));
auto form_obj = form->getLvObj();
lv_obj_set_width(form_obj, lv_pct(100));
auto box = new FormGroup(window, rect_t{});
box->setFlexLayout(LV_FLEX_FLOW_ROW, lv_dpx(8));
box->padLeft(lv_dpx(8));
auto box_obj = box->getLvObj();
lv_obj_set_width(box_obj, lv_pct(100));
lv_obj_set_style_flex_cross_place(box_obj, LV_FLEX_ALIGN_CENTER, 0);
new StaticText(box, rect_t{}, "Show mixer monitors", 0, COLOR_THEME_PRIMARY1);
new CheckBox(
box, rect_t{}, [=]() { return showMonitors; },
[=](uint8_t val) { enableMonitors(val); });
auto btn = new TextButton(window, rect_t{}, LV_SYMBOL_PLUS, [=]() {
newMix();
return 0;
});
auto btn_obj = btn->getLvObj();
lv_obj_set_width(btn_obj, lv_pct(100));
lv_group_focus_obj(btn_obj);
groups.clear();
lines.clear();
@@ -580,3 +564,24 @@ void ModelMixesPage::build(FormWindow * window)
}
}
void ModelMixesPage::enableMonitors(bool enabled)
{
if (showMonitors == enabled) return;
showMonitors = enabled;
auto form_obj = form->getLvObj();
auto h = lv_obj_get_height(form_obj);
for(auto* group : groups) {
if (enabled) {
group->enableMixerMonitor(group->getMixSrc() - MIXSRC_CH1);
} else {
group->disableMixerMonitor();
}
}
lv_obj_update_layout(form_obj);
auto diff = h - lv_obj_get_height(form_obj);
auto scroll_obj = scroll_win->getLvObj();
lv_obj_scroll_by_bounded(scroll_obj, 0, diff, LV_ANIM_OFF);
TRACE("diff = %d", diff);
}
+5
View File
@@ -26,6 +26,9 @@
class ModelMixesPage : public ModelInputsPage
{
bool showMonitors = false;
Window* scroll_win = nullptr;
public:
ModelMixesPage();
@@ -48,4 +51,6 @@ class ModelMixesPage : public ModelInputsPage
void pasteMix(uint8_t dst_idx, uint8_t channel);
void pasteMixBefore(uint8_t dst_idx);
void pasteMixAfter(uint8_t dst_idx);
void enableMonitors(bool enabled);
};
+199 -77
View File
@@ -21,98 +21,179 @@
#include "model_outputs.h"
#include "output_edit.h"
#include "input_mix_button.h"
#include "list_line_button.h"
#include "channel_bar.h"
#include "opentx.h"
#define SET_DIRTY() storageDirty(EE_MODEL)
#if LCD_W > LCD_H
static const coord_t btn_cols[] = {
90, 158, 208, 262, 306, 326
static const uint8_t _mask_textline_curve[] = {
#include "mask_textline_curve.lbm"
};
#else
static const coord_t btn_cols[] = {
264, 66, 123, 180, 232, 264
STATIC_LZ4_BITMAP(mask_textline_curve);
#define CH_BAR_WIDTH 92
#define CH_BAR_HEIGHT 14
#if LCD_W > LCD_H // Landscape
#define CH_BAR_COL 7
#define CH_BAR_COLSPAN 1
static const lv_coord_t col_dsc[] = {
80, 50, 54, 44, 60, 18, 18, LV_GRID_FR(1),
LV_GRID_TEMPLATE_LAST
};
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT,
LV_GRID_TEMPLATE_LAST};
#else // Portrait
#define CH_BAR_COL 3
#define CH_BAR_COLSPAN 3
static const lv_coord_t col_dsc[] = {
80, 50, 60, 18, 18, LV_GRID_FR(1),
LV_GRID_TEMPLATE_LAST
};
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT,
LV_GRID_CONTENT,
LV_GRID_TEMPLATE_LAST};
#endif
class OutputLineButton : public InputMixButton
class OutputLineButton : public ListLineButton
{
bool init = false;
lv_obj_t* source = nullptr;
lv_obj_t* revert = nullptr;
lv_obj_t* min = nullptr;
lv_obj_t* max = nullptr;
lv_obj_t* offset = nullptr;
lv_obj_t* center = nullptr;
lv_obj_t* curve = nullptr;
OutputChannelBar* bar = nullptr;
static lv_img_dsc_t curveIcon;
static void loadCurveIcon();
static void on_draw(lv_event_t * e)
{
lv_obj_t* target = lv_event_get_target(e);
auto line = (OutputLineButton*)lv_obj_get_user_data(target);
if (line && !line->init) line->delayed_init(e);
}
void delayed_init(lv_event_t* e)
{
uint8_t col = 1, row = 0;
min = lv_label_create(lvobj);
lv_obj_set_style_text_align(min, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_set_style_text_font(min, getFont(FONT(BOLD)), LV_STATE_USER_1);
lv_obj_set_grid_cell(min, LV_GRID_ALIGN_END, col++, 1, LV_GRID_ALIGN_START,
row, 1);
max = lv_label_create(lvobj);
lv_obj_set_style_text_align(max, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_set_style_text_font(max, getFont(FONT(BOLD)), LV_STATE_USER_1);
lv_obj_set_grid_cell(max, LV_GRID_ALIGN_END, col++, 1, LV_GRID_ALIGN_START,
row, 1);
#if LCD_H > LCD_W
col = 1;
row++;
#endif
offset = lv_label_create(lvobj);
lv_obj_set_grid_cell(offset, LV_GRID_ALIGN_END, col++, 1,
LV_GRID_ALIGN_START, row, 1);
center = lv_label_create(lvobj);
lv_obj_set_style_pad_left(center, 8, 0);
lv_obj_set_grid_cell(center, LV_GRID_ALIGN_START, col++, 1,
LV_GRID_ALIGN_START, row, 1);
revert = lv_img_create(lvobj);
lv_img_set_src(revert, LV_SYMBOL_SHUFFLE);
lv_obj_set_grid_cell(revert, LV_GRID_ALIGN_START, col++, 1,
LV_GRID_ALIGN_START, row, 1);
curve = lv_img_create(lvobj);
loadCurveIcon();
lv_img_set_src(curve, &curveIcon);
lv_obj_set_style_img_recolor(curve, makeLvColor(COLOR_THEME_SECONDARY1), 0);
lv_obj_set_style_img_recolor_opa(curve, LV_OPA_COVER, 0);
lv_obj_set_grid_cell(curve, LV_GRID_ALIGN_START, col++, 1,
LV_GRID_ALIGN_START, row, 1);
bar = new OutputChannelBar(this, rect_t{}, index);
bar->setWidth(CH_BAR_WIDTH);
bar->setHeight(CH_BAR_HEIGHT);
bar->setDrawLimits(false);
lv_obj_set_grid_cell(bar->getLvObj(), LV_GRID_ALIGN_END, CH_BAR_COL,
CH_BAR_COLSPAN, LV_GRID_ALIGN_CENTER, 0, 1);
init = true;
refresh();
lv_obj_update_layout(lvobj);
auto param = lv_event_get_param(e);
lv_event_send(lvobj, LV_EVENT_DRAW_MAIN, param);
}
public:
OutputLineButton(Window* parent, uint8_t channel) :
InputMixButton(parent, channel)
ListLineButton(parent, channel)
{
#if LCD_H > LCD_W
auto font_h = getFontHeight(FONT(STD));
rect_t r{ 150, (font_h - 14) / 2, 100, 14 };
#else
lv_obj_update_layout(lvobj);
auto font_h = getFontHeight(FONT(STD));
auto x = lv_obj_get_content_width(lvobj) - 92;
auto y = (font_h - 14) / 2;
lv_obj_set_layout(lvobj, LV_LAYOUT_GRID);
lv_obj_set_grid_dsc_array(lvobj, col_dsc, row_dsc);
rect_t r{ x, y, 92, 14 };
#endif
auto bar = new OutputChannelBar(this, r, channel);
bar->setDrawLimits(false);
source = lv_label_create(lvobj);
lv_obj_set_style_text_font(source, getFont(FONT(BOLD)), 0);
lv_obj_set_grid_cell(source, LV_GRID_ALIGN_START, 0, 1,
LV_GRID_ALIGN_START, 0, 1);
lv_obj_add_event_cb(lvobj, OutputLineButton::on_draw, LV_EVENT_DRAW_MAIN, nullptr);
}
void paint(BitmapBuffer *dc) override
void refresh() override
{
LcdFlags textColor = COLOR_THEME_SECONDARY1;
if (!init) return;
const LimitData* output = limitAddress(index);
int chanZero = output->ppmCenter;
coord_t border = lv_obj_get_style_border_width(lvobj, LV_PART_MAIN);
coord_t pad_left = lv_obj_get_style_pad_left(lvobj, LV_PART_MAIN);
coord_t left = pad_left + border;
// first line ...
coord_t x = left;
coord_t y = border + lv_obj_get_style_pad_top(lvobj, LV_PART_MAIN);
x = left;
dc->drawText(x, y, getSourceString(MIXSRC_CH1 + index), FONT(BOLD) | textColor);
lv_label_set_text(source, getSourceString(MIXSRC_CH1 + index));
if (output->revert) {
x = left + btn_cols[0];
dc->drawText(x, y, LV_SYMBOL_SHUFFLE, textColor);
lv_obj_clear_flag(revert, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(revert, LV_OBJ_FLAG_HIDDEN);
}
#if LCD_H > LCD_W
y += lv_obj_get_style_text_line_space(lvobj, LV_PART_MAIN)
+ getFontHeight(FONT(STD));
#endif
// Min
LcdFlags txtFlags = (value < chanZero - 5) ? FONT(BOLD) | textColor : textColor;
x = left + btn_cols[1];
drawValueOrGVar(dc, x, y, output->min, -GV_RANGELARGE, 0, RIGHT | PREC1 | txtFlags,
nullptr, -LIMITS_MIN_MAX_OFFSET);
char s[32];
getValueOrGVarString(s, sizeof(s), output->min, -GV_RANGELARGE, 0, PREC1,
nullptr, -LIMITS_MIN_MAX_OFFSET);
lv_label_set_text(min, s);
// Max
txtFlags = (value > chanZero + 5) ? FONT(BOLD) | textColor : textColor;
x = left + btn_cols[2];
drawValueOrGVar(dc, x, y, output->max, 0, GV_RANGELARGE,
RIGHT | PREC1 | txtFlags, nullptr, +LIMITS_MIN_MAX_OFFSET);
getValueOrGVarString(s, sizeof(s), output->max, 0, GV_RANGELARGE, PREC1,
nullptr, +LIMITS_MIN_MAX_OFFSET);
lv_label_set_text(max, s);
// Offset
x = left + btn_cols[3];
drawValueOrGVar(dc, x, y, output->offset, -LIMIT_STD_MAX,
+LIMIT_STD_MAX, RIGHT | PREC1 | textColor, nullptr);
getValueOrGVarString(s, sizeof(s), output->offset, -LIMIT_STD_MAX,
+LIMIT_STD_MAX, PREC1);
lv_label_set_text(offset, s);
// PPM center
x = left + btn_cols[4];
dc->drawNumber(x, y, PPM_CENTER + output->ppmCenter, RIGHT | textColor);
dc->drawText(x, y, output->symetrical ? "=" : STR_CHAR_DELTA, textColor);
lv_label_set_text_fmt(center, "%d%s", PPM_CENTER + output->ppmCenter,
output->symetrical ? " =" : STR_CHAR_DELTA);
if (output->curve) {
x = left + btn_cols[5];
// dc->drawText(x, y, getCurveString(output->curve), RIGHT | textColor);
dc->drawMask(x, y, mixerSetupCurveIcon, textColor);
lv_obj_clear_flag(curve, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(curve, LV_OBJ_FLAG_HIDDEN);
}
}
@@ -120,26 +201,63 @@ class OutputLineButton : public InputMixButton
int value = 0;
bool isActive() const override { return false; }
size_t getLines() const override
{
#if LCD_H > LCD_W
return 2;
#else
return 1;
#endif
}
void checkEvents() override
{
Window::checkEvents();
if (!init) return;
int newValue = channelOutputs[index];
if (value != newValue) {
value = newValue;
invalidate();
const LimitData* output = limitAddress(index);
int chanZero = output->ppmCenter;
if (value < chanZero - 5) {
lv_obj_add_state(min, LV_STATE_USER_1);
} else {
lv_obj_clear_state(min, LV_STATE_USER_1);
}
if (value > chanZero + 5) {
lv_obj_add_state(max, LV_STATE_USER_1);
} else {
lv_obj_clear_state(max, LV_STATE_USER_1);
}
}
}
};
lv_img_dsc_t OutputLineButton::curveIcon = {
.header =
{
.cf = LV_IMG_CF_ALPHA_8BIT,
.always_zero = 0,
.reserved = 0,
.w = 0,
.h = 0,
},
.data_size = 0,
.data = nullptr,
};
void OutputLineButton::loadCurveIcon()
{
if (curveIcon.data) return;
auto mask = (const uint8_t*)mask_textline_curve;
auto mask_hdr = (const uint16_t*)mask;
mask += 4;
auto w = mask_hdr[0];
auto h = mask_hdr[1];
curveIcon.header.w = w;
curveIcon.header.h = h;
curveIcon.data_size = w * h;
curveIcon.data = mask;
}
ModelOutputsPage::ModelOutputsPage() :
PageTab(STR_MENULIMITS, ICON_MODEL_OUTPUTS)
{
@@ -148,16 +266,16 @@ ModelOutputsPage::ModelOutputsPage() :
void ModelOutputsPage::build(FormWindow *window)
{
window->setFlexLayout();
window->padRow(lv_dpx(8));
window->padRow(lv_dpx(4));
auto form = new FormGroup(window, rect_t{});
form->setFlexLayout(LV_FLEX_FLOW_ROW_WRAP, lv_dpx(16));
form->padRow(lv_dpx(8));
form->padBottom(lv_dpx(4));
auto form_obj = form->getLvObj();
lv_obj_set_style_flex_cross_place(form_obj, LV_FLEX_ALIGN_CENTER, 0);
// auto btn =
new TextButton(form, rect_t{}, STR_ADD_ALL_TRIMS_TO_SUBTRIMS, [=]() {
moveTrimsToOffsets();
window->invalidate();
@@ -178,12 +296,13 @@ void ModelOutputsPage::build(FormWindow *window)
// Channel settings
auto btn = new OutputLineButton(window, ch);
// btn->refresh();
LimitData* output = limitAddress(ch);
btn->setPressHandler([=]() -> uint8_t {
Menu *menu = new Menu(window);
menu->addLine(STR_EDIT, [=]() {
editOutput(ch);
editOutput(ch, btn);
});
menu->addLine(STR_RESET, [=]() {
output->min = 0;
@@ -211,7 +330,10 @@ void ModelOutputsPage::build(FormWindow *window)
}
}
void ModelOutputsPage::editOutput(uint8_t channel)
void ModelOutputsPage::editOutput(uint8_t channel, OutputLineButton* btn)
{
new OutputEditWindow(channel);
auto btn_obj = btn->getLvObj();
auto edit = new OutputEditWindow(channel);
edit->setCloseHandler(
[=]() { lv_event_send(btn_obj, LV_EVENT_VALUE_CHANGED, nullptr); });
}
+3 -1
View File
@@ -24,6 +24,8 @@
#include "tabsgroup.h"
class OutputLineButton;
class ModelOutputsPage : public PageTab
{
public:
@@ -31,7 +33,7 @@ class ModelOutputsPage : public PageTab
void build(FormWindow* window) override;
protected:
void editOutput(uint8_t channel);
void editOutput(uint8_t channel, OutputLineButton* btn);
};
#endif // _MODEL_OUTPUTS_H_
@@ -839,15 +839,17 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
}
else if (lv_obj_check_type(obj, &input_mix_line_class)) {
lv_obj_add_style(obj, &styles.line_btn, 0);
lv_obj_add_style(obj, &styles.pad_small, 0);
lv_obj_add_style(obj, &styles.pad_tiny, 0);
lv_obj_set_style_bg_color(obj, makeLvColor(COLOR_THEME_SECONDARY3),
LV_STATE_CHECKED);
lv_obj_add_style(obj, &styles.focus_border, LV_STATE_FOCUSED);
lv_obj_set_style_text_color(obj, makeLvColor(COLOR_THEME_SECONDARY1), 0);
lv_obj_set_style_text_font(obj, getFont(FONT(STD)), 0);
}
#endif
else if (lv_obj_check_type(obj, &input_mix_group_class)) {
lv_obj_add_style(obj, &styles.line_btn, 0);
lv_obj_add_style(obj, &styles.pad_small, 0);
lv_obj_add_style(obj, &styles.pad_tiny, 0);
lv_obj_set_style_text_color(obj, makeLvColor(COLOR_THEME_SECONDARY1), 0);
lv_obj_set_style_text_font(obj, getFont(FONT(BOLD)), 0);
}
+4 -4
View File
@@ -250,14 +250,14 @@ class FlyskyTheme: public OpenTxTheme
delete mixerSetupLabelIcon;
mixerSetupLabelIcon = BitmapBuffer::loadMask(getFilePath("mask_textline_label.png"));
delete mixerSetupCurveIcon;
mixerSetupCurveIcon = BitmapBuffer::loadMask(getFilePath("mask_textline_curve.png"));
// delete mixerSetupCurveIcon;
// mixerSetupCurveIcon = BitmapBuffer::loadMask(getFilePath("mask_textline_curve.png"));
delete mixerSetupSwitchIcon;
mixerSetupSwitchIcon = BitmapBuffer::loadMask(getFilePath("mask_textline_switch.png"));
delete mixerSetupFlightmodeIcon;
mixerSetupFlightmodeIcon = BitmapBuffer::loadMask(getFilePath("mask_textline_fm.png"));
// delete mixerSetupFlightmodeIcon;
// mixerSetupFlightmodeIcon = BitmapBuffer::loadMask(getFilePath("mask_textline_fm.png"));
// delete mixerSetupSlowIcon;
// mixerSetupSlowIcon = BitmapBuffer::loadMask(getFilePath("mask_textline_slow.png"));
+15
View File
@@ -391,6 +391,21 @@ char *getGVarString(char *dest, int idx)
return dest;
}
#if defined(LIBOPENUI)
char *getValueOrGVarString(char *dest, size_t len, gvar_t value, gvar_t vmin, gvar_t vmax,
LcdFlags flags, const char* suffix, gvar_t offset)
{
if (GV_IS_GV_VALUE(value, vmin, vmax)) {
int index = GV_INDEX_CALC_DELTA(value, GV_GET_GV1_VALUE(vmin, vmax));
return getGVarString(dest, index);
}
value += offset;
BitmapBuffer::formatNumberAsString(dest, len, value, flags, 0, nullptr, suffix);
return dest;
}
#endif
char *getFlightModeString(char *dest, int8_t idx)
{
char *s = dest;
+5 -2
View File
@@ -76,8 +76,11 @@ char *getFormattedTimerString(char *dest, int32_t tme, TimerOptions timerOptions
char *getCurveString(char *dest, int idx);
char *getGVarString(char *dest, int idx);
char *getGVarString(int idx);
const char* getSwitchWarnSymbol(uint8_t pos);
const char* getSwitchPositionSymbol(uint8_t pos);
char *getValueOrGVarString(char *dest, size_t len, gvar_t value, gvar_t vmin,
gvar_t vmax, LcdFlags flags = 0,
const char *suffix = nullptr, gvar_t offset = 0);
const char *getSwitchWarnSymbol(uint8_t pos);
const char *getSwitchPositionSymbol(uint8_t pos);
char *getSwitchPositionName(char *dest, swsrc_t idx);
char *getSwitchName(char *dest, swsrc_t idx);