From 324622e11ea890c8582a9ebf14307872e89af158 Mon Sep 17 00:00:00 2001 From: "kkoenig1@optonline.com" Date: Thu, 21 Oct 2021 05:11:19 -0400 Subject: [PATCH] fix touchend and slide processing to make it consistent. --- radio/src/thirdparty/libopenui/mainwindow.cpp | 141 +++++ radio/src/thirdparty/libopenui/window.cpp | 564 ++++++++++++++++++ radio/src/thirdparty/libopenui/window.h | 451 ++++++++++++++ 3 files changed, 1156 insertions(+) create mode 100644 radio/src/thirdparty/libopenui/mainwindow.cpp create mode 100644 radio/src/thirdparty/libopenui/window.cpp create mode 100644 radio/src/thirdparty/libopenui/window.h diff --git a/radio/src/thirdparty/libopenui/mainwindow.cpp b/radio/src/thirdparty/libopenui/mainwindow.cpp new file mode 100644 index 000000000..de96d7fa9 --- /dev/null +++ b/radio/src/thirdparty/libopenui/mainwindow.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + */ + +#include "mainwindow.h" +#include "keyboard_base.h" + +#if defined(HARDWARE_TOUCH) +#include "touch.h" +#endif + +MainWindow * MainWindow::_instance = nullptr; + +#if defined(HARDWARE_TOUCH) +TouchState touchState; +Keyboard * Keyboard::activeKeyboard = nullptr; +#endif + +void MainWindow::emptyTrash() +{ + for (auto window: trash) { + delete window; + } + trash.clear(); +} + +void MainWindow::checkEvents() +{ +#if defined(HARDWARE_TOUCH) + if (touchPanelEventOccured()) { + touchPanelRead(); + } + + if (touchState.event == TE_DOWN) { + onTouchStart(touchState.x + scrollPositionX, touchState.y + scrollPositionY); + slidingWindow = nullptr; + } + else if (touchState.event == TE_UP) { + touchState.event = TE_NONE; + onTouchEnd(touchState.startX + scrollPositionX, touchState.startY + scrollPositionY); + } + else if (touchState.event == TE_SLIDE) { + if (touchState.deltaX || touchState.deltaY) { + onTouchSlide(touchState.x, touchState.y, touchState.startX, touchState.startY, touchState.deltaX, touchState.deltaY); + touchState.lastDeltaX = touchState.deltaX; + touchState.lastDeltaY = touchState.deltaY; + touchState.deltaX = 0; + touchState.deltaY = 0; + } + } + else if (touchState.event == TE_SLIDE_END && slidingWindow) { + if (touchState.lastDeltaX > SLIDE_SPEED_REDUCTION) + touchState.lastDeltaX -= SLIDE_SPEED_REDUCTION; + else if (touchState.lastDeltaX < -SLIDE_SPEED_REDUCTION) + touchState.lastDeltaX += SLIDE_SPEED_REDUCTION; + else + touchState.lastDeltaX = 0; + if (touchState.lastDeltaY > SLIDE_SPEED_REDUCTION) + touchState.lastDeltaY -= SLIDE_SPEED_REDUCTION; + else if (touchState.lastDeltaY < -SLIDE_SPEED_REDUCTION) + touchState.lastDeltaY += SLIDE_SPEED_REDUCTION; + else + touchState.lastDeltaY = 0; + + onTouchSlide(touchState.x, touchState.y, touchState.startX, touchState.startY, touchState.lastDeltaX, touchState.lastDeltaY); + slidingWindow = nullptr; + } +#endif + + Window::checkEvents(); +} + +void MainWindow::invalidate(const rect_t & rect) +{ + if (invalidatedRect.w) { + auto left = limit(0, rect.left(), invalidatedRect.left()); + auto right = limit(invalidatedRect.right(), rect.right(), LCD_W); + auto top = limit(0, rect.top(), invalidatedRect.top()); + auto bottom = limit(rect.bottom(), invalidatedRect.bottom(), LCD_H); + invalidatedRect = {left, top, right - left, bottom - top}; + } + else { + invalidatedRect = rect; + } +} + +bool MainWindow::refresh() +{ + if (invalidatedRect.w) { + if (invalidatedRect.x > 0 || invalidatedRect.y > 0 || invalidatedRect.w < LCD_W || invalidatedRect.h < LCD_H) { + TRACE_WINDOWS("Refresh rect: left=%d top=%d width=%d height=%d", invalidatedRect.left(), invalidatedRect.top(), invalidatedRect.w, invalidatedRect.h); + lcdCopy(lcd->getData(), lcdFront->getData()); + } + else { + TRACE_WINDOWS("Refresh full screen"); + } + lcd->setOffset(0, 0); + lcd->setClippingRect(invalidatedRect.left(), invalidatedRect.right(), invalidatedRect.top(), invalidatedRect.bottom()); + fullPaint(lcd); + invalidatedRect.w = 0; + return true; + } + else { + return false; + } +} + +void MainWindow::run(bool trash) +{ + auto start = ticksNow(); + + checkEvents(); + + if (trash) { + emptyTrash(); + } + + if (refresh()) { + lcdRefresh(); + } + + auto delta = ticksNow() - start; + if (delta > 10 * SYSTEM_TICKS_1MS) { + TRACE_WINDOWS("MainWindow::run took %dms", (ticksNow() - start) / SYSTEM_TICKS_1MS); + } +} diff --git a/radio/src/thirdparty/libopenui/window.cpp b/radio/src/thirdparty/libopenui/window.cpp new file mode 100644 index 000000000..46f5d4ce4 --- /dev/null +++ b/radio/src/thirdparty/libopenui/window.cpp @@ -0,0 +1,564 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + */ + +#include "window.h" +#include "touch.h" + +Window * Window::focusWindow = nullptr; +Window * Window::slidingWindow = nullptr; +Window * Window::touchedWindow = nullptr; +std::list Window::trash; + +Window::Window(Window * parent, const rect_t & rect, WindowFlags windowFlags, LcdFlags textFlags): + parent(parent), + rect(rect), + innerWidth(rect.w), + innerHeight(rect.h), + windowFlags(windowFlags), + textFlags(textFlags) +{ + if (parent) { + parent->addChild(this, windowFlags & PUSH_FRONT); + if (!(windowFlags & TRANSPARENT)) { + invalidate(); + } + } +} + +Window::~Window() +{ + TRACE_WINDOWS("Destroy %p %s", this, getWindowDebugString().c_str()); + + if (focusWindow == this) { + focusWindow = nullptr; + } + + deleteChildren(); +} + +void Window::attach(Window * newParent) +{ + if (parent) detach(); + parent = newParent; + if (newParent) newParent->addChild(this); +} + +void Window::detach() +{ + if (parent) { + parent->removeChild(this); + parent = nullptr; + } +} + +void Window::deleteLater(bool detach, bool trash) +{ + if (_deleted) + return; + + TRACE_WINDOWS("Delete %p %s", this, getWindowDebugString().c_str()); + + _deleted = true; + + if (static_cast(focusWindow) == static_cast(this)) { + focusWindow = nullptr; + } + + if (detach) + this->detach(); + else + parent = nullptr; + + deleteChildren(); + + if (closeHandler) { + closeHandler(); + } + + if (trash) { + Window::trash.push_back(this); + } +} + +void Window::clear() +{ + scrollPositionX = 0; + scrollPositionY = 0; + innerWidth = rect.w; + innerHeight = rect.h; + deleteChildren(); + invalidate(); +} + +void Window::deleteChildren() +{ + for (auto window: children) { + window->deleteLater(false); + } + children.clear(); +} + +void Window::clearFocus() +{ + if (focusWindow) { + focusWindow->onFocusLost(); + focusWindow = nullptr; + } +} + +void Window::setFocus(uint8_t flag, Window * from) +{ + if (deleted()) + return; + + TRACE_WINDOWS("%s setFocus()", getWindowDebugString().c_str()); + + if (focusWindow != this) { + // scroll before calling focusHandler so that the window can adjust the scroll position if needed + Window * parent = this->parent; + while (parent && parent->getWindowFlags() & FORWARD_SCROLL) { + parent = parent->parent; + } + if (parent) { + parent->scrollTo(this); + invalidate(); + } + + clearFocus(); + focusWindow = this; + if (focusHandler) { + focusHandler(true); + } + } +} + +void Window::setScrollPositionX(coord_t value) +{ + auto newScrollPosition = max(0, min(innerWidth - width(), value)); + if (newScrollPosition != scrollPositionX) { + scrollPositionX = newScrollPosition; + invalidate(); + } +} + +void Window::setScrollPositionY(coord_t value) +{ + auto newScrollPosition = min(innerHeight - height(), value); + + if (newScrollPosition < 0 && innerHeight != INFINITE_HEIGHT) { + newScrollPosition = 0; + } + + if (newScrollPosition != scrollPositionY) { + scrollPositionY = newScrollPosition; + invalidate(); + } +} + +void Window::scrollTo(Window * child) +{ + coord_t offsetX = 0; + coord_t offsetY = 0; + + Window * parentWindow = child->getParent(); + while (parentWindow && parentWindow != this) { + offsetX += parentWindow->left(); + offsetY += parentWindow->top(); + parentWindow = parentWindow->getParent(); + } + + const rect_t scrollRect = { + offsetX + child->left(), + offsetY + child->top(), + min(child->width(), width()), + min(child->height(), height()) + }; + + scrollTo(scrollRect); +} + +void Window::scrollTo(const rect_t & rect) +{ + if (rect.top() < scrollPositionY) { + setScrollPositionY(pageHeight ? rect.top() - (rect.top() % pageHeight) : rect.top() - 5); + } + else if (rect.bottom() > scrollPositionY + height() - 5) { + setScrollPositionY(pageHeight ? rect.top() - (rect.top() % pageHeight) : rect.bottom() - height() + 5); + } + + if (rect.left() < scrollPositionX) { + setScrollPositionX(pageWidth ? rect.left() - (rect.left() % pageWidth) : rect.left() - 5); + } + else if (rect.right() > scrollPositionX + width() - 5) { + setScrollPositionX(pageWidth ? rect.left() - (rect.left() % pageWidth) : rect.right() - width() + 5); + } +} + +bool Window::hasOpaqueRect(const rect_t & testRect) const +{ + if (!rect.contains(testRect)) + return false; + + if (windowFlags & OPAQUE) { + return true; + } + + rect_t relativeRect = {testRect.x - rect.x, testRect.y - rect.y, testRect.w, testRect.h}; + for (auto child: children) { + if (child->hasOpaqueRect(relativeRect)) + return true; + } + + return false; +} + +void Window::fullPaint(BitmapBuffer * dc) +{ + bool paintNeeded = true; + std::list::iterator firstChild; + + coord_t xmin, xmax, ymin, ymax; + dc->getClippingRect(xmin, xmax, ymin, ymax); + coord_t x = dc->getOffsetX(); + coord_t y = dc->getOffsetY(); + + if (windowFlags & PAINT_CHILDREN_FIRST) { + paintChildren(dc, children.begin()); + dc->setOffset(x, y); + dc->setClippingRect(xmin, xmax, ymin, ymax); + } + else { + firstChild = children.end(); + rect_t relativeRect = {xmin - x, ymin - y, xmax - xmin, ymax - ymin}; + while (firstChild != children.begin()) { + auto child = *(--firstChild); + if (child->hasOpaqueRect(relativeRect)) { + paintNeeded = false; + break; + } + } + } + + if (paintNeeded) { + TRACE_WINDOWS_INDENT("%s%s", getWindowDebugString().c_str(), hasFocus() ? " (*)" : ""); + paint(dc); +#if defined(WINDOWS_INSPECT_BORDER_COLOR) + dc->drawSolidRect(0, 0, width(), height(), 1, WINDOWS_INSPECT_BORDER_COLOR); +#endif + } + else { + TRACE_WINDOWS_INDENT("%s (skipped)", getWindowDebugString().c_str()); + } + + if (!(windowFlags & NO_SCROLLBAR)) { + drawVerticalScrollbar(dc); + } + + if (!(windowFlags & PAINT_CHILDREN_FIRST)) { + paintChildren(dc, firstChild); + } +} + +bool Window::isChildFullSize(const Window * child) const +{ + return child->top() == 0 && child->height() == height() && child->left() == 0 && child->width() == width(); +} + +bool Window::isChildVisible(const Window * window) const +{ + for (auto rit = children.rbegin(); rit != children.rend(); rit++) { + auto child = *rit; + if (child == window) { + return true; + } + if ((child->getWindowFlags() & OPAQUE) & isChildFullSize(child)) { + return false; + } + } + return false; +} + +void Window::setInsideParentScrollingArea() +{ + Window * parent = getParent(); + while (parent && parent->getWindowFlags() & FORWARD_SCROLL) { + parent = parent->parent; + } + if (parent) { + parent->scrollTo(this); + invalidate(); + } +} + +void Window::paintChildren(BitmapBuffer * dc, std::list::iterator it) +{ + coord_t x = dc->getOffsetX(); + coord_t y = dc->getOffsetY(); + coord_t xmin, xmax, ymin, ymax; + dc->getClippingRect(xmin, xmax, ymin, ymax); + + for (; it != children.end(); it++) { + auto child = *it; + + coord_t child_xmin = x + child->rect.x; + if (child_xmin >= xmax) + continue; + coord_t child_ymin = y + child->rect.y; + if (child_ymin >= ymax) + continue; + coord_t child_xmax = child_xmin + child->rect.w; + if (child_xmax <= xmin) + continue; + coord_t child_ymax = child_ymin + child->rect.h; + if (child_ymax <= ymin) + continue; + + dc->setOffset(x + child->rect.x - child->scrollPositionX, y + child->rect.y - child->scrollPositionY); + dc->setClippingRect(max(xmin, x + child->rect.left()), + min(xmax, x + child->rect.right()), + max(ymin, y + child->rect.top()), + min(ymax, y + child->rect.bottom())); + child->fullPaint(dc); + } +} + +#if defined(HARDWARE_TOUCH) +coord_t Window::getSnapStep(coord_t relativeScrollPosition, coord_t pageSize) +{ + coord_t result = 0; + if (relativeScrollPosition > pageSize / 2) { + // closer to next page + result = (pageSize - relativeScrollPosition); + } + else { + // closer to previous page + result = (0 - relativeScrollPosition); + } + + // do not get too slow + if (abs(result) > 32) + result /= 2; + + return result; +} +#endif + +void Window::checkEvents() +{ + auto copy = children; + for (auto child: copy) { + if (!child->deleted()) { + child->checkEvents(); + } + } + + if (this == Window::focusWindow) { + event_t event = getWindowEvent(); + if (event) { + this->onEvent(event); + } + } + + if (windowFlags & REFRESH_ALWAYS) { + invalidate(); + } + +#if defined(HARDWARE_TOUCH) + if (touchState.event != TE_SLIDE && touchState.lastDeltaX == 0 && touchState.lastDeltaY == 0) { + if (pageWidth) { + coord_t relativeScrollPosition = getScrollPositionX() % pageWidth; + if (relativeScrollPosition) { + setScrollPositionX(getScrollPositionX() + getSnapStep(relativeScrollPosition, pageWidth)); + } + } + if (pageHeight) { + coord_t relativeScrollPosition = getScrollPositionY() % pageHeight; + if (relativeScrollPosition) { + setScrollPositionY(getScrollPositionY() + getSnapStep(relativeScrollPosition, pageHeight)); + } + } + } +#endif +} + +void Window::onEvent(event_t event) +{ + TRACE_WINDOWS("%s received event 0x%X", Window::getWindowDebugString("Window").c_str(), event); + if (parent) { + parent->onEvent(event); + } +} + +#if defined(HARDWARE_TOUCH) +bool Window::onTouchStart(coord_t x, coord_t y) +{ + for (auto it = children.rbegin(); it != children.rend(); ++it) { + auto child = *it; + if (child->rect.contains(x, y)) { + if (child->onTouchStart(x - child->rect.x + child->scrollPositionX, y - child->rect.y + child->scrollPositionY)) { + // remember the first window that processed the touch start event. We will give that window + // the opportunity to process the touchend first, and also after a slide end so the touched window + // always gets a touchEnd if it processed the touch start. + if (touchedWindow == nullptr) { + touchedWindow = child; + } + return true; + } + } + } + + return false; +} + +bool Window::forwardTouchEnd(coord_t x, coord_t y) +{ + // give the touched window the opportunity to process the touch first before it is + // forwarded: See on touchstart + if (touchedWindow != nullptr) { + auto savedTouchWindow = touchedWindow; + touchedWindow = nullptr; + // transalte x and y through the parent hierarchy + auto myParent = savedTouchWindow->parent; + while(myParent != nullptr) { + x = x - myParent->rect.x + myParent->scrollPositionX; + y = y - myParent->rect.y + myParent->scrollPositionY; + myParent = myParent->parent; + } + + if (savedTouchWindow->onTouchEnd( + x - savedTouchWindow->rect.x + savedTouchWindow->scrollPositionX, + y - savedTouchWindow->rect.y + savedTouchWindow->scrollPositionY)) { + return true; + } + } + + for (auto it = children.rbegin(); it != children.rend(); ++it) { + auto child = *it; + if (child->rect.contains(x, y)) { + if (child->onTouchEnd(x - child->rect.x + child->scrollPositionX, y - child->rect.y + child->scrollPositionY)) { + return true; + } + } + } + + return false; +} + +bool Window::onTouchEnd(coord_t x, coord_t y) +{ + return forwardTouchEnd(x, y) ? true : (windowFlags & OPAQUE); +} + +bool Window::onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) +{ + startX += getScrollPositionX(); + startY += getScrollPositionY(); + + for (auto it = children.rbegin(); it != children.rend(); ++it) { + auto child = *it; + if (child->rect.contains(startX, startY)) { + if (child->onTouchSlide(x - child->rect.x, y - child->rect.y, startX - child->rect.x, startY - child->rect.y, slideX, slideY)) { + return true; + } + } + } + + if (slidingWindow && slidingWindow != this) { + return false; + } + + if (slideY && innerHeight > rect.h) { + setScrollPositionY(scrollPositionY - slideY); + slidingWindow = this; + return true; + } + + if (slideX && innerWidth > rect.w) { + setScrollPositionX(scrollPositionX - slideX); + slidingWindow = this; + return true; + } + + return false; +} +#endif + +void Window::adjustInnerHeight() +{ + coord_t bottomMax = 0; + for (auto child: children) { + bottomMax = max(bottomMax, child->rect.y + child->rect.h); + } + setInnerHeight(bottomMax); +} + +coord_t Window::adjustHeight() +{ + coord_t old = rect.h; + adjustInnerHeight(); + rect.h = innerHeight; + return rect.h - old; +} + +void Window::moveWindowsTop(coord_t y, coord_t delta) +{ + if (getWindowFlags() & FORWARD_SCROLL) { + parent->moveWindowsTop(bottom(), delta); + } + + for (auto child: children) { + if (child->rect.y >= y) { + child->rect.y += delta; + invalidate(); + } + } +} + +void Window::invalidate(const rect_t & rect) +{ + if (isVisible()) { + parent->invalidate({this->rect.x + rect.x - parent->scrollPositionX, this->rect.y + rect.y - parent->scrollPositionY, rect.w, rect.h}); + } +} + +void Window::drawVerticalScrollbar(BitmapBuffer * dc) +{ + if (innerHeight > rect.h) { + coord_t yofs = divRoundClosest(rect.h * scrollPositionY, innerHeight); + coord_t yhgt = divRoundClosest(rect.h * rect.h, innerHeight); + if (yhgt < 15) + yhgt = 15; + if (yhgt + yofs > rect.h) + yhgt = rect.h - yofs; + dc->drawSolidFilledRect(rect.w - SCROLLBAR_WIDTH, scrollPositionY + yofs, SCROLLBAR_WIDTH, yhgt, COLOR_THEME_PRIMARY3); + } +} + +void Window::drawHorizontalScrollbar(BitmapBuffer * dc) +{ + if (innerWidth > rect.w) { + coord_t xofs = divRoundClosest(rect.w * scrollPositionX, innerWidth); + coord_t xwdth = divRoundClosest(rect.w * rect.w, innerWidth); + if (xwdth < 15) + xwdth = 15; + if (xwdth + xofs > rect.w) + xwdth = rect.w - xofs; + dc->drawSolidFilledRect(scrollPositionX + xofs, rect.h - SCROLLBAR_WIDTH, xwdth, SCROLLBAR_WIDTH, COLOR_THEME_PRIMARY3); + } +} diff --git a/radio/src/thirdparty/libopenui/window.h b/radio/src/thirdparty/libopenui/window.h new file mode 100644 index 000000000..2b9b045e6 --- /dev/null +++ b/radio/src/thirdparty/libopenui/window.h @@ -0,0 +1,451 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "bitmapbuffer.h" +#include "libopenui_defines.h" +#include "libopenui_helpers.h" +#include "libopenui_config.h" + +typedef uint32_t WindowFlags; + +#if !defined(_GNUC_) + #undef OPAQUE + #undef TRANSPARENT +#endif + +constexpr int INFINITE_HEIGHT = INT32_MAX; + +constexpr WindowFlags OPAQUE = 1u << 0u; +constexpr WindowFlags TRANSPARENT = 1u << 1u; +constexpr WindowFlags NO_SCROLLBAR = 1u << 2u; +constexpr WindowFlags NO_FOCUS = 1u << 3u; +constexpr WindowFlags FORWARD_SCROLL = 1u << 4u; +constexpr WindowFlags REFRESH_ALWAYS = 1u << 5u; +constexpr WindowFlags PAINT_CHILDREN_FIRST = 1u << 6u; +constexpr WindowFlags PUSH_FRONT = 1u << 7u; +constexpr WindowFlags WINDOW_FLAGS_LAST = PUSH_FRONT; + +enum SetFocusFlag +{ + SET_FOCUS_DEFAULT, + SET_FOCUS_FORWARD, + SET_FOCUS_BACKWARD, + SET_FOCUS_FIRST +}; + +class Window +{ + friend class GridLayout; + + public: + Window(Window * parent, const rect_t & rect, WindowFlags windowFlags = 0, LcdFlags textFlags = 0); + + virtual ~Window(); + +#if defined(DEBUG_WINDOWS) + virtual std::string getName() const + { + return "Window"; + } + + std::string getRectString() const + { + char result[32]; + sprintf(result, "[%d, %d, %d, %d]", left(), top(), width(), height()); + return result; + } + + std::string getIndentString() const + { + std::string result; + auto tmp = parent; + while (tmp) { + result += " "; + tmp = tmp->getParent(); + } + return result; + } + + std::string getWindowDebugString(const char * name = nullptr) const + { + return getName() + (name ? std::string(" [") + name + "] " : " ") + getRectString(); + } +#endif + + Window * getParent() const + { + return parent; + } + + bool isChild(Window * window) const + { + return window == this || (parent && parent->isChild(window)); + } + + Window *getFullScreenWindow() + { + if (width() == LCD_W && height() == LCD_H) return this; + if (parent) return parent->getFullScreenWindow(); + return nullptr; + } + + WindowFlags getWindowFlags() const + { + return windowFlags; + } + + void setWindowFlags(WindowFlags flags) + { + windowFlags = flags; + } + + LcdFlags getTextFlags() const + { + return textFlags; + } + + void setTextFlags(LcdFlags flags) + { + textFlags = flags; + } + + void setCloseHandler(std::function handler) + { + closeHandler = std::move(handler); + } + + void setFocusHandler(std::function handler) + { + focusHandler = std::move(handler); + } + + const std::list getChildren() + { + return children; + } + + virtual void deleteLater(bool detach = true, bool trash = true); + + void clear(); + + void deleteChildren(); + + bool hasFocus() const + { + return focusWindow == this; + } + + static Window * getFocus() + { + return focusWindow; + } + + void scrollTo(const rect_t & rect); + + void scrollTo(Window * child); + + static void clearFocus(); + + virtual void setFocus(uint8_t flag = SET_FOCUS_DEFAULT, Window * from = nullptr); + + void setRect(rect_t value) + { + rect = value; + invalidate(); + } + + void setWidth(coord_t value) + { + rect.w = value; + invalidate(); + } + + void setWindowCentered() + { + rect.x = (parent->width() - width()) / 2; + rect.y = (parent->height() - height()) / 2; + } + + void setHeight(coord_t value) + { + rect.h = value; + if (windowFlags & FORWARD_SCROLL) + innerHeight = value; + else if (innerHeight <= value) { + setScrollPositionY(0); + } + invalidate(); + } + + void setLeft(coord_t x) + { + rect.x = x; + invalidate(); + } + + void setTop(coord_t y) + { + rect.y = y; + invalidate(); + } + + coord_t left() const + { + return rect.x; + } + + coord_t right() const + { + return rect.x + rect.w; + } + + coord_t top() const + { + return rect.y; + } + + coord_t bottom() const + { + return rect.y + rect.h; + } + + coord_t width() const + { + return rect.w; + } + + coord_t height() const + { + return rect.h; + } + + rect_t getRect() const + { + return rect; + } + + coord_t getInnerWidth() const + { + return innerWidth; + } + + void setInnerWidth(coord_t w) + { + innerWidth = w; + if (width() >= w) { + scrollPositionX = 0; + } + } + + void setPageWidth(coord_t w) + { + pageWidth = w; + } + + void setPageHeight(coord_t h) + { + pageHeight = h; + } + + uint8_t getPageCount() const + { + if (pageWidth) + return innerWidth / pageWidth; + else if (pageHeight) + return innerHeight / pageHeight; + else + return 1; + } + + uint8_t getPageIndex() const + { + if (pageWidth) + return (getScrollPositionX() + (pageWidth / 2)) / pageWidth; + else if (pageHeight) + return (getScrollPositionY() + (pageHeight / 2)) / pageHeight; + else + return 0; + } + + coord_t getInnerHeight() const + { + return innerHeight; + } + + void setInnerHeight(coord_t h) + { + innerHeight = h; + if (windowFlags & FORWARD_SCROLL) { + rect.h = innerHeight; + parent->adjustInnerHeight(); + } + else if (height() >= h) { + setScrollPositionY(0); + } + else { + coord_t maxScrollPosition = h - height(); + if (scrollPositionY > maxScrollPosition) { + setScrollPositionY(maxScrollPosition); + } + } + invalidate(); + } + + coord_t getScrollPositionX() const + { + return scrollPositionX; + } + + coord_t getScrollPositionY() const + { + return scrollPositionY; + } + + virtual void setScrollPositionX(coord_t value); + + virtual void setScrollPositionY(coord_t value); + + bool isChildVisible(const Window * window) const; + + bool isChildFullSize(const Window * window) const; + + bool isVisible() const + { + return parent && parent->isChildVisible(this); + } + + bool isInsideParentScrollingArea() const + { + return parent && right() >= parent->getScrollPositionX() && left() <= parent->getScrollPositionX() + parent->width(); + } + + void setInsideParentScrollingArea(); + + void drawVerticalScrollbar(BitmapBuffer * dc); + + void drawHorizontalScrollbar(BitmapBuffer * dc); + + virtual void onEvent(event_t event); + + void adjustInnerHeight(); + + coord_t adjustHeight(); + + void moveWindowsTop(coord_t y, coord_t delta); + + void invalidate() + { + invalidate({0, 0, rect.w, rect.h}); + } + + void bringToTop() + { + attach(parent); // does a detach + attach + } + + virtual void checkEvents(); + + void attach(Window * window); + + void detach(); + + bool deleted() const + { + return _deleted; + } + + protected: + Window * parent; + std::list children; + rect_t rect; + coord_t innerWidth; + coord_t innerHeight; + coord_t pageWidth = 0; + coord_t pageHeight = 0; + coord_t scrollPositionX = 0; + coord_t scrollPositionY = 0; + WindowFlags windowFlags; + LcdFlags textFlags; + bool _deleted = false; + + static Window * focusWindow; + static Window * slidingWindow; + static Window * touchedWindow; + static std::list trash; + + std::function closeHandler; + std::function focusHandler; + + void addChild(Window * window, bool front = false) + { + if (front) + children.push_front(window); + else + children.push_back(window); + } + + void removeChild(Window * window) + { + children.remove(window); + invalidate(); + } + + virtual void invalidate(const rect_t & rect); + + void paintChildren(BitmapBuffer * dc, std::list::iterator it); + + void fullPaint(BitmapBuffer * dc); + + virtual void paint(BitmapBuffer *) + { + } + + virtual void onFocusLost() + { + TRACE_WINDOWS("%s onFocusLost()", getWindowDebugString().c_str()); + if (focusHandler) { + focusHandler(false); + } + invalidate(); + } + +#if defined(HARDWARE_TOUCH) + static coord_t getSnapStep(coord_t relativeScrollPosition, coord_t pageSize); + + virtual bool onTouchStart(coord_t x, coord_t y); + + virtual bool onTouchEnd(coord_t x, coord_t y); + + virtual bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY); +#endif + + bool forwardTouchEnd(coord_t x, coord_t y); + + bool hasOpaqueRect(const rect_t & testRect) const; +}; +