radio hardware: flex layout WiP

This commit is contained in:
Raphael Coeffic
2022-06-14 22:57:59 +02:00
committed by Raphael Coeffic
parent a8a9477343
commit 51ca20535b
10 changed files with 668 additions and 458 deletions
+3
View File
@@ -74,6 +74,9 @@ set(GUI_SRC
radio_tools.cpp
radio_trainer.cpp
radio_version.cpp
hw_intmodule.cpp
hw_inputs.cpp
hw_serial.cpp
radio_hardware.cpp
radio_diagkeys.cpp
radio_diaganas.cpp
+225
View File
@@ -0,0 +1,225 @@
/*
* 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 "hw_inputs.h"
#include "opentx.h"
#define SET_DIRTY() storageDirty(EE_GENERAL)
struct HWInputEdit : public RadioTextEdit {
HWInputEdit(Window* parent, char* name, size_t len) :
RadioTextEdit(parent, rect_t{}, name, len)
{
setWidth(LV_DPI_DEF / 2);
}
};
static const lv_coord_t col_two_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2),
LV_GRID_TEMPLATE_LAST};
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST};
HWSticks::HWSticks(Window* parent) : FormGroup(parent, rect_t{})
{
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
setFlexLayout();
for (int i = 0; i < NUM_STICKS; i++) {
auto line = newLine(&grid);
new StaticText(line, rect_t{}, STR_VSRCRAW[i + 1], 0, COLOR_THEME_PRIMARY1);
new HWInputEdit(line, g_eeGeneral.anaNames[i], LEN_ANA_NAME);
}
#if defined(STICK_DEAD_ZONE)
auto line = newLine(&grid);
new StaticText(line, rect_t{}, STR_DEAD_ZONE, 0, COLOR_THEME_PRIMARY1);
auto dz = new Choice(line, rect_t{}, 0, 7,
GET_SET_DEFAULT(g_eeGeneral.stickDeadZone));
dz->setTextHandler([](uint8_t value) {
return std::to_string(value ? 2 << (value - 1) : 0);
});
#endif
}
HWPots::HWPots(Window* parent) : FormGroup(parent, rect_t{})
{
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
setFlexLayout();
for (int i = 0; i < NUM_POTS; i++) {
// Display EX3 & EX4 (= last two pots) only when FlySky gimbals are present
#if !defined(SIMU) && defined(RADIO_FAMILY_T16)
if (!globalData.flyskygimbals && (i >= (NUM_POTS - 2))) continue;
#endif
auto line = newLine(&grid);
new StaticText(line, rect_t{}, STR_VSRCRAW[i + NUM_STICKS + 1], 0,
COLOR_THEME_PRIMARY1);
auto box = new FormGroup(line, rect_t{});
box->setFlexLayout(LV_FLEX_FLOW_ROW, lv_dpx(4));
auto box_obj = box->getLvObj();
lv_obj_set_style_flex_cross_place(box_obj, LV_FLEX_ALIGN_CENTER, 0);
new HWInputEdit(box, g_eeGeneral.anaNames[i + NUM_STICKS], LEN_ANA_NAME);
new Choice(
box, rect_t{}, STR_POTTYPES, POT_NONE, POT_WITHOUT_DETENT,
[=]() -> int {
return bfGet<uint32_t>(g_eeGeneral.potsConfig, 2 * i, 2);
},
[=](int newValue) {
g_eeGeneral.potsConfig =
bfSet<uint32_t>(g_eeGeneral.potsConfig, newValue, 2 * i, 2);
SET_DIRTY();
});
}
}
HWSliders::HWSliders(Window* parent) : FormGroup(parent, rect_t{})
{
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
setFlexLayout();
#if (NUM_SLIDERS > 0)
for (int i = 0; i < NUM_SLIDERS; i++) {
const int idx = i + NUM_STICKS + NUM_POTS;
auto line = newLine(&grid);
new StaticText(line, rect_t{}, STR_VSRCRAW[idx + 1], 0,
COLOR_THEME_PRIMARY1);
auto box = new FormGroup(line, rect_t{});
box->setFlexLayout(LV_FLEX_FLOW_ROW, lv_dpx(4));
auto box_obj = box->getLvObj();
lv_obj_set_style_flex_cross_place(box_obj, LV_FLEX_ALIGN_CENTER, 0);
new HWInputEdit(box, g_eeGeneral.anaNames[idx], LEN_ANA_NAME);
new Choice(
box, rect_t{}, STR_SLIDERTYPES, SLIDER_NONE, SLIDER_WITH_DETENT,
[=]() -> int {
uint8_t mask = (0x01 << i);
return (g_eeGeneral.slidersConfig & mask) >> i;
},
[=](int newValue) {
uint8_t mask = (0x01 << i);
g_eeGeneral.slidersConfig &= ~mask;
g_eeGeneral.slidersConfig |= (newValue << i);
SET_DIRTY();
});
}
#endif
}
class SwitchDynamicLabel : public StaticText
{
public:
SwitchDynamicLabel(Window* parent, uint8_t index) :
StaticText(parent, rect_t{}, "", 0, COLOR_THEME_PRIMARY1),
index(index)
{
checkEvents();
}
std::string label()
{
std::string str(STR_VSRCRAW[index + MIXSRC_FIRST_SWITCH - MIXSRC_Rud + 1]);
return str + getSwitchPositionSymbol(lastpos);
}
uint8_t position()
{
auto value = getValue(MIXSRC_FIRST_SWITCH + index);
if (value > 0)
return 2;
else if (value < 0)
return 0;
else
return 1;
}
void checkEvents() override
{
uint8_t newpos = position();
if (newpos != lastpos) {
lastpos = newpos;
setText(label());
}
}
protected:
uint8_t index;
uint8_t lastpos = 0xff;
};
#if defined(PCBHORUS)
#define SWITCH_TYPE_MAX(sw) \
((MIXSRC_SF - MIXSRC_FIRST_SWITCH == sw || \
MIXSRC_SH - MIXSRC_FIRST_SWITCH == sw) \
? SWITCH_2POS \
: SWITCH_3POS)
#else
#define SWITCH_TYPE_MAX(sw) (SWITCH_3POS)
#endif
HWSwitches::HWSwitches(Window* parent) : FormGroup(parent, rect_t{})
{
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
setFlexLayout();
for (int i = 0; i < NUM_SWITCHES; i++) {
auto line = newLine(&grid);
new SwitchDynamicLabel(line, i);
auto box = new FormGroup(line, rect_t{});
box->setFlexLayout(LV_FLEX_FLOW_ROW, lv_dpx(4));
auto box_obj = box->getLvObj();
lv_obj_set_style_flex_cross_place(box_obj, LV_FLEX_ALIGN_CENTER, 0);
new HWInputEdit(box, g_eeGeneral.switchNames[i], LEN_SWITCH_NAME);
new Choice(
box, rect_t{}, STR_SWTYPES, SWITCH_NONE, SWITCH_TYPE_MAX(i),
[=]() -> int { return SWITCH_CONFIG(i); },
[=](int newValue) {
swconfig_t mask = (swconfig_t)0x03 << (2 * i);
g_eeGeneral.switchConfig = (g_eeGeneral.switchConfig & ~mask) |
((swconfig_t(newValue) & 0x03) << (2 * i));
SET_DIRTY();
});
}
}
template <class T>
HWInputDialog<T>::HWInputDialog(const char* title) :
Dialog(Layer::back(), std::string(), rect_t{})
{
setCloseWhenClickOutside(true);
if (title) content->setTitle(title);
new T(&content->form);
content->setWidth(LCD_W * 0.8);
content->updateSize();
}
template struct HWInputDialog<HWSticks>;
template struct HWInputDialog<HWPots>;
template struct HWInputDialog<HWSliders>;
template struct HWInputDialog<HWSwitches>;
+57
View File
@@ -0,0 +1,57 @@
/*
* 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 "form.h"
#include "dialog.h"
#include "button.h"
struct HWSticks : public FormGroup {
HWSticks(Window* parent);
};
struct HWPots : public FormGroup {
HWPots(Window* parent);
};
struct HWSliders : public FormGroup {
HWSliders(Window* parent);
};
struct HWSwitches : public FormGroup {
HWSwitches(Window* parent);
};
template<class T>
struct HWInputDialog : public Dialog
{
HWInputDialog(const char* title = nullptr);
};
template <class T>
TextButton* makeHWInputButton(Window* parent, const char* title)
{
return new TextButton(parent, rect_t{}, title, [=]() {
new HWInputDialog<T>(title);
return 0;
});
}
+94
View File
@@ -0,0 +1,94 @@
/*
* 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 "hw_intmodule.h"
#include "opentx.h"
#define SET_DIRTY() storageDirty(EE_GENERAL)
static const lv_coord_t col_two_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2),
LV_GRID_TEMPLATE_LAST};
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST};
InternalModuleWindow::InternalModuleWindow(Window *parent, const rect_t &rect) :
FormGroup(parent, rect, FORWARD_SCROLL),
lastModule(g_eeGeneral.internalModule)
{
setFlexLayout();
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
lv_obj_set_style_pad_left(lvobj, lv_dpx(8), 0);
auto line = newLine(&grid);
new StaticText(line, rect_t{}, TR_INTERNAL_MODULE, 0, COLOR_THEME_PRIMARY1);
auto internalModule = new Choice(
line, rect_t{}, STR_INTERNAL_MODULE_PROTOCOLS, MODULE_TYPE_NONE,
MODULE_TYPE_COUNT - 1, GET_DEFAULT(g_eeGeneral.internalModule),
[=](int type) { return setModuleType(type); });
internalModule->setAvailableHandler(
[](int module) { return isInternalModuleSupported(module); });
#if defined(CROSSFIRE)
line = newLine(&grid);
br_line = line->getLvObj();
new StaticText(line, rect_t{}, STR_BAUDRATE, 0, COLOR_THEME_PRIMARY1);
new Choice(line, rect_t{}, STR_CRSF_BAUDRATE, 0,
CROSSFIRE_MAX_INTERNAL_BAUDRATE, getBaudrate, setBaudrate);
updateBaudrateLine();
#endif
}
void InternalModuleWindow::setModuleType(int moduleType)
{
if (g_model.moduleData[INTERNAL_MODULE].type != moduleType) {
memclear(&g_model.moduleData[INTERNAL_MODULE], sizeof(ModuleData));
storageDirty(EE_MODEL);
}
g_eeGeneral.internalModule = moduleType;
updateBaudrateLine();
SET_DIRTY();
}
int InternalModuleWindow::getBaudrate()
{
return CROSSFIRE_STORE_TO_INDEX(g_eeGeneral.internalModuleBaudrate);
}
void InternalModuleWindow::setBaudrate(int val)
{
g_eeGeneral.internalModuleBaudrate = CROSSFIRE_INDEX_TO_STORE(val);
restartModule(INTERNAL_MODULE);
SET_DIRTY();
}
void InternalModuleWindow::updateBaudrateLine()
{
#if defined(CROSSFIRE)
if (isInternalModuleCrossfire()) {
lv_obj_clear_flag(br_line, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(br_line, LV_OBJ_FLAG_HIDDEN);
}
#endif
}
+40
View File
@@ -0,0 +1,40 @@
/*
* 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 "form.h"
class InternalModuleWindow : public FormGroup
{
public:
InternalModuleWindow(Window *parent, const rect_t &rect);
protected:
uint8_t lastModule = 0;
lv_obj_t* br_line = nullptr;
static int getBaudrate();
static void setBaudrate(int val);
void setModuleType(int moduleType);
void updateBaudrateLine();
};
+78
View File
@@ -0,0 +1,78 @@
/*
* 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 "hw_serial.h"
#include "opentx.h"
#define SET_DIRTY() storageDirty(EE_GENERAL)
static const lv_coord_t col_two_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2),
LV_GRID_TEMPLATE_LAST};
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST};
SerialConfigWindow::SerialConfigWindow(Window *parent, const rect_t &rect) :
FormGroup(parent, rect, FORWARD_SCROLL)
{
setFlexLayout();
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
lv_obj_set_style_pad_left(lvobj, lv_dpx(8), 0);
bool display_ttl_warning = false;
for (uint8_t port_nr = 0; port_nr < MAX_SERIAL_PORTS; port_nr++) {
auto port = serialGetPort(port_nr);
if (!port || !port->name) continue;
display_ttl_warning = true;
auto line = newLine(&grid);
new StaticText(line, rect_t{}, port->name, 0, COLOR_THEME_PRIMARY1);
auto aux = new Choice(
line, rect_t{}, STR_AUX_SERIAL_MODES, 0, UART_MODE_MAX,
[=]() { return serialGetMode(port_nr); },
[=](int value) {
serialSetMode(port_nr, value);
serialInit(port_nr, value);
SET_DIRTY();
});
aux->setAvailableHandler(
[=](int value) { return isSerialModeAvailable(port_nr, value); });
#if defined(SWSERIALPOWER)
if (port_nr < SP_VCP) {
line = newLine(&grid);
new StaticText(line, rect_t{}, STR_AUX_SERIAL_PORT_POWER, 0,
COLOR_THEME_PRIMARY1);
new CheckBox(
line, rect_t{}, [=] { return serialGetPower(port_nr); },
[=](int8_t newValue) {
serialSetPower(port_nr, (bool)newValue);
SET_DIRTY();
});
}
#endif
}
if (display_ttl_warning) {
grid.setColSpan(2);
auto line = newLine(&grid);
new StaticText(line, rect_t{}, STR_TTL_WARNING, 0, COLOR_THEME_WARNING);
}
}
+30
View File
@@ -0,0 +1,30 @@
/*
* 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 "form.h"
class SerialConfigWindow : public FormGroup
{
public:
SerialConfigWindow(Window *parent, const rect_t &rect);
};
+16 -27
View File
@@ -42,8 +42,8 @@ static const lv_coord_t line_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1),
static const lv_coord_t line_row_dsc[] = {LV_GRID_CONTENT,
LV_GRID_TEMPLATE_LAST};
static const lv_coord_t trims_col_dsc[] = {LV_GRID_FR(2), LV_GRID_FR(2),
LV_GRID_TEMPLATE_LAST};
// static const lv_coord_t trims_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1),
// LV_GRID_TEMPLATE_LAST};
static const lv_coord_t trims_row_dsc[] = {LV_GRID_CONTENT,
LV_GRID_CONTENT,
@@ -93,26 +93,23 @@ static std::string getFMTrimStr(uint8_t mode)
struct FMTrimSettings : public Dialog {
FMTrimSettings(Window* parent, FlightModeData* p_fm) :
Dialog(parent->getFullScreenWindow(), STR_TRIMS, rect_t{})
Dialog(parent->getFullScreenWindow(), STR_TRIMS, rect_t{})
{
auto lv_content = content->getLvObj();
lv_obj_set_flex_flow(lv_content, LV_FLEX_FLOW_COLUMN);
setCloseWhenClickOutside(true);
auto form = &content->form;
form->setFlexLayout();
FlexGridLayout trim_grid(trims_col_dsc, trims_row_dsc);
FlexGridLayout trim_grid(line_col_dsc, trims_row_dsc);
auto line = form->newLine(&trim_grid);
for (int t = 0; t < NUM_TRIMS; t++) {
auto trim_obj = window_create(line->getLvObj());
auto trim = new FormGroup::Line(line, trim_obj);
lv_obj_set_layout(trim_obj, LV_LAYOUT_FLEX);
lv_obj_set_style_pad_column(trim_obj, 4, 0);
lv_obj_set_style_pad_bottom(trim_obj, 8, 0);
auto trim = new FormGroup(line, rect_t{});
trim->setFlexLayout(LV_FLEX_FLOW_ROW, lv_dpx(8));
auto trim_obj = trim->getLvObj();
lv_obj_set_style_pad_column(trim_obj, lv_dpx(8), 0);
lv_obj_set_style_flex_cross_place(trim_obj, LV_FLEX_ALIGN_CENTER, 0);
lv_obj_set_style_grid_cell_x_align(trim_obj, LV_GRID_ALIGN_STRETCH, 0);
trim_t* tr = &p_fm->trim[t];
auto btn = new TextButton(
@@ -144,25 +141,19 @@ struct FMTrimSettings : public Dialog {
content->setWidth(LCD_W * 0.8);
content->updateSize();
// form->setFocus();
}
};
FlightModeEdit::FlightModeEdit(uint8_t index) :
Page(ICON_MODEL_FLIGHT_MODES)
{
std::string title =
std::string(TR_MENUFLIGHTMODE) + " " + std::to_string(index);
new StaticText(&header,
{PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT,
PAGE_LINE_HEIGHT},
title, 0, COLOR_THEME_PRIMARY2);
std::string title = std::string(TR_MENUFLIGHTMODE)
+ " " + std::to_string(index);
header.setTitle(title);
auto form = new FormWindow(&body, rect_t{0, 0, body.width(), body.height()},
NO_FOCUS /*| FORM_FORWARD_FOCUS*/);
form->setFlexLayout();
FlexGridLayout grid(line_col_dsc, line_row_dsc, 2);
auto form = new FormWindow(&body, rect_t{});
form->setFlexLayout();
FlightModeData* p_fm = &g_model.flightModeData[index];
@@ -198,8 +189,6 @@ FlightModeEdit::FlightModeEdit(uint8_t index) :
new FMTrimSettings(this, p_fm);
return 0;
});
// form->setFocus();
}
class FlightModeBtn: public Button
+124 -430
View File
@@ -27,83 +27,28 @@
#include "libopenui.h"
#include "hal/adc_driver.h"
#include "aux_serial_driver.h"
#include "hw_intmodule.h"
#include "hw_serial.h"
#include "hw_inputs.h"
#define SET_DIRTY() storageDirty(EE_GENERAL)
static const lv_coord_t col_two_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1),
LV_GRID_TEMPLATE_LAST};
static const lv_coord_t col_three_dsc[] = {LV_GRID_FR(2), LV_GRID_FR(1), LV_GRID_FR(1),
static const lv_coord_t col_three_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), 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};
#if defined(PCBHORUS)
#define SWITCH_TYPE_MAX(sw) ((MIXSRC_SF-MIXSRC_FIRST_SWITCH == sw || MIXSRC_SH-MIXSRC_FIRST_SWITCH == sw) ? SWITCH_2POS : SWITCH_3POS)
#else
#define SWITCH_TYPE_MAX(sw) (SWITCH_3POS)
#endif
class SwitchDynamicLabel: public StaticText {
public:
SwitchDynamicLabel(Window * parent, const rect_t & rect, uint8_t index):
StaticText(parent, rect, "", 0, COLOR_THEME_PRIMARY1),
index(index)
{
update();
}
std::string label()
{
static const char* switchPositions[] = {
STR_CHAR_UP,
"-",
STR_CHAR_DOWN
};
return TEXT_AT_INDEX(STR_VSRCRAW, (index + MIXSRC_FIRST_SWITCH - MIXSRC_Rud + 1)) + std::string(switchPositions[lastpos]);
}
void update()
{
uint8_t newpos = position();
if (newpos != lastpos) {
lastpos = newpos;
setText(label());
}
}
uint8_t position()
{
auto value = getValue(MIXSRC_FIRST_SWITCH + index);
if (value > 0)
return 2;
else if (value < 0)
return 0;
else
return 1;
}
void checkEvents() override
{
update();
}
protected:
uint8_t index;
uint8_t lastpos = 0xff;
};
RadioHardwarePage::RadioHardwarePage():
PageTab(STR_HARDWARE, ICON_RADIO_HARDWARE)
{
}
#if defined(BLUETOOTH)
class ModeChoice : public Choice
{
public:
ModeChoice(Window *parent, const rect_t &rect, const char **values,
int vmin, int vmax, std::function<int()> getValue,
ModeChoice(Window *parent, const rect_t &rect, const char **values, int vmin,
int vmax, std::function<int()> getValue,
std::function<void(int)> setValue = nullptr,
bool *menuOpen = nullptr) :
Choice(parent, rect, values, vmin, vmax, getValue, setValue),
@@ -123,440 +68,189 @@ class ModeChoice : public Choice
};
class BluetoothConfigWindow : public FormGroup
{
public:
BluetoothConfigWindow(Window * parent, const rect_t &rect) :
FormGroup(parent, rect, FORWARD_SCROLL // | FORM_FORWARD_FOCUS
)
{
update();
}
void checkEvents() override
{
FormGroup::checkEvents();
if (!rte) return;
if (bluetooth.state != lastbluetoothstate) {
lastbluetoothstate = bluetooth.state;
if (!(modechoiceopen || rte->hasFocus())) update();
}
}
void update()
{
clear();
setFlexLayout();
FlexGridLayout grid(col_three_dsc, row_dsc, 2);
auto line = newLine(&grid);
auto label = new StaticText(line, rect_t{}, STR_MODE, 0,
COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
modechoiceopen = false;
btMode = new ModeChoice(
line, rect_t{}, STR_BLUETOOTH_MODES, BLUETOOTH_OFF,
BLUETOOTH_TRAINER, GET_DEFAULT(g_eeGeneral.bluetoothMode),
[=](int32_t newValue) {
g_eeGeneral.bluetoothMode = newValue;
update();
SET_DIRTY();
// btMode->setFocus(SET_FOCUS_DEFAULT);
modechoiceopen = false;
},
&modechoiceopen);
line = newLine(&grid);
if (g_eeGeneral.bluetoothMode != BLUETOOTH_OFF) {
// Pin code (displayed for information only, not editable)
if (g_eeGeneral.bluetoothMode == BLUETOOTH_TELEMETRY) {
label = new StaticText(line, rect_t{}, STR_BLUETOOTH_PIN_CODE, 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new StaticText(line, rect_t{}, "000000", 0, COLOR_THEME_PRIMARY1);
line = newLine(&grid);
}
// Local MAC
label = new StaticText(line, rect_t{}, STR_BLUETOOTH_LOCAL_ADDR, 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new StaticText(line, rect_t{}, bluetooth.localAddr[0] == '\0' ? "---" : bluetooth.localAddr, 0, COLOR_THEME_PRIMARY1);
line = newLine(&grid);
// Remote MAC
label = new StaticText(line, rect_t{}, STR_BLUETOOTH_DIST_ADDR, 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new StaticText(line, rect_t{}, bluetooth.distantAddr[0] == '\0' ? "---" : bluetooth.distantAddr, 0, COLOR_THEME_PRIMARY1);
line = newLine(&grid);
// BT radio name
label = new StaticText(line, rect_t{}, STR_NAME, 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
rte = new RadioTextEdit(line, rect_t{}, g_eeGeneral.bluetoothName, LEN_BLUETOOTH_NAME);
line = newLine(&grid);
}
getParent()->moveWindowsTop(top() + 1, adjustHeight());
}
protected:
Choice *btMode = nullptr;
RadioTextEdit *rte = nullptr;
private:
bool modechoiceopen = false;
uint8_t lastbluetoothstate = BLUETOOTH_STATE_OFF;
};
#endif
class SerialConfigWindow : public FormGroup
{
public:
SerialConfigWindow(Window *parent, const rect_t &rect) :
FormGroup(parent, rect, FORWARD_SCROLL // | FORM_FORWARD_FOCUS
)
BluetoothConfigWindow(Window *parent, const rect_t &rect) :
FormGroup(parent, rect, FORWARD_SCROLL)
{
update();
}
void update()
{
clear();
setFlexLayout();
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
auto line = newLine(&grid);
bool display_ttl_warning = false;
for (uint8_t port_nr = 0; port_nr < MAX_SERIAL_PORTS; port_nr++) {
auto port = serialGetPort(port_nr);
if (!port || !port->name) continue;
display_ttl_warning = true;
auto label = new StaticText(line, rect_t{}, port->name, 0,
COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
auto aux = new Choice(
line, rect_t{}, STR_AUX_SERIAL_MODES, 0,
UART_MODE_MAX, [=]() { return serialGetMode(port_nr); },
[=](int value) {
serialSetMode(port_nr, value);
serialInit(port_nr, value);
SET_DIRTY();
});
aux->setAvailableHandler(
[=](int value) { return isSerialModeAvailable(port_nr, value); });
line = newLine(&grid);
#if defined(SWSERIALPOWER)
if (port_nr < SP_VCP)
{
auto label = new StaticText(line, rect_t{}, STR_AUX_SERIAL_PORT_POWER, 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new CheckBox(
line, rect_t{},
[=] { return serialGetPower(port_nr); },
[=](int8_t newValue) {
serialSetPower(port_nr, (bool)newValue);
SET_DIRTY();
}
);
line = newLine(&grid);
}
#endif
}
if (display_ttl_warning) {
grid.setColSpan(2);
auto label = new StaticText(line, rect_t{}, STR_TTL_WARNING, 0,
COLOR_THEME_WARNING);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
grid.setColSpan(1);
line = newLine(&grid);
}
getParent()->moveWindowsTop(top() + 1, adjustHeight());
}
};
class InternalModuleWindow : public FormGroup {
public:
InternalModuleWindow(Window *parent, const rect_t &rect) :
FormGroup(parent, rect, FORWARD_SCROLL),
lastModule(g_eeGeneral.internalModule)
{
update();
}
void update()
{
clear();
setFlexLayout();
FlexGridLayout grid(col_two_dsc, row_dsc, 2);
auto line = newLine(&grid);
auto label = new StaticText(line, rect_t{}, TR_INTERNAL_MODULE, 0,
COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
auto internalModule = new Choice(line, rect_t{},STR_INTERNAL_MODULE_PROTOCOLS, MODULE_TYPE_NONE, MODULE_TYPE_COUNT - 1,
GET_DEFAULT(g_eeGeneral.internalModule),
[=](int moduleType) {
if (g_model.moduleData[INTERNAL_MODULE].type != moduleType) {
memclear(&g_model.moduleData[INTERNAL_MODULE], sizeof(ModuleData));
storageDirty(EE_MODEL);
}
g_eeGeneral.internalModule = moduleType;
SET_DIRTY();
});
internalModule->setAvailableHandler([](int module){
return isInternalModuleSupported(module);
});
line = newLine(&grid);
#if defined(CROSSFIRE)
if (isInternalModuleCrossfire()) {
auto label = new StaticText(line, rect_t{}, STR_BAUDRATE, 0,COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new Choice(line, rect_t{}, STR_CRSF_BAUDRATE, 0,CROSSFIRE_MAX_INTERNAL_BAUDRATE,
[=]() -> int {
return CROSSFIRE_STORE_TO_INDEX(g_eeGeneral.internalModuleBaudrate);
},
[=](int newValue) {
g_eeGeneral.internalModuleBaudrate = CROSSFIRE_INDEX_TO_STORE(newValue);
SET_DIRTY();
restartModule(INTERNAL_MODULE);
});
line = newLine(&grid);
}
#endif
getParent()->moveWindowsTop(top() + 1, adjustHeight());
}
void checkEvents() override
{
if (g_eeGeneral.internalModule != lastModule) {
lastModule = g_eeGeneral.internalModule;
update();
}
FormGroup::checkEvents();
if (!rte) return;
if (bluetooth.state != lastbluetoothstate) {
lastbluetoothstate = bluetooth.state;
if (!(modechoiceopen || rte->hasFocus())) update();
}
}
void update()
{
clear();
setFlexLayout();
FlexGridLayout grid(col_three_dsc, row_dsc, 2);
lv_obj_set_style_pad_left(lvobj, lv_dpx(8), 0);
auto line = newLine(&grid);
new StaticText(line, rect_t{}, STR_MODE, 0, COLOR_THEME_PRIMARY1);
modechoiceopen = false;
btMode = new ModeChoice(
line, rect_t{}, STR_BLUETOOTH_MODES, BLUETOOTH_OFF, BLUETOOTH_TRAINER,
GET_DEFAULT(g_eeGeneral.bluetoothMode),
[=](int32_t newValue) {
g_eeGeneral.bluetoothMode = newValue;
update();
modechoiceopen = false;
SET_DIRTY();
},
&modechoiceopen);
line = newLine(&grid);
if (g_eeGeneral.bluetoothMode != BLUETOOTH_OFF) {
// Pin code (displayed for information only, not editable)
if (g_eeGeneral.bluetoothMode == BLUETOOTH_TELEMETRY) {
label = new StaticText(line, rect_t{}, STR_BLUETOOTH_PIN_CODE, 0,
COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new StaticText(line, rect_t{}, "000000", 0, COLOR_THEME_PRIMARY1);
line = newLine(&grid);
}
// Local MAC
label = new StaticText(line, rect_t{}, STR_BLUETOOTH_LOCAL_ADDR, 0,
COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new StaticText(
line, rect_t{},
bluetooth.localAddr[0] == '\0' ? "---" : bluetooth.localAddr, 0,
COLOR_THEME_PRIMARY1);
line = newLine(&grid);
// Remote MAC
label = new StaticText(line, rect_t{}, STR_BLUETOOTH_DIST_ADDR, 0,
COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
new StaticText(
line, rect_t{},
bluetooth.distantAddr[0] == '\0' ? "---" : bluetooth.distantAddr, 0,
COLOR_THEME_PRIMARY1);
line = newLine(&grid);
// BT radio name
label = new StaticText(line, rect_t{}, STR_NAME, 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
rte = new RadioTextEdit(line, rect_t{}, g_eeGeneral.bluetoothName,
LEN_BLUETOOTH_NAME);
line = newLine(&grid);
}
}
protected:
uint8_t lastModule = 0;
Choice *btMode = nullptr;
RadioTextEdit *rte = nullptr;
private:
bool modechoiceopen = false;
uint8_t lastbluetoothstate = BLUETOOTH_STATE_OFF;
};
#endif
void RadioHardwarePage::build(FormWindow * window)
{
window->setFlexLayout();
FlexGridLayout grid(col_three_dsc, row_dsc, 2);
auto line = window->newLine(&grid);
lv_obj_set_style_pad_all(window->getLvObj(), lv_dpx(8), 0);
// Calibration
new StaticText(line, rect_t{}, STR_INPUTS, 0, COLOR_THEME_PRIMARY1 | FONT(BOLD));
new Subtitle(window, rect_t{}, STR_INPUTS, 0, COLOR_THEME_PRIMARY1 | FONT(BOLD));
auto line = window->newLine(&grid);
grid.nextCell();
auto calib = new TextButton(line, rect_t{}, STR_CALIBRATION);
calib->setPressHandler([=]() -> uint8_t {
// auto calibrationPage =
new RadioCalibrationPage();
// calibrationPage->setCloseHandler([=]() {
// calib->setFocus(SET_FOCUS_DEFAULT);
// });
return 0;
});
line = window->newLine(&grid);
// Sticks
new Subtitle(line, rect_t{}, STR_STICKS, 0, COLOR_THEME_PRIMARY1);
line = window->newLine(&grid);
for (int i = 0; i < NUM_STICKS; i++) {
auto label = new StaticText(line, rect_t{}, TEXT_AT_INDEX(STR_VSRCRAW, (i + 1)), 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
auto textEdit = new RadioTextEdit(line, rect_t{}, g_eeGeneral.anaNames[i], LEN_ANA_NAME);
lv_obj_set_style_grid_cell_x_align(textEdit->getLvObj(), LV_GRID_ALIGN_STRETCH, 0);
line = window->newLine(&grid);
}
#if defined(STICK_DEAD_ZONE)
auto text = new StaticText(line, rect_t{}, STR_DEAD_ZONE);
lv_obj_set_style_pad_left(text->getLvObj(), lv_dpx(8), LV_PART_MAIN);
auto choice =
new Choice(line, rect_t{}, 0, 7,
GET_DEFAULT(g_eeGeneral.stickDeadZone), [=](uint8_t newValue) {
g_eeGeneral.stickDeadZone = newValue;
SET_DIRTY();
});
choice->setTextHandler([](uint8_t value) {
return std::to_string(value ? 2 << (value - 1) : 0);
});
line = window->newLine(&grid);
#endif
// new Subtitle(window, rect_t{}, STR_STICKS, 0, COLOR_THEME_PRIMARY1);
// new HWSticks(window);
makeHWInputButton<HWSticks>(window, STR_STICKS);
// Pots
new Subtitle(line, rect_t{}, STR_POTS, 0, COLOR_THEME_PRIMARY1);
line = window->newLine(&grid);
for (int i = 0; i < NUM_POTS; i++) {
// Display EX3 & EX4 (= last two pots) only when FlySky gimbals are present
#if !defined(SIMU) && defined(RADIO_FAMILY_T16)
if (!globalData.flyskygimbals && (i >= (NUM_POTS - 2)))
continue;
#endif
auto label = new StaticText(line, rect_t{}, TEXT_AT_INDEX(STR_VSRCRAW, (i + NUM_STICKS + 1)), 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
auto textEdit = new RadioTextEdit(line, rect_t{}, g_eeGeneral.anaNames[i + NUM_STICKS], LEN_ANA_NAME);
lv_obj_set_style_grid_cell_x_align(textEdit->getLvObj(), LV_GRID_ALIGN_STRETCH, 0);
new Choice(line, rect_t{}, STR_POTTYPES, POT_NONE, POT_WITHOUT_DETENT,
[=]() -> int {
return bfGet<uint32_t>(g_eeGeneral.potsConfig, 2*i, 2);
},
[=](int newValue) {
g_eeGeneral.potsConfig = bfSet<uint32_t>(g_eeGeneral.potsConfig, newValue, 2*i, 2);
SET_DIRTY();
});
line = window->newLine(&grid);
}
// new Subtitle(window, rect_t{}, STR_POTS, 0, COLOR_THEME_PRIMARY1);
// new HWPots(window);
makeHWInputButton<HWPots>(window, STR_POTS);
// Sliders
#if (NUM_SLIDERS > 0)
new Subtitle(line, rect_t{}, STR_SLIDERS, 0, COLOR_THEME_PRIMARY1);
line = window->newLine(&grid);
for (int i = 0; i < NUM_SLIDERS; i++) {
const int idx = i + NUM_STICKS + NUM_POTS;
auto label = new StaticText(line, rect_t{}, TEXT_AT_INDEX(STR_VSRCRAW, idx + 1), 0, COLOR_THEME_PRIMARY1);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
auto textEdit = new RadioTextEdit(line, rect_t{}, g_eeGeneral.anaNames[idx], LEN_ANA_NAME);
lv_obj_set_style_grid_cell_x_align(textEdit->getLvObj(), LV_GRID_ALIGN_STRETCH, 0);
new Choice(
window, rect_t{}, STR_SLIDERTYPES, SLIDER_NONE,
SLIDER_WITH_DETENT,
[=]() -> int {
uint8_t mask = (0x01 << i);
return (g_eeGeneral.slidersConfig & mask) >> i;
},
[=](int newValue) {
uint8_t mask = (0x01 << i);
g_eeGeneral.slidersConfig &= ~mask;
g_eeGeneral.slidersConfig |= (newValue << i);
SET_DIRTY();
});
line = window->newLine(&grid);
}
// new Subtitle(window, rect_t{}, STR_SLIDERS, 0, COLOR_THEME_PRIMARY1);
// new HWSliders(window);
makeHWInputButton<HWSliders>(window, STR_SLIDERS);
#endif
// Switches
new Subtitle(line, rect_t{}, STR_SWITCHES, 0, COLOR_THEME_PRIMARY1);
line = window->newLine(&grid);
for (int i = 0; i < NUM_SWITCHES; i++) {
auto label = new SwitchDynamicLabel(line, rect_t{}, i);
lv_obj_set_style_pad_left(label->getLvObj(), lv_dpx(8), LV_PART_MAIN);
auto textEdit = new RadioTextEdit(line, rect_t{}, g_eeGeneral.switchNames[i], LEN_SWITCH_NAME);
lv_obj_set_style_grid_cell_x_align(textEdit->getLvObj(), LV_GRID_ALIGN_STRETCH, 0);
new Choice(line, rect_t{}, STR_SWTYPES, SWITCH_NONE, SWITCH_TYPE_MAX(i),
[=]() -> int {
return SWITCH_CONFIG(i);
},
[=](int newValue) {
swconfig_t mask = (swconfig_t) 0x03 << (2 * i);
g_eeGeneral.switchConfig = (g_eeGeneral.switchConfig & ~mask) | ((swconfig_t(newValue) & 0x03) << (2 * i));
SET_DIRTY();
});
line = window->newLine(&grid);
}
// new Subtitle(window, rect_t{}, STR_SWITCHES, 0, COLOR_THEME_PRIMARY1);
// new HWSwitches(window);
makeHWInputButton<HWSwitches>(window, STR_SWITCHES);
// Bat calibration
// TODO: sub-title?
line = window->newLine(&grid);
new StaticText(line, rect_t{}, STR_BATT_CALIB, 0, COLOR_THEME_PRIMARY1);
auto batCal = new NumberEdit(line, rect_t{}, -127, 127, GET_SET_DEFAULT(g_eeGeneral.txVoltageCalibration));
auto batCal =
new NumberEdit(line, rect_t{}, -127, 127,
GET_SET_DEFAULT(g_eeGeneral.txVoltageCalibration));
batCal->setDisplayHandler([](int32_t value) {
return formatNumberAsString(getBatteryVoltage(), PREC2, 0, nullptr, "V");
return formatNumberAsString(getBatteryVoltage(), PREC2, 0, nullptr, "V");
});
batCal->setWindowFlags(REFRESH_ALWAYS);
line = window->newLine(&grid);
// RTC Batt display
line = window->newLine(&grid);
new StaticText(line, rect_t{}, STR_RTC_BATT, 0, COLOR_THEME_PRIMARY1);
new DynamicNumber<uint16_t>(line, rect_t{}, [] {
return getRTCBatteryVoltage();
}, COLOR_THEME_PRIMARY1 | PREC2, nullptr, "V");
line = window->newLine(&grid);
// RTC Batt check enable
line = window->newLine(&grid);
new StaticText(line, rect_t{}, STR_RTC_CHECK, 0, COLOR_THEME_PRIMARY1);
new CheckBox(line, rect_t{}, GET_SET_INVERTED(g_eeGeneral.disableRtcWarning ));
line = window->newLine(&grid);
#if defined(HARDWARE_INTERNAL_MODULE)
new Subtitle(line, rect_t{}, TR_INTERNALRF, 0, COLOR_THEME_PRIMARY1);
line = window->newLine(&grid);
lv_obj_set_style_pad_left(line->getLvObj(), 0, LV_PART_MAIN);
lv_obj_set_style_pad_right(line->getLvObj(), 0, LV_PART_MAIN);
// Date and Time
grid.setColSpan(2);
new InternalModuleWindow(line, rect_t{});
grid.setColSpan(1);
line = window->newLine(&grid);
new Subtitle(window, rect_t{}, TR_INTERNALRF, 0, COLOR_THEME_PRIMARY1);
new InternalModuleWindow(window, rect_t{});
#endif
#if defined(BLUETOOTH)
// Bluetooth mode
{
new Subtitle(line, rect_t{}, STR_BLUETOOTH, 0, COLOR_THEME_PRIMARY1);
line = window->newLine(&grid);
lv_obj_set_style_pad_left(line->getLvObj(), 0, LV_PART_MAIN);
lv_obj_set_style_pad_right(line->getLvObj(), 0, LV_PART_MAIN);
grid.setColSpan(2);
new BluetoothConfigWindow(line, rect_t{});
grid.setColSpan(1);
line = window->newLine(&grid);
}
new Subtitle(window, rect_t{}, STR_BLUETOOTH, 0, COLOR_THEME_PRIMARY1);
new BluetoothConfigWindow(window, rect_t{});
#endif
new Subtitle(line, rect_t{}, STR_AUX_SERIAL_MODE, 0,
COLOR_THEME_PRIMARY1);
line = window->newLine(&grid);
lv_obj_set_style_pad_left(line->getLvObj(), 0, LV_PART_MAIN);
lv_obj_set_style_pad_right(line->getLvObj(), 0, LV_PART_MAIN);
grid.setColSpan(2);
new SerialConfigWindow(line, rect_t{});
grid.setColSpan(1);
line = window->newLine(&grid);
new Subtitle(window, rect_t{}, STR_AUX_SERIAL_MODE, 0, COLOR_THEME_PRIMARY1);
new SerialConfigWindow(window, rect_t{});
// ADC filter
line = window->newLine(&grid);
new StaticText(line, rect_t{}, STR_JITTER_FILTER, 0, COLOR_THEME_PRIMARY1);
new CheckBox(line, rect_t{}, GET_SET_INVERTED(g_eeGeneral.noJitterFilter));
line = window->newLine(&grid);
// Debugs
line = window->newLine(&grid);
new StaticText(line, rect_t{}, STR_DEBUG, 0, COLOR_THEME_PRIMARY1 | FONT(BOLD));
auto debugAnas = new TextButton(line, rect_t{}, STR_ANALOGS_BTN);
debugAnas->setPressHandler([=]() -> uint8_t {
// auto debugAnalogsPage =
new RadioAnalogsDiagsViewPageGroup();
// debugAnalogsPage->setCloseHandler([=]() {
// calib->setFocus(SET_FOCUS_DEFAULT);
// });
return 0;
});
auto debugKeys = new TextButton(line, rect_t{}, STR_KEYS_BTN);
debugKeys->setPressHandler([=]() -> uint8_t {
// auto debugKeysPage =
new RadioKeyDiagsPage();
// debugKeysPage->setCloseHandler([=]() {
// calib->setFocus(SET_FOCUS_DEFAULT);
// });
new TextButton(line, rect_t{}, STR_ANALOGS_BTN, [=]() -> uint8_t {
new RadioAnalogsDiagsViewPageGroup();
return 0;
});
line = window->newLine(&grid);
// extra bottom padding if touchscreen
#if defined HARDWARE_TOUCH
new StaticText(line, rect_t{});
#endif
new TextButton(line, rect_t{}, STR_KEYS_BTN, [=]() -> uint8_t {
new RadioKeyDiagsPage();
return 0;
});
}