Scrolling layout

This commit is contained in:
IlyaShurupov 2024-10-17 16:46:49 +03:00
parent 03b2b5d038
commit 047d67c4fa
14 changed files with 288 additions and 35 deletions

View file

@ -122,7 +122,7 @@ namespace tp {
pos.y += dy;
}
Rect<Type>& scaleFromCenter(tp::halnf fac, bool add = false) {
Rect<Type>& shrinkFromCenter(tp::halnf fac, bool add = false) {
if (add) {
pos += fac;
size -= fac * 2;
@ -260,20 +260,20 @@ namespace tp {
Vec2<Type> center() const { return pos + size / 2.f; }
// splits by Factor Horizontally returning Left rect
Rect<Type> splitByFactorHL(halnf factor) {
Rect<Type> splitByFactorHL(halnf factor) const {
return { x, y, size.x * factor, size.y };
}
Rect<Type> splitByFactorHR(halnf factor) {
Rect<Type> splitByFactorHR(halnf factor) const {
const auto abs = size.x * factor;
return { x + abs, y, size.x - abs, size.y };
}
Rect<Type> splitByFactorVT(halnf factor) {
Rect<Type> splitByFactorVT(halnf factor) const {
return { x, y, size.x, size.y * factor };
}
Rect<Type> splitByFactorVB(halnf factor) {
Rect<Type> splitByFactorVB(halnf factor) const {
const auto abs = size.y * factor;
return { x, y + abs, size.x, size.y - abs };
}

7
TODO
View file

@ -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

View file

@ -1,4 +1,5 @@
#include "ScrollableWidget.hpp"
#include "../../WidgetsNew/public/widgets/ScrollableWidget.hpp"
using namespace tp;

View file

@ -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 {

View file

@ -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);
}

View 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;
}

View file

@ -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;

View file

@ -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)];
}

View file

@ -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;
}

View 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;
}

View file

@ -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 };
};

View 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;
};
}

View file

@ -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;

View 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();
};
}