From 4b6e7da99473777379a069d89038975002e5b757 Mon Sep 17 00:00:00 2001 From: IlyaShurupov Date: Tue, 8 Oct 2024 14:23:49 +0300 Subject: [PATCH] Refactor --- Graphics/private/Canvas.cpp | 30 +++ Graphics/private/EventHandler.cpp | 12 +- Graphics/public/EventHandler.hpp | 5 +- Graphics/public/Graphics.hpp | 2 + Math/public/Rect.hpp | 23 +- WidgetsNew/examples/Example.cpp | 28 +- WidgetsNew/private/AnimationTestWidget.cpp | 7 +- WidgetsNew/private/FloatingWidget.cpp | 30 ++- WidgetsNew/private/RootWidget.cpp | 282 ++++++++++++++------- WidgetsNew/private/SimpleWidgets.cpp | 45 +++- WidgetsNew/private/Widget.cpp | 85 +++++-- WidgetsNew/public/AnimationTestWidget.hpp | 4 +- WidgetsNew/public/FloatingWidget.hpp | 45 +++- WidgetsNew/public/LayoutWidget.hpp | 56 ++++ WidgetsNew/public/RootWidget.hpp | 50 ++-- WidgetsNew/public/SimpleWidgets.hpp | 20 +- WidgetsNew/public/SpringAnimations.hpp | 2 +- WidgetsNew/public/Widget.hpp | 69 +++-- 18 files changed, 592 insertions(+), 203 deletions(-) create mode 100644 WidgetsNew/public/LayoutWidget.hpp diff --git a/Graphics/private/Canvas.cpp b/Graphics/private/Canvas.cpp index 923332f..cb91af2 100644 --- a/Graphics/private/Canvas.cpp +++ b/Graphics/private/Canvas.cpp @@ -52,6 +52,36 @@ void Canvas::rect(RectF rec, const RGBA& col, halnf round) { nvgFill(mContext->vg); } +void Canvas::debugCross(RectF rec, const RGBA& col) { + nvgBeginPath(mContext->vg); + + nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y); + nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y); + + nvgMoveTo(mContext->vg, rec.p2().x, rec.p2().y); + nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y); + + nvgStrokeWidth(mContext->vg, 2); + nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a }); + nvgStroke(mContext->vg); + // nvgFill(mContext->vg); +} + +void Canvas::frame(RectF rec, const RGBA& col, halnf round) { + nvgBeginPath(mContext->vg); + + nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y); + nvgLineTo(mContext->vg, rec.p2().x, rec.p2().y); + nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y); + nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y); + nvgLineTo(mContext->vg, rec.p1().x, rec.p1().y); + + nvgStrokeWidth(mContext->vg, 2); + nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a }); + nvgStroke(mContext->vg); + // nvgFill(mContext->vg); +} + void Canvas::circle(Vec2F pos, halnf size, const RGBA& col) { pos += mOrigin; diff --git a/Graphics/private/EventHandler.cpp b/Graphics/private/EventHandler.cpp index e34fc56..4bac101 100644 --- a/Graphics/private/EventHandler.cpp +++ b/Graphics/private/EventHandler.cpp @@ -122,16 +122,22 @@ Vec2F EventHandler::getPointer() const { return mPointer - mPointerOrigin; } Vec2F EventHandler::getPointerPrev() const { return mPointerPrev - mPointerOrigin; } bool EventHandler::isPressed(InputID id) const { + if (!mEnableKeyEvents) return false; return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED; } bool EventHandler::isReleased(InputID id) const { + if (!mEnableKeyEvents) return false; return mInputStates[(int) id].mCurrentState == InputState::State::RELEASED; } -halnf EventHandler::getPointerPressure() const { return mPointerPressure; } +halnf EventHandler::getPointerPressure() const { + if (!mEnableKeyEvents) return 0; + return mPointerPressure; +} bool EventHandler::isDown(InputID id) const { + if (!mEnableKeyEvents) return false; return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED || mInputStates[(int) id].mCurrentState == InputState::State::HOLD; } @@ -139,3 +145,7 @@ bool EventHandler::isDown(InputID id) const { halnf EventHandler::getScrollY() const { return mScrollDelta.y; } Vec2F EventHandler::getPointerDelta() const { return mPointer - mPointerPrev; } + +void EventHandler::setEnableKeyEvents(bool enable) { + mEnableKeyEvents = enable; +} \ No newline at end of file diff --git a/Graphics/public/EventHandler.hpp b/Graphics/public/EventHandler.hpp index e79a874..c62bd89 100644 --- a/Graphics/public/EventHandler.hpp +++ b/Graphics/public/EventHandler.hpp @@ -73,7 +73,6 @@ namespace tp { void processEvent(); void processAllEvent(); - void setCursorOrigin(const Vec2F& origin); [[nodiscard]] Vec2F getPointer() const; @@ -87,6 +86,8 @@ namespace tp { [[nodiscard]] halnf getPointerPressure() const; + void setEnableKeyEvents(bool); + private: void processEventUnguarded(); @@ -108,6 +109,8 @@ namespace tp { InputState mInputStates[(int) InputID::LAST_KEY_CODE]{}; Timer mTimerEvent = Timer(1000); + + bool mEnableKeyEvents = true; }; } \ No newline at end of file diff --git a/Graphics/public/Graphics.hpp b/Graphics/public/Graphics.hpp index 2f3f775..9cd3507 100644 --- a/Graphics/public/Graphics.hpp +++ b/Graphics/public/Graphics.hpp @@ -60,7 +60,9 @@ namespace tp { void pushClamp(const RectF& rec); void popClamp(); + void debugCross(RectF rec, const RGBA& col); void rect(RectF rec, const RGBA& col, halnf round = 0); + void frame(RectF rec, const RGBA& col, halnf round = 0); void circle(Vec2F pos, halnf size, const RGBA& col); void text(const char*, const RectF&, halnf size, Align, halnf padding, const RGBA&); diff --git a/Math/public/Rect.hpp b/Math/public/Rect.hpp index cf796f6..60efbe5 100644 --- a/Math/public/Rect.hpp +++ b/Math/public/Rect.hpp @@ -122,11 +122,11 @@ namespace tp { // pos Vec2 p1() const { return pos; } + Vec2 p2() const { return { pos.x, pos.y + size.y }; } + // pos + size Vec2 p3() const { return pos + size; } - Vec2 p2() const { return { pos.x, pos.y + size.y }; } - Vec2 p4() const { return { pos.x + size.x, pos.y }; } inline bool isAbove(const Rect& rect) const { return (pos.y + size.y < rect.pos.y); } @@ -165,6 +165,21 @@ namespace tp { return { pos + val, size - val * 2 }; } + void expand(const Vec2& point) { + pos.x = min(point.x, pos.x); + pos.y = min(point.y, pos.y); + + auto p = pos + size; + p.x = max(point.x, p.x); + p.y = max(point.y, p.y); + size = p - pos; + } + + void expand(const Rect& rect) { + expand(rect.pos); + expand(rect.pos + rect.size); + } + // if only one point isInside bool clampOutside(Vec2& v1, Vec2& v2) { bool const in1 = isInside(v1); @@ -268,6 +283,10 @@ namespace tp { Type z; Type w; }; + struct { + Type width; + Type height; + }; }; }; } \ No newline at end of file diff --git a/WidgetsNew/examples/Example.cpp b/WidgetsNew/examples/Example.cpp index d07bb7b..e2817d5 100644 --- a/WidgetsNew/examples/Example.cpp +++ b/WidgetsNew/examples/Example.cpp @@ -5,19 +5,33 @@ #include "AnimationTestWidget.hpp" #include "FloatingWidget.hpp" #include "SimpleWidgets.hpp" +#include "LayoutWidget.hpp" using namespace tp; class WidgetApplication : public Application { public: WidgetApplication() { - mRootWidget.setRootWidget(&mWidget); + mRootWidget.setRootWidget(&mLayoutWidget); - mWidget.addChild(&mWidgetFloating); + mLayoutWidget.addChild(&mFloatingMenu); + // mLayoutWidget.addChild(&mButton2); - RootWidget::setWidgetArea(mWidgetFloating, { 100, 100, 150, 200 }); + // mWidgetFloating.addChild(&mButton); - mWidgetFloating.addChild(&mButton); + // mRootWidget.setRootWidget(&mAnimationTestWidget); + + // mAnimationTestWidget.addChild(&mWidgetFloating); + // mAnimationTestWidget.addChild(&mLayoutWidget); + + // RootWidget::setWidgetArea(mButton, { 100, 100, 150, 200 }); + + RootWidget::setWidgetArea(mFloatingMenu, { 100, 100, 150, 200 }); + + // mWidgetFloating.addChild(&mLayoutWidget); + + // mLayoutWidget.addChild(&mButton); + // mLayoutWidget.addChild(&mLabel); } void processFrame(EventHandler* eventHandler, halnf deltaTime) override { @@ -38,10 +52,12 @@ public: private: LabelWidget mLabel; ButtonWidget mButton; - + ButtonWidget mButton2; FloatingWidget mWidgetFloating; + AnimationTestWidget mAnimationTestWidget; + DesktopLayout mLayoutWidget; + FloatingMenu mFloatingMenu; - AnimationTestWidget mWidget; RootWidget mRootWidget; }; diff --git a/WidgetsNew/private/AnimationTestWidget.cpp b/WidgetsNew/private/AnimationTestWidget.cpp index 7c13106..8b09d57 100644 --- a/WidgetsNew/private/AnimationTestWidget.cpp +++ b/WidgetsNew/private/AnimationTestWidget.cpp @@ -13,7 +13,7 @@ void AnimationTestWidget::process(const EventHandler& events) { mTestSpring.updateCurrentRect(); - if (mTestSpring.checkAnimationShouldEnd()) { + if (mTestSpring.shouldEndTransition()) { mTestSpring.endAnimation(); } } @@ -23,7 +23,6 @@ void AnimationTestWidget::draw(Canvas& canvas) { canvas.rect(mTestSpring.getCurrentRect(), RGBA(1.f), 10); } -bool AnimationTestWidget::needUpdate() const { - if (Widget::needUpdate()) return true; - return !mTestSpring.checkAnimationShouldEnd(); +bool AnimationTestWidget::needsNextFrame() const { + return Widget::needsNextFrame() || !mTestSpring.shouldEndTransition(); } diff --git a/WidgetsNew/private/FloatingWidget.cpp b/WidgetsNew/private/FloatingWidget.cpp index 697a915..74d8c4f 100644 --- a/WidgetsNew/private/FloatingWidget.cpp +++ b/WidgetsNew/private/FloatingWidget.cpp @@ -12,21 +12,45 @@ void FloatingWidget::process(const EventHandler& events) { mIsFloating = true; } + if (mIsFloating && resizeHandleRect().isInside(relativePointer)) { + mIsResizing = true; + } + if (mIsFloating && events.isReleased(InputID::MOUSE1)) { mIsFloating = false; + mIsResizing = false; } mPointerCurrent = relativePointer; - setActive(mIsFloating || needUpdate()); + // triggerWidgetUpdate(); } -void FloatingWidget::updateArea(RectF& area) const { - if (mIsFloating) { +void FloatingWidget::adjustRect() { + auto area = getArea(); + + if (mIsResizing) { + area.size = mPointerCurrent + mHandleSize / 2.f; + } else if (mIsFloating) { area.pos += mPointerCurrent - mPointerStart; } + + setArea(area); } void FloatingWidget::draw(Canvas& canvas) { + canvas.rect(resizeHandleRect(), RGBA(0.7f), 2); canvas.rect(getRelativeArea(), RGBA(0.5f), 10); +} + +RectF FloatingWidget::resizeHandleRect() { + auto area = getRelativeArea(); + area.pos = area.p3() - mHandleSize; + area.size = mHandleSize; + area.shrink(mHandlePadding); + return area; +} + +bool FloatingWidget::propagateEventsToChildren() const { + return !mIsFloating; } \ No newline at end of file diff --git a/WidgetsNew/private/RootWidget.cpp b/WidgetsNew/private/RootWidget.cpp index dc386e4..de880f9 100644 --- a/WidgetsNew/private/RootWidget.cpp +++ b/WidgetsNew/private/RootWidget.cpp @@ -1,35 +1,77 @@ + +#include #include "RootWidget.hpp" +#include "imgui.h" + using namespace tp; -void RootWidget::setRootWidget(Widget* widget) { mRoot = widget; } +void RootWidget::setRootWidget(Widget* widget) { + mRoot = widget; + widget->mParent = this; +} void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) { - mNeedUpdate = false; - - auto currentPaths = &mActivePaths[mFlipFlop]; - auto prevPaths = &mActivePaths[!mFlipFlop]; - - updateActivePaths(currentPaths, events->getPointer()); + mScreenArea = screenArea; mRoot->setArea(screenArea); - clearProcessedFlag(*currentPaths); - clearProcessedFlag(*prevPaths); + // construct hierarchy tree of widgets to process + updateTreeToProcess(); - processPaths(*currentPaths, *events); - processPaths(*prevPaths, *events); + // update active tree animations + updateAnimations(&mWidgetsToProcess[mRoot]); - mActiveWidgets.erase_if([](auto widget) { return !widget->mIsActive; }); + // update all events and call all event processing callbacks + EventHandler dummyEvents; - mFlipFlop = !mFlipFlop; + processFocusItems(*events); + processActiveTree(&mWidgetsToProcess[mRoot], dummyEvents); + + // update widget sizes base on individual size policies + adjustSizes(&mWidgetsToProcess[mRoot]); + + // trigger some widgets by moise pointer + handleFocusChanges(*events); + + // check triggered widgets for removal + erase_if(mTriggeredWidgets, [](auto widget) { + auto end = !widget->needsNextFrame(); + if (end) { + widget->endAnimations(); + } + return end; + }); } -bool RootWidget::needsUpdate() const { return mNeedUpdate; } +bool RootWidget::needsUpdate() const { return !mTriggeredWidgets.empty(); } void RootWidget::drawFrame(Canvas& canvas) { // draw from top to bottom + canvas.rect(mScreenArea, RGBA(0, 0, 0, 1)); + + // draw all tree from top to bottom drawRecursion(canvas, mRoot, { 0, 0 }); + + if (mDebug) { + drawDebug(canvas, mRoot, { 0, 0 }); + ImGui::Text("Triggered Widgets: %i", (int) mTriggeredWidgets.size()); + ImGui::Text("Widgets To Process: %i", (int) mWidgetsToProcess.size()); + } +} + + +void RootWidget::updateTreeToProcess() { + mWidgetsToProcess.clear(); + for (auto widget : mTriggeredWidgets) { + for (auto iter = widget; iter; iter = iter->mParent) { + if (iter->mParent) { + mWidgetsToProcess[iter->mParent].children.insert(&mWidgetsToProcess[iter]); + mWidgetsToProcess[iter->mParent].widget= iter->mParent; + mWidgetsToProcess[iter].widget= iter; + } + } + } } void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) { @@ -37,8 +79,9 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) canvas.pushClamp({ pos, active->getArea().size }); active->draw(canvas); - for (auto child : active->mChildWidgets) { - drawRecursion(canvas, child.data(), pos + child->getArea().pos); + + for (auto child : active->mChildren) { + drawRecursion(canvas, child, pos + child->getArea().pos); } canvas.setOrigin(pos); @@ -47,57 +90,25 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) active->drawOverlay(canvas); } -void RootWidget::updateActivePaths(Buffer* activePaths, const Vec2F& pointer) { - activePaths->clear(); +void RootWidget::drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos) { + auto area = RectF{ pos, active->getArea().size }; - // based on pointer position - Widget* pointedWidget = nullptr; - { - ActivePath& activePath = activePaths->append(ActivePath{}); - activePath.path.append({ mRoot, {} }); - - auto pointerIter = pointer; - - bool newActive = true; - while (newActive) { - newActive = false; - for (auto child : activePath.path.last().widget->mChildWidgets) { - const auto area = child->mArea.getCurrentRect(); - if (area.isInside(pointerIter)) { - activePath.path.append({ child.data(), {} }); - newActive = true; - pointerIter -= child->getArea().pos; - break; - } - } - } - - pointedWidget = activePath.path.last().widget; + if (mWidgetsToProcess.find(active) != mWidgetsToProcess.end()) { + RGBA color = { 1, 0, 0, 1}; + canvas.frame(area, color); + canvas.text(active->mName.c_str(), area, 12, Canvas::Align::LC, 2, color); } - // based on individual widget requirements - { - for (auto widget : mActiveWidgets) { - if (widget.data() == pointedWidget) continue; - - ActivePath& activePath = activePaths->append(ActivePath{}); - - for (Widget* iter = widget.data(); iter; iter = iter->mParent) { - activePath.path.append({ iter, {} }); - } - - activePath.path.reverse(); - } + if (active->mInFocus) { + RGBA color = { 1, 0, 0, 0.3f}; + canvas.debugCross(area, color); + canvas.circle(pos + active->mLocalPoint, 5, active->mDebugColor); + canvas.circle(active->mGlobalPoint, 10, active->mDebugColor); } - // update paths global pos - for (auto activePath : *activePaths) { - Vec2F iterPos = { 0, 0 }; - for (auto activeWidget : activePath->path) { - iterPos += activeWidget->widget->getArea().pos; - activeWidget->globalPos = iterPos; - } + for (auto child : active->mChildren) { + drawDebug(canvas, child, pos + child->getArea().pos); } } @@ -106,50 +117,135 @@ void RootWidget::setWidgetArea(Widget& widget, const RectF& rect) { widget.mArea.endAnimation(); } -void RootWidget::processPaths(const Buffer& paths, EventHandler& events) { - EventHandler dummyEvents; - bool eventsHandled = false; +void RootWidget::updateAnimations(ActiveTreeNode* iter) { + if (!iter) return; + iter->widget->updateAnimations(); + for (auto child : iter->children) { + updateAnimations(child); + } +} - for (auto path : paths) { +void RootWidget::getWidgetPath(Widget* widget, std::vector& out) { + for (auto iter = widget; iter && iter != this; iter = iter->mParent) { + out.push_back(iter); + } +} - for (alni widgetIdx = (alni) path->path.size() - 1; widgetIdx >= 0; widgetIdx--) { - auto activeWidgetNode = path->path[widgetIdx]; - auto activeWidget = activeWidgetNode.widget; +void RootWidget::handleFocusChanges(EventHandler& events) { + auto prevFocus = mInFocusWidget; + findFocusWidget(mRoot, &mInFocusWidget, events.getPointer()); - activeWidget->mArea.updateCurrentRect(); + if (prevFocus) { + std::vector path1; + std::vector path2; - events.setCursorOrigin(activeWidgetNode.globalPos); + getWidgetPath(prevFocus, path1); + getWidgetPath(mInFocusWidget, path2); - bool useEvents = !eventsHandled || activeWidget->mIsActive; + size_t iter = 0; + while (path1[iter] == path2[iter]) { + iter++; + } - if (!activeWidget->mProcessedFlag) { - activeWidget->process(useEvents ? events : dummyEvents); - activeWidget->mProcessedFlag = true; - } + for (auto i = iter; i < path1.size(); i++) { + path1[i]->mouseLeave(); + } - eventsHandled = !activeWidget->isPassThroughEvents(); + for (auto i = iter; i < path2.size(); i++) { + path2[i]->mouseEnter(); + } - auto area = activeWidget->mArea.getCurrentRect(); - activeWidget->updateArea(area); - activeWidget->setArea(area); - - mNeedUpdate |= activeWidget->needUpdate(); - - if (!activeWidget->needUpdate()) activeWidget->finishAnimations(); - - if (activeWidget->mIsActive) { - if (mActiveWidgets.find(activeWidget) == -1) { - mActiveWidgets.append(activeWidget); - } - } + } else { + for (auto iter = mInFocusWidget; iter; iter = iter->mParent) { + iter->mouseEnter(); } } } -void RootWidget::clearProcessedFlag(const Buffer& paths) { - for (auto path : paths) { - for (auto widget : path->path) { - widget->widget->mProcessedFlag = false; +void RootWidget::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer) { + if (!iter->mArea.getTargetRect().isInside(pointer)) return; + + *focus = iter; + + for (auto child : iter->mChildren) { + findFocusWidget(child, focus, pointer - iter->mArea.getTargetRect().pos); + } +} + +void RootWidget::updateWidget(Widget* widget) { + mTriggeredWidgets.insert(widget); +} + +void RootWidget::adjustSizes(ActiveTreeNode* iter) { + if (!iter) return; + + iter->widget->adjustRect(); + iter->widget->adjustChildrenRect(); + + for (auto child : iter->children) { + adjustSizes(child); + } +} + +void RootWidget::processActiveTree(ActiveTreeNode* iter, EventHandler& events) { + if (!iter) return; + + if (!iter->widget->mInFocus) { + iter->widget->updateAnimations(); + iter->widget->process(events); + } + + for (auto child : iter->children) { + processActiveTree(child, events); + } +} + +void RootWidget::processFocusItems(EventHandler& events) { + if (!mInFocusWidget) return; + + // FIXME : cringe + + std::vector path; + getWidgetPath(mInFocusWidget, path); + + for (auto i = 0; i < path.size() / 2; i++) { + swap(path[i], path[path.size() - i - 1]); + } + + size_t len = path.size(); + for (auto i = 0; i < len; i++) { + if (!path[i]->propagateEventsToChildren()) { + len = i + 1; + break; + } + } + + std::vector widgetGlobalPos(path.size()); + + widgetGlobalPos[0] = 0; + for (auto widget = 0; widget < path.size() - 1; widget++) { + widgetGlobalPos[widget + 1] = widgetGlobalPos[widget] + path[widget + 1]->getArea().pos; + path[widget]->mGlobalPoint = widgetGlobalPos[widget]; + } + + bool eventsProcessed = false; + + for (int iter = (int) len - 1; iter >= 0; iter--) { + auto widget = path[iter]; + + events.setCursorOrigin(widgetGlobalPos[iter]); + + if (!eventsProcessed && widget->processesEvents()) { + events.setEnableKeyEvents(true); + + widget->updateAnimations(); + widget->process(events); + + events.setEnableKeyEvents(false); + eventsProcessed = true; + } else { + widget->updateAnimations(); + widget->process(events); } } } diff --git a/WidgetsNew/private/SimpleWidgets.cpp b/WidgetsNew/private/SimpleWidgets.cpp index db9b936..3d5c4d3 100644 --- a/WidgetsNew/private/SimpleWidgets.cpp +++ b/WidgetsNew/private/SimpleWidgets.cpp @@ -15,11 +15,15 @@ void LabelWidget::draw(Canvas& canvas) { canvas.text(mText.c_str(), getRelativeArea(), mSize, Canvas::LC, mPadding, mColor); } - ButtonWidget::ButtonWidget() { mAction = []() { printf("Button Pressed!\n"); }; + + setDebug("button", { 0.1, 0.1, 0.7, 0.7 }); + + mColorAnimated.setTargetColor(mColor); + mColorAnimated.endAnimation(); } void ButtonWidget::setAction(const std::function& action) { @@ -27,19 +31,11 @@ void ButtonWidget::setAction(const std::function& action) { } void ButtonWidget::process(const EventHandler& eventHandler) { - if (getRelativeArea().isInside(eventHandler.getPointer())) { + if (getArea().isInside(eventHandler.getPointer())) { if (eventHandler.isPressed(InputID::MOUSE1)) { mAction(); } - - mColorAnimated.setTargetColor(mColor); - } else { - mColorAnimated.setTargetColor(mColorHovered); } - - mColorAnimated.updateCurrentRect(); - - setActive(needUpdate()); } void ButtonWidget::draw(Canvas& canvas) { @@ -47,10 +43,31 @@ void ButtonWidget::draw(Canvas& canvas) { LabelWidget::draw(canvas); } -bool ButtonWidget::needUpdate() const { - return !mColorAnimated.checkAnimationShouldEnd(); +bool ButtonWidget::needsNextFrame() const { + return LabelWidget::needsNextFrame() || !mColorAnimated.shouldEndTransition(); } -void ButtonWidget::finishAnimations() { +void ButtonWidget::endAnimations() { mColorAnimated.endAnimation(); -} \ No newline at end of file + LabelWidget::endAnimations(); +} + +void ButtonWidget::updateAnimations() { + mColorAnimated.updateCurrentRect(); + LabelWidget::updateAnimations(); +} + +void ButtonWidget::mouseEnter() { + mColorAnimated.setTargetColor(mColor); + mColorAnimated.endAnimation(); + + mColorAnimated.setTargetColor(mColorHovered); + mColorAnimated.updateCurrentRect(); + triggerWidgetUpdate(); +} + +void ButtonWidget::mouseLeave() { + mColorAnimated.setTargetColor(mColor); + mColorAnimated.updateCurrentRect(); + triggerWidgetUpdate(); +} diff --git a/WidgetsNew/private/Widget.cpp b/WidgetsNew/private/Widget.cpp index 9262000..e5ed9de 100644 --- a/WidgetsNew/private/Widget.cpp +++ b/WidgetsNew/private/Widget.cpp @@ -2,6 +2,20 @@ using namespace tp; +WidgetManagerInterface* Widget::getRoot() { + Widget* iter = mParent; + while (iter && iter->mParent) { + iter = iter->mParent; + } + return dynamic_cast(iter); +} + +void Widget::triggerWidgetUpdate() { + if (auto root = getRoot()) { + root->updateWidget(this); + } +} + Widget::Widget() { mArea.setTargetRect({ 10, 10, 100, 40, }); mArea.endAnimation(); @@ -9,10 +23,10 @@ Widget::Widget() { void Widget::setArea(const RectF& area) { mArea.setTargetRect(area); -} -void Widget::draw(Canvas& canvas) { - canvas.rect(mArea.getCurrentRect(), RGBA(1.f), 10); + if (!mArea.shouldEndTransition()) { + triggerWidgetUpdate(); + } } RectF Widget::getArea() const { @@ -23,28 +37,47 @@ RectF Widget::getRelativeArea() const { return { {}, mArea.getCurrentRect().size }; } -void Widget::setActive(bool active) { - mIsActive = active; -} - -void Widget::addChild(Widget* widget) { - if (widget->mParent) { - widget->mParent->removeChild(widget); - } - mChildWidgets.pushBack(widget); - widget->mParent = this; -} - -void Widget::removeChild(Widget* widget) { - auto node = mChildWidgets.find(widget); - node->data->mParent = nullptr; - mChildWidgets.removeNode(node); -} - -bool Widget::needUpdate() const { - return !mArea.checkAnimationShouldEnd(); -} - -void Widget::finishAnimations() { +void Widget::endAnimations() { mArea.endAnimation(); +} + +bool Widget::processesEvents() const { + return true; +} + +void Widget::updateAnimations() { + mArea.updateCurrentRect(); +} + +bool Widget::needsNextFrame() const { + return !mArea.shouldEndTransition() || mInFocus; +} + +void Widget::adjustRect() { + if (mChildren.empty()) return; + + auto area = (*mChildren.begin())->getArea(); + for (auto widget : mChildren) { + area.expand(widget->getArea()); + } +} + +void Widget::adjustChildrenRect() { + auto area = RectF({}, getArea().size); + for (auto widget : mChildren) { + widget->setArea(area); + } +} + +void Widget::mouseEnter() { + mInFocus = true; + triggerWidgetUpdate(); +} + +void Widget::mouseLeave() { + mInFocus = false; +} + +bool Widget::propagateEventsToChildren() const { + return true; } \ No newline at end of file diff --git a/WidgetsNew/public/AnimationTestWidget.hpp b/WidgetsNew/public/AnimationTestWidget.hpp index c4176d2..dd3f66c 100644 --- a/WidgetsNew/public/AnimationTestWidget.hpp +++ b/WidgetsNew/public/AnimationTestWidget.hpp @@ -12,9 +12,9 @@ namespace tp { void draw(Canvas& canvas) override; void process(const EventHandler& events) override; - [[nodiscard]] bool needUpdate() const override; + [[nodiscard]] bool needsNextFrame() const override; private: - SpringRect mTestSpring; + mutable SpringRect mTestSpring; }; } \ No newline at end of file diff --git a/WidgetsNew/public/FloatingWidget.hpp b/WidgetsNew/public/FloatingWidget.hpp index 47debad..e501dc5 100644 --- a/WidgetsNew/public/FloatingWidget.hpp +++ b/WidgetsNew/public/FloatingWidget.hpp @@ -1,22 +1,61 @@ #pragma once -#include "Widget.hpp" +#include "SimpleWidgets.hpp" +#include "LayoutWidget.hpp" namespace tp { class FloatingWidget : public Widget { public: - FloatingWidget() = default; + FloatingWidget() { + setDebug("float", { 0.0, 0.9, 0.1, 0.7 }); + } void process(const EventHandler& events) override; - void updateArea(RectF& area) const override; + void adjustRect() override; void draw(Canvas& canvas) override; + RectF resizeHandleRect(); + + bool propagateEventsToChildren() const override; + private: bool mIsFloating = false; + bool mIsResizing = false; + + halnf mHandleSize = 10; + halnf mHandlePadding = 2; Vec2F mPointerStart; Vec2F mPointerCurrent; }; + + class FloatingMenu : public FloatingWidget { + public: + FloatingMenu() { + setDebug("float menu", { 0.0, 0.9, 0.1, 0.7 }); + + addChild(&mMenuLayout); + + mMenuLayout.addChild(&mHeader); + mMenuLayout.addChild(&mBodyLayout); + + addToMenu(&mTestButton); + + mHeader.setText("Menu"); + } + + public: + void addToMenu(Widget* widget) { + mBodyLayout.addChild(widget); + } + + private: + VerticalLayout mMenuLayout; + VerticalLayout mBodyLayout; + LabelWidget mHeader; + + ButtonWidget mTestButton; + }; } \ No newline at end of file diff --git a/WidgetsNew/public/LayoutWidget.hpp b/WidgetsNew/public/LayoutWidget.hpp new file mode 100644 index 0000000..68d8f13 --- /dev/null +++ b/WidgetsNew/public/LayoutWidget.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "Widget.hpp" + +namespace tp { + class DesktopLayout : public Widget { + public: + DesktopLayout() { setDebug("desktop layout", { 0, 0, 0.9, 0.9 }); } + + void draw(Canvas& canvas) override { + // canvas.rect(getRelativeArea(), RGBA(0, 0, 0, 1)); + } + + void adjustRect() override { + // do nothing + } + + void adjustChildrenRect() override { + // todo : adjust by viewport pos + } + + [[nodiscard]] bool processesEvents() const override { return false; } + }; + + + class VerticalLayout : public Widget { + public: + VerticalLayout() { + setDebug("vertical", { 0.8, 0.1, 0.1, 0.9 }); + } + + void draw(Canvas& canvas) override { + // canvas.frame(getRelativeArea(), RGBA(1)); + } + + void adjustChildrenRect() override { + auto content = &mChildren; + auto area = getRelativeArea(); + + auto iterRect = area.shrink(mPadding); + iterRect.height = ((iterRect.height + mGap) / (float) content->size()) - mGap; + + for (auto widget :(*content)) { + widget->setArea(iterRect); + iterRect.y += iterRect.height + mGap; + } + } + + [[nodiscard]] bool processesEvents() const override { return false; } + + private: + halnf mGap = 10; + halnf mPadding = 10; + }; + +} \ No newline at end of file diff --git a/WidgetsNew/public/RootWidget.hpp b/WidgetsNew/public/RootWidget.hpp index 3d428fe..b96dc94 100644 --- a/WidgetsNew/public/RootWidget.hpp +++ b/WidgetsNew/public/RootWidget.hpp @@ -2,46 +2,54 @@ #include "Widget.hpp" +#include +#include +#include + namespace tp { - class RootWidget { + class RootWidget : public WidgetManagerInterface { - // path from leaf to root of widgets that need to be updated - struct ActivePath { - struct ActiveNode { - Widget* widget = nullptr; - Vec2F globalPos; - }; - - Buffer path; + struct ActiveTreeNode { + ActiveTreeNode* parent = nullptr; + std::set children; + Widget* widget = nullptr; }; - public: - RootWidget() = default; + RootWidget() { setDebug("root", RGBA(1)); } void setRootWidget(Widget* widget); + void updateWidget(Widget*); + static void setWidgetArea(Widget& widget, const RectF& rect); + + public: void processFrame(EventHandler* events, const RectF& screenArea); void drawFrame(Canvas& canvas); [[nodiscard]] bool needsUpdate() const; - static void setWidgetArea(Widget& widget, const RectF& rect); - private: - void updateActivePaths(Buffer* activePaths, const Vec2F& pointer); + void updateTreeToProcess(); + void updateAnimations(ActiveTreeNode* iter); void drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos); - - void processPaths(const Buffer& path, EventHandler& events); - void clearProcessedFlag(const Buffer& paths); + void drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos); + void findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer); + void handleFocusChanges(EventHandler& events); + void getWidgetPath(Widget* widget, std::vector& out); + void processActiveTree(ActiveTreeNode* iter, EventHandler& events); + void processFocusItems(EventHandler& events); + void adjustSizes(ActiveTreeNode* iter); private: + RectF mScreenArea; Widget* mRoot = nullptr; - Buffer mActiveWidgets; - Buffer mActivePaths[2]; // current and prev - bool mFlipFlop = false; + // frame to frame changes + std::set mTriggeredWidgets; + std::map mWidgetsToProcess; + Widget* mInFocusWidget = nullptr; - bool mNeedUpdate = false; + bool mDebug = false; }; } \ No newline at end of file diff --git a/WidgetsNew/public/SimpleWidgets.hpp b/WidgetsNew/public/SimpleWidgets.hpp index 2886825..03e3a1b 100644 --- a/WidgetsNew/public/SimpleWidgets.hpp +++ b/WidgetsNew/public/SimpleWidgets.hpp @@ -7,14 +7,17 @@ namespace tp { class LabelWidget : public Widget { public: - LabelWidget() = default; + LabelWidget() { + setDebug("label", { 0.1, 0.1, 0.1, 0.1 }); + } void setText(const std::string& text); + [[nodiscard]] const std::string& getText() const; void draw(Canvas& canvas) override; - [[nodiscard]] bool isPassThroughEvents() const override { return true; } + [[nodiscard]] bool processesEvents() const override { return false; } private: std::string mText = "Text"; @@ -33,11 +36,14 @@ namespace tp { void process(const EventHandler& eventHandler) override; void draw(Canvas& canvas) override; - [[nodiscard]] bool isPassThroughEvents() const override { return false; } + void mouseEnter() override; + void mouseLeave() override; - [[nodiscard]] bool needUpdate() const override; + [[nodiscard]] bool processesEvents() const override { return true; } + [[nodiscard]] bool needsNextFrame() const override; - void finishAnimations() override; + void endAnimations() override; + void updateAnimations() override; private: std::function mAction; @@ -45,7 +51,7 @@ namespace tp { SpringRect mColorAnimated; halnf mRounding = 5; - RGBA mColor = { 1.0f, 0.0f, 0.0f, 1.f }; - RGBA mColorHovered = { 0.0f, 0.0f, 0.0f, 1.f }; + RGBA mColorHovered = { 0.0f, 0.4f, 0.4f, 1.f }; + RGBA mColor = { 0.0f, 0.0f, 0.0f, 1.f }; }; } \ No newline at end of file diff --git a/WidgetsNew/public/SpringAnimations.hpp b/WidgetsNew/public/SpringAnimations.hpp index ecab5bc..0c8b4f2 100644 --- a/WidgetsNew/public/SpringAnimations.hpp +++ b/WidgetsNew/public/SpringAnimations.hpp @@ -139,7 +139,7 @@ namespace tp { mEndPos.updateCurrentPosition(); } - [[nodiscard]] bool checkAnimationShouldEnd() const { + [[nodiscard]] bool shouldEndTransition() const { return mStartPos.checkAnimationShouldEnd() && mEndPos.checkAnimationShouldEnd(); } diff --git a/WidgetsNew/public/Widget.hpp b/WidgetsNew/public/Widget.hpp index 557beaa..a7e5807 100644 --- a/WidgetsNew/public/Widget.hpp +++ b/WidgetsNew/public/Widget.hpp @@ -5,7 +5,11 @@ #include "EventHandler.hpp" #include "Graphics.hpp" +#include + namespace tp { + class WidgetManagerInterface; + class Widget { friend class RootWidget; @@ -13,41 +17,68 @@ namespace tp { Widget(); virtual ~Widget() = default; - virtual void process(const EventHandler& events) {} - virtual void updateArea(RectF& area) const {} + void setDebug(const char* name, RGBA col) { + mName = name; + mDebugColor = col; + } - virtual void draw(Canvas& canvas); - virtual void drawOverlay(Canvas& canvas) {} + void addChild(Widget* child) { + mChildren.push_back(child); + child->mParent = this; - virtual bool isPassThroughEvents() const { return false; } + triggerWidgetUpdate(); + child->triggerWidgetUpdate(); + } - [[nodiscard]] virtual bool needUpdate() const; + WidgetManagerInterface* getRoot(); - virtual void finishAnimations(); + void triggerWidgetUpdate(); public: - [[nodiscard]] RectF getRelativeArea() const; - void setActive(bool active); + virtual void process(const EventHandler& events) {} + virtual void draw(Canvas& canvas) { canvas.rect(mArea.getCurrentRect(), RGBA(1.f), 10); } + virtual void drawOverlay(Canvas& canvas) {} - void addChild(Widget* widget); + virtual void mouseEnter(); + virtual void mouseLeave(); - private: + // size policies + virtual void adjustRect(); + virtual void adjustChildrenRect(); + + [[nodiscard]] virtual bool processesEvents() const; + [[nodiscard]] virtual bool propagateEventsToChildren() const; + + [[nodiscard]] virtual bool needsNextFrame() const; + + virtual void endAnimations(); + virtual void updateAnimations(); + + public: [[nodiscard]] RectF getArea() const; + [[nodiscard]] RectF getRelativeArea() const; void setArea(const RectF& area); - void removeChild(Widget* widget); - private: + protected: + Widget* mParent = nullptr; + + std::vector mChildren; + // relative to the parent SpringRect mArea; - // tree structure of widgets - List mChildWidgets; + ualni mZValue = 0; - // if needed updates even though pointer is not in area - bool mIsActive = false; + bool mInFocus = false; - Widget* mParent = nullptr; + // debug + std::string mName = "widget base"; + Vec2F mLocalPoint; + Vec2F mGlobalPoint; + RGBA mDebugColor = { 1, 1, 1, 0.3 }; + }; - bool mProcessedFlag = false; + struct WidgetManagerInterface : public Widget { + virtual void updateWidget(Widget*) = 0; }; } \ No newline at end of file