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.
 
 
 
 
 
 

647 lines
20 KiB

/*
* EEZ Generic 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 <assert.h>
#include <cstddef>
#include <limits.h>
#include <eez/core/debug.h>
#include <eez/core/os.h>
#include <eez/core/util.h>
#include <eez/gui/gui.h>
#include <eez/gui/assets.h>
#include <eez/gui/widgets/containers/app_view.h>
#include <eez/gui/widgets/containers/container.h>
#include <eez/gui/widgets/containers/grid.h>
#include <eez/gui/widgets/containers/layout_view.h>
#include <eez/gui/widgets/containers/list.h>
#include <eez/gui/widgets/containers/select.h>
#include <eez/gui/widgets/bar_graph.h>
#include <eez/gui/widgets/bitmap.h>
#include <eez/gui/widgets/button_group.h>
#include <eez/gui/widgets/button.h>
#include <eez/gui/widgets/canvas.h>
#include <eez/gui/widgets/display_data.h>
#include <eez/gui/widgets/drop_down_list.h>
#include <eez/gui/widgets/gauge.h>
#include <eez/gui/widgets/input.h>
#include <eez/gui/widgets/list_graph.h>
#include <eez/gui/widgets/multiline_text.h>
#include <eez/gui/widgets/progress.h>
#include <eez/gui/widgets/rectangle.h>
#include <eez/gui/widgets/roller.h>
#include <eez/gui/widgets/scroll_bar.h>
#include <eez/gui/widgets/slider.h>
#include <eez/gui/widgets/switch.h>
#include <eez/gui/widgets/text.h>
#include <eez/gui/widgets/toggle_button.h>
#include <eez/gui/widgets/up_down.h>
#include <eez/gui/widgets/yt_graph.h>
namespace eez {
namespace gui {
////////////////////////////////////////////////////////////////////////////////
bool g_isActiveWidget;
EnumWidgetsCallback g_findCallback;
bool g_foundWidgetAtDownInvalid;
bool g_isRTL = false;
////////////////////////////////////////////////////////////////////////////////
struct NoneWidgetState : public WidgetState {
bool updateState() {
WIDGET_STATE_START(Widget);
WIDGET_STATE_END()
}
};
static Widget g_noneWidget = { WIDGET_TYPE_NONE };
struct ReservedWidgetState : public WidgetState {};
// widget placementNew functions
#define WIDGET_TYPE(NAME_PASCAL_CASE, NAME, ID) \
void NAME##_placementNew(void *ptr) { new (ptr) NAME_PASCAL_CASE##WidgetState(); }
WIDGET_TYPES
#undef WIDGET_TYPE
typedef void (*WidgetStatePlacementNewFunctionType)(void *ptr);
#define WIDGET_TYPE(NAME_PASCAL_CASE, NAME, ID) NAME##_placementNew,
static WidgetStatePlacementNewFunctionType g_widgetStatePlacementNewFunctions[] = {
WIDGET_TYPES
};
#undef WIDGET_TYPE
// widget state sizes
#define WIDGET_TYPE(NAME_PASCAL_CASE, NAME, ID) sizeof(NAME_PASCAL_CASE##WidgetState),
static size_t g_widgetStateSizes[] = {
WIDGET_TYPES
};
#undef WIDGET_TYPE
////////////////////////////////////////////////////////////////////////////////
bool WidgetState::updateState() {
return false;
}
void WidgetState::render() {
}
void WidgetState::enumChildren() {
}
bool WidgetState::hasOnTouch() {
return false;
}
void WidgetState::onTouch(const WidgetCursor &widgetCursor, Event &touchEvent) {
}
bool WidgetState::hasOnKeyboard() {
return false;
}
bool WidgetState::onKeyboard(const WidgetCursor &widgetCursor, uint8_t key, uint8_t mod) {
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool WidgetCursor::isPage() const {
return widget->type == WIDGET_TYPE_CONTAINER && ((((ContainerWidget *)widget)->flags & PAGE_CONTAINER) != 0);
}
void WidgetCursor::pushBackground(int x, int y, const Style *style, bool active) {
if (backgroundStyleStackPointer == BACKGROUND_STYLE_STACK_SIZE) {
// make space: remove item at the bottom of stack
for (size_t i = 1; i < BACKGROUND_STYLE_STACK_SIZE; i++) {
backgroundStyleStack[i - 1] = backgroundStyleStack[i];
}
backgroundStyleStackPointer--;
}
backgroundStyleStack[backgroundStyleStackPointer].x = x;
backgroundStyleStack[backgroundStyleStackPointer].y = y;
backgroundStyleStack[backgroundStyleStackPointer].style = style;
backgroundStyleStack[backgroundStyleStackPointer].active = active;
backgroundStyleStackPointer++;
}
void WidgetCursor::popBackground() {
if (backgroundStyleStackPointer > 0) {
backgroundStyleStackPointer--;
}
}
////////////////////////////////////////////////////////////////////////////////
#define RENDER_WIDGET() \
if ((!widget->visible || widgetState->isVisible.toBool()) && widgetCursor.opacity > 0) { \
auto savedOpacity = display::setOpacity(widgetCursor.opacity); \
widgetState->render(); \
display::setOpacity(savedOpacity); \
}
void enumWidget() {
WidgetCursor &widgetCursor = g_widgetCursor;
const Widget *widget = widgetCursor.widget;
auto widgetState = widgetCursor.currentState;
bool savedIsActiveWidget = g_isActiveWidget;
g_isActiveWidget = g_isActiveWidget || widgetCursor == g_activeWidget;
if (g_findCallback) {
if (!widget->visible || widgetState->isVisible.toBool()) {
g_findCallback();
}
} else {
if (widgetCursor.hasPreviousState && widget->type == widgetState->type) {
// reuse existing widget state
bool refresh = widgetState->updateState();
if (refresh || widgetCursor.refreshed) {
RENDER_WIDGET();
}
} else {
if (widgetCursor.hasPreviousState) {
// clear old state from current state
freeWidgetStates(widgetState);
widgetCursor.hasPreviousState = false;
}
// create new widget state
g_widgetStatePlacementNewFunctions[widget->type](widgetState);
widgetState->type = widget->type;
widgetState->updateState();
RENDER_WIDGET();
if (g_foundWidgetAtDownInvalid) {
// find new cursor for g_foundWidgetAtDown
auto &foundWidgetAtDown = getFoundWidgetAtDown();
if (foundWidgetAtDown == widgetCursor) {
foundWidgetAtDown = widgetCursor;
g_foundWidgetAtDownInvalid = false;
}
}
}
}
widgetCursor.currentState = (WidgetState *)((uint8_t *)widgetCursor.currentState + g_widgetStateSizes[widget->type]);
uint32_t stateSize = (uint8_t *)widgetCursor.currentState - (uint8_t *)g_widgetStateStart;
if (stateSize > GUI_STATE_BUFFER_SIZE) {
return;
}
widgetState->enumChildren();
g_isActiveWidget = savedIsActiveWidget;
}
void enumNoneWidget() {
WidgetCursor &widgetCursor = g_widgetCursor;
auto savedWidget = widgetCursor.widget;
widgetCursor.widget = &g_noneWidget;
auto savedX = g_widgetCursor.x;
auto savedY = g_widgetCursor.y;
g_widgetCursor.x += g_noneWidget.x;
g_widgetCursor.y += g_noneWidget.y;
g_widgetCursor.w = g_noneWidget.width;
g_widgetCursor.h = g_noneWidget.height;
enumWidget();
g_widgetCursor.x = savedX;
g_widgetCursor.y = savedY;
widgetCursor.widget = savedWidget;
}
////////////////////////////////////////////////////////////////////////////////
void freeWidgetStates(WidgetState *widgetStateStart) {
WidgetState *widgetState = widgetStateStart;
while (widgetState < g_widgetStateEnd) {
auto nextWidgetState = (WidgetState *)((uint8_t*)widgetState + g_widgetStateSizes[widgetState->type]);
widgetState->~WidgetState();
widgetState = nextWidgetState;
}
// invalidate g_foundWidgetAtDown if it was among freed widgets
auto &widgetCursor = getFoundWidgetAtDown();
if (widgetCursor.currentState >= widgetStateStart) {
g_foundWidgetAtDownInvalid = true;
}
}
////////////////////////////////////////////////////////////////////////////////
void forEachWidget(EnumWidgetsCallback callback) {
g_findCallback = callback;
enumRootWidget();
g_findCallback = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
static int g_findWidgetAtX;
static int g_findWidgetAtY;
static bool g_clicked;
static bool g_found;
static WidgetCursor g_foundWidget;
static int g_distanceToFoundWidget;
int g_xOverlayOffset = 0;
int g_yOverlayOffset = 0;
static AppContext *g_popPageAppContext;
static void findWidgetStep() {
if (g_found) {
return;
}
WidgetCursor &widgetCursor = g_widgetCursor;
if (widgetCursor.appContext->isActivePageInternal()) {
auto internalPage = (InternalPage *)widgetCursor.appContext->getActivePage();
WidgetCursor foundWidget = internalPage->findWidgetInternalPage(g_findWidgetAtX, g_findWidgetAtY, g_clicked);
if (foundWidget) {
g_foundWidget = foundWidget;
g_found = true;
return;
}
if (g_clicked) {
if (internalPage->closeIfTouchedOutside()) {
// clicked outside internal page, close internal page (if not toast)
g_popPageAppContext = widgetCursor.appContext;
}
}
bool passThrough = internalPage->canClickPassThrough();
if (!passThrough) {
g_foundWidget = 0;
g_found = true;
return;
}
}
if (widgetCursor.isPage()) {
if (g_foundWidget && g_foundWidget.appContext == widgetCursor.appContext) {
g_foundWidget = widgetCursor;
g_distanceToFoundWidget = INT_MAX;
}
}
const Widget *widget = widgetCursor.widget;
Overlay *overlay = getOverlay(widgetCursor);
if (overlay) {
getOverlayOffset(widgetCursor, g_xOverlayOffset, g_yOverlayOffset);
}
int x = widgetCursor.x + g_xOverlayOffset;
int y = widgetCursor.y + g_yOverlayOffset;
if (overlay) {
g_xOverlayOffset = 0;
g_yOverlayOffset = 0;
}
static const int MIN_SIZE = 50;
int w = overlay ? overlay->width : widgetCursor.w;
if (w < MIN_SIZE) {
x = x - (MIN_SIZE - w) / 2;
w = MIN_SIZE;
}
int h = overlay ? overlay->height : widgetCursor.h;
if (h < MIN_SIZE) {
y = y - (MIN_SIZE - h) / 2;
h = MIN_SIZE;
}
bool inside =
g_findWidgetAtX >= x && g_findWidgetAtX < x + w &&
g_findWidgetAtY >= y && g_findWidgetAtY < y + h;
if (inside && (widget->type == WIDGET_TYPE_APP_VIEW || getWidgetTouchFunction(widgetCursor))) {
int dx = g_findWidgetAtX - (x + w / 2);
int dy = g_findWidgetAtY - (y + h / 2);
int distance = dx * dx + dy * dy;
auto action = getWidgetAction(widgetCursor);
if (action == ACTION_ID_DRAG_OVERLAY) {
if (overlay && !overlay->state) {
return;
}
g_foundWidget = widgetCursor;
g_distanceToFoundWidget = INT_MAX;
} else {
if (
!g_foundWidget ||
distance <= g_distanceToFoundWidget ||
g_foundWidget.widget->type == WIDGET_TYPE_APP_VIEW ||
g_foundWidget.widget->type == WIDGET_TYPE_LIST ||
g_foundWidget.widget->type == WIDGET_TYPE_GRID
) {
g_foundWidget = widgetCursor;
g_distanceToFoundWidget = distance;
// if found widget is AppView, make sure we set right AppContext
if (widget->type == WIDGET_TYPE_APP_VIEW) {
if (widget->data != DATA_ID_NONE) {
Value appContextValue = get(widgetCursor, widget->data);
g_foundWidget.appContext = appContextValue.getAppContext();
}
}
}
}
}
}
WidgetCursor findWidget(int16_t x, int16_t y, bool clicked) {
g_found = false;
g_foundWidget = 0;
g_clicked = clicked;
g_findWidgetAtX = x;
g_findWidgetAtY = y;
g_xOverlayOffset = 0;
g_yOverlayOffset = 0;
g_popPageAppContext = nullptr;
forEachWidget(findWidgetStep);
if (g_popPageAppContext) {
g_popPageAppContext->popPage();
g_popPageAppContext = nullptr;
}
return g_foundWidget;
}
////////////////////////////////////////////////////////////////////////////////
void resizeWidget(
const Widget *widget,
Rect &widgetRect,
int containerOriginalWidth,
int containerOriginalHeight,
int containerWidth,
int containerHeight
) {
auto flags = widget->flags;
auto pinToLeft = flags & WIDGET_FLAG_PIN_TO_LEFT;
auto pinToRight = flags & WIDGET_FLAG_PIN_TO_RIGHT;
auto pinToTop = flags & WIDGET_FLAG_PIN_TO_TOP;
auto pinToBottom = flags & WIDGET_FLAG_PIN_TO_BOTTOM;
auto fixWidth = flags & WIDGET_FLAG_FIX_WIDTH;
auto fixHeight = flags & WIDGET_FLAG_FIX_HEIGHT;
auto left = widgetRect.x;
auto right = widgetRect.x + widgetRect.w;
if (pinToLeft) {
// left = left;
} else {
if (!fixWidth) {
left =
(widgetRect.x * containerWidth) /
containerOriginalWidth;
}
}
if (pinToRight) {
right = containerWidth - (containerOriginalWidth - right);
} else {
if (!fixWidth) {
right = (right * containerWidth) / containerOriginalWidth;
}
}
if (fixWidth) {
if (pinToLeft && !pinToRight) {
right = left + widgetRect.w;
} else if (pinToRight && !pinToLeft) {
left = right - widgetRect.w;
} else if (!pinToLeft && !pinToRight) {
auto center =
((widgetRect.x + widgetRect.w / 2) *
containerWidth) /
containerOriginalWidth;
left = center - widgetRect.w / 2;
right = left + widgetRect.w;
}
}
auto top = widgetRect.y;
auto bottom = widgetRect.y + widgetRect.h;
if (pinToTop) {
//top = top;
} else {
if (!fixHeight) {
top =
(widgetRect.y * containerHeight) /
containerOriginalHeight;
}
}
if (pinToBottom) {
bottom = containerHeight - (containerOriginalHeight - bottom);
} else {
if (!fixHeight) {
bottom =
(bottom * containerHeight) / containerOriginalHeight;
}
}
if (fixHeight) {
if (pinToTop && !pinToBottom) {
bottom = top + widgetRect.h;
} else if (pinToBottom && !pinToTop) {
top = bottom - widgetRect.h;
} else if (!pinToTop && !pinToBottom) {
auto center =
((widgetRect.y + widgetRect.h / 2) *
containerHeight) /
containerOriginalHeight;
top = center - widgetRect.h / 2;
bottom = top + widgetRect.h;
}
}
widgetRect.x = left;
widgetRect.y = top;
widgetRect.w = right - left;
widgetRect.h = bottom - top;
}
void applyTimeline(WidgetCursor& widgetCursor, Rect &widgetRect) {
if (widgetCursor.widget->timeline.count > 0) {
auto x = widgetCursor.widget->x;
auto y = widgetCursor.widget->y;
auto w = widgetCursor.widget->width;
auto h = widgetCursor.widget->height;
float opacity = 1.0f;
auto timelinePosition = widgetCursor.flowState->timelinePosition;
for (uint32_t i = 0; i < widgetCursor.widget->timeline.count; i++) {
auto keyframe = widgetCursor.widget->timeline[i];
if (timelinePosition < keyframe->start) {
continue;
}
if (
timelinePosition >= keyframe->start &&
timelinePosition <= keyframe->end
) {
auto t =
keyframe->start == keyframe->end
? 1
: (timelinePosition - keyframe->start) /
(keyframe->end - keyframe->start);
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_X) {
auto savedX = x;
x += g_easingFuncs[keyframe->xEasingFunc](t) * (keyframe->x - x);
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_WIDTH) {
auto right = savedX + w;
right += g_easingFuncs[keyframe->widthEasingFunc](t) * ((keyframe->x + keyframe->width) - right);
w = right - x;
}
} else if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_WIDTH) {
w += g_easingFuncs[keyframe->widthEasingFunc](t) * (keyframe->width - w);
}
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_Y) {
auto savedY = y;
y += g_easingFuncs[keyframe->yEasingFunc](t) * (keyframe->y - y);
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_HEIGHT) {
auto bottom = savedY + h;
bottom += g_easingFuncs[keyframe->heightEasingFunc](t) * ((keyframe->y + keyframe->height) - bottom);
h = bottom - y;
}
} else if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_HEIGHT) {
h += g_easingFuncs[keyframe->heightEasingFunc](t) * (keyframe->height - h);
}
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_OPACITY) {
opacity += g_easingFuncs[keyframe->opacityEasingFunc](t) * (keyframe->opacity - opacity);
}
break;
}
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_X) {
x = keyframe->x;
}
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_Y) {
y = keyframe->y;
}
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_WIDTH) {
w = keyframe->width;
}
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_HEIGHT) {
h = keyframe->height;
}
if (keyframe->enabledProperties & WIDGET_TIMELINE_PROPERTY_OPACITY) {
opacity = keyframe->opacity;
}
}
widgetRect.x = x;
widgetRect.y = y;
widgetRect.w = w;
widgetRect.h = h;
widgetCursor.opacity = (uint8_t)roundf(255.0f * opacity);
} else {
widgetRect.x = widgetCursor.widget->x;
widgetRect.y = widgetCursor.widget->y;
widgetRect.w = widgetCursor.widget->width;
widgetRect.h = widgetCursor.widget->height;
}
}
void doStaticLayout(
WidgetCursor& widgetCursor,
const ListOfAssetsPtr<Widget> &widgets,
int containerOriginalWidth,
int containerOriginalHeight,
int containerWidth,
int containerHeight
) {
bool callResizeWidget = containerOriginalWidth != containerWidth || containerOriginalHeight != containerHeight;
for (uint32_t index = 0; index < widgets.count; ++index) {
widgetCursor.widget = widgets[index];
auto savedX = widgetCursor.x;
auto savedY = widgetCursor.y;
auto savedOpacity = widgetCursor.opacity;
Rect widgetRect;
applyTimeline(widgetCursor, widgetRect);
if (callResizeWidget) {
resizeWidget(widgetCursor.widget, widgetRect, containerOriginalWidth, containerOriginalHeight, containerWidth, containerHeight);
}
widgetCursor.x += widgetRect.x;
widgetCursor.y += widgetRect.y;
widgetCursor.w = widgetRect.w;
widgetCursor.h = widgetRect.h;
if (g_isRTL) {
widgetCursor.x = savedX + containerWidth - ((widgetCursor.x - savedX) + widgetCursor.w);
}
enumWidget();
widgetCursor.x = savedX;
widgetCursor.y = savedY;
widgetCursor.opacity = savedOpacity;
}
}
} // namespace gui
} // namespace eez