RadioSet: Sensible UI for configuring parameters

This commit is contained in:
Dominic Höglinger 2025-09-25 23:42:06 +02:00
parent 640ce09132
commit f57868b3fd
3 changed files with 524 additions and 29 deletions

View File

@ -4,3 +4,5 @@ idf_component_register(
SRCS ${SOURCE_FILES}
REQUIRES TactilitySDK
)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=uninitialized -Wno-error=maybe-uninitialized)

View File

@ -5,6 +5,8 @@
#include <ctype.h>
#include <tt_lvgl_toolbar.h>
#include <initializer_list>
#include "tt_app_alertdialog.h"
constexpr const char* TAG = "RadioSet";
@ -13,21 +15,378 @@ void crash(const char* const message) {
tt_app_alertdialog_start("RadioSet has crashed!", message, nullptr, 0);
}
// Debug function which colors all children randomly
// TODO: Remove before flight
void clownvomit(lv_obj_t *obj) {
static auto rng = []() {
static int color = 0xC0FE;
const int a = 4711;
const int m = 0x10001;
color = (a * color) % m;
return color;
};
const int darken = 0x0E0E0E;
const int lighten = 0xFEFEFE;
lv_obj_set_style_bg_color(obj, lv_color_hex(rng() & darken), 0);
uint32_t i;
for(i = 0; i < lv_obj_get_child_count(obj); i++) {
lv_obj_t * child = lv_obj_get_child(obj, i);
lv_obj_set_style_bg_color(child, lv_color_hex(rng() & darken), 0);
lv_obj_set_style_border_color(child, lv_color_hex(rng() | lighten), 0);
lv_obj_set_style_border_width(child, 1, 0);
clownvomit(child);
}
}
/*
void scrollbar_highlight(lv_event_t * e) {
lv_obj_t * obj = lv_event_get_target(e);
}*/
char *const toString(Modulation m) {
switch (m) {
case MODULATION_NONE:
return "None";
case MODULATION_LORA:
return "LoRa";
case MODULATION_FSK:
return "FSK";
case MODULATION_LRFHSS:
return "LR-FHSS";
default:
break;
}
crash("Unknown modulation passed.");
return "Unknown";
}
char *const toString(RadioParameter p) {
switch (p) {
case RADIO_POWER:
return "Power";
case RADIO_FREQUENCY:
return "Center Frequency";
case RADIO_BANDWIDTH:
return "Bandwidth";
case RADIO_SPREADFACTOR:
return "Spread Factor";
case RADIO_CODINGRATE:
return "Coding Rate";
case RADIO_SYNCWORD:
return "Sync Word";
case RADIO_PREAMBLES:
return "Preamble Length";
case RADIO_FREQDIV:
return "Frequency Deviation";
case RADIO_DATARATE:
return "Data Rate";
case RADIO_ADDRWIDTH:
return "Address Width";
case RADIO_NARROWGRID:
return "Narrow Grid";
default:
break;
}
crash("Unknown parameter passed.");
return "Unknown";
}
class TermView {
};
static lv_obj_t* initGridDropdownInput(lv_obj_t *container, int row, const char* const label, const char* const items) {
lv_obj_t* label_obj = lv_label_create(container);
lv_label_set_text(label_obj, label);
lv_obj_set_grid_cell(label_obj,
LV_GRID_ALIGN_STRETCH, 0, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);
lv_obj_t* input = lv_dropdown_create(container);
lv_obj_set_grid_cell(input,
LV_GRID_ALIGN_STRETCH, 1, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(input, lv_pct(100), LV_SIZE_CONTENT);
lv_dropdown_set_options(input, items);
return input;
}
static lv_obj_t* initGridTextInput(lv_obj_t *container, int row, const char* const label, const char* const defval, const char* const unit) {
const int height = LV_SIZE_CONTENT;
lv_obj_t* label_obj = lv_label_create(container);
lv_label_set_text(label_obj, label);
lv_obj_set_grid_cell(label_obj,
LV_GRID_ALIGN_STRETCH, 0, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(label_obj, lv_pct(100), height);
lv_obj_t* input = lv_textarea_create(container);
lv_obj_set_grid_cell(input,
LV_GRID_ALIGN_STRETCH, 1, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(input, lv_pct(100), height);
lv_textarea_set_text(input, defval);
lv_textarea_set_one_line(input, true);
lv_obj_t* unit_obj = lv_label_create(container);
lv_label_set_text(unit_obj, unit);
lv_obj_set_grid_cell(unit_obj,
LV_GRID_ALIGN_STRETCH, 2, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(unit_obj, lv_pct(100), height);
lv_obj_set_style_text_align(unit_obj , LV_TEXT_ALIGN_CENTER, 0);
return input;
}
struct ParameterInput {
const RadioHandle handle;
const RadioParameter param;
ParameterInput(RadioHandle handle, const RadioParameter param)
: handle(handle)
, param(param) {}
virtual void updatePreview() = 0;
};
struct NumericParameterInput : public ParameterInput {
lv_obj_t* label = nullptr;
lv_obj_t* input = nullptr;
lv_obj_t* unitlabel = nullptr;
char* fmt;
NumericParameterInput(RadioHandle handle, const RadioParameter param, lv_obj_t* container, int row, char* fmt = "%f", char* unit_override = nullptr)
: ParameterInput(handle, param)
, fmt(fmt) {
initUi(container, row, unit_override);
loadFromRadio();
}
void loadFromRadio() {
}
void initUi(lv_obj_t* container, int row, char* unit_override) {
const int height = LV_SIZE_CONTENT;
label = lv_label_create(container);
lv_label_set_text(label, toString(param));
lv_obj_set_grid_cell(label,
LV_GRID_ALIGN_STRETCH, 0, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(label, lv_pct(100), height);
input = lv_textarea_create(container);
lv_obj_set_grid_cell(input,
LV_GRID_ALIGN_STRETCH, 1, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(input, lv_pct(100), height);
//TODO: LOAD VALUE
//lv_textarea_set_text(input, defval);
lv_textarea_set_one_line(input, true);
unitlabel = lv_label_create(container);
lv_obj_set_grid_cell(unitlabel,
LV_GRID_ALIGN_STRETCH, 2, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(unitlabel, lv_pct(100), height);
lv_obj_set_style_text_align(unitlabel , LV_TEXT_ALIGN_CENTER, 0);
if (unit_override) {
lv_label_set_text(unitlabel, unit_override);
} else {
char unit[64] = {0};
tt_hal_radio_get_parameter_unit_str(handle, param, unit, sizeof(unit));
lv_label_set_text(unitlabel, unit);
}
}
virtual void updatePreview() override {}
};
struct SliderParameterInput : public ParameterInput {
char* fmt = nullptr;
lv_obj_t* label = nullptr;
lv_obj_t* slider = nullptr;
lv_obj_t* preview = nullptr;
SliderParameterInput(RadioHandle handle, const RadioParameter param, lv_obj_t* container, int row, int min, int max, char* fmt = "%i")
: ParameterInput(handle, param)
, fmt(fmt) {
initUi(container, row, min, max);
}
void initUi(lv_obj_t* container, int row, int min, int max) {
label = lv_label_create(container);
lv_label_set_text(label, toString(param));
lv_obj_set_grid_cell(label,
LV_GRID_ALIGN_STRETCH, 0, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT);
slider = lv_slider_create(container);
lv_obj_set_grid_cell(slider,
LV_GRID_ALIGN_STRETCH, 1, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(slider, lv_pct(100), 10);
lv_slider_set_range(slider, min, max);
preview = lv_label_create(container);
lv_obj_set_grid_cell(preview,
LV_GRID_ALIGN_STRETCH, 2, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(preview, lv_pct(100), LV_SIZE_CONTENT);
lv_obj_set_style_text_align(preview , LV_TEXT_ALIGN_LEFT, 0);
updatePreview();
lv_obj_add_event_cb(slider, [](lv_event_t * e) {
lv_obj_t* slider = lv_event_get_target_obj(e);
SliderParameterInput* self = (SliderParameterInput*)lv_event_get_user_data(e);
self->updatePreview();
}, LV_EVENT_VALUE_CHANGED, this);
}
virtual void updatePreview() override {
char buf[64] = {0};
lv_snprintf(buf, sizeof(buf), fmt, lv_slider_get_value(slider));
lv_label_set_text(preview, buf);
}
};
struct SliderSelectParameterInput : public ParameterInput {
static constexpr float SELECT_END = -1;
const float* selections;
const size_t selectionsSize;
char unit[64] = {0};
char* fmt;
lv_obj_t* label = nullptr;
lv_obj_t* slider = nullptr;
lv_obj_t* preview = nullptr;
static constexpr size_t get_selection_num(const float selections[]) {
constexpr size_t MAX_SELECTIONS = 32;
if (selections) {
for (size_t i = 0; i < MAX_SELECTIONS; ++i) {
if (selections[i] == SELECT_END) {
return i;
}
}
}
return 0;
}
SliderSelectParameterInput(RadioHandle handle, const RadioParameter param, lv_obj_t* container, int row, const float selections[] = nullptr, char* fmt = "%f")
: ParameterInput(handle, param)
, selections(selections)
, selectionsSize(get_selection_num(selections))
, fmt(fmt) {
initUi(container, row);
}
void initUi(lv_obj_t* container, int row) {
label = lv_label_create(container);
lv_label_set_text(label, toString(param));
lv_obj_set_grid_cell(label,
LV_GRID_ALIGN_STRETCH, 0, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT);
slider = lv_slider_create(container);
lv_obj_set_grid_cell(slider,
LV_GRID_ALIGN_STRETCH, 1, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(slider, lv_pct(100), 10);
lv_slider_set_range(slider, 0, selectionsSize - 1);
preview = lv_label_create(container);
lv_obj_set_grid_cell(preview,
LV_GRID_ALIGN_STRETCH, 2, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(preview, lv_pct(100), LV_SIZE_CONTENT);
lv_obj_set_style_text_align(preview , LV_TEXT_ALIGN_LEFT, 0);
tt_hal_radio_get_parameter_unit_str(handle, param, unit, sizeof(unit));
updatePreview();
lv_obj_add_event_cb(slider, [](lv_event_t * e) {
lv_obj_t* slider = lv_event_get_target_obj(e);
SliderSelectParameterInput* self = (SliderSelectParameterInput*)lv_event_get_user_data(e);
self->updatePreview();
}, LV_EVENT_VALUE_CHANGED, this);
}
virtual void updatePreview() override {
Str text;
text.appendf(fmt, selections[lv_slider_get_value(slider)]);
text.append(unit);
lv_label_set_text(preview, text.c_str());
}
};
static ParameterInput* makeLoraInput(RadioHandle handle, const RadioParameter param, lv_obj_t* container, int row) {
static constexpr float bw_values[] = {7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0, 500.0, SliderSelectParameterInput::SELECT_END};
switch (param) {
case RADIO_POWER: //no break
case RADIO_FREQUENCY:
return new NumericParameterInput(handle, param, container, row);
case RADIO_BANDWIDTH:
return new SliderSelectParameterInput(handle, param, container, row, bw_values, "%.1f");
case RADIO_SPREADFACTOR:
return new SliderParameterInput(handle, param, container, row, 7, 12);
case RADIO_CODINGRATE:
return new SliderParameterInput(handle, param, container, row, 5, 8);
case RADIO_SYNCWORD:
return new SliderParameterInput(handle, param, container, row, 0, 255, "%02X");
case RADIO_PREAMBLES:
return new SliderParameterInput(handle, param, container, row, 0, 0xFFFF);
default:
return new NumericParameterInput(handle, param, container, row);
}
}
static ParameterInput* makeParameterInput(RadioHandle handle, const RadioParameter param, const Modulation modulation, lv_obj_t* container, int row) {
switch (modulation) {
case MODULATION_LORA:
return makeLoraInput(handle, param, container, row);
default:
return new NumericParameterInput(handle, param, container, row);
}
}
class SettingsView {
private:
static constexpr size_t MAX_RADIOS = 32;
static constexpr Modulation FIRST_MODULATION = MODULATION_NONE;
static constexpr Modulation LAST_MODULATION = MODULATION_LRFHSS;
static constexpr size_t MAX_MODEMS = LAST_MODULATION + 1;
static constexpr size_t MAX_PARAMS = RADIO_NARROWGRID + 1;
RadioHandle radios[MAX_RADIOS] = {0};
size_t radioCount = 0;
RadioHandle radioSelected = nullptr;
Modulation modemsAvailable[MAX_MODEMS] = {};
size_t modemsAvailableCount = 0;
RadioParameter paramsAvailable[MAX_PARAMS] = {};
ParameterInput* paramInputs[MAX_PARAMS] = {0};
size_t paramsAvailableCount = 0;
lv_obj_t *mainPanel = nullptr;
lv_obj_t *deviceForm = nullptr;
lv_obj_t *radioDropdown = nullptr;
lv_obj_t *radioSwitch = nullptr;
lv_obj_t *modemDropdown = nullptr;
lv_obj_t *propertiesForm = nullptr;
public:
void queryRadios() {
DeviceId devices[MAX_RADIOS];
@ -68,60 +427,193 @@ public:
}
}
lv_obj_t* initGridDropdownInput(lv_obj_t *container, int row, const char* const label, const char* const items) {
lv_obj_t* label_obj = lv_label_create(container);
lv_label_set_text(label_obj, label);
lv_obj_set_grid_cell(label_obj,
LV_GRID_ALIGN_STRETCH, 0, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);
int getModemAvailableIndex(Modulation m) {
for (size_t i = 0; i < modemsAvailableCount; ++i) {
if (modemsAvailable[i] == m) {
return i;
}
}
return -1;
}
lv_obj_t* input = lv_dropdown_create(container);
lv_obj_set_grid_cell(input,
LV_GRID_ALIGN_STRETCH, 1, 1,
LV_GRID_ALIGN_CENTER, row, 1);
lv_obj_set_size(input, lv_pct(100), LV_SIZE_CONTENT);
lv_dropdown_set_options(input, items);
lv_obj_t* initParameterFormGeneric(lv_obj_t *parent, const Modulation modem) {
lv_obj_t *container = propertiesForm;
if (container) {
lv_obj_clean(container);
lv_obj_del(container);
}
return input;
paramsAvailableCount = 0;
container = lv_obj_create(parent);
lv_obj_set_style_pad_all(container, 0, 0);
lv_obj_set_layout(container, LV_LAYOUT_GRID);
lv_obj_align(container, LV_ALIGN_TOP_MID, 0, 0);
const int grid_row_size = 40;
const int grid_col_size = 140;
static constexpr size_t row_dsc_last = RADIO_NARROWGRID + 1;
static lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), grid_col_size, LV_GRID_TEMPLATE_LAST};
static lv_coord_t row_dsc[row_dsc_last] = {0};
for (size_t i = 0; i < row_dsc_last; ++i) {
row_dsc[i] = grid_row_size; //LV_GRID_FR(1);
}
row_dsc[row_dsc_last - 1] = LV_GRID_TEMPLATE_LAST;
lv_obj_set_grid_dsc_array(container, col_dsc, row_dsc);
char unit_buffer[32] = {0};
for (RadioParameter param = RADIO_POWER;
param <= RADIO_NARROWGRID;
param = static_cast<RadioParameter>((size_t)param + 1)) {
float value = 0.0;
Str value_buffer;
auto status = tt_hal_radio_get_parameter(radioSelected, param, &value);
if (status == RADIO_PARAM_SUCCESS) {
auto input = makeParameterInput(radioSelected, param, modem, container, paramsAvailableCount);
paramInputs[paramsAvailableCount] = input;
//lv_group_focus_obj(input);
paramsAvailable[paramsAvailableCount] = param;
paramsAvailableCount++;
}
}
row_dsc[paramsAvailableCount] = LV_GRID_TEMPLATE_LAST;
lv_obj_set_grid_dsc_array(container, col_dsc, row_dsc);
lv_obj_set_size(container, lv_pct(100), lv_pct(100));
return container;
}
void selectModulation(int modemIndex) {
lv_dropdown_set_selected(modemDropdown, modemIndex);
if (tt_hal_radio_set_modulation(radioSelected, modemsAvailable[modemIndex])) {
propertiesForm = initParameterFormGeneric(mainPanel, modemsAvailable[modemIndex]);
//clownvomit(propertiesForm);
}
}
void selectRadio(int index) {
radioSelected = radios[index];
assert(radioSelected);
for (size_t i = 0; i < MAX_MODEMS; ++i) {
modemsAvailable[i] = MODULATION_NONE;
}
Str modulation_list;
modemsAvailableCount = 1;
modemsAvailable[0] = MODULATION_NONE;
modulation_list.append(LV_SYMBOL_MINUS);
modulation_list.append(" ");
modulation_list.append(LV_SYMBOL_MINUS);
modulation_list.append(" ");
modulation_list.append("Disabled\n");
for (Modulation mod = FIRST_MODULATION;
mod <= LAST_MODULATION;
mod = static_cast<Modulation>((size_t)mod + 1)) {
bool canRx = tt_hal_radio_can_receive(radioSelected, mod);
bool canTx = tt_hal_radio_can_transmit(radioSelected, mod);
bool place_sep = (canRx || canTx) && (mod != LAST_MODULATION);
if (!canRx && !canTx) {
continue;
}
modemsAvailable[modemsAvailableCount] = mod;
modemsAvailableCount++;
if (canRx) {
modulation_list.append(LV_SYMBOL_DOWNLOAD);
modulation_list.append(" ");
} else {
modulation_list.append(LV_SYMBOL_MINUS);
modulation_list.append(" ");
}
if (canTx) {
modulation_list.append(LV_SYMBOL_UPLOAD);
modulation_list.append(" ");
} else {
modulation_list.append(LV_SYMBOL_MINUS);
modulation_list.append(" ");
}
modulation_list.append(toString(mod));
if (place_sep) {
modulation_list.append("\n");
}
}
lv_dropdown_set_options(modemDropdown, modulation_list.c_str());
auto modemIndexConfigured = getModemAvailableIndex(tt_hal_radio_get_modulation(radioSelected));
if (modemIndexConfigured > -1) {
lv_dropdown_set_selected(modemDropdown, modemIndexConfigured);
selectModulation(modemIndexConfigured);
}
}
lv_obj_t *initDeviceForm(lv_obj_t *parent) {
lv_obj_t *container = lv_obj_create(parent);
lv_obj_set_size(container, lv_pct(100), lv_pct(100));
lv_obj_set_size(container, lv_pct(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(container, 0, 0);
const int grid_row_size = 40;
const int grid_col_size = 45;
static lv_coord_t lora_col_dsc[] = {LV_GRID_FR(3), LV_GRID_FR(2), grid_col_size, LV_GRID_TEMPLATE_LAST};
static lv_coord_t lora_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), grid_col_size, LV_GRID_TEMPLATE_LAST};
static lv_coord_t lora_row_dsc[] = {
grid_row_size,
grid_row_size,
LV_GRID_TEMPLATE_LAST};
lv_obj_set_layout(container, LV_LAYOUT_GRID);
lv_obj_set_grid_dsc_array(container, lora_col_dsc, lora_row_dsc);
Str radio_names;
getRadioNames(radio_names, "\n");
radioDropdown = initGridDropdownInput(container, 0, "Device", radio_names.c_str());
modemDropdown = initGridDropdownInput(container, 1, "Modulation", "none available");
radioSwitch = lv_switch_create(container);
lv_obj_set_grid_cell(radioSwitch,
LV_GRID_ALIGN_STRETCH, 2, 1,
LV_GRID_ALIGN_CENTER, 0, 1);
lv_obj_set_size(radioSwitch, lv_pct(100), 20);
lv_obj_add_event_cb(modemDropdown, [](lv_event_t * e) {
SettingsView* self = (SettingsView*)lv_event_get_user_data(e);
lv_obj_t* input = lv_event_get_target_obj(e);
self->selectModulation(lv_dropdown_get_selected(input));
}, LV_EVENT_VALUE_CHANGED, this);
selectRadio(0);
return container;
}
void initUi(lv_obj_t *parent) {
lv_obj_t *container = lv_obj_create(parent);
lv_obj_set_size(container, lv_pct(100), lv_pct(80));
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
lv_obj_align(container, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_border_width(container, 0, 0);
lv_obj_set_style_pad_all(container, 0, 0);
mainPanel = lv_obj_create(parent);
lv_obj_set_size(mainPanel, lv_pct(100), lv_pct(80));
lv_obj_set_flex_flow(mainPanel, LV_FLEX_FLOW_COLUMN);
lv_obj_align(mainPanel, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_border_width(mainPanel, 0, 0);
lv_obj_set_style_pad_all(mainPanel, 0, 0);
// Only needed if container needs to be scrollable through encoder long long press
//lv_obj_add_flag(container, (lv_obj_flag_t)(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLL_ON_FOCUS));
deviceForm = initDeviceForm(container);
lv_obj_add_flag(mainPanel, (lv_obj_flag_t)(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLL_ON_FOCUS));
auto* group = lv_group_get_default();
lv_group_add_obj(group, mainPanel);
//lv_obj_add_event_cb(btn, scrollbar_highlight, LV_EVENT_FOCUS, NULL);
//lv_obj_add_event_cb(btn, scrollbar_restore, LV_EVENT_DEFOCUSED, NULL);
// Create once (e.g. during init)
static lv_style_t style_scroll_focus;
lv_style_init(&style_scroll_focus);
lv_style_set_bg_color(&style_scroll_focus, lv_color_make(0x40,0xA0,0xFF));
lv_style_set_bg_opa(&style_scroll_focus, LV_OPA_COVER);
lv_style_set_border_width(&style_scroll_focus, 1);
lv_style_set_border_color(&style_scroll_focus, lv_color_black());
// Apply style targeted to the scrollbar part when the object is FOCUSED
// LV_PART_SCROLLBAR | LV_STATE_FOCUSED will ensure the style is used only while focused.
lv_obj_add_style(mainPanel, &style_scroll_focus, LV_PART_SCROLLBAR | LV_STATE_FOCUSED);
deviceForm = initDeviceForm(mainPanel);
}
explicit SettingsView(lv_obj_t *parent) {
@ -141,12 +633,13 @@ void RadioSet::onShow(AppHandle appHandle, lv_obj_t* parent) {
uiDropDownMenu = lv_dropdown_create(toolbar);
lv_dropdown_set_options(uiDropDownMenu, LV_SYMBOL_ENVELOPE " Terminal\n" LV_SYMBOL_SETTINGS " Settings");
lv_dropdown_set_text(uiDropDownMenu, "Main Menu");
lv_dropdown_set_text(uiDropDownMenu, "Menu");
lv_dropdown_set_symbol(uiDropDownMenu, LV_SYMBOL_DOWN);
lv_dropdown_set_selected_highlight(uiDropDownMenu, true);
lv_obj_set_style_border_color(uiDropDownMenu, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
lv_obj_set_style_border_width(uiDropDownMenu, 1, LV_PART_MAIN);
lv_obj_align(uiDropDownMenu, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_set_width(uiDropDownMenu, 120);
lv_obj_t* wrapper = lv_obj_create(parent);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);

View File

@ -6,8 +6,8 @@ platforms=esp32,esp32s3
[app]
id=com.d49406.RadioSet
version=0.0.1
name=RadioSet
description=Receive and transmit radio messages
name=Radio Terminal
description=Receive and transmit radio packages
[author]
name=Dominic Hoeglinger
website=https://git.hoeglinger.eu
website=https://github.com/ByteWelder/Tactility