diff --git a/CMakeLists.txt b/CMakeLists.txt index e713723b..6710b2e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,7 +128,6 @@ SET(SOURCES src/framework/ui/uibutton.cpp src/framework/ui/uilineedit.cpp src/framework/ui/uiwindow.cpp - src/framework/ui/uianchorlayout.cpp src/framework/ui/uianchor.cpp src/framework/ui/uilist.cpp ) diff --git a/modules/core/constants.lua b/modules/core/constants.lua index c79df321..e5654526 100644 --- a/modules/core/constants.lua +++ b/modules/core/constants.lua @@ -1,4 +1,4 @@ --- AnchorPoint +-- AnchorEdge AnchorNone = 0 AnchorTop = 1 AnchorBottom = 2 diff --git a/src/framework/const.h b/src/framework/const.h index da642335..007ad766 100644 --- a/src/framework/const.h +++ b/src/framework/const.h @@ -29,7 +29,7 @@ enum AlignmentFlag { AlignCenter = AlignVerticalCenter | AlignHorizontalCenter }; -enum AnchorPoint { +enum AnchorEdge { AnchorNone = 0, AnchorTop, AnchorBottom, diff --git a/src/framework/ui/declarations.h b/src/framework/ui/declarations.h index 18dfb745..9e00182d 100644 --- a/src/framework/ui/declarations.h +++ b/src/framework/ui/declarations.h @@ -5,27 +5,23 @@ #include "const.h" class UIManager; -class UILayout; -class UIAnchorLayout; -class UIStyle; +class UIAnchor; class UIWidget; class UILabel; class UIButton; class UILineEdit; class UIWindow; -class UIConsole; typedef std::shared_ptr UIWidgetPtr; typedef std::weak_ptr UIWidgetWeakPtr; -typedef std::deque UIWidgetList; -typedef std::shared_ptr UILayoutPtr; -typedef std::shared_ptr UIAnchorLayoutPtr; -typedef std::shared_ptr UIStylePtr; typedef std::shared_ptr UILabelPtr; typedef std::shared_ptr UIButtonPtr; typedef std::shared_ptr UILineEditPtr; typedef std::shared_ptr UIWindowPtr; -typedef std::shared_ptr UIConsolePtr; + +typedef std::vector UIAnchorList; +typedef std::deque UIWidgetList; +typedef std::deque UIWeakWidgetList; #endif diff --git a/src/framework/ui/ui.h b/src/framework/ui/ui.h index 9d7dc420..f7b76b50 100644 --- a/src/framework/ui/ui.h +++ b/src/framework/ui/ui.h @@ -5,7 +5,6 @@ #include "uiwidget.h" #include "uibutton.h" #include "uilabel.h" -#include "uilayout.h" #include "uilineedit.h" #include "uiwindow.h" diff --git a/src/framework/ui/uianchor.cpp b/src/framework/ui/uianchor.cpp index cc4155a4..6121c3ab 100644 --- a/src/framework/ui/uianchor.cpp +++ b/src/framework/ui/uianchor.cpp @@ -1,45 +1,31 @@ #include "uianchor.h" #include "uiwidget.h" -UIAnchor::UIAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine) - : m_anchoredWidget(anchoredWidget), m_anchoredEdge(anchoredEdge), m_anchorLine(anchorLine) { +UIAnchor::UIAnchor(AnchorEdge anchoredEdge, const std::string& hookedWidgetId, AnchorEdge hookedEdge) : + m_anchoredEdge(anchoredEdge), m_hookedWidgetId(hookedWidgetId), m_hookedEdge(hookedEdge) { } -UIWidgetPtr UIAnchor::getAnchorLineWidget() const { - UIWidgetPtr anchoredWidget = m_anchoredWidget.lock(); - if(anchoredWidget && !anchoredWidget->isDestroyed()) - return anchoredWidget->backwardsGetWidgetById(m_anchorLine.widgetId); - return nullptr; -} - - -UIWidgetPtr UIAnchor::getAnchoredWidget() const { - return m_anchoredWidget.lock(); -} +int UIAnchor::getHookedPoint() const { + UIWidgetPtr hookedWidget = getHookedWidget(); -AnchorPoint UIAnchor::getAnchoredEdge() const { - return m_anchoredEdge; -} - -int UIAnchor::getAnchorLinePoint() const { - UIWidgetPtr anchorLineWidget = getAnchorLineWidget(); - if(anchorLineWidget) { - switch(m_anchorLine.edge) { + if(hookedWidget) { + switch(m_hookedEdge) { case AnchorLeft: - return anchorLineWidget->getGeometry().left(); + return hookedWidget->getRect().left(); case AnchorRight: - return anchorLineWidget->getGeometry().right(); + return hookedWidget->getRect().right(); case AnchorTop: - return anchorLineWidget->getGeometry().top(); + return hookedWidget->getRect().top(); case AnchorBottom: - return anchorLineWidget->getGeometry().bottom(); + return hookedWidget->getRect().bottom(); case AnchorHorizontalCenter: - return anchorLineWidget->getGeometry().horizontalCenter(); + return hookedWidget->getRect().horizontalCenter(); case AnchorVerticalCenter: - return anchorLineWidget->getGeometry().verticalCenter(); + return hookedWidget->getRect().verticalCenter(); default: break; } } - return -9999; -} \ No newline at end of file + + return INVALID_POINT; +} diff --git a/src/framework/ui/uianchor.h b/src/framework/ui/uianchor.h index 9eff2968..06b79fdd 100644 --- a/src/framework/ui/uianchor.h +++ b/src/framework/ui/uianchor.h @@ -3,25 +3,27 @@ #include "declarations.h" -struct AnchorLine { - AnchorLine(std::string widgetId, AnchorPoint edge) : widgetId(widgetId), edge(edge) { } - std::string widgetId; - AnchorPoint edge; -}; - class UIAnchor { public: - UIAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine); - UIWidgetPtr getAnchorLineWidget() const; - UIWidgetPtr getAnchoredWidget() const; - AnchorPoint getAnchoredEdge() const; - int getAnchorLinePoint() const; + enum { + INVALID_POINT = -999999 + }; + + UIAnchor(AnchorEdge anchoredEdge, const std::string& hookedWidgetId, AnchorEdge hookedEdge); + + AnchorEdge getAnchoredEdge() const { return m_anchoredEdge; } + UIWidgetPtr getHookedWidget() const { return m_hookedWidget.lock(); } + std::string getHookedWidgetId() const { return m_hookedWidgetId; } + int getHookedPoint() const; + + void setHookedWidget(const UIWidgetPtr hookedWidget) { m_hookedWidget = hookedWidget; } private: - UIWidgetWeakPtr m_anchoredWidget; - AnchorPoint m_anchoredEdge; - AnchorLine m_anchorLine; + AnchorEdge m_anchoredEdge; + UIWidgetWeakPtr m_hookedWidget; + std::string m_hookedWidgetId; + AnchorEdge m_hookedEdge; }; #endif diff --git a/src/framework/ui/uianchorlayout.cpp b/src/framework/ui/uianchorlayout.cpp deleted file mode 100644 index 6bbc2776..00000000 --- a/src/framework/ui/uianchorlayout.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "uianchorlayout.h" -#include "uiwidget.h" - -bool UIAnchorLayout::addAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine) -{ - UIAnchor anchor(anchoredWidget, anchoredEdge, anchorLine); - UIWidgetPtr anchorLineWidget = anchor.getAnchorLineWidget(); - - /* - if(!anchorLineWidget) { - logError("could not find the widget to anchor on, wrong id?"); - return false; - } - */ - - // we can never anchor with itself - if(anchoredWidget == anchorLineWidget) { - logError("anchoring with itself is not possible"); - return false; - } - - // we must never anchor to an anchor child - if(anchoredWidget && hasWidgetInAnchorTree(anchorLineWidget, anchoredWidget)) { - logError("anchors is miss configured, you must never make an anchor chains in loops"); - return false; - } - - // avoid duplicated anchors - for(auto it = m_anchors.begin(); it != m_anchors.end(); ++it) { - const UIAnchor& otherAnchor = *it; - if(otherAnchor.getAnchoredWidget() == anchoredWidget && otherAnchor.getAnchoredEdge() == anchoredEdge) { - m_anchors.erase(it); - break; - } - } - - // setup the anchor - m_anchors.push_back(anchor); - - // recalculate anchored widget layout - updateWidget(anchoredWidget); - return true; -} - -void UIAnchorLayout::updateWidget(const UIWidgetPtr& widget) -{ - Rect rect = widget->getGeometry(); - bool verticalMoved = false; - bool horizontalMoved = false; - - // FIXME: release expired anchors - for(const UIAnchor& anchor : m_anchors) { - if(anchor.getAnchoredWidget() == widget && anchor.getAnchorLineWidget()) { - int point = anchor.getAnchorLinePoint(); - switch(anchor.getAnchoredEdge()) { - case AnchorHorizontalCenter: - rect.moveHorizontalCenter(point + widget->getMarginLeft() - widget->getMarginRight()); - horizontalMoved = true; - break; - case AnchorLeft: - if(!horizontalMoved) { - rect.moveLeft(point + widget->getMarginLeft()); - horizontalMoved = true; - } else - rect.setLeft(point + widget->getMarginLeft()); - break; - case AnchorRight: - if(!horizontalMoved) { - rect.moveRight(point - widget->getMarginRight()); - horizontalMoved = true; - } else - rect.setRight(point - widget->getMarginRight()); - break; - case AnchorVerticalCenter: - rect.moveVerticalCenter(point + widget->getMarginTop() - widget->getMarginBottom()); - verticalMoved = true; - break; - case AnchorTop: - if(!verticalMoved) { - rect.moveTop(point + widget->getMarginTop()); - verticalMoved = true; - } else - rect.setTop(point + widget->getMarginTop()); - break; - case AnchorBottom: - if(!verticalMoved) { - rect.moveBottom(point - widget->getMarginBottom()); - verticalMoved = true; - } else - rect.setBottom(point - widget->getMarginBottom()); - break; - default: - break; - } - } - } - - if(rect != widget->getGeometry()) - widget->setGeometry(rect); -} - -void UIAnchorLayout::updateWidgetChildren(const UIWidgetPtr& parent) -{ - for(const UIAnchor& anchor : m_anchors) { - if(anchor.getAnchorLineWidget() == parent) { - UIWidgetPtr child = anchor.getAnchoredWidget(); - if(child) - updateWidget(child); - } - } -} - -bool UIAnchorLayout::hasWidgetInAnchorTree(const UIWidgetPtr& widget, const UIWidgetPtr& treeAnchor) -{ - for(const UIAnchor& anchor : m_anchors) { - if(anchor.getAnchorLineWidget() == treeAnchor) { - if(anchor.getAnchoredWidget() == widget || hasWidgetInAnchorTree(widget, anchor.getAnchoredWidget())) - return true; - } - } - return false; -} - diff --git a/src/framework/ui/uianchorlayout.h b/src/framework/ui/uianchorlayout.h deleted file mode 100644 index 04abb76c..00000000 --- a/src/framework/ui/uianchorlayout.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef UIANCHORLAYOUT_H -#define UIANCHORLAYOUT_H - -#include "uilayout.h" -#include "uianchor.h" - -class UIAnchorLayout : public UILayout -{ -public: - bool addAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine); - void updateWidget(const UIWidgetPtr& widget); - void updateWidgetChildren(const UIWidgetPtr& parent); - - bool hasWidgetInAnchorTree(const UIWidgetPtr& widget, const UIWidgetPtr& treeAnchor); - -private: - std::list m_anchors; -}; - -#endif diff --git a/src/framework/ui/uibutton.cpp b/src/framework/ui/uibutton.cpp index 6c959267..248ee09b 100644 --- a/src/framework/ui/uibutton.cpp +++ b/src/framework/ui/uibutton.cpp @@ -63,7 +63,7 @@ void UIButton::render() UIWidget::render(); const ButtonStateStyle& currentStyle = m_statesStyle[m_state]; - Rect textRect = getGeometry(); + Rect textRect = getRect(); if(currentStyle.image) { g_graphics.bindColor(currentStyle.color); @@ -93,7 +93,7 @@ void UIButton::onMousePress(UIMouseEvent& event) void UIButton::onMouseRelease(UIMouseEvent& event) { if(m_state == ButtonDown) { - if(m_onClick && getGeometry().contains(event.pos())) + if(m_onClick && getRect().contains(event.pos())) m_onClick(); m_state = (isHovered() && isEnabled()) ? ButtonHover : ButtonUp; } else diff --git a/src/framework/ui/uievent.h b/src/framework/ui/uievent.h index 083d849b..5e23532a 100644 --- a/src/framework/ui/uievent.h +++ b/src/framework/ui/uievent.h @@ -88,10 +88,10 @@ private: bool m_hovered; }; -class UIGeometryUpdateEvent : public UIEvent +class UIRectUpdateEvent : public UIEvent { public: - UIGeometryUpdateEvent(const Rect& oldRect, const Rect& rect) + UIRectUpdateEvent(const Rect& oldRect, const Rect& rect) : m_oldRect(oldRect), m_rect(rect) { } Point pos() const { return m_rect.topLeft(); } diff --git a/src/framework/ui/uilayout.cpp b/src/framework/ui/uilayout.cpp deleted file mode 100644 index 78af228f..00000000 --- a/src/framework/ui/uilayout.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "uilayout.h" - diff --git a/src/framework/ui/uilayout.h b/src/framework/ui/uilayout.h deleted file mode 100644 index 574bae1a..00000000 --- a/src/framework/ui/uilayout.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef UILAYOUT_H -#define UILAYOUT_H - -#include "declarations.h" - -class UILayout -{ -public: - virtual void updateWidget(const UIWidgetPtr& widget) = 0; - virtual void updateWidgetChildren(const UIWidgetPtr& widget) = 0; -}; - -#endif diff --git a/src/framework/ui/uilineedit.cpp b/src/framework/ui/uilineedit.cpp index 9a09d09e..f5945dd3 100644 --- a/src/framework/ui/uilineedit.cpp +++ b/src/framework/ui/uilineedit.cpp @@ -18,7 +18,6 @@ UILineEdit::UILineEdit() : UIWidget(UITypeLabel) UILineEditPtr UILineEdit::create() { UILineEditPtr lineEdit(new UILineEdit); - lineEdit->setStyle("LineEdit"); return lineEdit; } @@ -38,13 +37,16 @@ void UILineEdit::render() { UIWidget::render(); + if(!m_rect.isValid()) + return; + //TODO: text rendering could be much optimized by using vertex buffer or caching the render into a texture int textLength = m_text.length(); const TexturePtr& texture = m_font->getTexture(); - for(int i=0;i= 0) { @@ -334,7 +336,7 @@ int UILineEdit::getTextPos(Point pos) return candidatePos; } -void UILineEdit::onGeometryUpdate(UIGeometryUpdateEvent& event) +void UILineEdit::onRectUpdate(UIRectUpdateEvent& event) { update(); } diff --git a/src/framework/ui/uilineedit.h b/src/framework/ui/uilineedit.h index 4efa67e1..c06ed907 100644 --- a/src/framework/ui/uilineedit.h +++ b/src/framework/ui/uilineedit.h @@ -30,7 +30,7 @@ public: int getTextPos(Point pos); protected: - virtual void onGeometryUpdate(UIGeometryUpdateEvent& event); + virtual void onRectUpdate(UIRectUpdateEvent& event); virtual void onFocusChange(UIFocusEvent& event); virtual void onKeyPress(UIKeyEvent& event); virtual void onMousePress(UIMouseEvent& event); diff --git a/src/framework/ui/uimanager.cpp b/src/framework/ui/uimanager.cpp index c4dcacda..df7b2362 100644 --- a/src/framework/ui/uimanager.cpp +++ b/src/framework/ui/uimanager.cpp @@ -1,6 +1,5 @@ #include "uimanager.h" #include "ui.h" -#include "uianchorlayout.h" #include #include @@ -13,9 +12,6 @@ void UIManager::init() m_rootWidget = UIWidgetPtr(new UIWidget); m_rootWidget->setId("root"); m_rootWidget->setHovered(true); - - UIAnchorLayoutPtr anchorLayout(new UIAnchorLayout); - m_rootWidget->setLayout(anchorLayout); m_rootWidget->resize(g_graphics.getScreenSize()); } @@ -187,7 +183,7 @@ UIWidgetPtr UIManager::loadWidgetFromOTML(const OTMLNodePtr& widgetNode) throw OTMLException(styleNode, "cannot determine widget type"); widget->loadStyleFromOTML(styleNode); - widget->updateGeometry(); + widget->updateLayout(); for(const OTMLNodePtr& childNode : widgetNode->children()) { if(!childNode->isUnique()) diff --git a/src/framework/ui/uiwidget.cpp b/src/framework/ui/uiwidget.cpp index 432653d0..b1124adc 100644 --- a/src/framework/ui/uiwidget.cpp +++ b/src/framework/ui/uiwidget.cpp @@ -1,7 +1,5 @@ #include "uiwidget.h" #include "uimanager.h" -#include "uilayout.h" -#include "uianchorlayout.h" #include #include @@ -9,6 +7,7 @@ #include #include #include +#include "uianchor.h" UIWidget::UIWidget(UIWidgetType type) { @@ -18,7 +17,10 @@ UIWidget::UIWidget(UIWidgetType type) m_hovered = false; m_focusable = true; m_destroyed = false; - m_updateScheduled = false; + m_layoutUpdated = true; + m_updateEventScheduled = false; + m_layoutUpdateScheduled = false; + m_childrenLayoutUpdateScheduled = false; m_opacity = 255; m_marginLeft = m_marginRight = m_marginTop = m_marginBottom = 0; m_backgroundColor = Color::white; @@ -158,7 +160,7 @@ void UIWidget::loadStyleFromOTML(const OTMLNodePtr& styleNode) } else if(what == "centerIn") { centerIn(node->value()); } else { - AnchorPoint myEdge = fw::translateAnchorPoint(what); + AnchorEdge myEdge = fw::translateAnchorEdge(what); std::string anchorDescription = node->value(); std::vector split; @@ -167,15 +169,15 @@ void UIWidget::loadStyleFromOTML(const OTMLNodePtr& styleNode) throw OTMLException(node, "invalid anchor description"); std::string target = split[0]; - AnchorPoint targetEdge = fw::translateAnchorPoint(split[1]); + AnchorEdge hookedEdge = fw::translateAnchorEdge(split[1]); if(myEdge == AnchorNone) throw OTMLException(node, "invalid anchor edge"); - if(targetEdge == AnchorNone) + if(hookedEdge == AnchorNone) throw OTMLException(node, "invalid anchor target edge"); - addAnchor(myEdge, target, targetEdge); + addAnchor(myEdge, target, hookedEdge); } } else if(node->tag() == "onLoad") { @@ -190,7 +192,7 @@ void UIWidget::render() assert(!m_destroyed); if(m_image) - m_image->draw(getGeometry()); + m_image->draw(getRect()); for(const UIWidgetPtr& child : m_children) { if(child->isExplicitlyVisible()) { @@ -203,23 +205,13 @@ void UIWidget::render() // debug draw box //g_graphics.bindColor(Color::green); - //g_graphics.drawBoundingRect(child->getGeometry()); + //g_graphics.drawBoundingRect(child->getRect()); g_graphics.setOpacity(oldOpacity); } } } -void UIWidget::updateGeometry() -{ - assert(!m_destroyed); - - if(UILayoutPtr layout = getLayout()) { - layout->updateWidget(asUIWidget()); - layout->updateWidgetChildren(asUIWidget()); - } -} - void UIWidget::setParent(const UIWidgetPtr& parent) { assert(!m_destroyed); @@ -247,32 +239,33 @@ void UIWidget::setStyle(const std::string& styleName) loadStyleFromOTML(styleNode); // forces layout recalculation - updateGeometry(); + updateLayout(); } catch(std::exception& e) { logError("couldn't change widget '", m_id, "' style: ", e.what()); } } -void UIWidget::setGeometry(const Rect& rect) +void UIWidget::setRect(const Rect& rect) { assert(!m_destroyed); Rect oldRect = m_rect; m_rect = rect; - if(UILayoutPtr layout = getLayout()) - layout->updateWidgetChildren(asUIWidget()); - // avoid massive updates - if(!m_updateScheduled) { + // updates children geometry + updateChildrenLayout(); + + // avoid massive update events + if(!m_updateEventScheduled) { UIWidgetPtr self = asUIWidget(); g_dispatcher.addEvent([self, oldRect]() { - self->m_updateScheduled = false; - UIGeometryUpdateEvent e(oldRect, self->getGeometry()); + self->m_updateEventScheduled = false; + UIRectUpdateEvent e(oldRect, self->getRect()); // this widget could be destroyed before the event happens if(!self->isDestroyed()) - self->onGeometryUpdate(e); + self->onRectUpdate(e); }); - m_updateScheduled = true; + m_updateEventScheduled = true; } } @@ -310,16 +303,12 @@ bool UIWidget::hasChild(const UIWidgetPtr& child) return false; } -UILayoutPtr UIWidget::getLayout() const +UIWidgetPtr UIWidget::getRootParent() { - assert(!m_destroyed); - - if(m_layout) - return m_layout; - else if(getParent() && getParent()->getLayout()) - return getParent()->getLayout(); - // fallback to root layout - return g_ui.getRootWidget()->getLayout(); + if(UIWidgetPtr parent = getParent()) + return parent->getRootParent(); + else + return asUIWidget(); } UIWidgetPtr UIWidget::getChildAfter(const UIWidgetPtr& relativeChild) @@ -383,7 +372,7 @@ UIWidgetPtr UIWidget::getChildByPos(const Point& childPos) for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { const UIWidgetPtr& widget = (*it); - if(widget->isExplicitlyVisible() && widget->getGeometry().contains(childPos)) + if(widget->isExplicitlyVisible() && widget->getRect().contains(childPos)) return widget; } @@ -399,35 +388,19 @@ UIWidgetPtr UIWidget::getChildByIndex(int childIndex) return nullptr; } -UIWidgetPtr UIWidget::recursiveGetChildById(const std::string& childId) +UIWidgetPtr UIWidget::recursiveGetChildById(const std::string& id) { assert(!m_destroyed); - if(getId() == childId || childId == "self") - return asUIWidget(); - else if(childId == "parent") - return getParent(); - else if(childId == "root") - return g_ui.getRootWidget(); - else if(childId == "prev") { - if(UIWidgetPtr parent = getParent()) - return parent->getChildBefore(asUIWidget()); - } else if(childId == "next") { - if(UIWidgetPtr parent = getParent()) - return parent->getChildAfter(asUIWidget()); - } else { + UIWidgetPtr widget = getChildById(id); + if(!widget) { for(const UIWidgetPtr& child : m_children) { - if(child->getId() == childId) - return child; - } - for(const UIWidgetPtr& child : m_children) { - if(UIWidgetPtr subChild = child->recursiveGetChildById(childId)) { - if(subChild->getId() == childId) - return subChild; - } + widget = child->recursiveGetChildById(id); + if(widget) + break; } } - return nullptr; + return widget; } UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos) @@ -435,7 +408,7 @@ UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos) assert(!m_destroyed); for(const UIWidgetPtr& child : m_children) { - if(child->getGeometry().contains(childPos)) { + if(child->getRect().contains(childPos)) { if(UIWidgetPtr subChild = child->recursiveGetChildByPos(childPos)) return subChild; return child; @@ -449,27 +422,12 @@ UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id) { assert(!m_destroyed); - UIWidgetPtr widget; - if(getId() == id || id == "self") - widget = asUIWidget(); - else if(id == "parent") - widget = getParent(); - else if(id == "root") - widget = g_ui.getRootWidget(); - else if(id == "prev") { - if(UIWidgetPtr parent = getParent()) - widget = parent->getChildBefore(asUIWidget()); - } else if(id == "next") { - if(UIWidgetPtr parent = getParent()) - widget = parent->getChildAfter(asUIWidget()); - } else { - widget = recursiveGetChildById(id); - if(widget) - return widget; - + UIWidgetPtr widget = getChildById(id); + if(!widget) { if(UIWidgetPtr parent = getParent()) widget = parent->backwardsGetWidgetById(id); } + return widget; } @@ -503,8 +461,11 @@ void UIWidget::addChild(const UIWidgetPtr& childToAdd) m_children.push_back(childToAdd); childToAdd->setParent(asUIWidget()); - // updates geometry - updateGeometry(); + // recalculate anchors + getRootParent()->recalculateAnchoredWidgets(); + + // may need to update children layout + updateChildrenLayout(); // always focus new children if(childToAdd->isFocusable()) @@ -529,8 +490,11 @@ void UIWidget::insertChild(const UIWidgetPtr& childToInsert, int index) m_children.insert(it, childToInsert); childToInsert->setParent(asUIWidget()); - // updates geometry - updateGeometry(); + // recalculate anchors + getRootParent()->recalculateAnchoredWidgets(); + + // may need to update children layout + updateChildrenLayout(); } void UIWidget::removeChild(const UIWidgetPtr& childToRemove) @@ -555,6 +519,13 @@ void UIWidget::removeChild(const UIWidgetPtr& childToRemove) // reset child parent assert(childToRemove->getParent() == asUIWidget()); childToRemove->setParent(nullptr); + + // recalculate anchors + UIWidgetPtr parent = getRootParent(); + parent->recalculateAnchoredWidgets(); + + // may need to update children layout + updateChildrenLayout(); } void UIWidget::focusNextChild(FocusReason reason) @@ -633,32 +604,227 @@ void UIWidget::unlockChild(const UIWidgetPtr& childToUnlock) } } -void UIWidget::addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge) +void UIWidget::updateLayout() { assert(!m_destroyed); - UIAnchorLayoutPtr layout = std::dynamic_pointer_cast(getLayout()); - assert(layout); - if(layout) - layout->addAnchor(asUIWidget(), edge, AnchorLine(targetId, targetEdge)); + if(!m_layoutUpdateScheduled) { + m_layoutUpdateScheduled = true; + UIWidgetPtr self = asUIWidget(); + g_dispatcher.addEvent([self] { + self->m_layoutUpdateScheduled = false; + if(!self->isDestroyed()) + self->internalUpdateLayout(); + }); + } } -void UIWidget::centerIn(const std::string& targetId) +void UIWidget::updateChildrenLayout() { assert(!m_destroyed); - addAnchor(AnchorHorizontalCenter, targetId, AnchorHorizontalCenter); - addAnchor(AnchorVerticalCenter, targetId, AnchorVerticalCenter); + if(!m_childrenLayoutUpdateScheduled) { + m_childrenLayoutUpdateScheduled = true; + UIWidgetPtr self = asUIWidget(); + g_dispatcher.addEvent([self] { + self->m_childrenLayoutUpdateScheduled = false; + if(!self->isDestroyed()) + self->internalUpdateChildrenLayout(); + }); + } } -void UIWidget::fill(const std::string& targetId) +bool UIWidget::addAnchor(AnchorEdge edge, const std::string& hookedWidgetId, AnchorEdge hookedEdge) { assert(!m_destroyed); - addAnchor(AnchorLeft, targetId, AnchorLeft); - addAnchor(AnchorRight, targetId, AnchorRight); - addAnchor(AnchorTop, targetId, AnchorTop); - addAnchor(AnchorBottom, targetId, AnchorBottom); + UIAnchor anchor(edge, hookedWidgetId, hookedEdge); + + UIWidgetPtr hookedWidget = backwardsGetWidgetById(hookedWidgetId); + anchor.setHookedWidget(hookedWidget); + + // we can never anchor with itself + if(hookedWidget == asUIWidget()) { + logError("anchoring with itself is not possible"); + return false; + } + + // we must never anchor to an anchor child + //TODO: this check + + // duplicated anchors must be replaced + for(auto it = m_anchors.begin(); it != m_anchors.end(); ++it) { + const UIAnchor& otherAnchor = *it; + if(otherAnchor.getAnchoredEdge() == edge) { + m_anchors.erase(it); + break; + } + } + + m_anchors.push_back(anchor); + + updateLayout(); + return true; +} + +void UIWidget::centerIn(const std::string& hookedWidgetId) +{ + assert(!m_destroyed); + + addAnchor(AnchorHorizontalCenter, hookedWidgetId, AnchorHorizontalCenter); + addAnchor(AnchorVerticalCenter, hookedWidgetId, AnchorVerticalCenter); +} + +void UIWidget::fill(const std::string& hookedWidgetId) +{ + assert(!m_destroyed); + + addAnchor(AnchorLeft, hookedWidgetId, AnchorLeft); + addAnchor(AnchorRight, hookedWidgetId, AnchorRight); + addAnchor(AnchorTop, hookedWidgetId, AnchorTop); + addAnchor(AnchorBottom, hookedWidgetId, AnchorBottom); +} + +void UIWidget::internalUpdateLayout() +{ + assert(!m_destroyed); + + for(const UIAnchor& anchor : m_anchors) { + // ignore invalid anchors + if(!anchor.getHookedWidget()) + continue; + + // the update should only happens if the hooked widget is already updated + if(!anchor.getHookedWidget()->m_layoutUpdated) + return; + } + + Rect newRect = m_rect; + bool verticalMoved = false; + bool horizontalMoved = false; + + // calculate new rect based on anchors + for(const UIAnchor& anchor : m_anchors) { + int point = anchor.getHookedPoint(); + + // ignore invalid anchors + if(point == UIAnchor::INVALID_POINT) + continue; + + switch(anchor.getAnchoredEdge()) { + case AnchorHorizontalCenter: + newRect.moveHorizontalCenter(point + getMarginLeft() - getMarginRight()); + horizontalMoved = true; + break; + case AnchorLeft: + if(!horizontalMoved) { + newRect.moveLeft(point + getMarginLeft()); + horizontalMoved = true; + } else + newRect.setLeft(point + getMarginLeft()); + break; + case AnchorRight: + if(!horizontalMoved) { + newRect.moveRight(point - getMarginRight()); + horizontalMoved = true; + } else + newRect.setRight(point - getMarginRight()); + break; + case AnchorVerticalCenter: + newRect.moveVerticalCenter(point + getMarginTop() - getMarginBottom()); + verticalMoved = true; + break; + case AnchorTop: + if(!verticalMoved) { + newRect.moveTop(point + getMarginTop()); + verticalMoved = true; + } else + newRect.setTop(point + getMarginTop()); + break; + case AnchorBottom: + if(!verticalMoved) { + newRect.moveBottom(point - getMarginBottom()); + verticalMoved = true; + } else + newRect.setBottom(point - getMarginBottom()); + break; + default: + break; + } + } + + m_layoutUpdated = true; + + // changes the rect only if really needed + if(newRect != m_rect) { + // setRect will update children layout too + setRect(newRect); + } else { + // update children + internalUpdateChildrenLayout(); + } +} + +void UIWidget::internalUpdateChildrenLayout() +{ + assert(!m_destroyed); + + // reset all children anchors update state + resetLayoutUpdateState(false); + + // update children layouts + for(const UIWidgetWeakPtr& anchoredWidgetWeak : m_anchoredWidgets) { + if(UIWidgetPtr anchoredWidget = anchoredWidgetWeak.lock()) + anchoredWidget->updateLayout(); + } +} + +void UIWidget::resetLayoutUpdateState(bool resetOwn) +{ + if(resetOwn) + m_layoutUpdated = false; + + // resets children layout update state too + for(const UIWidgetWeakPtr& anchoredWidgetWeak : m_anchoredWidgets) { + if(UIWidgetPtr anchoredWidget = anchoredWidgetWeak.lock()) + anchoredWidget->resetLayoutUpdateState(true); + } +} + +void UIWidget::addAnchoredWidget(const UIWidgetPtr& widget) +{ + // prevent duplicated anchored widgets + for(const UIWidgetWeakPtr& anchoredWidget : m_anchoredWidgets) + if(anchoredWidget.lock() == widget) + return; + m_anchoredWidgets.push_back(widget); +} + +void UIWidget::recalculateAnchoredWidgets() +{ + clearAnchoredWidgets(); + computeAnchoredWidgets(); +} + +void UIWidget::clearAnchoredWidgets() +{ + m_anchoredWidgets.clear(); + for(const UIWidgetPtr& child : m_children) + child->clearAnchoredWidgets(); +} + +void UIWidget::computeAnchoredWidgets() +{ + // update anchors's hooked widget + for(UIAnchor& anchor : m_anchors) { + UIWidgetPtr hookedWidget = backwardsGetWidgetById(anchor.getHookedWidgetId()); + anchor.setHookedWidget(hookedWidget); + if(hookedWidget) + hookedWidget->addAnchoredWidget(asUIWidget()); + } + + for(const UIWidgetPtr& child : m_children) + child->computeAnchoredWidgets(); } void UIWidget::onFocusChange(UIFocusEvent& event) @@ -726,7 +892,7 @@ void UIWidget::onMousePress(UIMouseEvent& event) continue; // mouse press events only go to children that contains the mouse position - if(child->getGeometry().contains(event.pos()) && child == getChildByPos(event.pos())) { + if(child->getRect().contains(event.pos()) && child == getChildByPos(event.pos())) { // focus it if(child->isFocusable()) focusChild(child, MouseFocusReason); @@ -775,7 +941,7 @@ void UIWidget::onMouseMove(UIMouseEvent& event) // check if the mouse is relally over this child bool overChild = (isHovered() && - child->getGeometry().contains(event.pos()) && + child->getRect().contains(event.pos()) && child == getChildByPos(event.pos())); if(overChild != child->isHovered()) { child->setHovered(overChild); @@ -806,7 +972,7 @@ void UIWidget::onMouseWheel(UIMouseEvent& event) continue; // mouse wheel events only go to children that contains the mouse position - if(child->getGeometry().contains(event.pos()) && child == getChildByPos(event.pos())) { + if(child->getRect().contains(event.pos()) && child == getChildByPos(event.pos())) { event.accept(); child->onMouseWheel(event); } diff --git a/src/framework/ui/uiwidget.h b/src/framework/ui/uiwidget.h index e6548d53..3d9a8bda 100644 --- a/src/framework/ui/uiwidget.h +++ b/src/framework/ui/uiwidget.h @@ -27,34 +27,30 @@ public: /// Draw widget on screen virtual void render(); - /// Notifies the layout system that this widget has changed and may need to change geometry - void updateGeometry(); - void setEnabled(bool enable) { m_enabled = enable; } - void setLayout(const UILayoutPtr& layout) { m_layout = layout; } void setId(const std::string& id) { m_id = id; } void setFocusable(bool focusable) { m_focusable = focusable; } void setHovered(bool hovered) { m_hovered = hovered; } void setVisible(bool visible) { m_visible = visible; } void setParent(const UIWidgetPtr& parent); void setStyle(const std::string& styleName); - void setGeometry(const Rect& rect); + void setRect(const Rect& rect); void setX(int x) { move(Point(x, getY())); } void setY(int y) { move(Point(getX(), y)); } void setWidth(int width) { resize(Size(width, getHeight())); } void setHeight(int height) { resize(Size(getWidth(), height)); } - void resize(const Size& size) { setGeometry(Rect(getPosition(), size)); updateGeometry(); } - void move(const Point& pos) { setGeometry(Rect(pos, getSize())); } + void resize(const Size& size) { setRect(Rect(getPosition(), size)); } + void move(const Point& pos) { setRect(Rect(pos, getSize())); } void setImage(const ImagePtr& image) { m_image = image; } virtual void setFont(const FontPtr& font) { m_font = font; } void setOpacity(int opacity) { m_opacity = opacity; } void setBackgroundColor(const Color& color) { m_backgroundColor = color; } void setForegroundColor(const Color& color) { m_foregroundColor = color; } - void setMarginLeft(int margin) { m_marginLeft = margin; updateGeometry(); } - void setMarginRight(int margin) { m_marginRight = margin; updateGeometry(); } - void setMarginTop(int margin) { m_marginTop = margin; updateGeometry(); } - void setMarginBottom(int margin) { m_marginBottom = margin; updateGeometry(); } + void setMarginLeft(int margin) { m_marginLeft = margin; updateLayout(); } + void setMarginRight(int margin) { m_marginRight = margin; updateLayout(); } + void setMarginTop(int margin) { m_marginTop = margin; updateLayout(); } + void setMarginBottom(int margin) { m_marginBottom = margin; updateLayout(); } void hide() { setVisible(false); } void show() { setVisible(true); } @@ -72,13 +68,13 @@ public: bool hasChild(const UIWidgetPtr& child); UIWidgetType getWidgetType() const { return m_type; } - UILayoutPtr getLayout() const; std::string getId() const { return m_id; } int getChildCount() const { return m_children.size(); } UIWidgetPtr getParent() const { return m_parent.lock(); } + UIWidgetPtr getRootParent(); Point getPosition() const { return m_rect.topLeft(); } Size getSize() const { return m_rect.size(); } - Rect getGeometry() const { return m_rect; } + Rect getRect() const { return m_rect; } int getX() const { return m_rect.x(); } int getY() const { return m_rect.y(); } int getWidth() const { return m_rect.width(); } @@ -101,7 +97,7 @@ public: UIWidgetPtr getChildById(const std::string& childId); UIWidgetPtr getChildByPos(const Point& childPos); UIWidgetPtr getChildByIndex(int childIndex); - UIWidgetPtr recursiveGetChildById(const std::string& childId); + UIWidgetPtr recursiveGetChildById(const std::string& id); UIWidgetPtr recursiveGetChildByPos(const Point& childPos); UIWidgetPtr backwardsGetWidgetById(const std::string& id); @@ -114,16 +110,33 @@ public: void lockChild(const UIWidgetPtr& childToLock); void unlockChild(const UIWidgetPtr& childToUnlock); - // for using only with anchor layouts - void addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge); - void centerIn(const std::string& targetId); - void fill(const std::string& targetId); + void updateLayout(); + void updateChildrenLayout(); + + bool addAnchor(AnchorEdge edge, const std::string& hookedWidgetId, AnchorEdge hookedEdge); + void centerIn(const std::string& hookedWidgetId); + void fill(const std::string& hookedWidgetId); UIWidgetPtr asUIWidget() { return std::static_pointer_cast(shared_from_this()); } +private: + void internalUpdateLayout(); + void internalUpdateChildrenLayout(); + + void addAnchoredWidget(const UIWidgetPtr& widget); + void recalculateAnchoredWidgets(); + void clearAnchoredWidgets(); + void computeAnchoredWidgets(); + void resetLayoutUpdateState(bool resetOwn); + + bool m_layoutUpdated; + bool m_updateEventScheduled; + bool m_layoutUpdateScheduled; + bool m_childrenLayoutUpdateScheduled; + protected: /// Triggered when widget is moved or resized - virtual void onGeometryUpdate(UIGeometryUpdateEvent& event) { } + virtual void onRectUpdate(UIRectUpdateEvent& event) { } // Triggered when widget change visibility/enabled/style/children/parent/layout/... //virtual void onChange(const UIEvent& event); /// Triggered when widget gets or loses focus @@ -155,9 +168,9 @@ protected: bool m_hovered; bool m_focusable; bool m_destroyed; - bool m_updateScheduled; Rect m_rect; - UILayoutPtr m_layout; + UIAnchorList m_anchors; + UIWeakWidgetList m_anchoredWidgets; UIWidgetWeakPtr m_parent; UIWidgetList m_children; UIWidgetList m_lockedWidgets; diff --git a/src/framework/ui/uiwindow.cpp b/src/framework/ui/uiwindow.cpp index dfc19379..e14028d0 100644 --- a/src/framework/ui/uiwindow.cpp +++ b/src/framework/ui/uiwindow.cpp @@ -42,7 +42,7 @@ void UIWindow::loadStyleFromOTML(const OTMLNodePtr& styleNode) void UIWindow::render() { // draw window head - Rect headRect = getGeometry(); + Rect headRect = getRect(); headRect.setHeight(m_headHeight); if(m_headImage && m_headHeight > 0) { @@ -58,7 +58,7 @@ void UIWindow::render() } // draw window body - Rect bodyRect = getGeometry(); + Rect bodyRect = getRect(); bodyRect.setTop(headRect.bottom() + 1); if(m_bodyImage) m_bodyImage->draw(bodyRect); @@ -67,13 +67,13 @@ void UIWindow::render() UIWidget::render(); } -void UIWindow::onGeometryUpdate(UIGeometryUpdateEvent& event) +void UIWindow::onRectUpdate(UIRectUpdateEvent& event) { // bind window rect to parent rect Rect boundRect = event.rect(); UIWidgetPtr parent = getParent(); if(parent) { - Rect parentRect = parent->getGeometry(); + Rect parentRect = parent->getRect(); if(boundRect.left() < parentRect.left()) boundRect.moveLeft(parentRect.left()); if(boundRect.top() < parentRect.top()) @@ -85,7 +85,7 @@ void UIWindow::onGeometryUpdate(UIGeometryUpdateEvent& event) } if(boundRect != event.rect()) - setGeometry(boundRect); + setRect(boundRect); } void UIWindow::onFocusChange(UIFocusEvent& event) @@ -97,11 +97,11 @@ void UIWindow::onFocusChange(UIFocusEvent& event) void UIWindow::onMousePress(UIMouseEvent& event) { - Rect headRect = getGeometry(); + Rect headRect = getRect(); headRect.setHeight(m_headHeight); if(headRect.contains(event.pos())) { m_moving = true; - m_movingReference = event.pos() - getGeometry().topLeft(); + m_movingReference = event.pos() - getRect().topLeft(); } else UIWidget::onMousePress(event); } diff --git a/src/framework/ui/uiwindow.h b/src/framework/ui/uiwindow.h index b7c06a73..16582d31 100644 --- a/src/framework/ui/uiwindow.h +++ b/src/framework/ui/uiwindow.h @@ -17,7 +17,7 @@ public: std::string getTitle() const { return m_title; } protected: - virtual void onGeometryUpdate(UIGeometryUpdateEvent& event); + virtual void onRectUpdate(UIRectUpdateEvent& event); virtual void onFocusChange(UIFocusEvent& event); virtual void onMousePress(UIMouseEvent& event); virtual void onMouseRelease(UIMouseEvent& event); diff --git a/src/framework/util/translator.cpp b/src/framework/util/translator.cpp index ba46a078..f406f798 100644 --- a/src/framework/util/translator.cpp +++ b/src/framework/util/translator.cpp @@ -25,7 +25,7 @@ AlignmentFlag fw::translateAlignment(std::string aligment) return AlignCenter; } -AnchorPoint fw::translateAnchorPoint(const std::string& anchorPoint) +AnchorEdge fw::translateAnchorEdge(const std::string& anchorPoint) { if(anchorPoint == "left") return AnchorLeft; diff --git a/src/framework/util/translator.h b/src/framework/util/translator.h index b3420aee..8aae9cd4 100644 --- a/src/framework/util/translator.h +++ b/src/framework/util/translator.h @@ -7,7 +7,7 @@ namespace fw { AlignmentFlag translateAlignment(std::string aligment); -AnchorPoint translateAnchorPoint(const std::string& anchorPoint); +AnchorEdge translateAnchorEdge(const std::string& anchorPoint); };