You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
910 lines
30 KiB
910 lines
30 KiB
2 years ago
|
/*
|
||
|
* EEZ Modular Firmware
|
||
|
* Copyright (C) 2015-present, Envox d.o.o.
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include <eez/gui/gui.h>
|
||
|
|
||
|
namespace eez {
|
||
|
namespace gui {
|
||
|
|
||
|
Value g_alertMessage;
|
||
|
|
||
|
void data_alert_message(DataOperationEnum operation, const WidgetCursor &widgetCursor, Value &value) {
|
||
|
if (operation == DATA_OPERATION_GET) {
|
||
|
value = g_alertMessage;
|
||
|
} else if (operation == DATA_OPERATION_SET) {
|
||
|
g_alertMessage = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void Page::pageAlloc() {
|
||
|
}
|
||
|
|
||
|
void Page::pageFree() {
|
||
|
}
|
||
|
|
||
|
void Page::pageWillAppear() {
|
||
|
}
|
||
|
|
||
|
void Page::onEncoder(int counter) {
|
||
|
}
|
||
|
|
||
|
void Page::onEncoderClicked() {
|
||
|
}
|
||
|
|
||
|
Unit Page::getEncoderUnit() {
|
||
|
return UNIT_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
int Page::getDirty() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void Page::set() {
|
||
|
}
|
||
|
|
||
|
void Page::discard() {
|
||
|
getFoundWidgetAtDown().appContext->popPage();
|
||
|
}
|
||
|
|
||
|
bool Page::showAreYouSureOnDiscard() {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void SetPage::edit() {
|
||
|
}
|
||
|
|
||
|
void SetPage::onSetValue(float value) {
|
||
|
getFoundWidgetAtDown().appContext->popPage();
|
||
|
SetPage *page = (SetPage *)getFoundWidgetAtDown().appContext->getActivePage();
|
||
|
page->setValue(value);
|
||
|
}
|
||
|
|
||
|
void SetPage::setValue(float value) {
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
bool InternalPage::canClickPassThrough() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool InternalPage::closeIfTouchedOutside() {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static ToastMessagePage g_toastMessagePage;
|
||
|
|
||
|
ToastMessagePage *ToastMessagePage::findFreePage() {
|
||
|
return &g_toastMessagePage;
|
||
|
}
|
||
|
|
||
|
void ToastMessagePage::pageFree() {
|
||
|
appContext = nullptr;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
ToastMessagePage *ToastMessagePage::create(AppContext *appContext, ToastType type, const char *message, bool autoDismiss) {
|
||
|
ToastMessagePage *page = ToastMessagePage::findFreePage();
|
||
|
|
||
|
page->actionLabel = type == ERROR_TOAST && !autoDismiss ? "Close" : nullptr;
|
||
|
page->actionWidget.action = type == ERROR_TOAST && !autoDismiss ? ACTION_ID_INTERNAL_TOAST_ACTION_WITHOUT_PARAM : 0;
|
||
|
page->actionWithoutParam = nullptr;
|
||
|
|
||
|
page->init(appContext, type, Value(message));
|
||
|
|
||
|
return page;
|
||
|
}
|
||
|
|
||
|
ToastMessagePage *ToastMessagePage::create(AppContext *appContext, ToastType type, Value message) {
|
||
|
ToastMessagePage *page = ToastMessagePage::findFreePage();
|
||
|
|
||
|
page->actionLabel = type == ERROR_TOAST ? "Close" : nullptr;
|
||
|
page->actionWidget.action = type == ERROR_TOAST ? ACTION_ID_INTERNAL_TOAST_ACTION_WITHOUT_PARAM : 0;
|
||
|
page->actionWithoutParam = nullptr;
|
||
|
|
||
|
page->init(appContext, type, message);
|
||
|
|
||
|
return page;
|
||
|
}
|
||
|
|
||
|
ToastMessagePage *ToastMessagePage::create(AppContext *appContext, ToastType type, Value message, void (*action)(int param), const char *actionLabel, int actionParam) {
|
||
|
ToastMessagePage *page = ToastMessagePage::findFreePage();
|
||
|
|
||
|
page->actionLabel = actionLabel;
|
||
|
page->actionWidget.action = ACTION_ID_INTERNAL_TOAST_ACTION;
|
||
|
page->action = action;
|
||
|
page->actionParam = actionParam;
|
||
|
|
||
|
page->init(appContext, type, message);
|
||
|
|
||
|
return page;
|
||
|
}
|
||
|
|
||
|
ToastMessagePage *ToastMessagePage::create(AppContext *appContext, ToastType type, const char *message, void (*action)(), const char *actionLabel) {
|
||
|
ToastMessagePage *page = ToastMessagePage::findFreePage();
|
||
|
|
||
|
page->actionLabel = actionLabel;
|
||
|
page->actionWidget.action = ACTION_ID_INTERNAL_TOAST_ACTION_WITHOUT_PARAM;
|
||
|
page->actionWithoutParam = action;
|
||
|
|
||
|
page->init(appContext, type, Value(message));
|
||
|
|
||
|
return page;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
void ToastMessagePage::init(AppContext *appContext, ToastType type, const Value& message) {
|
||
|
this->appContext = appContext;
|
||
|
this->type = type;
|
||
|
this->messageValue = message;
|
||
|
|
||
|
lastActiveWidget = WidgetCursor();
|
||
|
|
||
|
auto styleId = type == INFO_TOAST ? STYLE_ID_INFO_ALERT : STYLE_ID_ERROR_ALERT;
|
||
|
auto style = getStyle(styleId);
|
||
|
auto actionStyle = getStyle(STYLE_ID_ERROR_ALERT_BUTTON);
|
||
|
|
||
|
font::Font font = styleGetFont(style);
|
||
|
|
||
|
char *line1 = nullptr;
|
||
|
char *line2 = nullptr;
|
||
|
char *line3 = nullptr;
|
||
|
|
||
|
message.toText(messageBuffer, sizeof(messageBuffer));
|
||
|
|
||
|
// split message to up to three lines
|
||
|
line1 = messageBuffer;
|
||
|
line2 = strchr(line1, '\n');
|
||
|
if (line2) {
|
||
|
*line2 = 0;
|
||
|
line2++;
|
||
|
line3 = strchr(line2, '\n');
|
||
|
if (line3) {
|
||
|
*line3 = 0;
|
||
|
line3++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int minTextWidth = 80;
|
||
|
int line1Width = display::measureStr(line1, -1, font, 0);
|
||
|
int line2Width = line2 ? display::measureStr(line2, -1, font, 0) : 0;
|
||
|
int line3Width = line3 ? display::measureStr(line3, -1, font, 0) : 0;
|
||
|
int actionLabelWidth = actionLabel ? (actionStyle->paddingLeft + display::measureStr(actionLabel, -1, font, 0) + actionStyle->paddingRight) : 0;
|
||
|
int textWidth = MAX(MAX(MAX(MAX(minTextWidth, line1Width), line2Width), line3Width), actionLabelWidth);
|
||
|
|
||
|
int textHeight = font.getHeight();
|
||
|
|
||
|
width = style->borderSizeLeft + style->paddingLeft +
|
||
|
textWidth +
|
||
|
style->paddingRight + style->borderSizeRight;
|
||
|
|
||
|
int numLines = (line3 ? 3 : line2 ? 2 : 1);
|
||
|
|
||
|
auto actionLabelHeight = actionStyle->paddingTop + textHeight + actionStyle->paddingBottom;
|
||
|
|
||
|
height = style->borderSizeTop + style->paddingTop +
|
||
|
numLines * textHeight + (actionLabel ? (style->paddingTop + actionLabelHeight) : 0) +
|
||
|
style->paddingBottom + style->borderSizeBottom;
|
||
|
|
||
|
containerRectangleWidget.type = WIDGET_TYPE_RECTANGLE;
|
||
|
containerRectangleWidget.data = DATA_ID_NONE;
|
||
|
containerRectangleWidget.action = ACTION_ID_NONE;
|
||
|
containerRectangleWidget.style = styleId;
|
||
|
containerRectangleWidget.flags.ignoreLuminosity = 0;
|
||
|
containerRectangleWidget.flags.invertColors = 1;
|
||
|
containerRectangleWidget.x = 0;
|
||
|
containerRectangleWidget.y = 0;
|
||
|
containerRectangleWidget.width = width;
|
||
|
containerRectangleWidget.height = height;
|
||
|
|
||
|
int yText = style->paddingTop;
|
||
|
|
||
|
line2Widget.type = WIDGET_TYPE_TEXT;
|
||
|
line1Widget.data = DATA_ID_NONE;
|
||
|
line1Widget.action = ACTION_ID_NONE;
|
||
|
line1Widget.style = styleId;
|
||
|
line1Widget.text = line1;
|
||
|
line1Widget.flags = 0;
|
||
|
line1Widget.x = style->paddingLeft + (textWidth - line1Width) / 2;
|
||
|
line1Widget.y = yText;
|
||
|
line1Widget.width = line1Width;
|
||
|
line1Widget.height = textHeight;
|
||
|
|
||
|
yText += textHeight;
|
||
|
|
||
|
if (line2) {
|
||
|
line2Widget.type = WIDGET_TYPE_TEXT;
|
||
|
line2Widget.data = DATA_ID_NONE;
|
||
|
line2Widget.action = ACTION_ID_NONE;
|
||
|
line2Widget.style = styleId;
|
||
|
line2Widget.text = line2;
|
||
|
line2Widget.flags = 0;
|
||
|
line2Widget.x = style->paddingLeft + (textWidth - line2Width) / 2;
|
||
|
line2Widget.y = yText;
|
||
|
line2Widget.width = line2Width;
|
||
|
line2Widget.height = textHeight;
|
||
|
|
||
|
yText += textHeight;
|
||
|
|
||
|
if (line3) {
|
||
|
line3Widget.type = WIDGET_TYPE_TEXT;
|
||
|
line3Widget.data = DATA_ID_NONE;
|
||
|
line3Widget.action = ACTION_ID_NONE;
|
||
|
line3Widget.style = styleId;
|
||
|
line3Widget.text = line3;
|
||
|
line3Widget.flags = 0;
|
||
|
line3Widget.x = style->paddingLeft + (textWidth - line3Width) / 2;
|
||
|
line3Widget.y = yText;
|
||
|
line3Widget.width = line3Width;
|
||
|
line3Widget.height = textHeight;
|
||
|
|
||
|
yText += textHeight;
|
||
|
} else {
|
||
|
line3Widget.type = WIDGET_TYPE_NONE;
|
||
|
}
|
||
|
} else {
|
||
|
line2Widget.type = WIDGET_TYPE_NONE;
|
||
|
line3Widget.type = WIDGET_TYPE_NONE;
|
||
|
}
|
||
|
|
||
|
if (actionLabel) {
|
||
|
actionWidget.type = WIDGET_TYPE_BUTTON;
|
||
|
actionWidget.data = DATA_ID_NONE;
|
||
|
actionWidget.style = STYLE_ID_ERROR_ALERT_BUTTON;
|
||
|
actionWidget.text = actionLabel;
|
||
|
actionWidget.x = style->paddingLeft + (textWidth - actionLabelWidth) / 2;
|
||
|
actionWidget.y = style->paddingTop + yText;
|
||
|
actionWidget.width = actionLabelWidth;
|
||
|
actionWidget.height = actionLabelHeight;
|
||
|
|
||
|
yText += textHeight;
|
||
|
} else {
|
||
|
actionWidget.type = WIDGET_TYPE_NONE;
|
||
|
}
|
||
|
|
||
|
Rect rect;
|
||
|
appContext->getBoundingRect(rect);
|
||
|
|
||
|
x = rect.x + (rect.w - width) / 2;
|
||
|
y = rect.y + (rect.h - height) / 2;
|
||
|
}
|
||
|
|
||
|
void ToastMessagePage::updateInternalPage() {
|
||
|
WidgetCursor &widgetCursor = g_widgetCursor;
|
||
|
|
||
|
if (widgetCursor.hasPreviousState && g_activeWidget == lastActiveWidget) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
lastActiveWidget = g_activeWidget;
|
||
|
|
||
|
auto savedWidgetCursor = widgetCursor;
|
||
|
|
||
|
widgetCursor.widget = &containerRectangleWidget;
|
||
|
widgetCursor.x = x + containerRectangleWidget.x;
|
||
|
widgetCursor.y = y + containerRectangleWidget.y;
|
||
|
widgetCursor.w = containerRectangleWidget.width;
|
||
|
widgetCursor.h = containerRectangleWidget.height;
|
||
|
RectangleWidgetState rectangleWidgetState;
|
||
|
rectangleWidgetState.flags.active = 0;
|
||
|
rectangleWidgetState.render();
|
||
|
|
||
|
if (g_findCallback == nullptr && !widgetCursor.refreshed) {
|
||
|
widgetCursor.pushBackground(widgetCursor.x, widgetCursor.y, getStyle(containerRectangleWidget.style), false);
|
||
|
}
|
||
|
|
||
|
TextWidgetState textWidgetState;
|
||
|
textWidgetState.flags.active = 0;
|
||
|
textWidgetState.flags.blinking = 0;
|
||
|
textWidgetState.flags.focused = 0;
|
||
|
|
||
|
widgetCursor.widget = &line1Widget;
|
||
|
widgetCursor.x = x + line1Widget.x;
|
||
|
widgetCursor.y = y + line1Widget.y;
|
||
|
widgetCursor.w = line1Widget.width;
|
||
|
widgetCursor.h = line1Widget.height;
|
||
|
textWidgetState.render();
|
||
|
|
||
|
if (line2Widget.type == WIDGET_TYPE_TEXT) {
|
||
|
widgetCursor.widget = &line2Widget;
|
||
|
widgetCursor.x = x + line2Widget.x;
|
||
|
widgetCursor.y = y + line2Widget.y;
|
||
|
widgetCursor.w = line2Widget.width;
|
||
|
widgetCursor.h = line2Widget.height;
|
||
|
textWidgetState.render();
|
||
|
|
||
|
if (line3Widget.type == WIDGET_TYPE_TEXT) {
|
||
|
widgetCursor.widget = &line3Widget;
|
||
|
widgetCursor.x = x + line3Widget.x;
|
||
|
widgetCursor.y = y + line3Widget.y;
|
||
|
widgetCursor.w = line3Widget.width;
|
||
|
widgetCursor.h = line3Widget.height;
|
||
|
textWidgetState.render();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (actionWidget.type == WIDGET_TYPE_BUTTON) {
|
||
|
ButtonWidgetState buttonWidgetState;
|
||
|
|
||
|
buttonWidgetState.flags.active = g_activeWidget.widget == &actionWidget;
|
||
|
buttonWidgetState.flags.blinking = 0;
|
||
|
buttonWidgetState.flags.focused = 0;
|
||
|
buttonWidgetState.flags.enabled = 1;
|
||
|
widgetCursor.widget = &actionWidget;
|
||
|
widgetCursor.x = x + actionWidget.x;
|
||
|
widgetCursor.y = y + actionWidget.y;
|
||
|
widgetCursor.w = actionWidget.width;
|
||
|
widgetCursor.h = actionWidget.height;
|
||
|
buttonWidgetState.render();
|
||
|
}
|
||
|
|
||
|
if (g_findCallback == nullptr && !widgetCursor.refreshed) {
|
||
|
widgetCursor.popBackground();
|
||
|
}
|
||
|
|
||
|
widgetCursor = savedWidgetCursor;
|
||
|
}
|
||
|
|
||
|
WidgetCursor ToastMessagePage::findWidgetInternalPage(int x, int y, bool clicked) {
|
||
|
if (actionWidget.type == WIDGET_TYPE_BUTTON) {
|
||
|
auto xActionWidget = this->x + actionWidget.x;
|
||
|
auto yActionWidget = this->y + actionWidget.y;
|
||
|
|
||
|
if (
|
||
|
x >= xActionWidget && x < xActionWidget + actionWidget.width &&
|
||
|
y >= yActionWidget && y < yActionWidget + actionWidget.height
|
||
|
) {
|
||
|
WidgetCursor widgetCursor = g_widgetCursor;
|
||
|
widgetCursor.appContext = appContext;
|
||
|
widgetCursor.widget = &actionWidget;
|
||
|
widgetCursor.x = xActionWidget;
|
||
|
widgetCursor.y = yActionWidget;
|
||
|
return widgetCursor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return WidgetCursor();
|
||
|
}
|
||
|
|
||
|
void ToastMessagePage::onEncoder(int counter) {
|
||
|
g_hooks.toastMessagePageOnEncoder(this, counter);
|
||
|
}
|
||
|
|
||
|
void ToastMessagePage::onEncoderClicked() {
|
||
|
if (hasAction()) {
|
||
|
WidgetCursor widgetCursor;
|
||
|
eez::gui::executeAction(widgetCursor, (int)(int16_t)actionWidget.action);
|
||
|
} else {
|
||
|
appContext->popPage();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool ToastMessagePage::canClickPassThrough() {
|
||
|
return !hasAction();
|
||
|
}
|
||
|
|
||
|
bool ToastMessagePage::closeIfTouchedOutside() {
|
||
|
return hasAction();
|
||
|
}
|
||
|
|
||
|
void ToastMessagePage::executeAction() {
|
||
|
auto appContext = g_toastMessagePage.appContext;
|
||
|
appContext->popPage();
|
||
|
g_toastMessagePage.action(g_toastMessagePage.actionParam);
|
||
|
}
|
||
|
|
||
|
void ToastMessagePage::executeActionWithoutParam() {
|
||
|
auto appContext = g_toastMessagePage.appContext;
|
||
|
appContext->popPage();
|
||
|
if (g_toastMessagePage.actionWithoutParam) {
|
||
|
g_toastMessagePage.actionWithoutParam();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
SelectFromEnumPage g_selectFromEnumPage;
|
||
|
|
||
|
void SelectFromEnumPage::pushSelectFromEnumPage(
|
||
|
AppContext *appContext,
|
||
|
const EnumItem *enumItems,
|
||
|
uint16_t currentValue,
|
||
|
bool (*disabledCallback)(uint16_t value),
|
||
|
void (*onSet)(uint16_t),
|
||
|
bool smallFont,
|
||
|
bool showRadioButtonIcon
|
||
|
) {
|
||
|
g_selectFromEnumPage.init(appContext, enumItems, currentValue, disabledCallback, onSet, smallFont, showRadioButtonIcon);
|
||
|
appContext->pushPage(INTERNAL_PAGE_ID_SELECT_FROM_ENUM, &g_selectFromEnumPage);
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::pushSelectFromEnumPage(
|
||
|
AppContext *appContext,
|
||
|
void (*enumDefinitionFunc)(DataOperationEnum operation, const WidgetCursor &widgetCursor, Value &value),
|
||
|
uint16_t currentValue,
|
||
|
bool (*disabledCallback)(uint16_t value),
|
||
|
void (*onSet)(uint16_t),
|
||
|
bool smallFont,
|
||
|
bool showRadioButtonIcon
|
||
|
) {
|
||
|
g_selectFromEnumPage.init(appContext, enumDefinitionFunc, currentValue, disabledCallback, onSet, smallFont, showRadioButtonIcon);
|
||
|
appContext->pushPage(INTERNAL_PAGE_ID_SELECT_FROM_ENUM, &g_selectFromEnumPage);
|
||
|
}
|
||
|
|
||
|
const EnumItem *SelectFromEnumPage::getActiveSelectEnumDefinition() {
|
||
|
if (g_selectFromEnumPage.appContext && g_selectFromEnumPage.appContext->getActivePage() == &g_selectFromEnumPage) {
|
||
|
return g_selectFromEnumPage.getEnumDefinition();
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::selectEnumItem() {
|
||
|
g_selectFromEnumPage.doSelectEnumItem();
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::popSelectFromEnumPage() {
|
||
|
if (g_selectFromEnumPage.appContext) {
|
||
|
g_selectFromEnumPage.appContext->popPage();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::init(
|
||
|
AppContext *appContext_,
|
||
|
const EnumItem *enumDefinition_,
|
||
|
uint16_t currentValue_,
|
||
|
bool (*disabledCallback_)(uint16_t value),
|
||
|
void (*onSet_)(uint16_t),
|
||
|
bool smallFont_,
|
||
|
bool showRadioButtonIcon_
|
||
|
) {
|
||
|
appContext = appContext_;
|
||
|
enumDefinition = enumDefinition_;
|
||
|
enumDefinitionFunc = nullptr;
|
||
|
currentValue = currentValue_;
|
||
|
disabledCallback = disabledCallback_;
|
||
|
onSet = onSet_;
|
||
|
smallFont = smallFont_;
|
||
|
showRadioButtonIcon = showRadioButtonIcon_;
|
||
|
|
||
|
init();
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::init(
|
||
|
AppContext *appContext_,
|
||
|
void (*enumDefinitionFunc_)(DataOperationEnum operation, const WidgetCursor &widgetCursor, Value &value),
|
||
|
uint16_t currentValue_,
|
||
|
bool (*disabledCallback_)(uint16_t value),
|
||
|
void (*onSet_)(uint16_t),
|
||
|
bool smallFont_,
|
||
|
bool showRadioButtonIcon_
|
||
|
) {
|
||
|
appContext = appContext_;
|
||
|
enumDefinition = nullptr;
|
||
|
enumDefinitionFunc = enumDefinitionFunc_;
|
||
|
currentValue = currentValue_;
|
||
|
disabledCallback = disabledCallback_;
|
||
|
onSet = onSet_;
|
||
|
smallFont = smallFont_;
|
||
|
showRadioButtonIcon = showRadioButtonIcon_;
|
||
|
|
||
|
init();
|
||
|
}
|
||
|
|
||
|
|
||
|
void SelectFromEnumPage::init() {
|
||
|
numColumns = 1;
|
||
|
if (!calcSize()) {
|
||
|
numColumns = 2;
|
||
|
calcSize();
|
||
|
}
|
||
|
|
||
|
activeItemIndex = -1;
|
||
|
|
||
|
findPagePosition();
|
||
|
}
|
||
|
|
||
|
uint16_t SelectFromEnumPage::getValue(int i) {
|
||
|
if (enumDefinitionFunc) {
|
||
|
Value value;
|
||
|
WidgetCursor widgetCursor;
|
||
|
widgetCursor.cursor = i;
|
||
|
enumDefinitionFunc(DATA_OPERATION_GET_VALUE, widgetCursor, value);
|
||
|
return value.getUInt8();
|
||
|
}
|
||
|
|
||
|
return enumDefinition[i].value;
|
||
|
}
|
||
|
|
||
|
bool SelectFromEnumPage::getLabel(int i, char *text, int count) {
|
||
|
if (enumDefinitionFunc) {
|
||
|
Value value;
|
||
|
WidgetCursor widgetCursor;
|
||
|
widgetCursor.cursor = i;
|
||
|
enumDefinitionFunc(DATA_OPERATION_GET_LABEL, widgetCursor, value);
|
||
|
if (value.getType() != VALUE_TYPE_UNDEFINED) {
|
||
|
if (text) {
|
||
|
value.toText(text, count);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (enumDefinition[i].menuLabel) {
|
||
|
if (text) {
|
||
|
stringCopy(text, count, enumDefinition[i].menuLabel);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool SelectFromEnumPage::isDisabled(int i) {
|
||
|
return disabledCallback && disabledCallback(getValue(i));
|
||
|
}
|
||
|
|
||
|
bool SelectFromEnumPage::calcSize() {
|
||
|
const Style *containerStyle = getStyle(smallFont ? STYLE_ID_SELECT_ENUM_ITEM_POPUP_CONTAINER_S : STYLE_ID_SELECT_ENUM_ITEM_POPUP_CONTAINER);
|
||
|
const Style *itemStyle = getStyle(smallFont ? STYLE_ID_SELECT_ENUM_ITEM_POPUP_ITEM_S : STYLE_ID_SELECT_ENUM_ITEM_POPUP_ITEM);
|
||
|
|
||
|
font::Font font = styleGetFont(itemStyle);
|
||
|
|
||
|
// calculate geometry
|
||
|
itemHeight = itemStyle->paddingTop + font.getHeight() + itemStyle->paddingBottom;
|
||
|
itemWidth = 0;
|
||
|
|
||
|
char text[64];
|
||
|
|
||
|
numItems = 0;
|
||
|
for (int i = 0; getLabel(i, nullptr, 0); ++i) {
|
||
|
++numItems;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < numItems; ++i) {
|
||
|
getItemLabel(i, text, sizeof(text));
|
||
|
int width = display::measureStr(text, -1, font);
|
||
|
if (width > itemWidth) {
|
||
|
itemWidth = width;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
itemWidth = itemStyle->paddingLeft + itemWidth + itemStyle->paddingRight;
|
||
|
|
||
|
Rect rect;
|
||
|
appContext->getBoundingRect(rect);
|
||
|
|
||
|
width = containerStyle->paddingLeft + (numColumns == 2 ? itemWidth + containerStyle->paddingLeft + itemWidth : itemWidth) + containerStyle->paddingRight;
|
||
|
if (width > rect.w) {
|
||
|
width = rect.w;
|
||
|
}
|
||
|
|
||
|
height = containerStyle->paddingTop + (numColumns == 2 ? (numItems + 1) / 2 : numItems) * itemHeight + containerStyle->paddingBottom;
|
||
|
if (height >rect.h) {
|
||
|
if (numColumns == 1) {
|
||
|
return false;
|
||
|
}
|
||
|
height = rect.h;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::findPagePosition() {
|
||
|
Rect rect;
|
||
|
appContext->getBoundingRect(rect);
|
||
|
|
||
|
const WidgetCursor &widgetCursorAtTouchDown = getFoundWidgetAtDown();
|
||
|
if (widgetCursorAtTouchDown.widget) {
|
||
|
x = widgetCursorAtTouchDown.x + widgetCursorAtTouchDown.w - width;
|
||
|
int xMargin = MAX(MIN(22, (rect.w - width) / 2), 0);
|
||
|
int right = rect.x + rect.w - xMargin;
|
||
|
if (x + width > right) {
|
||
|
x = right - width;
|
||
|
}
|
||
|
|
||
|
y = widgetCursorAtTouchDown.y + widgetCursorAtTouchDown.h;
|
||
|
int yMargin = MAX(MIN(30, (rect.h - height) / 2), 0);
|
||
|
int bottom = rect.y + rect.h - yMargin;
|
||
|
if (y + height > bottom) {
|
||
|
y = bottom - height;
|
||
|
}
|
||
|
} else {
|
||
|
x = rect.x + (rect.w - width) / 2;
|
||
|
y = rect.y + (rect.h - height) / 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::updateInternalPage() {
|
||
|
const WidgetCursor &widgetCursor = g_widgetCursor;
|
||
|
|
||
|
if (widgetCursor.hasPreviousState && !dirty) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const Style *containerStyle = getStyle(smallFont ? STYLE_ID_SELECT_ENUM_ITEM_POPUP_CONTAINER_S : STYLE_ID_SELECT_ENUM_ITEM_POPUP_CONTAINER);
|
||
|
const Style *itemStyle = getStyle(smallFont ? STYLE_ID_SELECT_ENUM_ITEM_POPUP_ITEM_S : STYLE_ID_SELECT_ENUM_ITEM_POPUP_ITEM);
|
||
|
const Style *disabledItemStyle = getStyle(smallFont ? STYLE_ID_SELECT_ENUM_ITEM_POPUP_DISABLED_ITEM_S : STYLE_ID_SELECT_ENUM_ITEM_POPUP_DISABLED_ITEM);
|
||
|
|
||
|
// draw background
|
||
|
display::setColor(containerStyle->backgroundColor);
|
||
|
display::fillRect(x, y, x + width - 1, y + height - 1);
|
||
|
|
||
|
// draw labels
|
||
|
char text[64];
|
||
|
for (int i = 0; getLabel(i, nullptr, 0); ++i) {
|
||
|
int xItem, yItem;
|
||
|
getItemPosition(i, xItem, yItem);
|
||
|
|
||
|
getItemLabel(i, text, sizeof(text));
|
||
|
drawText(
|
||
|
text, -1,
|
||
|
xItem, yItem, itemWidth, itemHeight,
|
||
|
isDisabled(i) ? disabledItemStyle : itemStyle,
|
||
|
i == activeItemIndex
|
||
|
);
|
||
|
}
|
||
|
|
||
|
dirty = false;
|
||
|
}
|
||
|
|
||
|
WidgetCursor SelectFromEnumPage::findWidgetInternalPage(int x, int y, bool clicked) {
|
||
|
const WidgetCursor &widgetCursor = g_widgetCursor;
|
||
|
|
||
|
for (int i = 0; i < numItems; ++i) {
|
||
|
int xItem, yItem;
|
||
|
getItemPosition(i, xItem, yItem);
|
||
|
if (!isDisabled(i)) {
|
||
|
if (x >= xItem && x < xItem + itemWidth && y >= yItem && y < yItem + itemHeight) {
|
||
|
|
||
|
if (clicked) {
|
||
|
activeItemIndex = i;
|
||
|
currentValue = getValue(i);
|
||
|
dirty = true;
|
||
|
}
|
||
|
|
||
|
widget.action = ACTION_ID_INTERNAL_SELECT_ENUM_ITEM;
|
||
|
widget.data = (uint16_t)i;
|
||
|
return WidgetCursor(g_mainAssets, appContext, &widget, x, y, widgetCursor.currentState, widgetCursor.refreshed, widgetCursor.hasPreviousState);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return WidgetCursor();
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::doSelectEnumItem() {
|
||
|
int itemIndex = getFoundWidgetAtDown().widget->data;
|
||
|
if (onSet) {
|
||
|
onSet(getValue(itemIndex));
|
||
|
} else {
|
||
|
appContext->popPage();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::getItemPosition(int itemIndex, int &xItem, int &yItem) {
|
||
|
const Style *containerStyle = getStyle(smallFont ? STYLE_ID_SELECT_ENUM_ITEM_POPUP_CONTAINER_S : STYLE_ID_SELECT_ENUM_ITEM_POPUP_CONTAINER);
|
||
|
|
||
|
if (numColumns == 1 || itemIndex < (numItems + 1) / 2) {
|
||
|
xItem = x + containerStyle->paddingLeft;
|
||
|
yItem = y + containerStyle->paddingTop + itemIndex * itemHeight;
|
||
|
} else {
|
||
|
xItem = x + containerStyle->paddingLeft + itemWidth + containerStyle->paddingLeft / 2;
|
||
|
yItem = y + containerStyle->paddingTop + (itemIndex - (numItems + 1) / 2) * itemHeight;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SelectFromEnumPage::getItemLabel(int itemIndex, char *text, int count) {
|
||
|
if (showRadioButtonIcon) {
|
||
|
if (getValue(itemIndex) == currentValue) {
|
||
|
text[0] = (char)142;
|
||
|
} else {
|
||
|
text[0] = (char)141;
|
||
|
}
|
||
|
|
||
|
text[1] = ' ';
|
||
|
|
||
|
getLabel(itemIndex, text + 2, count - 2);
|
||
|
} else {
|
||
|
getLabel(itemIndex, text, count);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void showMenu(AppContext *appContext, const char *message, MenuType menuType, const char **menuItems, void(*callback)(int)) {
|
||
|
if (menuType == MENU_TYPE_BUTTON) {
|
||
|
appContext->pushPage(INTERNAL_PAGE_ID_MENU_WITH_BUTTONS, MenuWithButtonsPage::create(appContext, message, menuItems, callback));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
static MenuWithButtonsPage g_menuWithButtonsPage;
|
||
|
|
||
|
MenuWithButtonsPage *MenuWithButtonsPage::create(AppContext *appContext, const char *message, const char **menuItems, void(*callback)(int)) {
|
||
|
MenuWithButtonsPage *page = &g_menuWithButtonsPage;
|
||
|
|
||
|
page->init(appContext, message, menuItems, callback);
|
||
|
|
||
|
return page;
|
||
|
}
|
||
|
|
||
|
void MenuWithButtonsPage::init(AppContext *appContext, const char *message, const char **menuItems, void(*callback)(int)) {
|
||
|
m_appContext = appContext;
|
||
|
m_callback = callback;
|
||
|
|
||
|
m_containerRectangleWidget.type = WIDGET_TYPE_RECTANGLE;
|
||
|
m_containerRectangleWidget.data = DATA_ID_NONE;
|
||
|
m_containerRectangleWidget.action = ACTION_ID_NONE;
|
||
|
m_containerRectangleWidget.style = STYLE_ID_MENU_WITH_BUTTONS_CONTAINER;
|
||
|
m_containerRectangleWidget.flags.ignoreLuminosity = 0;
|
||
|
m_containerRectangleWidget.flags.invertColors = 1;
|
||
|
|
||
|
m_messageTextWidget.type = WIDGET_TYPE_TEXT;
|
||
|
m_messageTextWidget.data = DATA_ID_NONE;
|
||
|
m_messageTextWidget.action = ACTION_ID_NONE;
|
||
|
m_messageTextWidget.style = STYLE_ID_MENU_WITH_BUTTONS_MESSAGE;
|
||
|
m_messageTextWidget.text = message;
|
||
|
m_messageTextWidget.flags = 0;
|
||
|
TextWidget_autoSize(m_messageTextWidget);
|
||
|
|
||
|
size_t i;
|
||
|
|
||
|
for (i = 0; menuItems[i]; i++) {
|
||
|
m_buttonTextWidgets[i].type = WIDGET_TYPE_TEXT;
|
||
|
m_buttonTextWidgets[i].data = DATA_ID_NONE;
|
||
|
m_buttonTextWidgets[i].action = ACTION_ID_INTERNAL_MENU_WITH_BUTTONS;
|
||
|
m_buttonTextWidgets[i].style = STYLE_ID_MENU_WITH_BUTTONS_BUTTON;
|
||
|
m_buttonTextWidgets[i].text = menuItems[i];
|
||
|
m_buttonTextWidgets[i].flags = 0;
|
||
|
TextWidget_autoSize(m_buttonTextWidgets[i]);
|
||
|
}
|
||
|
|
||
|
m_numButtonTextWidgets = i;
|
||
|
|
||
|
const Style *styleContainer = getStyle(STYLE_ID_MENU_WITH_BUTTONS_CONTAINER);
|
||
|
const Style *styleButton = getStyle(STYLE_ID_MENU_WITH_BUTTONS_BUTTON);
|
||
|
|
||
|
int maxMenuItemWidth = 0;
|
||
|
for (size_t i = 0; i < m_numButtonTextWidgets; i++) {
|
||
|
maxMenuItemWidth = MAX(maxMenuItemWidth, m_buttonTextWidgets[i].width);
|
||
|
}
|
||
|
|
||
|
int menuItemsWidth = maxMenuItemWidth * m_numButtonTextWidgets + (m_numButtonTextWidgets - 1) * styleButton->paddingLeft;
|
||
|
|
||
|
int contentWidth = MAX(m_messageTextWidget.width, menuItemsWidth);
|
||
|
int contentHeight = m_messageTextWidget.height + m_buttonTextWidgets[0].height;
|
||
|
|
||
|
width = styleContainer->borderSizeLeft + styleContainer->paddingLeft + contentWidth + styleContainer->paddingRight + styleContainer->borderSizeRight;
|
||
|
height = styleContainer->borderSizeTop + styleContainer->paddingTop + contentHeight + styleContainer->paddingBottom + styleContainer->borderSizeBottom;
|
||
|
|
||
|
Rect rect;
|
||
|
m_appContext->getBoundingRect(rect);
|
||
|
|
||
|
x = rect.x + (rect.w - width) / 2;
|
||
|
y = rect.y + (rect.h - height) / 2;
|
||
|
|
||
|
m_containerRectangleWidget.x = 0;
|
||
|
m_containerRectangleWidget.y = 0;
|
||
|
m_containerRectangleWidget.width = width;
|
||
|
m_containerRectangleWidget.height = height;
|
||
|
|
||
|
m_messageTextWidget.x = styleContainer->borderSizeLeft + styleContainer->paddingLeft + (contentWidth - m_messageTextWidget.width) / 2;
|
||
|
m_messageTextWidget.y = styleContainer->borderSizeTop + styleContainer->paddingTop;
|
||
|
|
||
|
int xButtonTextWidget = styleContainer->borderSizeLeft + styleContainer->paddingLeft + (contentWidth - menuItemsWidth) / 2;
|
||
|
int yButtonTextWidget = styleContainer->borderSizeTop + styleContainer->paddingTop + m_messageTextWidget.height;
|
||
|
for (size_t i = 0; i < m_numButtonTextWidgets; i++) {
|
||
|
m_buttonTextWidgets[i].x = xButtonTextWidget;
|
||
|
m_buttonTextWidgets[i].y = yButtonTextWidget;
|
||
|
m_buttonTextWidgets[i].width = maxMenuItemWidth;
|
||
|
xButtonTextWidget += maxMenuItemWidth + styleButton->paddingLeft;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void MenuWithButtonsPage::updateInternalPage() {
|
||
|
WidgetCursor &widgetCursor = g_widgetCursor;
|
||
|
|
||
|
auto savedWidgetCursor = widgetCursor;
|
||
|
|
||
|
widgetCursor.widget = &m_containerRectangleWidget;
|
||
|
widgetCursor.x = x + m_containerRectangleWidget.x;
|
||
|
widgetCursor.y = y + m_containerRectangleWidget.y;
|
||
|
widgetCursor.w = m_containerRectangleWidget.width;
|
||
|
widgetCursor.h = m_containerRectangleWidget.height;
|
||
|
RectangleWidgetState rectangleWidgetState;
|
||
|
rectangleWidgetState.flags.active = 0;
|
||
|
rectangleWidgetState.render();
|
||
|
|
||
|
if (g_findCallback == nullptr && !widgetCursor.refreshed) {
|
||
|
widgetCursor.pushBackground(widgetCursor.x, widgetCursor.y, getStyle(m_containerRectangleWidget.style), false);
|
||
|
}
|
||
|
|
||
|
widgetCursor.widget = &m_messageTextWidget;
|
||
|
widgetCursor.x = x + m_messageTextWidget.x;
|
||
|
widgetCursor.y = y + m_messageTextWidget.y;
|
||
|
widgetCursor.w = m_messageTextWidget.width;
|
||
|
widgetCursor.h = m_messageTextWidget.height;
|
||
|
TextWidgetState textWidgetState;
|
||
|
textWidgetState.flags.active = 0;
|
||
|
textWidgetState.flags.blinking = 0;
|
||
|
textWidgetState.flags.focused = 0;
|
||
|
textWidgetState.render();
|
||
|
|
||
|
for (size_t i = 0; i < m_numButtonTextWidgets; i++) {
|
||
|
widgetCursor.widget = &m_buttonTextWidgets[i];
|
||
|
widgetCursor.x = x + m_buttonTextWidgets[i].x;
|
||
|
widgetCursor.y = y + m_buttonTextWidgets[i].y;
|
||
|
widgetCursor.w = m_buttonTextWidgets[i].width;
|
||
|
widgetCursor.h = m_buttonTextWidgets[i].height;
|
||
|
widgetCursor.cursor = i;
|
||
|
TextWidgetState textWidgetState;
|
||
|
textWidgetState.flags.active = widgetCursor == g_activeWidget;
|
||
|
textWidgetState.flags.blinking = 0;
|
||
|
textWidgetState.flags.focused = 0;
|
||
|
textWidgetState.render();
|
||
|
}
|
||
|
|
||
|
if (g_findCallback == nullptr && !widgetCursor.refreshed) {
|
||
|
widgetCursor.popBackground();
|
||
|
}
|
||
|
|
||
|
widgetCursor = savedWidgetCursor;
|
||
|
}
|
||
|
|
||
|
WidgetCursor MenuWithButtonsPage::findWidgetInternalPage(int x, int y, bool clicked) {
|
||
|
WidgetCursor widgetCursor = g_widgetCursor;
|
||
|
widgetCursor.appContext = m_appContext;
|
||
|
|
||
|
for (size_t i = 0; i < m_numButtonTextWidgets; i++) {
|
||
|
widgetCursor.widget = &m_buttonTextWidgets[i];
|
||
|
widgetCursor.x = this->x + m_buttonTextWidgets[i].x;
|
||
|
widgetCursor.y = this->y + m_buttonTextWidgets[i].y;
|
||
|
widgetCursor.w = m_buttonTextWidgets[i].width;
|
||
|
widgetCursor.h = m_buttonTextWidgets[i].height;
|
||
|
widgetCursor.cursor = i;
|
||
|
if (
|
||
|
x >= widgetCursor.x && x < widgetCursor.x + widgetCursor.w &&
|
||
|
y >= widgetCursor.y && y < widgetCursor.y + widgetCursor.h
|
||
|
) {
|
||
|
return widgetCursor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
widgetCursor.widget = &m_containerRectangleWidget;
|
||
|
widgetCursor.x = this->x + m_containerRectangleWidget.x;
|
||
|
widgetCursor.y = this->y + m_containerRectangleWidget.y;
|
||
|
|
||
|
return widgetCursor;
|
||
|
}
|
||
|
|
||
|
void MenuWithButtonsPage::executeAction() {
|
||
|
(*g_menuWithButtonsPage.m_callback)(getFoundWidgetAtDown().cursor);
|
||
|
}
|
||
|
|
||
|
} // namespace gui
|
||
|
} // namespace eez
|