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.
909 lines
30 KiB
909 lines
30 KiB
/* |
|
* 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
|
|
|