diff --git a/radio/src/bitmaps/480x272/default_theme/mask_model_grid_large.png b/radio/src/bitmaps/480x272/default_theme/mask_model_grid_large.png new file mode 100644 index 00000000000..862f7676929 Binary files /dev/null and b/radio/src/bitmaps/480x272/default_theme/mask_model_grid_large.png differ diff --git a/radio/src/bitmaps/480x272/default_theme/mask_model_grid_small.png b/radio/src/bitmaps/480x272/default_theme/mask_model_grid_small.png new file mode 100644 index 00000000000..b07da8fa8bf Binary files /dev/null and b/radio/src/bitmaps/480x272/default_theme/mask_model_grid_small.png differ diff --git a/radio/src/bitmaps/480x272/default_theme/mask_model_list_one.png b/radio/src/bitmaps/480x272/default_theme/mask_model_list_one.png new file mode 100644 index 00000000000..040b0227059 Binary files /dev/null and b/radio/src/bitmaps/480x272/default_theme/mask_model_list_one.png differ diff --git a/radio/src/bitmaps/480x272/default_theme/mask_model_list_two.png b/radio/src/bitmaps/480x272/default_theme/mask_model_list_two.png new file mode 100644 index 00000000000..c483cd71870 Binary files /dev/null and b/radio/src/bitmaps/480x272/default_theme/mask_model_list_two.png differ diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index 897896b7c78..47c5f4657bb 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -957,19 +957,20 @@ PACK(struct RadioData { // Radio level tabs control (global settings) #if defined(COLORLCD) - uint8_t radioThemesDisabled:1; + NOBACKUP(uint8_t modelSelectLayout:2); + NOBACKUP(uint8_t radioThemesDisabled:1); #endif - uint8_t radioGFDisabled:1; - uint8_t radioTrainerDisabled:1; + NOBACKUP(uint8_t radioGFDisabled:1); + NOBACKUP(uint8_t radioTrainerDisabled:1); // Model level tabs control (global setting) - uint8_t modelHeliDisabled:1; - uint8_t modelFMDisabled:1; - uint8_t modelCurvesDisabled:1; - uint8_t modelGVDisabled:1; - uint8_t modelLSDisabled:1; - uint8_t modelSFDisabled:1; - uint8_t modelCustomScriptsDisabled:1; - uint8_t modelTelemetryDisabled:1; + NOBACKUP(uint8_t modelHeliDisabled:1); + NOBACKUP(uint8_t modelFMDisabled:1); + NOBACKUP(uint8_t modelCurvesDisabled:1); + NOBACKUP(uint8_t modelGVDisabled:1); + NOBACKUP(uint8_t modelLSDisabled:1); + NOBACKUP(uint8_t modelSFDisabled:1); + NOBACKUP(uint8_t modelCustomScriptsDisabled:1); + NOBACKUP(uint8_t modelTelemetryDisabled:1); NOBACKUP(uint8_t getBrightness() const { diff --git a/radio/src/gui/colorlcd/bitmaps.cpp b/radio/src/gui/colorlcd/bitmaps.cpp index 04d4e490402..1b065f5ac1b 100644 --- a/radio/src/gui/colorlcd/bitmaps.cpp +++ b/radio/src/gui/colorlcd/bitmaps.cpp @@ -191,6 +191,18 @@ static const uint8_t stick_pointer[] = { static const uint8_t stick_background[] = { #include "alpha_stick_background.lbm" }; +static const uint8_t mask_model_grid_large[] = { +#include "mask_model_grid_large.lbm" +}; +static const uint8_t mask_model_grid_small[] = { +#include "mask_model_grid_small.lbm" +}; +static const uint8_t mask_model_list_one[] = { +#include "mask_model_list_one.lbm" +}; +static const uint8_t mask_model_list_two[] = { +#include "mask_model_list_two.lbm" +}; BitmapBuffer * calibStick = nullptr; BitmapBuffer * calibStickBackground = nullptr; @@ -285,6 +297,10 @@ static const _BuiltinIcon _builtinIcons[] = { {ICON_MONITOR_CHANNELS3, mask_monitor_channels3}, {ICON_MONITOR_CHANNELS4, mask_monitor_channels4}, {ICON_MONITOR_LOGICAL_SWITCHES, mask_monitor_logsw}, + {ICON_MODEL_GRID_LARGE, mask_model_grid_large}, + {ICON_MODEL_GRID_SMALL, mask_model_grid_small}, + {ICON_MODEL_LIST_TWO, mask_model_list_two}, + {ICON_MODEL_LIST_ONE, mask_model_list_one}, }; const uint8_t* getBuiltinIcon(MenuIcons id) diff --git a/radio/src/gui/colorlcd/menus.h b/radio/src/gui/colorlcd/menus.h index 5feae9011e7..dbdf43d38c3 100644 --- a/radio/src/gui/colorlcd/menus.h +++ b/radio/src/gui/colorlcd/menus.h @@ -78,6 +78,10 @@ enum MenuIcons { ICON_MONITOR_CHANNELS3, ICON_MONITOR_CHANNELS4, ICON_MONITOR_LOGICAL_SWITCHES, + ICON_MODEL_GRID_LARGE, + ICON_MODEL_GRID_SMALL, + ICON_MODEL_LIST_TWO, + ICON_MODEL_LIST_ONE, MENUS_ICONS_COUNT }; diff --git a/radio/src/gui/colorlcd/model_select.cpp b/radio/src/gui/colorlcd/model_select.cpp index 38eb09126fd..792f277a1eb 100644 --- a/radio/src/gui/colorlcd/model_select.cpp +++ b/radio/src/gui/colorlcd/model_select.cpp @@ -32,17 +32,32 @@ inline tmr10ms_t getTicks() { return g_tmr10ms; } -constexpr coord_t MODEL_CELL_PADDING = 6; -constexpr coord_t MODEL_SELECT_CELL_HEIGHT = 92; constexpr int BUTTONS_HEIGHT = 32; -constexpr int MODEL_CELLS_PER_LINE = 2; + +struct ModelButtonLayout { + uint16_t width; + uint16_t height; + uint16_t padding; + bool hsaImage; + uint16_t font; +}; + +ModelButtonLayout modelLayouts[] = { +#if LCD_W > LCD_H // Landscape + {165, 92, 6, true, FONT(STD)}, + {108, 61, 6, true, FONT(XS)}, + {165, 32, 4, false, FONT(STD)}, + {336, 32, 4, false, FONT(STD)}, +#else // Portrait + {147, 92, 6, true, FONT(STD)}, + {96, 61, 6, true, FONT(XS)}, + {147, 32, 4, false, FONT(STD)}, + {300, 32, 4, false, FONT(STD)}, +#endif +}; #if LCD_W > LCD_H // Landscape -constexpr int LABELS_WIDTH = 132; constexpr int LAY_MARGIN = 5; -constexpr coord_t MODEL_SELECT_CELL_WIDTH = - (LCD_W - LABELS_WIDTH - (MODEL_CELLS_PER_LINE + 1) * MODEL_CELL_PADDING) / - MODEL_CELLS_PER_LINE; constexpr int LABELS_ROW = 0; constexpr int MODELS_COL = 1; constexpr int MODELS_ROW = 0; @@ -50,10 +65,6 @@ constexpr int MODELS_ROW_CNT = 2; constexpr int BUTTONS_ROW = 1; #else // Portrait constexpr int LAY_MARGIN = 8; -constexpr int LABELS_HEIGHT = 140; -constexpr coord_t MODEL_SELECT_CELL_WIDTH = - (LCD_W - LAY_MARGIN - (MODEL_CELLS_PER_LINE + 1) * MODEL_CELL_PADDING) / - MODEL_CELLS_PER_LINE; constexpr int LABELS_ROW = 1; constexpr int MODELS_COL = 0; constexpr int MODELS_ROW = 0; @@ -65,16 +76,17 @@ class ModelButton : public Button { public: ModelButton(FormWindow *parent, const rect_t &rect, ModelCell *modelCell, - std::function setSelected) : + std::function setSelected, uint8_t layout) : Button(parent, rect, nullptr, 0, 0, etx_button_create), + layout(layout), modelCell(modelCell), m_setSelected(std::move(setSelected)) { padAll(0); lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE); - setWidth(MODEL_SELECT_CELL_WIDTH); - setHeight(MODEL_SELECT_CELL_HEIGHT); + setWidth(modelLayouts[layout].width); + setHeight(modelLayouts[layout].height); check(modelCell == modelslist.getCurrentModel()); @@ -98,38 +110,53 @@ class ModelButton : public Button coord_t w = width() - 8; coord_t h = height() - 8; - GET_FILENAME(filename, BITMAPS_PATH, modelCell->modelBitmap, ""); - const BitmapBuffer *bitmap = BitmapBuffer::loadBitmap(filename); - - if (bitmap) { - buffer = new BitmapBuffer(BMP_RGB565, w, h); - if (buffer) { - buffer->clear(bg_color); - buffer->drawScaledBitmap(bitmap, 0, 0, w, h); - delete bitmap; + LcdFlags font = modelLayouts[layout].font; + if ((getTextWidth(modelCell->modelName, 0, font) > w)) + font = (font == FONT(STD)) ? FONT(XS) : FONT(XXS); + + if (modelLayouts[layout].hsaImage) { + GET_FILENAME(filename, BITMAPS_PATH, modelCell->modelBitmap, ""); + const BitmapBuffer *bitmap = BitmapBuffer::loadBitmap(filename); + + if (bitmap) { + buffer = new BitmapBuffer(BMP_RGB565, w, h); + if (buffer) { + buffer->clear(bg_color); + buffer->drawScaledBitmap(bitmap, 0, 0, w, h); + delete bitmap; + + lv_obj_t *bm = lv_canvas_create(lvobj); + lv_obj_center(bm); + lv_canvas_set_buffer(bm, buffer->getData(), buffer->width(), + buffer->height(), LV_IMG_CF_TRUE_COLOR); + } + } - lv_obj_t *bm = lv_canvas_create(lvobj); - lv_obj_center(bm); - lv_canvas_set_buffer(bm, buffer->getData(), buffer->width(), - buffer->height(), LV_IMG_CF_TRUE_COLOR); + if (!buffer) { + std::string errorMsg = "("; + errorMsg += STR_NO_PICTURE; + errorMsg += ")"; + new StaticText(this, {2, h / 2, w, 17}, errorMsg, 0, + CENTERED | COLOR_THEME_SECONDARY1 | FONT(XS)); } - } - if (!buffer) { - std::string errorMsg = "("; - errorMsg += STR_NO_PICTURE; - errorMsg += ")"; - new StaticText(this, {2, h / 2, w, 17}, errorMsg, 0, - CENTERED | COLOR_THEME_SECONDARY1 | FONT(XS)); + coord_t fh = (font == FONT(STD)) ? 17 : (font == FONT(XS)) ? 14 : 11; + coord_t fo = (font == FONT(STD)) ? -3 : (font == FONT(XS)) ? -3 : -1; + + auto name = new StaticText(this, {2, 2, w, fh}, modelCell->modelName, 0, + CENTERED | COLOR_THEME_SECONDARY1 | font); + lv_label_set_long_mode(name->getLvObj(), LV_LABEL_LONG_DOT); + name->setWidth(w); + name->setHeight(fh); + name->setBackgroudOpacity(LV_OPA_80); + name->setBackgroundColor(bg_color); + name->padTop(fo); + } else { + auto name = new StaticText(this, {2, 4, w, 24}, modelCell->modelName, 0, + COLOR_THEME_SECONDARY1 | font); + lv_label_set_long_mode(name->getLvObj(), LV_LABEL_LONG_DOT); + name->setWidth(w); } - - auto name = - new StaticText(this, {2, 2, width() - 8, 17}, modelCell->modelName, 0, - CENTERED | COLOR_THEME_SECONDARY1); - name->setHeight(17); - name->setBackgroudOpacity(LV_OPA_80); - name->setBackgroundColor(bg_color); - name->padTop(-3); } static void on_draw(lv_event_t *e) @@ -157,6 +184,7 @@ class ModelButton : public Button protected: bool loaded = false; + uint8_t layout; ModelCell *modelCell; BitmapBuffer *buffer = nullptr; std::function m_setSelected = nullptr; @@ -182,14 +210,16 @@ class ModelsPageBody : public FormWindow #else padLeft(LAY_MARGIN); #endif - setFlexLayout(LV_FLEX_FLOW_ROW_WRAP, MODEL_CELL_PADDING); - padRow(MODEL_CELL_PADDING); } void update() { clear(); + setFlexLayout(LV_FLEX_FLOW_ROW_WRAP, + modelLayouts[g_eeGeneral.modelSelectLayout].padding); + padRow(modelLayouts[g_eeGeneral.modelSelectLayout].padding); + ModelsVector models; if (selectedLabels.size()) { models = modelslabels.getModelsInLabels(selectedLabels); @@ -206,8 +236,9 @@ class ModelsPageBody : public FormWindow ModelButton *focusedButton = nullptr; for (auto &model : models) { - auto button = new ModelButton(this, rect_t{}, model, - [=]() { focusedModel = model; }); + auto button = new ModelButton( + this, rect_t{}, model, [=]() { focusedModel = model; }, + g_eeGeneral.modelSelectLayout); if (!firstButton) firstButton = button; if (model == modelslist.getCurrentModel()) focusedButton = button; @@ -481,6 +512,46 @@ class LabelDialog : public Dialog //----------------------------------------------------------------------------- +class ModelLayoutButton : public TextButton +{ + public: + ModelLayoutButton(Window *parent, std::function pressHandler) : + TextButton(parent, {0, 0, 32, 32}, "", pressHandler) + { + } + + ~ModelLayoutButton() + { + if (bmBuffer) delete bmBuffer; + } + + uint8_t getLayout() const { return layout; } + + void setLayout(uint8_t newLayout) + { + layout = newLayout; + invalidate(); + } + + void paint(BitmapBuffer *dc) override + { + auto maskData = getBuiltinIcon((MenuIcons)(ICON_MODEL_GRID_LARGE + layout)); + auto mask = BitmapBuffer::load8bitMaskLZ4(maskData); + if (!bmBuffer) + bmBuffer = new BitmapBuffer(BMP_RGB565, mask->width(), mask->height()); + bmBuffer->clear(COLOR_THEME_PRIMARY2); + bmBuffer->drawMask(0, 0, mask, COLOR_THEME_SECONDARY1); + delete mask; + dc->drawBitmap(3, 3, bmBuffer); + } + + protected: + uint8_t layout; + BitmapBuffer *bmBuffer = nullptr; +}; + +//----------------------------------------------------------------------------- + ModelLabelsWindow::ModelLabelsWindow() : Page(ICON_MODEL) { buildHead(&header); @@ -629,7 +700,7 @@ void ModelLabelsWindow::buildHead(PageHeader *hdr) setTitle(); // new model button - auto btn = new TextButton(hdr, rect_t{}, STR_NEW, [=]() { + auto btn = new TextButton(hdr, rect_t{0, 0, 60, 32}, STR_NEW, [=]() { auto menu = new Menu(this); menu->setTitle(STR_CREATE_NEW); menu->addLine(STR_NEW_MODEL, [=]() { newModel(); }); @@ -642,14 +713,28 @@ void ModelLabelsWindow::buildHead(PageHeader *hdr) // button placement hdr->padRight(lv_dpx(8)); lv_obj_align(btn->getLvObj(), LV_ALIGN_RIGHT_MID, 0, 0); + + mldLayout = new ModelLayoutButton(this, [=]() { + uint8_t l = mldLayout->getLayout(); + l = (l + 1) & 3; + mldLayout->setLayout(l); + g_eeGeneral.modelSelectLayout = l; + storageDirty(EE_GENERAL); + mdlselector->update(); + return 0; + }); + mldLayout->setLayout(g_eeGeneral.modelSelectLayout); + lv_obj_set_pos(mldLayout->getLvObj(), LCD_W - 105, 6); } #if LCD_W > LCD_H +constexpr int LABELS_WIDTH = 132; static const lv_coord_t col_dsc[] = {LABELS_WIDTH, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST}; static const lv_coord_t row_dsc[] = {LV_GRID_FR(1), BUTTONS_HEIGHT, LV_GRID_TEMPLATE_LAST}; #else +constexpr int LABELS_HEIGHT = 140; static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST}; static const lv_coord_t row_dsc[] = {LV_GRID_FR(1), LABELS_HEIGHT, BUTTONS_HEIGHT, LV_GRID_TEMPLATE_LAST}; @@ -672,6 +757,9 @@ void ModelLabelsWindow::buildBody(FormWindow *window) lv_obj_set_grid_cell(mdl_obj, LV_GRID_ALIGN_STRETCH, MODELS_COL, 1, LV_GRID_ALIGN_STRETCH, MODELS_ROW, MODELS_ROW_CNT); + if (mdlselector->getSortOrder() == NO_SORT) + mdlselector->setSortOrder(NAME_ASC); + // Labels auto box = new Window(window, rect_t{}); box->padAll(0); diff --git a/radio/src/gui/colorlcd/model_select.h b/radio/src/gui/colorlcd/model_select.h index 0ec1ffc01f5..2051e7772cf 100644 --- a/radio/src/gui/colorlcd/model_select.h +++ b/radio/src/gui/colorlcd/model_select.h @@ -27,6 +27,7 @@ #include "tabsgroup.h" class ModelsPageBody; +class ModelLayoutButton; class ModelLabelsWindow : public Page { @@ -38,6 +39,7 @@ class ModelLabelsWindow : public Page char tmpLabel[LABEL_LENGTH + 1] = "\0"; ListBox *lblselector; ModelsPageBody *mdlselector; + ModelLayoutButton *mldLayout; std::string currentLabel; LabelsVector getLabels() diff --git a/radio/src/storage/modelslist.cpp b/radio/src/storage/modelslist.cpp index 55721f41163..ddd59798bae 100644 --- a/radio/src/storage/modelslist.cpp +++ b/radio/src/storage/modelslist.cpp @@ -840,7 +840,7 @@ bool ModelMap::updateModelFile(ModelCell *cell) /** * @brief Sorts a ModelsVector by sortby * - * @param mv ModeslVector to sort + * @param mv ModelsVector to sort * @param sortby NO_SORT, NAME_ASC, NAME_DES, DATE_ASC, DATE_DES, */ diff --git a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp index fc448baa07b..99636753747 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp @@ -367,6 +367,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_SIGNED( "uartSampleMode", 2 ), YAML_UNSIGNED( "stickDeadZone", 3 ), YAML_STRING("selectedTheme", 26), + YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), YAML_UNSIGNED( "radioGFDisabled", 1 ), YAML_UNSIGNED( "radioTrainerDisabled", 1 ), diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index 408b5db261e..7b3aa92091f 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -367,6 +367,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_SIGNED( "uartSampleMode", 2 ), YAML_UNSIGNED( "stickDeadZone", 3 ), YAML_STRING("selectedTheme", 26), + YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), YAML_UNSIGNED( "radioGFDisabled", 1 ), YAML_UNSIGNED( "radioTrainerDisabled", 1 ), diff --git a/radio/src/storage/yaml/yaml_datastructs_x10.cpp b/radio/src/storage/yaml/yaml_datastructs_x10.cpp index f2fdf8a1bed..abbfb21f809 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x10.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x10.cpp @@ -369,6 +369,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_SIGNED( "imuMax", 8 ), YAML_SIGNED( "imuOffset", 8 ), YAML_STRING("selectedTheme", 26), + YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), YAML_UNSIGNED( "radioGFDisabled", 1 ), YAML_UNSIGNED( "radioTrainerDisabled", 1 ), diff --git a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp index f2fdf8a1bed..abbfb21f809 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp @@ -369,6 +369,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_SIGNED( "imuMax", 8 ), YAML_SIGNED( "imuOffset", 8 ), YAML_STRING("selectedTheme", 26), + YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), YAML_UNSIGNED( "radioGFDisabled", 1 ), YAML_UNSIGNED( "radioTrainerDisabled", 1 ),