Skip to content

Commit

Permalink
feat(color): Single select option for model labels. label AND/OR filt…
Browse files Browse the repository at this point in the history
…ering logic (#4532)
  • Loading branch information
philmoz authored Jan 26, 2024
1 parent 63bb205 commit 2c01bee
Show file tree
Hide file tree
Showing 32 changed files with 343 additions and 56 deletions.
6 changes: 6 additions & 0 deletions radio/src/datastructs_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,12 @@ PACK(struct RadioData {
NOBACKUP(uint8_t modelCustomScriptsDisabled:1);
NOBACKUP(uint8_t modelTelemetryDisabled:1);

#if defined(COLORLCD)
uint8_t labelSingleSelect:1; // 0 = multi-select, 1 = single select labels
uint8_t labelMultiMode:1; // 0 = match all labels (AND), 1 = match any labels (OR)
uint8_t favMultiMode:1; // 0 = match all (AND), 1 = match any (OR)
#endif

NOBACKUP(uint8_t getBrightness() const
{
#if defined(OLED_SCREEN)
Expand Down
17 changes: 13 additions & 4 deletions radio/src/gui/colorlcd/listbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ void ListBase::setLineHeight(uint8_t height)
lv_obj_set_style_max_height(lvobj, height, LV_PART_ITEMS);
}

void ListBase::setSelected(int selected)
void ListBase::setSelected(int selected, bool force)
{
select(selected, 0);
select(selected, 0, force);
}

void ListBase::setSelected(std::set<uint32_t> selected)
Expand All @@ -127,6 +127,11 @@ int ListBase::getSelected() const
return -1;
}

bool ListBase::isRowSelected(uint16_t row)
{
return lv_table_has_cell_ctrl(lvobj, row, 0, LV_TABLE_CELL_CTRL_CUSTOM_1);
}

std::set<uint32_t> ListBase::getSelection()
{
if(!multiSelect) return std::set<uint32_t>();
Expand Down Expand Up @@ -212,9 +217,13 @@ void ListBase::onDrawEnd(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc
lv_coord_t font_h = getFontHeight(FONT(STD));

coords.x1 = dsc->draw_area->x2 - cell_right - font_h;
coords.x2 = coords.x1 + font_h;
coords.x2 = coords.x1 + 36;
coords.y1 = dsc->draw_area->y1 + (area_h - font_h) / 2;
coords.y2 = coords.y1 + font_h - 1;

lv_draw_label(dsc->draw_ctx, dsc->label_dsc, &coords, LV_SYMBOL_OK, nullptr);
const char* sym = LV_SYMBOL_OK;
if (getSelectedSymbol)
sym = getSelectedSymbol(row);

lv_draw_label(dsc->draw_ctx, dsc->label_dsc, &coords, sym, nullptr);
}
9 changes: 8 additions & 1 deletion radio/src/gui/colorlcd/listbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ListBase : public TableField
std::function<void()> longPressHandler = nullptr;
std::function<void()> pressHandler = nullptr;
std::function<void(std::set<uint32_t>, std::set<uint32_t>)> _multiSelectHandler = nullptr;
std::function<const char*(uint16_t row)> getSelectedSymbol = nullptr;
bool autoEdit = false;

public:
Expand All @@ -49,10 +50,11 @@ class ListBase : public TableField
void setNames(const std::vector<std::string>& names);
void setLineHeight(uint8_t height);

virtual void setSelected(int selected);
virtual void setSelected(int selected, bool force = false);
virtual void setSelected(std::set<uint32_t> selected);

int getSelected() const;
bool isRowSelected(uint16_t row);
std::set<uint32_t> getSelection();

void setMultiSelect(bool mode) {
Expand All @@ -67,6 +69,11 @@ class ListBase : public TableField
_multiSelectHandler = std::move(handler);
}

void setGetSelectedSymbol(std::function<const char*(uint16_t)> handler)
{
getSelectedSymbol = std::move(handler);
}

void setLongPressHandler(std::function<void()> handler)
{
longPressHandler = std::move(handler);
Expand Down
119 changes: 86 additions & 33 deletions radio/src/gui/colorlcd/model_select.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -604,26 +604,38 @@ void ModelLabelsWindow::onLongPressTELE()
}
void ModelLabelsWindow::onPressPG(bool isNext)
{
std::set<uint32_t> curSel = lblselector->getSelection();
std::set<uint32_t> sellist;
int select = 0;
int rowcount = lblselector->getRowCount();
std::set<uint32_t> sellist;
int select = -1;

if (g_eeGeneral.labelSingleSelect) {
select = lblselector->getActiveItem();
} else {
std::set<uint32_t> sel = lblselector->getSelection();
if (sel.size()) {
if (isNext)
select = *sel.rbegin();
else
select = *sel.begin();
}
}

if (isNext) {
if (curSel.size()) select = (*curSel.rbegin() + 1) % rowcount;
select = (select + 1) % rowcount;
} else {
if (curSel.size()) {
select = (int)*curSel.begin() - 1;
if (select < 0) select += rowcount;
} else {
select = select - 1;
if (select < 0)
select = rowcount - 1;
}
}

sellist.insert(select);
if (g_eeGeneral.labelSingleSelect)
lblselector->setActiveItem(select);

if (select >= 0)
sellist.insert(select);
lblselector->setSelected(sellist); // Check the items
lblselector->setSelected(-1); // Force update
lblselector->setSelected(select); // Causes the list to scroll
lblselector->setSelected(select, true); // Causes the list to scroll

updateFilteredLabels(sellist); // Update the models
}
void ModelLabelsWindow::onPressPGUP() { onPressPG(false); }
Expand Down Expand Up @@ -789,32 +801,73 @@ void ModelLabelsWindow::buildBody(FormWindow *window)
lv_obj_update_layout(mdl_obj);
lblselector->setColumnWidth(0, lv_obj_get_content_width(lbl_obj));

lblselector->setMultiSelect(true);
lblselector->setSelected(modelslabels.filteredLabels());
updateFilteredLabels(modelslabels.filteredLabels(), false);
lv_obj_set_scrollbar_mode(lbl_obj, LV_SCROLLBAR_MODE_AUTO);
lv_obj_set_scrollbar_mode(mdl_obj, LV_SCROLLBAR_MODE_AUTO);

lblselector->setMultiSelectHandler([=](std::set<uint32_t> selected,
std::set<uint32_t> oldselection) {
if (modelslabels.getUnlabeledModels().size() != 0) {
// Special case for mutually exclusive Unsorted
bool unsrt_is_selected =
selected.find(lblselector->getRowCount() - 1) != selected.end();
bool unsrt_was_selected = oldselection.find(lblselector->getRowCount() -
1) != oldselection.end();

// Unsorted was just picked
if (unsrt_is_selected && !unsrt_was_selected) {
selected.clear();
selected.insert(lblselector->getRowCount() - 1);
} else if (unsrt_is_selected && unsrt_was_selected) {
selected.erase(selected.find(lblselector->getRowCount() - 1));
std::set<uint32_t> filteredLabels = modelslabels.filteredLabels();

if (g_eeGeneral.labelSingleSelect == 0) {
lblselector->setMultiSelect(true);
lblselector->setSelected(filteredLabels);
lblselector->setMultiSelectHandler([=](std::set<uint32_t> selected,
std::set<uint32_t> oldselection) {
if (modelslabels.getUnlabeledModels().size() != 0) {
// Special case for mutually exclusive Unsorted
bool unsrt_is_selected =
selected.find(lblselector->getRowCount() - 1) != selected.end();
bool unsrt_was_selected = oldselection.find(lblselector->getRowCount() -
1) != oldselection.end();

// Unsorted was just picked
if (unsrt_is_selected && !unsrt_was_selected) {
selected.clear();
selected.insert(lblselector->getRowCount() - 1);
} else if (unsrt_is_selected && unsrt_was_selected) {
selected.erase(selected.find(lblselector->getRowCount() - 1));
}
}
}

lblselector->setSelected(selected);
updateFilteredLabels(selected);
lblselector->setSelected(selected);
updateFilteredLabels(selected);
});
} else {
if (filteredLabels.size() > 0)
lblselector->setActiveItem(*filteredLabels.begin());
lblselector->setPressHandler([=]() {
int item = lblselector->getActiveItem();
int selected = lblselector->getSelected();
std::set<uint32_t> newset;
// Clicking active label unselects it and selects all models
if (selected == item) {
lblselector->setActiveItem(-1);
} else {
lblselector->setActiveItem(selected);
newset.insert(selected);
}
updateFilteredLabels(newset);
});
}

updateFilteredLabels(filteredLabels, false);

lblselector->setGetSelectedSymbol([=](uint16_t row) {
if (g_eeGeneral.labelSingleSelect)
return LV_SYMBOL_OK;
if (lblselector->getSelection().size() == 1)
return LV_SYMBOL_OK;
bool hasMoreSelections = false;
for (uint16_t i = row + 1; i < lblselector->getRowCount(); i += 1)
if (lblselector->isRowSelected(i)) {
hasMoreSelections = true;
break;
}
if (!hasMoreSelections)
return LV_SYMBOL_OK;
if (row == 0 && (g_eeGeneral.labelMultiMode == 0 || g_eeGeneral.favMultiMode == 0))
return STR_VCSWFUNC[7]; // AND
if (g_eeGeneral.labelMultiMode == 0)
return STR_VCSWFUNC[7]; // AND
return STR_VCSWFUNC[8]; // OR
});

lblselector->setLongPressHandler([=]() {
Expand Down
76 changes: 71 additions & 5 deletions radio/src/gui/colorlcd/radio_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "opentx.h"
#include "libopenui.h"
#include "input_mapping.h"
#include "storage/modelslist.h"

#include "tasks/mixer_task.h"
#include "hal/adc_driver.h"
Expand Down Expand Up @@ -677,6 +678,71 @@ class ViewOptionsPage : public SubPage
}
};

class ManageModelsSetupPage : public SubPage
{
public:
ManageModelsSetupPage() : SubPage(ICON_MODEL, STR_MANAGE_MODELS, false)
{
FlexGridLayout grid(col_two_dsc, row_dsc, 2);

auto form = new FormWindow(&body, rect_t{});
form->setFlexLayout();
form->padAll(0);

// Model quick select
auto line = form->newLine(&grid);
new StaticText(line, rect_t{}, STR_MODEL_QUICK_SELECT, 0,
COLOR_THEME_PRIMARY1);
new ToggleSwitch(line, rect_t{},
GET_SET_DEFAULT(g_eeGeneral.modelQuickSelect));

// Label single/multi select
line = form->newLine(&grid);
new StaticText(line, rect_t{}, STR_LABELS_SELECT, 0, COLOR_THEME_PRIMARY1);
new Choice(line, rect_t{}, STR_LABELS_SELECT_MODE, 0, 1,
GET_DEFAULT(g_eeGeneral.labelSingleSelect),
[=](int newValue) {
g_eeGeneral.labelSingleSelect = newValue;
modelslabels.clearFilter();
SET_DIRTY();
});

// Label multi select matching mode
multiSelectMatch = form->newLine(&grid);
new StaticText(multiSelectMatch, rect_t{}, STR_LABELS_MATCH, 0,
COLOR_THEME_PRIMARY1);
new Choice(multiSelectMatch, rect_t{}, STR_LABELS_MATCH_MODE, 0, 1,
GET_SET_DEFAULT(g_eeGeneral.labelMultiMode));

// Favorites multi select matching mode
favSelectMatch = form->newLine(&grid);
new StaticText(favSelectMatch, rect_t{}, STR_FAV_MATCH, 0,
COLOR_THEME_PRIMARY1);
new Choice(favSelectMatch, rect_t{}, STR_FAV_MATCH_MODE, 0, 1,
GET_SET_DEFAULT(g_eeGeneral.favMultiMode));

checkEvents();
}

void checkEvents() override
{
if (g_eeGeneral.labelSingleSelect) {
lv_obj_add_flag(multiSelectMatch->getLvObj(), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(favSelectMatch->getLvObj(), LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(multiSelectMatch->getLvObj(), LV_OBJ_FLAG_HIDDEN);
if (g_eeGeneral.labelMultiMode == 0)
lv_obj_add_flag(favSelectMatch->getLvObj(), LV_OBJ_FLAG_HIDDEN);
else
lv_obj_clear_flag(favSelectMatch->getLvObj(), LV_OBJ_FLAG_HIDDEN);
}
}

protected:
Window* multiSelectMatch = nullptr;
Window* favSelectMatch = nullptr;
};

RadioSetupPage::RadioSetupPage():
PageTab(STR_RADIO_SETUP, ICON_RADIO_SETUP)
{
Expand All @@ -690,6 +756,10 @@ void RadioSetupPage::build(FormWindow * window)
// Date & time picker including labels
new DateTimeWindow(window, rect_t{});

// TODO: sort out all caps title strings VS quick menu strings
std::string manageModelsTitle(STR_MAIN_MENU_MANAGE_MODELS);
std::replace(manageModelsTitle.begin(), manageModelsTitle.end(), '\n', ' ');

// Sub-pages
new WindowButtonGroup(window, rect_t{}, {
{STR_SOUND_LABEL, []() { new SoundPage(); }},
Expand All @@ -703,6 +773,7 @@ void RadioSetupPage::build(FormWindow * window)
{STR_BACKLIGHT_LABEL, []() { new BacklightPage(); }},
{STR_GPS, [](){new GpsPage();}},
{STR_ENABLED_FEATURES, [](){new ViewOptionsPage();}},
{manageModelsTitle.c_str(), []() { new ManageModelsSetupPage(); }},
});

// Splash screen
Expand Down Expand Up @@ -867,9 +938,4 @@ void RadioSetupPage::build(FormWindow * window)
std::string(getMainControlLabel(stick0)) + "+" +
std::string(getMainControlLabel(stick1));
});

// Model quick select
line = window->newLine(&grid);
new StaticText(line, rect_t{}, STR_MODEL_QUICK_SELECT, 0, COLOR_THEME_PRIMARY1);
new ToggleSwitch(line, rect_t{}, GET_SET_DEFAULT(g_eeGeneral.modelQuickSelect));
}
32 changes: 26 additions & 6 deletions radio/src/storage/modelslist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ ModelsVector ModelMap::getModelsByLabels(const LabelsVector &lbls)
}

/**
* @brief Returns all models that are in multiple labels (AND function)
* @brief Returns all models that match the selected labels
*
* @param lbls Labels to search
* @return ModelsVector aka vector<ModelCell*> of all models belonging to a
Expand All @@ -229,17 +229,37 @@ ModelsVector ModelMap::getModelsInLabels(const LabelsVector &lbls)

for (const auto &mdl : modelslist) {
bool hasAllLabels = true;
bool hasAnyLabels = false;
bool favLabelIncluded = false;
bool hasFavLabel = false;
LabelsVector mdllables = getLabelsByModel(mdl);
for (const auto &lbl : lbls) {
if (lbl == STR_UNLABELEDMODEL) // If requesting unlabeled model ignore it
break;
if (std::find(mdllables.begin(), mdllables.end(), lbl) ==
mdllables.end()) {
hasAllLabels = false;
break;
bool hasLabel = std::find(mdllables.begin(), mdllables.end(), lbl) != mdllables.end();
if (lbl == STR_FAVORITE_LABEL) {
favLabelIncluded = true;
hasFavLabel = hasLabel;
} else {
if (hasLabel) {
hasAnyLabels = true;
} else {
hasAllLabels = false;
}
}
}
if (favLabelIncluded) {
if (g_eeGeneral.favMultiMode == 0) {
hasAnyLabels = hasAnyLabels && hasFavLabel;
hasAllLabels = hasAllLabels && hasFavLabel;
} else if (g_eeGeneral.favMultiMode == 1) {
hasAnyLabels = hasAnyLabels || hasFavLabel;
hasAllLabels = hasAllLabels && hasFavLabel;
}
}
if (hasAllLabels) rv.push_back(mdl);
if (((g_eeGeneral.labelMultiMode == 0) && hasAllLabels) ||
((g_eeGeneral.labelMultiMode == 1) && hasAnyLabels))
rv.push_back(mdl);
}

sortModelsBy(rv, _sortOrder);
Expand Down
Loading

0 comments on commit 2c01bee

Please sign in to comment.