diff --git a/radio/src/gui/colorlcd/listbox.cpp b/radio/src/gui/colorlcd/listbox.cpp index 412df568e..bc38fa32d 100644 --- a/radio/src/gui/colorlcd/listbox.cpp +++ b/radio/src/gui/colorlcd/listbox.cpp @@ -51,12 +51,48 @@ ListBase::ListBase(Window *parent, const rect_t &rect, std::vector void ListBase::setSelected(int selected) { - if (selected >= 0 && selected < (int)names.size() && selected != this->selected) { - this->selected = selected; - setScrollPositionY(lineHeight * this->selected - lineHeight); - if (_setValue != nullptr) { - _setValue(this->selected); + int count = (int)names.size(); + if(selected < 0 || selected >= count) + return; + + if(selectionType == LISTBOX_SINGLE_SELECT) { + if (selected != this->selected) { + this->selected = selected; + setScrollPositionY(lineHeight * this->selected - lineHeight); + if (_setValue != nullptr) { + _setValue(this->selected); + } + invalidate(); } + } else if(selectionType == LISTBOX_MULTI_SELECT) { + this->selected = selected; + if(selectedIndexes.find(selected) != selectedIndexes.end()) { // Already selected -> Deselect it + selectedIndexes.erase(selected); + setScrollPositionY(lineHeight * this->selected - lineHeight); + if(_multiSelectHandler != nullptr) + _multiSelectHandler(selectedIndexes); + invalidate(); + } else { // Not Selected -> Select it + selectedIndexes.insert(selected); + setScrollPositionY(lineHeight * this->selected - lineHeight); + if (_setValue != nullptr) { + _setValue(this->selected); + } + if(_multiSelectHandler != nullptr) + _multiSelectHandler(selectedIndexes); + invalidate(); + } + } +} + +void ListBase::setFocusLine(int selected) +{ + if(selectionType == LISTBOX_MULTI_SELECT) { + int count = (int)names.size(); + if(selected < 0 || selected >= count) + return; + + this->selected = selected; invalidate(); } } @@ -83,9 +119,23 @@ void ListBase::paint(BitmapBuffer *dc) int curY = 0; for (int n = 0; n < (int)names.size(); n++) { - dc->drawSolidFilledRect(1, curY, rect.w - 2, lineHeight, n == selected ? COLOR_THEME_FOCUS : COLOR_THEME_PRIMARY2); + LcdFlags bgcolor = COLOR_THEME_PRIMARY2; + LcdFlags textColor = COLOR_THEME_SECONDARY1; - LcdFlags textColor = n == selected ? COLOR_THEME_PRIMARY2 : COLOR_THEME_SECONDARY1; + // Color selected line(s) + if((selectionType == LISTBOX_SINGLE_SELECT && n == selected) || + (selectionType == LISTBOX_MULTI_SELECT && (selectedIndexes.find(n) != selectedIndexes.end()))) { + bgcolor = COLOR_THEME_FOCUS; + textColor = COLOR_THEME_PRIMARY2; + } + + dc->drawSolidFilledRect(1, curY, rect.w - 2, lineHeight, bgcolor); + + // Draw a dotted line around the current item + if(n == selected && selectionType == LISTBOX_MULTI_SELECT && + hasFocus()) { + dc->drawRect(2, curY, rect.w - 4, lineHeight, 2, DOTTED); + } auto fontHeight = getFontHeight(FONT(STD)); drawLine(dc, { 8, curY + (lineHeight - fontHeight) / 2, rect.w, lineHeight}, n, textColor); @@ -105,13 +155,19 @@ void ListBase::paint(BitmapBuffer *dc) switch (event) { case EVT_ROTARY_RIGHT: oldSelected = (selected + 1) % names.size(); - setSelected(oldSelected); + if(selectionType == LISTBOX_SINGLE_SELECT) + setSelected(oldSelected); + else + setFocusLine(oldSelected); onKeyPress(); break; case EVT_ROTARY_LEFT: oldSelected--; if (oldSelected < 0) oldSelected = names.size() - 1; - setSelected(oldSelected); + if(selectionType == LISTBOX_SINGLE_SELECT) + setSelected(oldSelected); + else + setFocusLine(oldSelected); onKeyPress(); break; case EVT_KEY_LONG(KEY_ENTER): @@ -121,6 +177,8 @@ void ListBase::paint(BitmapBuffer *dc) } break; case EVT_KEY_BREAK(KEY_ENTER): + if(selectionType == LISTBOX_MULTI_SELECT) + setSelected(this->selected); if (pressHandler) { killEvents(event); pressHandler(event); @@ -147,10 +205,14 @@ void ListBase::checkEvents(void) if (isLongPress()) { if (longPressHandler) { - setSelected(yDown / lineHeight); + if(selectionType == LISTBOX_SINGLE_SELECT) + setSelected(yDown / lineHeight); + else + setFocusLine(yDown / lineHeight); longPressHandler(0); killAllEvents(); duration10ms = 0; + waslongpress = true; return; } } @@ -158,15 +220,16 @@ void ListBase::checkEvents(void) bool ListBase::onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) { - if (touchState.event == TE_SLIDE_END) { + if (touchState.event == TE_SLIDE_END) { duration10ms = 0; } - + return FormField::onTouchSlide(x, y, startX, startY, slideX, slideY); } bool ListBase::onTouchStart(coord_t x, coord_t y) { + waslongpress = false; if (duration10ms == 0) { duration10ms = getTicks(); } @@ -180,7 +243,7 @@ bool ListBase::onTouchStart(coord_t x, coord_t y) bool ListBase::onTouchEnd(coord_t x, coord_t y) { if (!isEnabled()) return false; - if (slidingWindow) + if (slidingWindow || waslongpress) return false; // if we slide then this is not a selection auto selected = yDown / lineHeight; diff --git a/radio/src/gui/colorlcd/listbox.h b/radio/src/gui/colorlcd/listbox.h index e9102d9d4..2bfacd308 100644 --- a/radio/src/gui/colorlcd/listbox.h +++ b/radio/src/gui/colorlcd/listbox.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "bitmapbuffer.h" #include "libopenui.h" #include "touch.h" @@ -37,13 +38,24 @@ class ListBase : public FormField uint8_t lineHeight = MENUS_LINE_HEIGHT, WindowFlags windowFlags = 0, LcdFlags lcdFlags = 0); - + typedef enum { + LISTBOX_SINGLE_SELECT, + LISTBOX_MULTI_SELECT + } selecttype_e; + // draw one line of the list. Default implementation just draws the names. Oher types of lists // can draw anything they want. virtual void drawLine(BitmapBuffer *dc, const rect_t &rect, uint32_t index, LcdFlags lcdFlags); void paint (BitmapBuffer *dc) override; virtual void setSelected(int selected); + virtual void setFocusLine(int selected); + void clearSelection() {selectedIndexes.clear(); invalidate();} + + void setMultiSelectHandler(std::function)> handler) + { + _multiSelectHandler = std::move(handler); + } void setLongPressHandler(std::function handler) { @@ -55,6 +67,11 @@ class ListBase : public FormField pressHandler = std::move(handler); } + void setSelectionMode(selecttype_e st) + { + selectionType = st; + } + void setActiveIndex(int index) { if (index >= 0 && index < (int) names.size()) { @@ -108,9 +125,13 @@ class ListBase : public FormField std::vector names; std::function _getValue; std::function _setValue; + std::function)> _multiSelectHandler = nullptr; int lineHeight; int32_t selected = -1; int32_t activeIndex = -1; + std::set selectedIndexes; + selecttype_e selectionType = LISTBOX_SINGLE_SELECT; + bool waslongpress = false; #if defined(HARDWARE_TOUCH) uint32_t duration10ms;