#ifndef UI_LIST_H #define UI_LIST_H #undef min #undef max #include #include #include "UIElement.h" #include "UIButton.h" class UIListModel { std::vector entries; public: void add(const std::string& str) { entries.push_back(str); } void remove(const size_t idx) { entries.erase(entries.begin()+idx); } size_t size() const { return entries.size(); } const std::string& get(const size_t idx) const { return entries[idx]; } }; class UIList : public UIElement, public UIButton::Listener { public: class Listener { public: virtual void onSelected(UIList* lst, int idx) = 0; }; private: UIListModel model; int offset = 0; int selectedIndex = -1; UIButton btnUp; UIButton btnDown; static constexpr const int btnW = 32; Color cRect = Color::fromRGB(0,0,0); Color cNormalBG = Color::fromRGB(230,230,230); Color cSelectedBG = Color::fromRGB(190,190,255); Color cText = Color::fromRGB(0,0,0); Listener* listener = nullptr; static constexpr const char* TAG = "UIList"; public: /** ctor */ UIList() : btnUp("<"), btnDown(">") { addChild(&btnUp); addChild(&btnDown); btnUp.setListener(this); btnDown.setListener(this); } void setListener(Listener* l) { this->listener = l; } // UIListModel& getModel() { // setNeedsRedraw(); // return model; // } // const UIListModel& getModel() const { // return model; // } void add(const std::string& str) { model.add(str); btnUp.setVisible(needsScroll()); btnDown.setVisible(needsScroll()); setNeedsRedraw(); } void remove(const size_t idx) { model.remove(idx); if (idx == selectedIndex) { selectedIndex = -1; if (listener) {listener->onSelected(this, selectedIndex);} } else if (idx < selectedIndex) { --selectedIndex; if (listener) {listener->onSelected(this, selectedIndex);} } if (offset > maxOffset()) {offset = maxOffset();} btnUp.setVisible(needsScroll()); btnDown.setVisible(needsScroll()); setNeedsRedraw(); } size_t size() const { return model.size(); } const std::string& get(const size_t idx) const { return model.get(idx); } int getSelectedIndex() const { return this->selectedIndex; } void reLayout() override { int h = rect.h / 2 - 1; btnUp.setRect(rect.x+rect.w-btnW-1, rect.y+1, btnW, h); btnDown.setRect(rect.x+rect.w-btnW-1, rect.y+h+2, btnW, h); } virtual void draw(UIPainter& p) override { debugMod(TAG, "draw()"); debugMod4(TAG, "rect: %d %d %d %d", rect.x, rect.y, rect.w, rect.h); const uint8_t oy = (elementHeight() - fnt_f1.getHeight()) / 2; const uint16_t entryW = rect.w - (needsScroll() ? btnW : 0); // outline rectangle p.setFG(cNormalBG); p.fillRect(rect.x+1, rect.y+1, entryW, rect.h-2); p.setFG(cRect); p.drawRect(rect); // display as many elements as fit within the list's height for (unsigned int i = 0; i < elementsVisible(); ++i) { // determine position (y-coordinate) const uint16_t y = i * elementHeight(); // determine element from model const int idx = i+offset; const bool selected = idx == selectedIndex; // draw background depending on selection //p.setFG( selected ? cSelectedBG : cNormalBG ); //p.fillRect(UIRect(rect.x+1, rect.y+y+1, entryW-1, elementHeight()-1)); if (selected) { p.setFG( cSelectedBG ); p.fillRect(UIRect(rect.x+1, rect.y+y+1, entryW-1, elementHeight()-1)); } // draw text p.setFG(cText); p.drawText(rect.x+3, rect.y + y + oy, model.get(idx).c_str()); } } virtual void onTouchDown(uint16_t, uint16_t y) override { unsigned int idx = y / elementHeight() + offset; this->selectedIndex = (idx < numElements()) ? (idx) : (-1); debugMod1(TAG, "selected: %d", selectedIndex); setNeedsRedraw(); if (listener) {listener->onSelected(this, selectedIndex);} } virtual void onTouch(uint16_t, uint16_t) override { } virtual void onTouchUp() override { } virtual void onClick(UIButton* btn) override { if (btn == &btnUp) { if (offset > 0) {--offset; setNeedsRedraw();} } else if (btn == &btnDown) { if (offset < maxOffset()) {++offset; setNeedsRedraw();} } } private: unsigned int elementHeight() const {return 16;} unsigned int maxElementsVisible() const {return rect.h / elementHeight();} unsigned int elementsVisible() const {return std::min(maxElementsVisible(), numElements());} unsigned int numElements() const {return model.size();} unsigned int maxOffset() const {return numElements()-maxElementsVisible();} bool needsScroll() const {return numElements() > maxElementsVisible();} }; #endif // UI_LIST_H