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.
 
 
 
 
 
 

543 lines
17 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 <math.h>
#include <assert.h>
#include <memory.h>
#include <eez/conf.h>
#include <eez/core/sound.h>
#include <eez/core/os.h>
#include <eez/core/util.h>
#if OPTION_KEYBOARD
#include <eez/core/keyboard.h>
#endif
#if OPTION_MOUSE
#include <eez/core/mouse.h>
#endif
#include <eez/gui/gui.h>
#include <eez/gui/thread.h>
#include <eez/gui/touch_calibration.h>
#include <eez/gui/widgets/button.h>
#include <eez/flow/debugger.h>
#include <eez/flow/private.h>
#include <eez/core/hmi.h>
#define CONF_GUI_TOAST_DURATION_MS 1000L
namespace eez {
namespace gui {
////////////////////////////////////////////////////////////////////////////////
AppContext::AppContext() {
m_updatePageIndex = -1;
}
void AppContext::stateManagment() {
// remove alert message after period of time
if (getActivePageId() == INTERNAL_PAGE_ID_TOAST_MESSAGE) {
ToastMessagePage *page = (ToastMessagePage *)getActivePage();
if (!page->hasAction() && eez::hmi::getInactivityPeriodMs() >= CONF_GUI_TOAST_DURATION_MS) {
popPage();
}
}
}
////////////////////////////////////////////////////////////////////////////////
bool AppContext::isActivePageInternal() {
return isPageInternal(getActivePageId());
}
bool AppContext::isWidgetActionEnabled(const WidgetCursor &widgetCursor) {
const Widget *widget = widgetCursor.widget;
auto action = getWidgetAction(widgetCursor);
if (action) {
if (widget->type == WIDGET_TYPE_BUTTON) {
auto buttonWidget = (const ButtonWidget *)widget;
auto enabled = get(widgetCursor, buttonWidget->enabled);
if (!(enabled.getType() == VALUE_TYPE_UNDEFINED || enabled.getInt() ? 1 : 0)) {
return false;
}
}
return true;
}
return false;
}
bool AppContext::isAutoRepeatAction(int action) {
return false;
}
bool AppContext::isFocusWidget(const WidgetCursor &widgetCursor) {
return false;
}
////////////////////////////////////////////////////////////////////////////////
void AppContext::onPageChanged(int previousPageId, int activePageId) {
display::turnOn();
hmi::noteActivity();
#if OPTION_MOUSE
mouse::onPageChanged();
#endif
#if OPTION_KEYBOARD
keyboard::onPageChanged();
#endif
flow::onPageChanged(previousPageId, activePageId);
}
void AppContext::doShowPage(int pageId, Page *page, int previousPageId) {
#if CONF_OPTION_FPGA
pageId = PAGE_ID_WELCOME_800X480;
page = nullptr;
#endif
page = page ? page : g_hooks.getPageFromId(pageId);
m_pageNavigationStack[m_pageNavigationStackPointer].page = page;
m_pageNavigationStack[m_pageNavigationStackPointer].pageId = pageId;
m_pageNavigationStack[m_pageNavigationStackPointer].displayBufferIndex = -1;
m_pageNavigationStack[m_pageNavigationStackPointer].timelinePosition = 0;
if (page) {
page->pageWillAppear();
}
m_showPageTime = millis();
onPageChanged(previousPageId, pageId);
refreshScreen();
}
void AppContext::setPage(int pageId) {
int previousPageId = getActivePageId();
// delete stack
for (int i = 0; i <= m_pageNavigationStackPointer; ++i) {
if (m_pageNavigationStack[i].page) {
m_pageNavigationStack[i].page->pageFree();
}
}
m_pageNavigationStackPointer = 0;
//
doShowPage(pageId, nullptr, previousPageId);
}
void AppContext::replacePage(int pageId, Page *page) {
int previousPageId = getActivePageId();
Page *activePage = getActivePage();
if (activePage) {
activePage->pageFree();
}
doShowPage(pageId, page, previousPageId);
}
void AppContext::pushPage(int pageId, Page *page) {
if (pushPageInGuiThread(this, pageId, page)) {
return;
}
int previousPageId = getActivePageId();
// advance stack pointer
if (getActivePageId() != PAGE_ID_NONE && getActivePageId() != PAGE_ID_ASYNC_OPERATION_IN_PROGRESS && getActivePageId() != INTERNAL_PAGE_ID_TOAST_MESSAGE) {
m_pageNavigationStackPointer++;
assert (m_pageNavigationStackPointer < CONF_GUI_PAGE_NAVIGATION_STACK_SIZE);
}
doShowPage(pageId, page, previousPageId);
}
void AppContext::popPage() {
if (m_pageNavigationStackPointer > 0) {
int previousPageId = getActivePageId();
if (m_pageNavigationStack[m_pageNavigationStackPointer].page) {
m_pageNavigationStack[m_pageNavigationStackPointer].page->pageFree();
m_pageNavigationStack[m_pageNavigationStackPointer].page = nullptr;
}
--m_pageNavigationStackPointer;
doShowPage(m_pageNavigationStack[m_pageNavigationStackPointer].pageId, m_pageNavigationStack[m_pageNavigationStackPointer].page, previousPageId);
}
}
void AppContext::removePageFromStack(int pageId) {
for (int i = m_pageNavigationStackPointer; i > 0; i--) {
if (m_pageNavigationStack[i].pageId == pageId) {
if (i == m_pageNavigationStackPointer) {
popPage();
} else {
if (m_pageNavigationStack[i].page) {
m_pageNavigationStack[i].page->pageFree();
}
for (int j = i + 1; j <= m_pageNavigationStackPointer; j++) {
memcpy(m_pageNavigationStack + j - 1, m_pageNavigationStack + j, sizeof(PageOnStack));
}
m_pageNavigationStack[m_pageNavigationStackPointer].page = nullptr;
--m_pageNavigationStackPointer;
}
refreshScreen();
break;
}
}
}
Page *AppContext::getPage(int pageId) {
for (int i = 0; i <= m_pageNavigationStackPointer; ++i) {
if (m_pageNavigationStack[i].pageId == pageId) {
return m_pageNavigationStack[i].page;
}
}
return nullptr;
}
bool AppContext::isPageOnStack(int pageId) {
for (int i = 0; i <= m_pageNavigationStackPointer; ++i) {
if (m_pageNavigationStack[i].pageId == pageId) {
return true;
}
}
return false;
}
bool AppContext::isExternalPageOnStack() {
for (int i = 0; i <= m_pageNavigationStackPointer; ++i) {
if (m_pageNavigationStack[i].pageId < 0) {
return true;
}
}
return false;
}
void AppContext::removeExternalPagesFromTheStack() {
for (int i = 0; i <= m_pageNavigationStackPointer; ++i) {
if (m_pageNavigationStack[i].pageId < 0) {
removePageFromStack(m_pageNavigationStack[i].pageId);
i = 0;
}
}
}
void AppContext::showPage(int pageId) {
if (showPageInGuiThread(this, pageId)) {
return;
}
if (pageId != getActivePageId()) {
setPage(pageId);
}
}
////////////////////////////////////////////////////////////////////////////////
bool AppContext::testExecuteActionOnTouchDown(int action) {
return false;
}
bool AppContext::isBlinking(const WidgetCursor &widgetCursor, int16_t id) {
return false;
}
bool AppContext::canExecuteActionWhenTouchedOutsideOfActivePage(int pageId, int action) {
return false;
}
void AppContext::onPageTouch(const WidgetCursor &foundWidget, Event &touchEvent) {
int activePageId = getActivePageId();
#if OPTION_TOUCH_CALIBRATION
if (activePageId == PAGE_ID_TOUCH_CALIBRATION) {
onTouchCalibrationPageTouch(foundWidget, touchEvent);
return;
}
#endif
if (activePageId != PAGE_ID_NONE && !isPageInternal(activePageId)) {
auto page = getPageAsset(activePageId);
if ((page->flags & CLOSE_PAGE_IF_TOUCHED_OUTSIDE_FLAG) != 0) {
int xPage;
int yPage;
int wPage;
int hPage;
getPageRect(activePageId, getActivePage(), xPage, yPage, wPage, hPage);
if (!pointInsideRect(touchEvent.x, touchEvent.y, xPage, yPage, wPage, hPage)) {
int activePageId = getActivePageId();
// clicked outside page, close page
popPage();
auto widgetCursor = findWidget(touchEvent.x, touchEvent.y);
if (widgetCursor.widget) {
auto action = getWidgetAction(widgetCursor);
if (action != ACTION_ID_NONE && canExecuteActionWhenTouchedOutsideOfActivePage(activePageId, action)) {
processTouchEvent(touchEvent);
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
void AppContext::updatePage(int i, WidgetCursor &widgetCursor) {
if (g_findCallback == nullptr) {
m_pageNavigationStack[i].displayBufferIndex = display::beginBufferRendering();
}
m_updatePageIndex = i;
int x;
int y;
int width;
int height;
bool withShadow;
if (isPageInternal(m_pageNavigationStack[i].pageId)) {
auto internalPage = ((InternalPage *)m_pageNavigationStack[i].page);
x = internalPage->x;
y = internalPage->y;
width = internalPage->width;
height = internalPage->height;
withShadow = true;
widgetCursor.w = width;
widgetCursor.h = height;
if (g_findCallback == nullptr) {
internalPage->updateInternalPage();
}
enumNoneWidget();
} else {
auto page = getPageAsset(m_pageNavigationStack[i].pageId, widgetCursor);
if (widgetCursor.flowState && widgetCursor.flowState->timelinePosition != m_pageNavigationStack[i].timelinePosition) {
widgetCursor.hasPreviousState = false;
m_pageNavigationStack[i].timelinePosition = widgetCursor.flowState->timelinePosition;
}
auto savedWidget = widgetCursor.widget;
widgetCursor.widget = page;
if ((page->flags & PAGE_SCALE_TO_FIT) && flow::g_debuggerMode == flow::DEBUGGER_MODE_RUN) {
x = rect.x;
y = rect.y;
width = rect.w;
height = rect.h;
} else {
x = widgetCursor.x + page->x;
y = widgetCursor.y + page->y;
width = page->width;
height = page->height;
}
withShadow = page->x > 0;
auto savedX = widgetCursor.x;
auto savedY = widgetCursor.y;
widgetCursor.x = x;
widgetCursor.y = y;
widgetCursor.w = width;
widgetCursor.h = height;
enumWidget();
widgetCursor.x = savedX;
widgetCursor.y = savedY;
widgetCursor.widget = savedWidget;
}
if (g_findCallback == nullptr) {
pageRenderCustom(i, widgetCursor);
display::endBufferRendering(m_pageNavigationStack[i].displayBufferIndex, x, y, width, height, withShadow, 255, 0, 0, withShadow && g_hooks.activePageHasBackdrop() ? &rect : nullptr);
}
m_updatePageIndex = -1;
}
void AppContext::pageRenderCustom(int i, WidgetCursor &widgetCursor) {
}
bool isRect1FullyCoveredByRect2(int xRect1, int yRect1, int wRect1, int hRect1, int xRect2, int yRect2, int wRect2, int hRect2) {
return xRect2 <= xRect1 && yRect2 <= yRect1 && xRect2 + wRect2 >= xRect1 + wRect1 && yRect2 + hRect2 >= yRect1 + hRect1;
}
void AppContext::getPageRect(int pageId, const Page *page, int &x, int &y, int &w, int &h) {
if (isPageInternal(pageId)) {
x = rect.x + ((InternalPage *)page)->x;
y = rect.y + ((InternalPage *)page)->y;
w = ((InternalPage *)page)->width;
h = ((InternalPage *)page)->height;
} else {
auto page = getPageAsset(pageId);
if ((page->flags & PAGE_SCALE_TO_FIT) && flow::g_debuggerMode == flow::DEBUGGER_MODE_RUN) {
x = rect.x;
y = rect.y;
w = rect.w;
h = rect.h;
} else {
x = rect.x + page->x;
y = rect.y + page->y;
w = page->width;
h = page->height;
}
}
}
bool AppContext::isPageFullyCovered(int pageNavigationStackIndex) {
int xPage, yPage, wPage, hPage;
getPageRect(m_pageNavigationStack[pageNavigationStackIndex].pageId, m_pageNavigationStack[pageNavigationStackIndex].page, xPage, yPage, wPage, hPage);
for (int i = pageNavigationStackIndex + 1; i <= m_pageNavigationStackPointer; i++) {
int xPageAbove, yPageAbove, wPageAbove, hPageAbove;
getPageRect(m_pageNavigationStack[i].pageId, m_pageNavigationStack[i].page, xPageAbove, yPageAbove, wPageAbove, hPageAbove);
if (isRect1FullyCoveredByRect2(xPage, yPage, wPage, hPage, xPageAbove, yPageAbove, wPageAbove, hPageAbove)) {
return true;
}
}
return false;
}
int AppContext::getLongTouchActionHook(const WidgetCursor &widgetCursor) {
return ACTION_ID_NONE;
}
void AppContext::yesNoDialog(int yesNoPageId, const char *message, void (*yes_callback)(), void (*no_callback)(), void (*cancel_callback)()) {
set(WidgetCursor(), DATA_ID_ALERT_MESSAGE, Value(message));
m_dialogYesCallback = yes_callback;
m_dialogNoCallback = no_callback;
m_dialogCancelCallback = cancel_callback;
pushPage(yesNoPageId);
}
void AppContext::yesNoDialog(int yesNoPageId, Value value, void(*yes_callback)(), void(*no_callback)(), void(*cancel_callback)()) {
set(WidgetCursor(), DATA_ID_ALERT_MESSAGE, value);
m_dialogYesCallback = yes_callback;
m_dialogNoCallback = no_callback;
m_dialogCancelCallback = cancel_callback;
pushPage(yesNoPageId);
}
void AppContext::infoMessage(const char *message) {
pushToastMessage(ToastMessagePage::create(this, INFO_TOAST, message));
}
void AppContext::infoMessage(Value value) {
pushToastMessage(ToastMessagePage::create(this, INFO_TOAST, value));
}
void AppContext::infoMessage(const char *message, void (*action)(), const char *actionLabel) {
pushToastMessage(ToastMessagePage::create(this, INFO_TOAST, message, action, actionLabel));
}
void AppContext::errorMessage(const char *message, bool autoDismiss) {
AppContext::pushToastMessage(ToastMessagePage::create(this, ERROR_TOAST, message, autoDismiss));
sound::playBeep();
}
void AppContext::errorMessage(Value value) {
AppContext::pushToastMessage(ToastMessagePage::create(this, ERROR_TOAST, value));
sound::playBeep();
}
void AppContext::errorMessageWithAction(Value value, void (*action)(int param), const char *actionLabel, int actionParam) {
AppContext::pushToastMessage(ToastMessagePage::create(this, ERROR_TOAST, value, action, actionLabel, actionParam));
sound::playBeep();
}
void AppContext::errorMessageWithAction(const char *message, void (*action)(), const char *actionLabel) {
AppContext::pushToastMessage(ToastMessagePage::create(this, ERROR_TOAST, message, action, actionLabel));
sound::playBeep();
}
void AppContext::pushToastMessage(ToastMessagePage *toastMessage) {
pushPage(INTERNAL_PAGE_ID_TOAST_MESSAGE, toastMessage);
}
void AppContext::getBoundingRect(Rect &rectBounding) {
if (m_pageNavigationStackPointer >= 0) {
int x1 = rect.x + rect.w;
int y1 = rect.y + rect.h;
int x2 = rect.x;
int y2 = rect.y;
for (int i = 0; i <= m_pageNavigationStackPointer; ++i) {
if (!isPageInternal(m_pageNavigationStack[i].pageId)) {
int xPage, yPage, wPage, hPage;
getPageRect(m_pageNavigationStack[i].pageId, m_pageNavigationStack[i].page, xPage, yPage, wPage, hPage);
if (xPage < x1) x1 = xPage;
if (xPage + wPage > x2) x2 = xPage + wPage;
if (yPage < y1) y1 = yPage;
if (yPage + hPage > y2) y2 = yPage + hPage;
}
}
rectBounding.x = x1;
rectBounding.y = y1;
rectBounding.w = x2 - x1;
rectBounding.h = y2 - y1;
} else {
rectBounding.x = rect.x;
rectBounding.y = rect.y;
rectBounding.w = rect.w;
rectBounding.h = rect.h;
}
}
AppContext *getRootAppContext() {
#ifdef EEZ_PLATFORM_SIMULATOR
return getAppContextFromId(APP_CONTEXT_ID_SIMULATOR_FRONT_PANEL);
#else
return getAppContextFromId(APP_CONTEXT_ID_DEVICE);
#endif
}
} // namespace gui
} // namespace eez