diff --git a/radio/src/thirdparty/libopenui/src/menu.cpp b/radio/src/thirdparty/libopenui/src/menu.cpp index 8283e6bc3..753fc9ccd 100644 --- a/radio/src/thirdparty/libopenui/src/menu.cpp +++ b/radio/src/thirdparty/libopenui/src/menu.cpp @@ -21,59 +21,15 @@ #include "font.h" #include "theme.h" - -// ensure index is in range and also handle wrapping index -int MenuBody::rangeCheck(int index) -{ - if (index < 0) - index = lines.size() - 1; - else if (index > lines.size() - 1) - index = 0; - - return index; -} - -void MenuBody::setIndex(int index) -{ - selectedIndex = index; - // calculate the correct scroll postion based on there being separators - int scrollY = 0; - for (int i = 0; i < selectedIndex; i++) - scrollY += lines[i].lineHeight(); - - if (innerHeight > height()) { - setScrollPositionY(scrollY - 3 * MENUS_LINE_HEIGHT); - } - - invalidate(); -} - void MenuBody::select(int index) { - // adjust the selection based on separators - for (int i = 0; i <= index; i++) { - if (lines[i].isSeparator) - index++; - index = rangeCheck(index); + selectedIndex = index; + if (innerHeight > height()) { + setScrollPositionY(MENUS_LINE_HEIGHT * index - 3 * MENUS_LINE_HEIGHT); } - - setIndex(index); + invalidate(); } -void MenuBody::selectNext(MENU_DIRECTION direction) -{ - // look for the next non separator line - int index = selectedIndex + direction; - index = rangeCheck(index); - while (lines[index].isSeparator) { - index += direction; - index = rangeCheck(index); - } - - setIndex(index); -} - - #if defined(HARDWARE_KEYS) void MenuBody::onEvent(event_t event) { @@ -81,13 +37,13 @@ void MenuBody::onEvent(event_t event) if (event == EVT_ROTARY_RIGHT) { if (!lines.empty()) { - selectNext(DIRECTION_UP); + select(int((selectedIndex + 1) % lines.size())); onKeyPress(); } } else if (event == EVT_ROTARY_LEFT) { if (!lines.empty()) { - selectNext(DIRECTION_DOWN); + select(int(selectedIndex <= 0 ? lines.size() - 1 : selectedIndex - 1)); onKeyPress(); } } @@ -127,39 +83,22 @@ void MenuBody::onEvent(event_t event) bool MenuBody::onTouchEnd(coord_t /*x*/, coord_t y) { Menu * menu = getParentMenu(); - - int index = 0; - - // dynamically find the selected element based on variable line heights - coord_t yAccumulator = 0; - for (int i = 0; i < lines.size(); i++) { - if (y >= yAccumulator && y <= yAccumulator + lines[i].lineHeight()) { - index = i; - break; - } - yAccumulator += lines[i].lineHeight(); - } - - // dont allow selecting separators - if (lines[index].isSeparator) - return false; - + int index = y / MENUS_LINE_HEIGHT; if (index < (int)lines.size()) { onKeyPress(); if (menu->multiple) { if (selectedIndex == index) lines[index].onPress(); else - setIndex(index); + select(index); menu->invalidate(); } else { - setIndex(index); + select(index); menu->deleteLater(); lines[index].onPress(); } } - return true; } #endif @@ -168,43 +107,37 @@ void MenuBody::paint(BitmapBuffer * dc) { dc->clear(COLOR_THEME_PRIMARY2); - int y = 0; - for (int i = 0; i < lines.size(); i++) { + for (unsigned i = 0; i < lines.size(); i++) { auto& line = lines[i]; LcdFlags flags = COLOR_THEME_SECONDARY1 | MENU_FONT; - - // draw selection if appropriate - if (selectedIndex == i && ! line.isSeparator) { + if (selectedIndex == (int)i) { flags = COLOR_THEME_PRIMARY2 | MENU_FONT; if (COLOR_THEME_FOCUS != COLOR_THEME_PRIMARY2) { - dc->drawSolidFilledRect(0, y, width(), + dc->drawSolidFilledRect(0, i * MENUS_LINE_HEIGHT, width(), MENUS_LINE_HEIGHT, COLOR_THEME_FOCUS); } } - - if (line.isSeparator) { - dc->drawHorizontalLine(5, y + MENUS_SEPARATOR_HEIGHT / 2, width() - 10, 255, COLOR_THEME_SECONDARY2); - } else if (line.drawLine) { - line.drawLine(dc, 0, y, flags); + if (line.drawLine) { + line.drawLine(dc, 0, i * MENUS_LINE_HEIGHT, flags); } else { const char* text = line.text.data(); dc->drawText(10, - y + (MENUS_LINE_HEIGHT - getFontHeight(MENU_FONT)) / 2, + i * MENUS_LINE_HEIGHT + + (MENUS_LINE_HEIGHT - getFontHeight(MENU_FONT)) / 2, text[0] == '\0' ? "---" : text, flags); } Menu* menu = getParentMenu(); if (menu->multiple && line.isChecked) { theme->drawCheckBox(dc, line.isChecked(), width() - 35, - y + (MENUS_LINE_HEIGHT - 20) / 2, + i * MENUS_LINE_HEIGHT + (MENUS_LINE_HEIGHT - 20) / 2, 0); } - // if (i > 0 && ! lines[i].isSeparator && ! lines[i-1].isSeparator) { - // dc->drawSolidHorizontalLine(0, y, MENUS_WIDTH, COLOR_THEME_SECONDARY2); - // } - - y += line.lineHeight(); + if (i > 0) { + dc->drawSolidHorizontalLine(0, i * MENUS_LINE_HEIGHT, MENUS_WIDTH, + COLOR_THEME_SECONDARY2); + } } } @@ -240,22 +173,18 @@ Menu::Menu(Window * parent, bool multiple): void Menu::updatePosition() { - // calcualte the correct menu height given that the line heights are variable - coord_t height = 0; - for (int i = 0; i < content->body.lines.size(); i++) - height += content->body.lines[i].lineHeight(); - if (!toolbar) { // there is no navigation bar at the left, we may center the window on screen auto headerHeight = content->title.empty() ? 0 : POPUP_HEADER_HEIGHT; - auto bodyHeight = limit(MENUS_MIN_HEIGHT, height, MENUS_MAX_HEIGHT); + auto bodyHeight = limit( + MENUS_MIN_HEIGHT, content->body.lines.size() * MENUS_LINE_HEIGHT, + MENUS_MAX_HEIGHT); content->setTop((LCD_H - headerHeight - bodyHeight) / 2 + MENUS_OFFSET_TOP); content->setHeight(headerHeight + bodyHeight); content->body.setTop(headerHeight); content->body.setHeight(bodyHeight); } - - content->body.setInnerHeight(height); + content->body.setInnerHeight(content->body.lines.size() * MENUS_LINE_HEIGHT); } void Menu::setTitle(std::string text) @@ -270,12 +199,6 @@ void Menu::addLine(const std::string & text, std::function onPress, std: updatePosition(); } -void Menu::addSeparator() -{ - content->body.addSeparator(); - updatePosition(); -} - void Menu::addCustomLine(std::function drawLine, std::function onPress, std::function isChecked) { content->body.addCustomLine(std::move(drawLine), std::move(onPress), std::move(isChecked)); diff --git a/radio/src/thirdparty/libopenui/src/menu.h b/radio/src/thirdparty/libopenui/src/menu.h index 8283e6bc3..9fdc382e5 100644 --- a/radio/src/thirdparty/libopenui/src/menu.h +++ b/radio/src/thirdparty/libopenui/src/menu.h @@ -17,300 +17,230 @@ * Lesser General Public License for more details. */ -#include "menu.h" -#include "font.h" -#include "theme.h" +#pragma once +#include +#include +#include +#include "modal_window.h" -// ensure index is in range and also handle wrapping index -int MenuBody::rangeCheck(int index) +class Menu; +class MenuWindowContent; + +class MenuBody: public Window { - if (index < 0) - index = lines.size() - 1; - else if (index > lines.size() - 1) - index = 0; + friend class MenuWindowContent; + friend class Menu; - return index; -} + class MenuLine { + friend class MenuBody; -void MenuBody::setIndex(int index) -{ - selectedIndex = index; - // calculate the correct scroll postion based on there being separators - int scrollY = 0; - for (int i = 0; i < selectedIndex; i++) - scrollY += lines[i].lineHeight(); + public: + MenuLine(std::string text, std::function onPress, std::function isChecked): + text(std::move(text)), + onPress(std::move(onPress)), + isChecked(std::move(isChecked)) + { + } - if (innerHeight > height()) { - setScrollPositionY(scrollY - 3 * MENUS_LINE_HEIGHT); - } + MenuLine(std::function drawLine, std::function onPress, std::function isChecked): + drawLine(std::move(drawLine)), + onPress(std::move(onPress)), + isChecked(std::move(isChecked)) + { + } - invalidate(); -} + MenuLine(MenuLine &) = delete; -void MenuBody::select(int index) -{ - // adjust the selection based on separators - for (int i = 0; i <= index; i++) { - if (lines[i].isSeparator) - index++; - index = rangeCheck(index); - } + MenuLine(MenuLine &&) = default; - setIndex(index); -} + protected: + std::string text; + std::function drawLine; + std::function onPress; + std::function isChecked; + }; -void MenuBody::selectNext(MENU_DIRECTION direction) -{ - // look for the next non separator line - int index = selectedIndex + direction; - index = rangeCheck(index); - while (lines[index].isSeparator) { - index += direction; - index = rangeCheck(index); - } + public: + MenuBody(Window * parent, const rect_t & rect): + Window(parent, rect, OPAQUE) + { + setPageHeight(MENUS_LINE_HEIGHT); + } - setIndex(index); -} +#if defined(DEBUG_WINDOWS) + std::string getName() const override + { + return "MenuBody"; + } +#endif + void select(int index); + + int selection() const + { + return selectedIndex; + } + + int count() const + { + return lines.size(); + } #if defined(HARDWARE_KEYS) -void MenuBody::onEvent(event_t event) -{ - TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), event); - - if (event == EVT_ROTARY_RIGHT) { - if (!lines.empty()) { - selectNext(DIRECTION_UP); - onKeyPress(); - } - } - else if (event == EVT_ROTARY_LEFT) { - if (!lines.empty()) { - selectNext(DIRECTION_DOWN); - onKeyPress(); - } - } - else if (event == EVT_KEY_BREAK(KEY_ENTER)) { - if (!lines.empty()) { - onKeyPress(); - if (selectedIndex < 0) { - select(0); - } - else { - Menu * menu = getParentMenu(); - if (menu->multiple) { - lines[selectedIndex].onPress(); - menu->invalidate(); - } - else { - menu->deleteLater(); - lines[selectedIndex].onPress(); - } - } - } - } - else if (event == EVT_KEY_BREAK(KEY_EXIT)) { - onKeyPress(); - if (onCancel) { - onCancel(); - } - Window::onEvent(event); - } - else { - Window::onEvent(event); - } -} + void onEvent(event_t event) override; #endif #if defined(HARDWARE_TOUCH) -bool MenuBody::onTouchEnd(coord_t /*x*/, coord_t y) -{ - Menu * menu = getParentMenu(); - - int index = 0; - - // dynamically find the selected element based on variable line heights - coord_t yAccumulator = 0; - for (int i = 0; i < lines.size(); i++) { - if (y >= yAccumulator && y <= yAccumulator + lines[i].lineHeight()) { - index = i; - break; - } - yAccumulator += lines[i].lineHeight(); - } - - // dont allow selecting separators - if (lines[index].isSeparator) - return false; - - if (index < (int)lines.size()) { - onKeyPress(); - if (menu->multiple) { - if (selectedIndex == index) - lines[index].onPress(); - else - setIndex(index); - menu->invalidate(); - } - else { - setIndex(index); - menu->deleteLater(); - lines[index].onPress(); - } - } - - return true; -} + bool onTouchEnd(coord_t x, coord_t y) override; #endif -void MenuBody::paint(BitmapBuffer * dc) + void addLine(const std::string & text, std::function onPress, std::function isChecked) + { + lines.emplace_back(text, std::move(onPress), std::move(isChecked)); + invalidate(); + } + + void addCustomLine(std::function drawLine, std::function onPress, std::function isChecked) + { + lines.emplace_back(std::move(drawLine), std::move(onPress), std::move(isChecked)); + invalidate(); + } + + void removeLines() + { + lines.clear(); + invalidate(); + } + + void setCancelHandler(std::function handler) + { + onCancel = std::move(handler); + } + + void paint(BitmapBuffer * dc) override; + + protected: + std::vector lines; + int selectedIndex = 0; + std::function onCancel; + + inline Menu * getParentMenu(); +}; + +class MenuWindowContent: public ModalWindowContent { - dc->clear(COLOR_THEME_PRIMARY2); + friend class Menu; - int y = 0; - for (int i = 0; i < lines.size(); i++) { - auto& line = lines[i]; - LcdFlags flags = COLOR_THEME_SECONDARY1 | MENU_FONT; + public: + explicit MenuWindowContent(Menu * parent); - // draw selection if appropriate - if (selectedIndex == i && ! line.isSeparator) { - flags = COLOR_THEME_PRIMARY2 | MENU_FONT; - if (COLOR_THEME_FOCUS != COLOR_THEME_PRIMARY2) { - dc->drawSolidFilledRect(0, y, width(), - MENUS_LINE_HEIGHT, COLOR_THEME_FOCUS); + void deleteLater(bool detach = true, bool trash = true) override + { + if (_deleted) + return; + + body.deleteLater(true, false); + ModalWindowContent::deleteLater(detach, trash); + } + +#if defined(DEBUG_WINDOWS) + std::string getName() const override + { + return "MenuWindowContent"; + } +#endif + + void paint(BitmapBuffer * dc) override; + + protected: + MenuBody body; +}; + +class Menu: public ModalWindow +{ + friend class MenuBody; + + public: + explicit Menu(Window * parent, bool multiple = false); + +#if defined(DEBUG_WINDOWS) + std::string getName() const override + { + return "Menu"; + } +#endif + + void setCancelHandler(std::function handler) + { + content->body.setCancelHandler(std::move(handler)); + } + + void setWaitHandler(std::function handler) + { + waitHandler = std::move(handler); + } + + void setFocusBody(uint8_t flag = SET_FOCUS_DEFAULT) + { + content->body.setFocus(flag); + } + + void setToolbar(Window * window) + { + toolbar = window; + content->setLeft(toolbar->right()); + content->setTop(toolbar->top()); + content->setHeight(toolbar->height()); + } + + void setTitle(std::string text); + + void addLine(const std::string & text, std::function onPress, std::function isChecked = nullptr); + + void addCustomLine(std::function drawLine, std::function onPress, std::function isChecked = nullptr); + + void removeLines(); + + unsigned count() const + { + return content->body.count(); + } + + int selection() const + { + return content->body.selection(); + } + + void select(int index) + { + content->body.select(index); + } + +#if defined(HARDWARE_KEYS) + void onEvent(event_t event) override; +#endif + + void checkEvents() override + { + ModalWindow::checkEvents(); + if (waitHandler) { + waitHandler(); } } - if (line.isSeparator) { - dc->drawHorizontalLine(5, y + MENUS_SEPARATOR_HEIGHT / 2, width() - 10, 255, COLOR_THEME_SECONDARY2); - } else if (line.drawLine) { - line.drawLine(dc, 0, y, flags); - } else { - const char* text = line.text.data(); - dc->drawText(10, - y + (MENUS_LINE_HEIGHT - getFontHeight(MENU_FONT)) / 2, - text[0] == '\0' ? "---" : text, flags); - } + void paint(BitmapBuffer * dc) override; - Menu* menu = getParentMenu(); - if (menu->multiple && line.isChecked) { - theme->drawCheckBox(dc, line.isChecked(), width() - 35, - y + (MENUS_LINE_HEIGHT - 20) / 2, - 0); - } + protected: + MenuWindowContent * content; + bool multiple; + Window * toolbar = nullptr; + std::function waitHandler; + void updatePosition(); +}; - // if (i > 0 && ! lines[i].isSeparator && ! lines[i-1].isSeparator) { - // dc->drawSolidHorizontalLine(0, y, MENUS_WIDTH, COLOR_THEME_SECONDARY2); - // } - - y += line.lineHeight(); - } -} - -MenuWindowContent::MenuWindowContent(Menu* parent) : - ModalWindowContent(parent, {(LCD_W - MENUS_WIDTH) / 2, - (LCD_H - MENUS_WIDTH) / 2, MENUS_WIDTH, 0}), - body(this, {0, 0, width(), height()}) +Menu * MenuBody::getParentMenu() { - body.setFocus(SET_FOCUS_DEFAULT); + return static_cast(getParent()->getParent()); } -void MenuWindowContent::paint(BitmapBuffer * dc) -{ - // the background - dc->clear(COLOR_THEME_PRIMARY2); - - // the title - if (!title.empty()) { - dc->drawText(MENUS_WIDTH / 2, - (POPUP_HEADER_HEIGHT - getFontHeight(MENU_HEADER_FONT)) / 2, - title.c_str(), CENTERED | MENU_HEADER_FONT); - dc->drawSolidHorizontalLine(0, POPUP_HEADER_HEIGHT - 1, MENUS_WIDTH, - COLOR_THEME_SECONDARY2); - } -} - -Menu::Menu(Window * parent, bool multiple): - ModalWindow(parent, true), - content(createMenuWindow(this)), - multiple(multiple) -{ -} - -void Menu::updatePosition() -{ - // calcualte the correct menu height given that the line heights are variable - coord_t height = 0; - for (int i = 0; i < content->body.lines.size(); i++) - height += content->body.lines[i].lineHeight(); - - if (!toolbar) { - // there is no navigation bar at the left, we may center the window on screen - auto headerHeight = content->title.empty() ? 0 : POPUP_HEADER_HEIGHT; - auto bodyHeight = limit(MENUS_MIN_HEIGHT, height, MENUS_MAX_HEIGHT); - content->setTop((LCD_H - headerHeight - bodyHeight) / 2 + MENUS_OFFSET_TOP); - content->setHeight(headerHeight + bodyHeight); - content->body.setTop(headerHeight); - content->body.setHeight(bodyHeight); - } - - content->body.setInnerHeight(height); -} - -void Menu::setTitle(std::string text) -{ - content->setTitle(std::move(text)); - updatePosition(); -} - -void Menu::addLine(const std::string & text, std::function onPress, std::function isChecked) -{ - content->body.addLine(text, std::move(onPress), std::move(isChecked)); - updatePosition(); -} - -void Menu::addSeparator() -{ - content->body.addSeparator(); - updatePosition(); -} - -void Menu::addCustomLine(std::function drawLine, std::function onPress, std::function isChecked) -{ - content->body.addCustomLine(std::move(drawLine), std::move(onPress), std::move(isChecked)); - updatePosition(); -} - -void Menu::removeLines() -{ - content->body.removeLines(); - updatePosition(); -} - -#if defined(HARDWARE_KEYS) -void Menu::onEvent(event_t event) -{ - if (toolbar && (event == EVT_KEY_BREAK(KEY_PGDN) || event == EVT_KEY_LONG(KEY_PGDN))) { - toolbar->onEvent(event); - } - else if (event == EVT_KEY_BREAK(KEY_EXIT)) { - deleteLater(); - } - else if (event == EVT_KEY_BREAK(KEY_ENTER) && !multiple) { - deleteLater(); - } -} -#endif - -void Menu::paint(BitmapBuffer * dc) -{ - ModalWindow::paint(dc); - - rect_t r(content->getRect()); - if (toolbar) { - r.x = toolbar->left(); - r.w += toolbar->width(); - } - dc->drawSolidRect(r.x - 1, r.y - 1, r.w + 2, r.h + 2, 1, COLOR_THEME_SECONDARY2); -}