Scrolling layout
This commit is contained in:
parent
03b2b5d038
commit
047d67c4fa
14 changed files with 288 additions and 35 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
53
WidgetsNew/private/layouts/ScrollableLayout.cpp
Normal file
53
WidgetsNew/private/layouts/ScrollableLayout.cpp
Normal file
|
|
@ -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<ScrollableBarWidget*>(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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "DebugManager.hpp"
|
||||
|
||||
#include "DockLayout.hpp"
|
||||
#include "ScrollableLayout.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -91,6 +92,7 @@ int LayoutManager::getLayoutOrder(WidgetLayout* parent, WidgetLayout* child) con
|
|||
auto policyChild = parent->getSizePolicy()[mVertical];
|
||||
|
||||
if (dynamic_cast<DockLayout*>(parent)) return -1;
|
||||
// if (dynamic_cast<ScrollableLayout*>(parent)) return -1;
|
||||
|
||||
return sizePolicyDep[int(policyParent)][int(policyChild)];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
87
WidgetsNew/private/widgets/ScrollableWidget.cpp
Normal file
87
WidgetsNew/private/widgets/ScrollableWidget.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<SizePolicy> mSizePolicy = { SizePolicy::Fixed, SizePolicy::Fixed };
|
||||
Vec2<SizePolicy> mSizePolicy = { SizePolicy::Fixed, SizePolicy::Minimal };
|
||||
Vec2F mMinSize = { 50, 50 };
|
||||
Vec2F mMaxSize = { FLT_MAX / 2, FLT_MAX / 2 };
|
||||
};
|
||||
|
|
|
|||
22
WidgetsNew/public/layouts/ScrollableLayout.hpp
Normal file
22
WidgetsNew/public/layouts/ScrollableLayout.hpp
Normal file
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
57
WidgetsNew/public/widgets/ScrollableWidget.hpp
Normal file
57
WidgetsNew/public/widgets/ScrollableWidget.hpp
Normal file
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue