From 8c4ba37e596879501a775167a6a51cc415aae27b Mon Sep 17 00:00:00 2001 From: IlyaShurupov Date: Thu, 17 Oct 2024 16:46:49 +0300 Subject: [PATCH] Scrolling layout --- Math/public/Rect.hpp | 10 +-- TODO | 7 +- Widgets/private/ScrollableWidget.cpp | 1 + WidgetsNew/examples/Example.cpp | 61 +++++++++---- WidgetsNew/private/layouts/BasicLayout.cpp | 6 +- .../private/layouts/ScrollableLayout.cpp | 53 +++++++++++ WidgetsNew/private/managers/DebugManager.cpp | 7 +- WidgetsNew/private/managers/LayoutManager.cpp | 2 + WidgetsNew/private/managers/UpdateManager.cpp | 2 +- .../private/widgets/ScrollableWidget.cpp | 87 +++++++++++++++++++ WidgetsNew/public/Layout.hpp | 6 +- .../public/layouts/ScrollableLayout.hpp | 22 +++++ WidgetsNew/public/mangers/DebugManager.hpp | 2 +- .../public/widgets/ScrollableWidget.hpp | 57 ++++++++++++ 14 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 WidgetsNew/private/layouts/ScrollableLayout.cpp create mode 100644 WidgetsNew/private/widgets/ScrollableWidget.cpp create mode 100644 WidgetsNew/public/layouts/ScrollableLayout.hpp create mode 100644 WidgetsNew/public/widgets/ScrollableWidget.hpp diff --git a/Math/public/Rect.hpp b/Math/public/Rect.hpp index 3331b61..a03655e 100644 --- a/Math/public/Rect.hpp +++ b/Math/public/Rect.hpp @@ -122,7 +122,7 @@ namespace tp { pos.y += dy; } - Rect& scaleFromCenter(tp::halnf fac, bool add = false) { + Rect& shrinkFromCenter(tp::halnf fac, bool add = false) { if (add) { pos += fac; size -= fac * 2; @@ -260,20 +260,20 @@ namespace tp { Vec2 center() const { return pos + size / 2.f; } // splits by Factor Horizontally returning Left rect - Rect splitByFactorHL(halnf factor) { + Rect splitByFactorHL(halnf factor) const { return { x, y, size.x * factor, size.y }; } - Rect splitByFactorHR(halnf factor) { + Rect splitByFactorHR(halnf factor) const { const auto abs = size.x * factor; return { x + abs, y, size.x - abs, size.y }; } - Rect splitByFactorVT(halnf factor) { + Rect splitByFactorVT(halnf factor) const { return { x, y, size.x, size.y * factor }; } - Rect splitByFactorVB(halnf factor) { + Rect splitByFactorVB(halnf factor) const { const auto abs = size.y * factor; return { x, y + abs, size.x, size.y - abs }; } diff --git a/TODO b/TODO index c1e841e..d453905 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,10 @@ Widgets-New: - Lock-focus feature - Collapsing menu + Make visual parameters static + Make visual parameters configurable + Make get-areas fetch cache and not animation objects + Scrolling in basic layout + Collapsing menu Add Widgets: popups, drop-downs, tool-tips diff --git a/Widgets/private/ScrollableWidget.cpp b/Widgets/private/ScrollableWidget.cpp index 91fe2c8..de74468 100644 --- a/Widgets/private/ScrollableWidget.cpp +++ b/Widgets/private/ScrollableWidget.cpp @@ -1,4 +1,5 @@ #include "ScrollableWidget.hpp" +#include "../../WidgetsNew/public/widgets/ScrollableWidget.hpp" using namespace tp; diff --git a/WidgetsNew/examples/Example.cpp b/WidgetsNew/examples/Example.cpp index 58422f7..dee5608 100644 --- a/WidgetsNew/examples/Example.cpp +++ b/WidgetsNew/examples/Example.cpp @@ -2,9 +2,11 @@ #include "GraphicApplication.hpp" #include "RootWidget.hpp" -#include "AnimationTestWidget.hpp" #include "FloatingWidget.hpp" #include "DockWidget.hpp" +#include "ScrollableWidget.hpp" + +#include "ScrollableLayout.hpp" using namespace tp; @@ -21,8 +23,27 @@ using namespace tp; class WidgetApplication : public Application { public: WidgetApplication() { - exampleNestedMenus(); - // examplePopup(); + exampleScrolling(); + } + + void exampleScrolling() { + static Widget widget; + static Widget content; + static ButtonWidget buttons[10]; + static ScrollableBarWidget scrollBar; + + mRootWidget.setRootWidget(&widget); + + widget.addChild(&scrollBar); + widget.addChild(&content); + + for (auto& button : buttons) { + content.addChild(&button); + } + + widget.setLayout(new ScrollableLayout(&widget)); + + RootWidget::setWidgetArea(content, { 333, 333, 522, 522 }); } void examplePopup() { @@ -72,6 +93,22 @@ public: menu2.addToMenu(&menu1); + // popup + { + buttons[0].setAction([&](){ + buttons[10].setArea({ 30, 30, 100, 100 }); + buttons[0].openPopup(&buttons[10]); + }); + + buttons[0].setText("open popup"); + + buttons[10].setAction([&](){ + buttons[10].closePopup(&buttons[10]); + }); + + buttons[10].setText("close popup"); + } + RootWidget::setWidgetArea(menu1, { 300, 100, 150, 500 }); RootWidget::setWidgetArea(menu2, { 100, 100, 150, 300 }); } @@ -107,7 +144,7 @@ public: ((BasicLayout*)widgets[1].getLayout())->setLayoutPolicy(LayoutPolicy::Horizontal); } - void setup1() { + void exampleDock() { static DockWidget dock; static FloatingMenu menu1; static FloatingMenu menu2; @@ -140,22 +177,8 @@ public: buttons[5].setText("dock"); buttons[6].setText("undock"); - // mLayoutWidget.addChild(&buttons[2]); - - // mWidgetFloating.addChild(&buttons[0]); - - // mRootWidget.setRootWidget(&mAnimationTestWidget); - - // mAnimationTestWidget.addChild(&mWidgetFloating); - // mAnimationTestWidget.addChild(&mLayoutWidget); - RootWidget::setWidgetArea(menu2, { 300, 100, 150, 500 }); RootWidget::setWidgetArea(menu1, { 100, 100, 150, 300 }); - - // mWidgetFloating.addChild(&mLayoutWidget); - - // mLayoutWidget.addChild(&buttons[0]); - // mLayoutWidget.addChild(&mLabel); } void processFrame(EventHandler* eventHandler, halnf deltaTime) override { @@ -166,7 +189,7 @@ public: void drawFrame(Canvas* canvas) override { mRootWidget.drawFrame(*canvas); - drawDebug(); + // drawDebug(); } bool forceNewFrame() override { diff --git a/WidgetsNew/private/layouts/BasicLayout.cpp b/WidgetsNew/private/layouts/BasicLayout.cpp index 720c959..c02b14d 100644 --- a/WidgetsNew/private/layouts/BasicLayout.cpp +++ b/WidgetsNew/private/layouts/BasicLayout.cpp @@ -11,8 +11,10 @@ void BasicLayout::pickRect(bool vertical) { auto current = getArea(); auto children = getChildrenEnclosure(); auto parent = getParentEnclosure(); - RectF newArea = current; + children.shrinkFromCenter(-mLayoutMargin, true); + + RectF newArea; if (vertical) { auto rangeY = pickRange(current.getRangeY(), children.getRangeY(), parent.getRangeY(), true); newArea = RectF(current.getRangeX(), rangeY); @@ -143,5 +145,5 @@ void BasicLayout::adjustLayout(bool vertical) { RectF BasicLayout::getAvailableChildArea() const { auto out = WidgetLayout::getAvailableChildArea(); - return out.scaleFromCenter(mLayoutMargin, true); + return out.shrinkFromCenter(mLayoutMargin, true); } \ No newline at end of file diff --git a/WidgetsNew/private/layouts/ScrollableLayout.cpp b/WidgetsNew/private/layouts/ScrollableLayout.cpp new file mode 100644 index 0000000..b848952 --- /dev/null +++ b/WidgetsNew/private/layouts/ScrollableLayout.cpp @@ -0,0 +1,53 @@ +#include "ScrollableLayout.hpp" + +// fixme : this dependency should be removed +#include "ScrollableWidget.hpp" + +using namespace tp; + +void ScrollableLayout::updateLayout(bool vertical) { + if (vertical) return; + + // TODO : make better interface to get scroller and content widget + if (children().size() != 2) return; + + auto scroller = dynamic_cast(children().front()); + auto content = children().back(); + + if (!scroller || !content) return; + + updateWidgetRects(getArea(), content, scroller); +} + +void ScrollableLayout::updateWidgetRects(const RectF& area, Widget* content, ScrollableBarWidget* scroller) const { + bool dir = scroller->getDirection(); + + // update content + // auto contentItems = content->getLayout()->getChildrenEnclosure(); + // content->getLayout()->setArea(contentItems.shrinkFromCenter(-10, true)); + content->getLayout()->pickRect(dir); + + auto sizeFactor = (area.size[dir] / content->getLayout()->getArea().size[dir]); + auto splitFactor = 1 - ((sizeFactor > 1.f) ? 0 : mScrollerSize / area.size[!dir]); + + auto holderArea = area.splitByFactorHL(splitFactor); + auto contentArea = content->getLayout()->getArea(); + auto scrollerArea = area.splitByFactorHR(splitFactor); + + scroller->updateSizeFactor(sizeFactor); + + contentArea.pos[dir] = -scroller->getPosFactor() * contentArea.size[dir]; + contentArea.size[!dir] = holderArea.size[!dir]; + contentArea.pos[!dir] = 0; + + scroller->getLayout()->setArea(scrollerArea); + content->getLayout()->setArea(contentArea); +} + +RectF ScrollableLayout::getAvailableChildArea() const { + auto out = getArea(); + // dont constrain on scroll axis + out.pos = -FLT_MAX / 4; + out.size = FLT_MAX / 2; + return out; +} diff --git a/WidgetsNew/private/managers/DebugManager.cpp b/WidgetsNew/private/managers/DebugManager.cpp index 66ddc18..0e390fe 100644 --- a/WidgetsNew/private/managers/DebugManager.cpp +++ b/WidgetsNew/private/managers/DebugManager.cpp @@ -14,6 +14,9 @@ DebugManager tp::gDebugWidget; bool DebugManager::update(RootWidget* rootWidget, EventHandler& events) { mRootWidget = rootWidget; + events.setEnableKeyEvents(true); + if (events.isPressed(InputID::D)) mDebug = !mDebug; + if (mDebug) { auto area = rootWidget->mRoot.getAreaT(); area.size -= { 400, 0 }; @@ -53,12 +56,12 @@ bool DebugManager::update(RootWidget* rootWidget, EventHandler& events) { void DebugManager::drawDebug(RootWidget* rootWidget, Canvas& canvas) { mRootWidget = rootWidget; - ImGui::Checkbox("Draw debug", &mDebug); - if (!mDebug) { return; } + // ImGui::Checkbox("Draw debug", &mDebug); + ImGui::SameLine(); ImGui::Text("To Toggle processing press k"); auto& upd = rootWidget->mUpdateManager; diff --git a/WidgetsNew/private/managers/LayoutManager.cpp b/WidgetsNew/private/managers/LayoutManager.cpp index 3e30e80..749b7e6 100644 --- a/WidgetsNew/private/managers/LayoutManager.cpp +++ b/WidgetsNew/private/managers/LayoutManager.cpp @@ -2,6 +2,7 @@ #include "DebugManager.hpp" #include "DockLayout.hpp" +#include "ScrollableLayout.hpp" #include @@ -91,6 +92,7 @@ int LayoutManager::getLayoutOrder(WidgetLayout* parent, WidgetLayout* child) con auto policyChild = parent->getSizePolicy()[mVertical]; if (dynamic_cast(parent)) return -1; + // if (dynamic_cast(parent)) return -1; return sizePolicyDep[int(policyParent)][int(policyChild)]; } diff --git a/WidgetsNew/private/managers/UpdateManager.cpp b/WidgetsNew/private/managers/UpdateManager.cpp index 6b38ac2..4dcf24c 100644 --- a/WidgetsNew/private/managers/UpdateManager.cpp +++ b/WidgetsNew/private/managers/UpdateManager.cpp @@ -206,6 +206,6 @@ void UpdateManager::lockFocus(tp::Widget* widget) { } void UpdateManager::freeFocus(tp::Widget* widget) { - DEBUG_ASSERT(mFocusLockWidget == widget) + // DEBUG_ASSERT(mFocusLockWidget == widget) mFocusLockWidget = nullptr; } \ No newline at end of file diff --git a/WidgetsNew/private/widgets/ScrollableWidget.cpp b/WidgetsNew/private/widgets/ScrollableWidget.cpp new file mode 100644 index 0000000..6497947 --- /dev/null +++ b/WidgetsNew/private/widgets/ScrollableWidget.cpp @@ -0,0 +1,87 @@ +#include "ScrollableWidget.hpp" + +using namespace tp; + +void ScrollableBarWidget::process(const EventHandler& events) { + // all content is visible no need to process anything + if (mSizeFactor >= 1) { + mScrolling = false; + mPosFactor = mSizeFactor / 2.f; + freeFocus(); + return; + } + + updateHandleRect(); + + auto pointer = events.getPointer(); + auto size = getArea().size; + + mHandleHovered = getHandleRect().isInside(pointer); + + if (getRelativeAreaT().isInside(pointer) && events.isPressed(InputID::MOUSE1)) { + if (!mHandleHovered) { + jumpTo((pointer / size)[mVertical]); + mStartPos = { 0, 0 }; + } else { + mStartPos = pointer - getHandleRect().center(); + } + mScrolling = true; + lockFocus(); + } + + if (mScrolling && events.isReleased(InputID::MOUSE1)) { + mScrolling = false; + freeFocus(); + } + + if (mScrolling) { + updateHandleRect(); + auto dragDelta = (pointer - getHandleRect().center()) - mStartPos; + moveBy((dragDelta / size)[mVertical]); + } +} + +void ScrollableBarWidget::jumpTo(halnf pos) { + mPosFactor = pos; + clamp(); +} + +void ScrollableBarWidget::moveBy(halnf delta) { + mPosFactor += delta; + clamp(); +} + +void ScrollableBarWidget::updateSizeFactor(halnf factor) { + mSizeFactor = factor; + clamp(); +} + +void ScrollableBarWidget::clamp() { + mSizeFactor = ::clamp(mSizeFactor, 0.f, 1.f); + mPosFactor = ::clamp(mPosFactor, mSizeFactor / 2, 1.f - mSizeFactor / 2); +} + +void ScrollableBarWidget::draw(Canvas& canvas) { + updateHandleRect(); + + canvas.rect(getArea().relative(), mBGColor, mRounding); + + auto& handleColor = mScrolling ? mHandleSlideColor : (mHandleHovered ? mHandleHoverColor : mHandleColor); + canvas.rect(getHandleRect(), handleColor, mRounding); +} + +void ScrollableBarWidget::updateHandleRect() { + auto area = getArea().relative(); + + mHandleRect = area; + mHandleRect.size[mVertical] = area.size[mVertical] * mSizeFactor; + mHandleRect.pos[mVertical] = area.size[mVertical] * mPosFactor; + + mHandleRect.pos[mVertical] -= mHandleRect.size[mVertical] / 2; + + mHandleRect.shrinkFromCenter(mHandlePadding, true); +} + +const RectF& ScrollableBarWidget::getHandleRect() const { + return mHandleRect; +} \ No newline at end of file diff --git a/WidgetsNew/public/Layout.hpp b/WidgetsNew/public/Layout.hpp index 10c8965..3bb662a 100644 --- a/WidgetsNew/public/Layout.hpp +++ b/WidgetsNew/public/Layout.hpp @@ -27,8 +27,8 @@ namespace tp { virtual void updateLayout(bool vertical) {} - virtual void pickRect(bool vertical) = 0; - virtual void clampRect() = 0; + virtual void pickRect(bool vertical) {} + virtual void clampRect() {} [[nodiscard]] virtual RectF getAvailableChildArea() const; public: @@ -59,7 +59,7 @@ namespace tp { Widget* mWidget = nullptr; protected: - Vec2 mSizePolicy = { SizePolicy::Fixed, SizePolicy::Fixed }; + Vec2 mSizePolicy = { SizePolicy::Fixed, SizePolicy::Minimal }; Vec2F mMinSize = { 50, 50 }; Vec2F mMaxSize = { FLT_MAX / 2, FLT_MAX / 2 }; }; diff --git a/WidgetsNew/public/layouts/ScrollableLayout.hpp b/WidgetsNew/public/layouts/ScrollableLayout.hpp new file mode 100644 index 0000000..d15d3b7 --- /dev/null +++ b/WidgetsNew/public/layouts/ScrollableLayout.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "Layout.hpp" + +namespace tp { + class ScrollableBarWidget; + + class ScrollableLayout : public WidgetLayout { + public: + explicit ScrollableLayout(Widget* widget) : + WidgetLayout(widget) {} + + void updateLayout(bool vertical) override; + [[nodiscard]] RectF getAvailableChildArea() const override; + + private: + void updateWidgetRects(const RectF& area, Widget* content, ScrollableBarWidget* scroller) const; + + private: + halnf mScrollerSize = 15; + }; +} \ No newline at end of file diff --git a/WidgetsNew/public/mangers/DebugManager.hpp b/WidgetsNew/public/mangers/DebugManager.hpp index e169261..f33425d 100644 --- a/WidgetsNew/public/mangers/DebugManager.hpp +++ b/WidgetsNew/public/mangers/DebugManager.hpp @@ -37,7 +37,7 @@ namespace tp { RootWidget* mRootWidget = nullptr; // debug - bool mDebug = true; + bool mDebug = false; bool mDebugStopProcessing = false; bool mDebugRedrawAlways = false; bool mSlowMotion = false; diff --git a/WidgetsNew/public/widgets/ScrollableWidget.hpp b/WidgetsNew/public/widgets/ScrollableWidget.hpp new file mode 100644 index 0000000..13026ca --- /dev/null +++ b/WidgetsNew/public/widgets/ScrollableWidget.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "Widget.hpp" + +namespace tp { + // just the scrolling bar + class ScrollableBarWidget : public Widget { + public: + ScrollableBarWidget() = default; + + [[nodiscard]] bool processesEvents() const override { return true; } + [[nodiscard]] bool propagateEventsToChildren() const override { return false; } + [[nodiscard]] bool needsNextFrame() const override { return mScrolling; } + + void process(const EventHandler& events) override; + void draw(Canvas& canvas) override; + + void updateSizeFactor(halnf factor); // holder-size divided by content-size + + [[nodiscard]] bool getDirection() const { return mVertical; } + [[nodiscard]] halnf getPosFactor() const { return mPosFactor - mSizeFactor / 2; } + + private: + [[nodiscard]] const RectF& getHandleRect() const; + void updateHandleRect(); + void jumpTo(halnf); + void moveBy(halnf); + void clamp(); + + private: // state + bool mVertical = true; + bool mScrolling = false; + bool mHandleHovered = false; + + Vec2F mStartPos = {}; + + halnf mPosFactor = 0.2f; // (center-of-holder - content-start) / content-size + halnf mSizeFactor = 0.3f; + + private: // TODO : make params static + halnf mRounding = 5; + halnf mHandlePadding = 0; + + RGBA mBGColor = { 0.1, 0.1, 0.1, 0.0 }; + RGBA mHandleColor = { 0.2, 0.2, 0.2, 1 }; + RGBA mHandleHoverColor = { 0.3, 0.3, 0.3, 1 }; + RGBA mHandleSlideColor = { 0.5, 0.5, 0.5, 1 }; + + private: // cache + RectF mHandleRect; + }; + + class ScrollableWidget : public Widget { + public: + ScrollableWidget(); + }; +} \ No newline at end of file