Replace Old Widgets

This commit is contained in:
IlyaShurupov 2024-10-18 10:56:42 +03:00
parent cce8f0e1bc
commit fcd5eb2d2a
81 changed files with 388 additions and 278 deletions

View file

@ -1,103 +0,0 @@
#include "Animations.hpp"
using namespace tp;
bool AnimValue::gInTransition = false;
halnf AnimValue::interpolate() const {
if (!mTimeAnim) {
return mVal;
}
auto dt = gCurrentTime - mTimeStart;
if (dt > mTimeAnim) dt = mTimeAnim;
auto t = (halnf) dt / (halnf) mTimeAnim;
t = (halnf) (0.511 + atan((t - 0.36) * 28) / 2.9);
t = clamp(t, 0.f, 1.f);
auto out = mValPrev + (mVal - mValPrev) * t;
return out;
}
AnimValue::AnimValue() {
mTimeStart = gCurrentTime;
}
void AnimValue::setAnimTime(halni time) { mTimeAnim = time; }
AnimValue::AnimValue(halnf val) {
mVal = val;
mValPrev = mVal;
mTimeStart = gCurrentTime;
}
bool AnimValue::inTransition() const {
auto timePassed = gCurrentTime - mTimeStart >= mTimeAnim;
return !timePassed;
}
void AnimValue::set(halnf val) {
if (!inTransition()) mValPrev = mVal;
if (val == mVal) return;
mValPrev = get();
mVal = val;
if (!inTransition()) {
mTimeStart = gCurrentTime;
}
}
halnf AnimValue::getTarget() const { return mVal; }
void AnimValue::setNoTransition(halnf val) {
mValPrev = val;
mVal = val;
mTimeStart = gCurrentTime - mTimeAnim;
}
halnf AnimValue::get() const {
if (inTransition()) {
gInTransition = true;
return interpolate();
}
return mVal;
}
void AnimValue::operator=(halnf val) {
setNoTransition(val);
}
AnimValue::operator halnf() const { return get(); }
RectF AnimRect::get() const { return { x.get(), y.get(), z.get(), w.get() }; }
RectF AnimRect::getTarget() const { return { x.getTarget(), y.getTarget(), z.getTarget(), w.getTarget() }; }
void AnimRect::setNoTransition(RectF rec) {
x.setNoTransition(rec.x);
y.setNoTransition(rec.y);
z.setNoTransition(rec.z);
w.setNoTransition(rec.w);
}
void AnimRect::setAnimTime(const halni time) {
x.setAnimTime(time);
y.setAnimTime(time);
z.setAnimTime(time);
w.setAnimTime(time);
}
void AnimRect::set(const RectF& in) {
x.set(in.x);
y.set(in.y);
z.set(in.z);
w.set(in.w);
}
AnimColor::AnimColor() : mColor() {}
RGBA AnimColor::get() const {
auto col = mColor.get();
return { col.x, col.y, col.z, col.w };
}
void AnimColor::set(const RGBA& col) { mColor.set(RectF(col.r, col.g, col.b, col.a)); }

View file

@ -1,46 +0,0 @@
#include "ButtonWidget.hpp"
using namespace tp;
ButtonWidget::ButtonWidget() {
this->setArea({ 0, 0, 100, 30 });
this->mChildWidgets.pushBack(&mLabel);
}
ButtonWidget::ButtonWidget(const std::string& label, const tp::RectF& aArea) {
this->setArea(aArea);
mLabel.mLabel = label;
this->mChildWidgets.pushBack(&mLabel);
}
bool ButtonWidget::isFired() { return this->isReleased(); }
void ButtonWidget::eventProcess(const Events&) {
mLabel.setArea(this->mArea);
if (isFired()) {
mCallback();
}
}
void ButtonWidget::eventDraw(Canvas& canvas) {
if (this->isHolding()) {
canvas.rect(this->mArea, pressedColor, rounding);
} else if (this->isFocus()) {
canvas.rect(this->mArea, hoveredColor, rounding);
} else {
canvas.rect(this->mArea, accentColor, rounding);
}
}
void ButtonWidget::setLabel(const std::string& string) { mLabel.mLabel = string; }
void ButtonWidget::eventUpdateConfiguration(WidgetManager& wm) {
wm.setActiveId("Button");
pressedColor = wm.getColor("Pressed", "Action");
hoveredColor = wm.getColor("Hovered", "Interaction");
accentColor = wm.getColor("Default", "Accent");
rounding = wm.getNumber("Rounding", "Rounding");
}

View file

@ -1,96 +0,0 @@
#include "CollapsableMenu.hpp"
using namespace tp;
CollapsableMenu::CollapsableMenu() {
this->mChildWidgets.pushBack(&mHeader);
this->mChildWidgets.pushBack(&mBody);
}
void CollapsableMenu::eventProcess(const Events&) {
if (mHeader.isReleased()) {
toggleCollapsed();
}
updateGeometry();
}
void CollapsableMenu::eventDraw(Canvas& canvas) {
if (mBorders) {
canvas.rect(this->mArea, mBorderColor, rounding);
canvas.rect(this->mArea.shrink(mBorderSize), mMenuColor, rounding);
} else {
canvas.rect(this->mArea, mMenuColor, rounding);
}
}
void CollapsableMenu::addWidgetToMenu(Widget* widget) {
mBody.addWidget(widget);
EventHandler ev;
mBody.procWrapper(ev, this->mArea);
updateGeometry();
}
void CollapsableMenu::setLabel(const std::string& string) { mHeader.mLabel = string; }
void CollapsableMenu::toggleCollapsed() { setCollapsed(!getCollapsed()); }
void CollapsableMenu::setCollapsed(bool collapsed) {
if (mLocked) return;
if (collapsed && !mCollapsed) mPrevHeight = this->mArea.size.y;
if (!collapsed && mCollapsed) this->mArea.size.y = mPrevHeight;
mCollapsed = collapsed;
}
bool CollapsableMenu::getCollapsed() const { return mCollapsed; }
void CollapsableMenu::updateGeometry() {
mHeader.setArea(getHeaderRect());
mBody.mEnable = !mCollapsed;
if (mCollapsed) {
this->mArea.size.y = headerHeight;
} else {
if (mAdjustHeight) {
this->mArea.size.y = headerHeight + getBodyRect().size.y + mPadding * 2;
}
mBody.setArea(getBodyRect());
}
}
RectF CollapsableMenu::getHeaderRect() {
RectF out = { this->mArea.pos, { this->mArea.size.x, headerHeight } };
if (mBorders) out = out.shrink(mPadding);
return out;
}
RectF CollapsableMenu::getBodyRect() {
RectF out = { Vec2F{ this->mArea.pos.x, this->mArea.pos.y + headerHeight },
Vec2F{ this->mArea.size.x, this->mArea.size.y - headerHeight - mPadding } };
out.size.y -= mBorderSize * 2;
if (mAdjustHeight) out.size.y = mBody.getContentSize();
if (mBody.getContentSize() && mBorders) {
out = out.shrink(mPadding);
out.size.y += mPadding * 2 + 1;
}
return out;
}
void CollapsableMenu::eventUpdateConfiguration(WidgetManager& wm) {
wm.setActiveId("CollapsableMenu");
headerHeight = wm.getNumber("HeaderHeight", 35);
mMenuColor = wm.getColor("MenuColor", RGBA(0, 0, 0, 1.f));
rounding = wm.getNumber("Rounding", "Rounding");
mBorderColor = wm.getColor("BorderColor", RGBA(0.16, 0.16, 0.16, 1.f));
mBorderSize = wm.getNumber("BorderSize", 2.f);
mPadding = wm.getNumber("Padding", "Padding");
}

View file

@ -1,47 +0,0 @@
#include "FloatingLayoutWidget.hpp"
using namespace tp;
FloatingLayoutWidget::FloatingLayoutWidget() = default;
void FloatingLayoutWidget::eventProcess(const tp::Events& events) {
updateActiveWindow(events);
}
void FloatingLayoutWidget::updateActiveWindow(const tp::Events& events) {
mIsPassThrough = true;
for (auto childNode = this->mChildWidgets.firstNode(); childNode; childNode = childNode->next) {
auto child = childNode->data;
if (child->mArea.isInside(events.getPointer())) {
mIsPassThrough = false;
}
}
if (events.isPressed(InputID::MOUSE1)) {
Widget* activeChild = nullptr;
for (auto childNode = this->mChildWidgets.firstNode(); childNode; childNode = childNode->next) {
auto child = childNode->data;
if (child->mArea.isInside(events.getPointer())) {
mChildWidgets.detach(childNode);
mChildWidgets.pushFront(childNode);
child->mHandlesEvents = true;
activeChild = child;
break;
}
}
if (activeChild) {
for (auto child : this->mChildWidgets) {
if (activeChild != child.data() && !child->mIsDocked) child->mHandlesEvents = false;
}
}
}
}
bool FloatingLayoutWidget::handlesEvent() const { return !mIsPassThrough; }

View file

@ -1,89 +0,0 @@
#include "FloatingWidget.hpp"
using namespace tp;
FloatingWidget::FloatingWidget() {
this->mArea = { 0, 0, 300, 300 };
this->mAdjustHeight = false;
}
void FloatingWidget::eventProcess(const Events& events) {
mActionStartRelativePos = events.getPointerPrev() - this->mArea.pos;
checkFloating(events);
if (mResizable) checkResizing(events);
CollapsableMenu::eventProcess(events);
}
void FloatingWidget::eventDraw(Canvas& canvas) {
CollapsableMenu::eventDraw(canvas);
if (!this->getCollapsed() && mResizable) {
auto rect = getResizeHandle();
canvas.rect(rect, mResizeHandleColor, 0);
canvas.circle(rect.pos - this->mBorderSize, rect.w, this->mMenuColor);
}
}
void FloatingWidget::eventUpdateConfiguration(WidgetManager& wm) {
CollapsableMenu::eventUpdateConfiguration(wm);
wm.setActiveId("FloatingWidget");
mResizeHandleSize = wm.getNumber("ResizeHandleSize", 15);
mResizeHandleColor = wm.getColor("ResizeHandleColor", RGBA(0.16, 0.16, 0.16, 1.f));
}
void FloatingWidget::checkFloating(const Events& events) {
mDropped = false;
if (this->mHeader.isHolding() && events.getPointerDelta().length2() > 4) {
mFloating = true;
}
if (mFloating && this->mHeader.isReleased()) {
mFloating = false;
this->mHeader.clearEvents();
mDropped = true;
}
if (mFloating) {
auto relativePos = events.getPointer() - this->mArea.pos;
this->mArea.pos += relativePos - mActionStartRelativePos;
}
}
void FloatingWidget::checkResizing(const Events& events) {
if (this->getCollapsed()) return;
if (events.isPressed(InputID::MOUSE1) && getResizeHandle().isInside(events.getPointer())) {
mResizing = true;
}
if (events.isReleased(InputID::MOUSE1)) {
mResizing = false;
}
if (mResizing) {
auto relativePos = events.getPointer() - this->mArea.pos;
this->mArea.size += relativePos - mActionStartRelativePos;
}
if (!this->mCollapsed) {
this->mArea.size.clamp(mMinSize, { FLT_MAX, FLT_MAX });
}
}
RectF FloatingWidget::getResizeHandle() {
auto size = Vec2F(mResizeHandleSize);
auto pos = this->mArea.pos + this->mArea.size - size;
return { pos, size };
}
bool FloatingWidget::isFloating() const { return mFloating; }
void FloatingWidget::stopFloating() {
mFloating = false;
}

View file

@ -1,328 +0,0 @@
#include "GridLayoutWidget.hpp"
using namespace tp;
DockWidget::DockWidget() {
mSideWidgets[0].side = LEFT;
mSideWidgets[1].side = TOP;
mSideWidgets[2].side = RIGHT;
mSideWidgets[3].side = BOTTOM;
}
void DockWidget::addSideWidget(Widget* widget, Side side) {
if (sideExists(side)) return;
auto& sideWidget = mSideWidgets[side];
sideWidget.widget = widget;
for (auto& order : mSideWidgets) {
if (order.order == -1) {
order.order = side;
break;
}
}
sideWidget.hidden = false;
mChildWidgets.pushBack(widget);
widget->mIsDocked = true;
}
void DockWidget::removeSideWidget(Side side) {
if (!sideExists(side)) return;
bool removed = false;
for (ualni i = 0; i < 3; i++) {
if (mSideWidgets[i].order == side) {
removed = true;
}
if (removed) {
swapV(mSideWidgets[i].order, mSideWidgets[i + 1].order);
}
}
mSideWidgets[3].order = -1;
auto widget = mSideWidgets[side].widget;
widget->mIsDocked = false;
mChildWidgets.removeNode(mChildWidgets.find(widget));
mSideWidgets[side].widget = nullptr;
}
void DockWidget::setCenterWidget(Widget* widget) {
mChildWidgets.removeNode(mChildWidgets.find(mCenterWidget));
mCenterWidget = widget;
mChildWidgets.pushBack(mCenterWidget);
}
void DockWidget::toggleHiddenState(DockWidget::Side side) {
if (!sideExists(side)) return;
mSideWidgets[side].hidden = !mSideWidgets[side].hidden;
}
void DockWidget::eventProcess(const tp::Events& events) {
calculateSideAreas();
calculateResizeHandles();
// calculateHeaderAreas();
handlePreview(events);
handleResizeEvents(events);
updateChildSideWidgets();
}
void DockWidget::eventDraw(Canvas& canvas) {
canvas.rect(this->mArea, mBackgroundColor, 0);
for (auto& sideWidget : mSideWidgets) {
if (!isSideVisible(sideWidget.side)) continue;
auto& handle = sideWidget.resizeHandle;
if (handle.active) {
canvas.rect(handle.area.shrink(mPadding / 1.5f), mResizeHandleColorActive, 0);
} else if (handle.hover) {
canvas.rect(handle.area.shrink(mPadding / 1.5f), mResizeHandleColorHovered, 0);
}
}
for (auto& sideWidget : mSideWidgets) {
if (!isSideVisible(sideWidget.side)) continue;
canvas.rect(sideWidget.headerArea, mResizeHandleColorActive, 0);
}
}
void DockWidget::eventDrawOver(Canvas& canvas) {
if (!mPreview) return;
if (mPreviewSide != NONE) canvas.rect(mPreviewArea.shrink(mPadding * 2), mPreviewColor, mRounding);
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.widget) continue;
canvas.rect(sideWidget.previewHandleArea, mPreviewColor, mRounding);
}
}
void DockWidget::calculateSideAreas() {
auto startArea = this->mArea;
for (auto& sideWidget : mSideWidgets) {
const auto side = sideWidget.order;
if (side == -1) break;
if (!isSideVisible(Side(side))) continue;
bool vertical = side == TOP || side == BOTTOM;
bool opposite = side == BOTTOM || side == RIGHT;
auto& sideSize = mSideWidgets[side].absoluteSize;
auto factor = sideSize / startArea.size[vertical];
if (opposite) factor = factor * -1 + 1;
auto& area = mSideWidgets[side].area;
if (!vertical) {
const auto first = startArea.splitByFactorHL(factor);
const auto second = startArea.splitByFactorHR(factor);
area = side == LEFT ? first : second;
startArea = side == LEFT ? second : first;
} else {
const auto first = startArea.splitByFactorVT(factor);
const auto second = startArea.splitByFactorVB(factor);
area = side == TOP ? first : second;
startArea = side == TOP ? second : first;
}
area = area.shrink(mPadding);
}
mCenterArea = startArea.shrink(mPadding);
}
void DockWidget::calculateResizeHandles() {
RectF rec;
if (isSideVisible(LEFT)) {
auto& side = mSideWidgets[LEFT];
rec = { side.area.p4(), { mPadding * 2, side.area.size.y } };
side.resizeHandle = { rec, 0, mCenterArea.p3().x };
}
if (isSideVisible(RIGHT)) {
auto& side = mSideWidgets[RIGHT];
rec = { side.area.p1(), { mPadding * 2, side.area.size.y } };
rec.x -= mPadding * 2;
side.resizeHandle = { rec, 0, (this->mArea.p3() - mCenterArea.p1()).x };
}
if (isSideVisible(TOP)) {
auto& side = mSideWidgets[TOP];
rec = { side.area.p2(), { side.area.size.x, mPadding * 2 } };
side.resizeHandle = { rec, 0, mCenterArea.p2().y };
}
if (isSideVisible(BOTTOM)) {
auto& side = mSideWidgets[BOTTOM];
rec = { side.area.p1(), { side.area.size.x, mPadding * 2 } };
rec.y -= mPadding * 2;
side.resizeHandle = { rec, 0, (this->mArea.p3() - mCenterArea.p1()).y };
}
}
void DockWidget::handleResizeEvents(const Events& events) {
for (auto& sideWidget : mSideWidgets) {
auto& sideSize = sideWidget.absoluteSize;
auto& resizeHandle = sideWidget.resizeHandle;
if (resizeHandle.end < mSideSizePadding * 2) {
sideSize = resizeHandle.end / 2.f;
} else {
sideSize = clamp(sideSize, resizeHandle.start + mSideSizePadding, resizeHandle.end - mSideSizePadding);
}
}
for (auto& sideWidget : mSideWidgets) {
sideWidget.resizeHandle.hover = false;
if (sideWidget.resizeHandle.area.isInside(events.getPointerPrev())) {
sideWidget.resizeHandle.hover = true;
}
}
for (auto& sideWidget : mSideWidgets) {
sideWidget.resizeHandle.active = false;
}
if (mPreview) return;
auto resizeSideWidget = [&events](SideWidgetData& sideWidget, Vec2F deltaVec) {
halnf delta = deltaVec[(sideWidget.side == TOP || sideWidget.side == BOTTOM)];
if (sideWidget.side == BOTTOM || sideWidget.side == RIGHT) delta *= -1;
sideWidget.absoluteSize += delta;
sideWidget.resizeHandle.active = true;
};
if (events.isDown(InputID::MOUSE1)) {
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.resizeHandle.area.isInside(events.getPointerPrev())) {
resizeSideWidget(sideWidget, events.getPointerDelta());
}
}
}
if (events.isDown(InputID::LEFT_ALT)) {
const auto pointer = events.getPointerPrev();
if (!mCenterArea.isInside(pointer)) return;
if (events.isPressed(InputID::MOUSE1)) {
for (auto i : { 0, 1 }) {
const auto step = mCenterArea.size[i] / 3.f;
const auto rec1 = mCenterArea.pos[i];
const auto pos = pointer[i];
if (pos > rec1 && pos < rec1 + step) resizeType[i] = 0;
if (pos > rec1 + step && pos < rec1 + step * 2) resizeType[i] = 1;
if (pos > rec1 + step * 2) resizeType[i] = 2;
}
} else if (events.isDown(InputID::MOUSE1)) {
const auto deltaVec = events.getPointerDelta();
if (resizeType[0] == 0) resizeSideWidget(mSideWidgets[LEFT], deltaVec);
if (resizeType[1] == 0) resizeSideWidget(mSideWidgets[TOP], deltaVec);
if (resizeType[0] == 2) resizeSideWidget(mSideWidgets[RIGHT], deltaVec);
if (resizeType[1] == 2) resizeSideWidget(mSideWidgets[BOTTOM], deltaVec);
}
}
}
void DockWidget::updateChildSideWidgets() {
// Update Child Widgets
{
for (ualni i = 0; i < 4; i++) {
if (!sideExists(Side(i))) continue;
auto widget = mSideWidgets[i].widget;
if (!isSideVisible(Side(i))) {
widget->mEnable = false;
} else {
widget->setArea(mSideWidgets[i].area);
widget->mEnable = true;
}
}
if (mCenterWidget) mCenterWidget->setArea(mCenterArea);
}
// update depth order
/*
if (mChildWidgets.size() > 1) {
for (auto sideChild : mSideWidgets) {
if (!sideChild) continue;
for (auto childNode = mChildWidgets.firstNode(); childNode; childNode = childNode->next) {
if (childNode->data == sideChild) {
mChildWidgets.detach(childNode);
mChildWidgets.pushBack(childNode);
break;
}
}
}
}
*/
}
void DockWidget::calculateHeaderAreas() {
for (ualni i = 0; i < 4; i++) {
if (!isSideVisible(Side(i))) continue;
auto& area = mSideWidgets[i].area;
const auto factor = mHeaderSize / area.size.y;
mSideWidgets[i].headerArea = area.splitByFactorVT(factor);
area = area.splitByFactorVB(factor);
mSideWidgets[i].headerArea.size.y -= mPadding;
}
}
bool DockWidget::isSideVisible(DockWidget::Side side) {
return sideExists(side) && !mSideWidgets[side].hidden;
}
bool DockWidget::sideExists(DockWidget::Side side) { return mSideWidgets[side].widget; }
ualni DockWidget::getVisibleSidesSize() {
ualni out = 0;
for (ualni i = 0; i < 4; i++) {
if (isSideVisible(Side(i))) out++;
}
return out;
}
DockWidget::Side DockWidget::getPreviewSide() { return mPreviewSide; }
void DockWidget::handlePreview(const Events& events) {
if (!mPreview) {
mPreviewSide = NONE;
return;
}
const halnf factor = 0.3;
const halnf handleFactor = 0.1;
const auto handleSize = min(mCenterArea.size.x, mCenterArea.size.y) * handleFactor;
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.widget) continue;
switch (sideWidget.side) {
case TOP: sideWidget.area = mCenterArea.splitByFactorVT(factor); break;
case BOTTOM: sideWidget.area = mCenterArea.splitByFactorVB(1 - factor); break;
case LEFT: sideWidget.area = mCenterArea.splitByFactorHL(factor); break;
case RIGHT: sideWidget.area = mCenterArea.splitByFactorHR(1 - factor); break;
default: break;
}
sideWidget.area = sideWidget.area.shrink(mPadding * 2);
sideWidget.previewHandleArea = sideWidget.area.getSizedFromCenter({ handleSize, handleSize });
}
mPreviewArea = {};
mPreviewSide = NONE;
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.widget) continue;
if (sideWidget.previewHandleArea.isInside(events.getPointer())) {
mPreviewArea = sideWidget.area;
mPreviewSide = sideWidget.side;
}
}
}

View file

@ -1,17 +0,0 @@
#include "LabelWidget.hpp"
using namespace tp;
LabelWidget::LabelWidget() { this->mArea = { 0, 0, 100, 30 }; }
void LabelWidget::eventDraw(Canvas& canvas) {
canvas.text(mLabel.c_str(), this->mArea, fontSize, Canvas::LC, padding, fontColor);
}
void LabelWidget::eventUpdateConfiguration(WidgetManager& wm) {
wm.setActiveId("Label");
fontSize = wm.getNumber("Size", "FontSize");
padding = wm.getNumber("Padding", "Padding");
fontColor = wm.getColor("Default", "Front");
}

View file

@ -0,0 +1,96 @@
#include <algorithm>
#include "Layout.hpp"
#include "Widget.hpp"
using namespace tp;
const RectF& WidgetLayout::getArea() const {
return mWidget->mAreaCache;
}
RectF WidgetLayout::getAnimatedArea() const {
return mWidget->getArea();
}
void WidgetLayout::setArea(const RectF& area) {
mWidget->setAreaCache(area);
}
Widget* WidgetLayout::parent() const { return mWidget->mParent; }
const std::vector<Widget*>& WidgetLayout::children() const { return mWidget->mChildren; }
const Vec2F& WidgetLayout::getMinSize() { return mMinSize; }
void WidgetLayout::setMinSize(const Vec2F& size) { mMinSize = size; }
const Vec2<SizePolicy>& WidgetLayout::getSizePolicy() const { return mSizePolicy; }
void WidgetLayout::setSizePolicy(SizePolicy x, SizePolicy y) { mSizePolicy = { x, y }; }
RangeF WidgetLayout::pickRange(const RangeF& current, const RangeF& children, const RangeF& parent, bool vertical) const {
RangeF out;
switch (mSizePolicy[vertical]) {
case SizePolicy::Fixed: out = current; break;
case SizePolicy::Expanding: out = parent; break;
case SizePolicy::Minimal: out = children; break;
}
out = clampRange(out, children, parent, vertical);
return out;
}
void WidgetLayout::clampMinMaxSize() {
auto current = getArea();
current.size.clamp(mMinSize, mMaxSize);
setArea(current);
}
RangeF WidgetLayout::clampRange(const RangeF& current, const RangeF& child, const RangeF& parent, bool vertical) const {
auto out = current;
if (!children().empty()) {
auto clampedChild = child;
clampedChild.clamp(parent);
out.clamp(clampedChild, parent);
} else {
out.clamp(parent);
}
// clamp min max sizes
auto len = clamp(current.size(), mMinSize[vertical], mMaxSize[vertical]);
if (len != current.size()) {
out.resizeFromCenter(len);
}
return out;
}
RectF WidgetLayout::getChildrenEnclosure() const {
RectF out;
if (children().empty()) {
out = { getArea().center(), { 0, 0 } };
} else {
out = children().front()->getLayout()->getArea();
for (auto child : children()) {
out.expand(child->getLayout()->getArea());
}
out.pos += getArea().pos;
}
return out;
}
RectF WidgetLayout::getAvailableChildArea() const {
return getArea().relative();
}
RectF WidgetLayout::getParentEnclosure() const {
DEBUG_ASSERT(parent())
if (!parent()) return { { 0, 0 }, mMaxSize };
auto out = parent()->getLayout()->getAvailableChildArea();
return out;
}

View file

@ -0,0 +1,155 @@
#include "RootWidget.hpp"
#include "BasicLayout.hpp"
#include "OverlayLayout.hpp"
#include <algorithm>
using namespace tp;
WidgetLayout* WidgetManagerInterface::defaultLayout(Widget* widget) {
return new BasicLayout(widget);
}
RootWidget::RootWidget() {
setDebug("root", RGBA(1));
addChild(&mRoot);
mRoot.addChild(&mPopups);
mRoot.setLayout(new OverlayLayout(&mRoot));
dynamic_cast<BasicLayout*>(mPopups.getLayout())->setLayoutPolicy(LayoutPolicy::Passive);
}
void RootWidget::setRootWidget(Widget* widget) {
mRoot.removeChild(mUserRoot);
mRoot.addChild(widget);
widget->bringToBack();
mUserRoot = widget;
}
void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
mRoot.setArea(screenArea);
if (!gDebugWidget.update(this, *events)) return;
// construct hierarchy tree of widgets to process
mUpdateManager.updateTreeToProcess(&mRoot);
updateAnimations();
updateAreaCache(&mRoot, true);
// update all events and call all event processing callbacks
mUpdateManager.processWidgets(&mRoot, *events);
updateAreaCache(&mRoot, true);
// update widget sizes base on individual size policies
mLayoutManager.adjust(&mRoot);
updateAreaCache(&mRoot, false);
// trigger some widgets by moise pointer
mUpdateManager.handleFocusChanges(&mRoot, *events);
// check triggered widgets for removal
mUpdateManager.clean();
}
bool RootWidget::needsUpdate() const {
if (gDebugWidget.isRedrawAlways()) return true;
return mUpdateManager.isPendingUpdates();
}
void RootWidget::drawFrame(Canvas& canvas) {
canvas.rect(mRoot.getAreaT(), RGBA(0, 0, 0, 1));
drawRecursion(canvas, &mRoot, { 0, 0 });
gDebugWidget.drawDebug(this, canvas);
}
void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) {
if (!active->mFlags.get(ENABLED)) return;
canvas.setOrigin(pos);
canvas.pushClamp({ pos, active->getArea().size });
if (canvas.getClampedArea().size.length2() > EPSILON) {
active->draw(canvas);
for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) {
drawRecursion(canvas, child->data, pos + child->data->getArea().pos);
}
}
canvas.setOrigin(pos);
canvas.popClamp();
active->drawOverlay(canvas);
}
void RootWidget::setWidgetArea(Widget& widget, const RectF& rect) {
widget.mArea.setTargetRect(rect);
widget.mArea.endAnimation();
}
void RootWidget::updateAnimations() {
dfs(&mRoot, [](Widget* widget) {
widget->updateAnimations();
});
}
void RootWidget::updateAreaCache(Widget* iter, bool read) {
if (!iter || !iter->isUpdate()) return;
if (read) {
iter->mAreaCache = iter->getAreaT();
} else {
iter->setArea(iter->mAreaCache);
iter->mArea.updateCurrentRect();
}
for (auto child : iter->mChildren) {
if (read) {
child->mAreaCache = child->getAreaT();
} else {
child->setArea(child->mAreaCache);
child->mArea.updateCurrentRect();
}
}
for (auto child : iter->mDepthOrder) {
updateAreaCache(child.data(), read);
}
}
void RootWidget::updateWidget(Widget* widget, const char* reason) {
DEBUG_ASSERT(reason)
mUpdateManager.scheduleUpdate(widget, reason);
}
void RootWidget::openPopup(Widget* widget) {
auto relativeArea = widget->getAreaT();
for (auto iter = widget->mParent; iter && iter->mParent; iter = iter->mParent) {
relativeArea.pos += iter->getAreaT().pos;
}
setWidgetArea(*widget, relativeArea);
mPopups.addChild(widget);
mUpdateManager.lockFocus(widget);
}
void RootWidget::closePopup(Widget* widget) {
mPopups.removeChild(widget);
mUpdateManager.freeFocus(widget);
}
void RootWidget::lockFocus(Widget* widget) { mUpdateManager.lockFocus(widget); }
void RootWidget::freeFocus(Widget* widget) { mUpdateManager.freeFocus(widget); }
bool RootWidget::isDebug() const { return gDebugWidget.isDebug(); }

View file

@ -1,179 +0,0 @@
#include "ScrollableWidget.hpp"
#include "../../WidgetsNew/public/widgets/ScrollableWidget.hpp"
using namespace tp;
ScrollBarWidget::ScrollBarWidget() = default;
void ScrollBarWidget::eventProcess(const Events& events) {
auto area = getHandle();
mHovered = getHandleHandle().isInside(events.getPointer());
if (mSizeFraction > 1.f) {
mPositionFraction = 0;
return;
}
if (events.getScrollY() != 0 && mArea.isInside(events.getPointer())) {
auto offset = events.getScrollY() < 0 ? 1.0f : -1.0f;
mPositionFraction += mSizeFraction * offset * 0.3f;
mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction);
}
if (events.isPressed(InputID::MOUSE1) && area.isInside(events.getPointer())) {
mIsScrolling = true;
} else if (events.isReleased(InputID::MOUSE1)) {
mIsScrolling = false;
}
if (mIsScrolling) {
tp::halnf pos = events.getPointer().y;
pos = (pos - area.y - mSizeFraction * area.w / 2.f) / area.w;
mPositionFraction = tp::clamp(pos, 0.f, 1.f - mSizeFraction);
}
mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction);
}
void ScrollBarWidget::eventDraw(Canvas& canvas) {
auto area = getHandle();
if (mSizeFraction > 1.f) return;
// if (!areaParent.isOverlap(getHandle())) return;
tp::RGBA col = mHandleColor;
if (mIsScrolling) {
col = mScrollingColor;
} else if (mHovered) {
col = mHoveredColor;
}
canvas.rect(area, mDefaultColor, mRounding);
canvas.rect(getHandleHandle(), col, mRounding);
}
RectF ScrollBarWidget::getHandleHandle() const {
auto area = getHandle();
auto sliderSize = tp::clamp(area.w * mSizeFraction, mMinSize * 2, area.w);
auto diffSize = sliderSize - area.w * mSizeFraction;
return { area.x, area.y + (area.w - diffSize) * mPositionFraction, area.z, sliderSize };
}
RectF ScrollBarWidget::getViewport() const {
if (mSizeFraction > 1.f) {
return this->mArea;
}
return { this->mArea.x, this->mArea.y, this->mArea.z - mHandleSize, this->mArea.w };
}
RectF ScrollBarWidget::getHandle() const {
return { this->mArea.x + this->mArea.z - mHandleSize + mPadding,
this->mArea.y + mPadding,
mHandleSize - mPadding * 2,
this->mArea.w - mPadding * 2 };
}
void ScrollBarWidget::eventUpdateConfiguration(WidgetManager& wm) {
wm.setActiveId("Scrollbar");
mDefaultColor = wm.getColor("Default", "Base");
mHandleColor = wm.getColor("Handle", "Accent");
mHoveredColor = wm.getColor("Hovered", "Interaction");
mScrollingColor = wm.getColor("Scrolling", "Action");
mPadding = wm.getNumber("Padding", "Padding");
mHandleSize = wm.getNumber("HandleSize", 20.f);
mMinSize = wm.getNumber("MinSize", 20.f);
mRounding = wm.getNumber("Rounding", "Rounding");
}
ScrollableWindow::ScrollableWindow() {
this->mChildWidgets.pushBack(&mScroller);
this->mChildWidgets.pushBack(&mContentWidget);
}
ScrollableWindow::~ScrollableWindow() = default;
void ScrollableWindow::eventProcess(const Events& events) {
List<Widget*>& content = mContentWidget.mChildWidgets;
// to account all changed geometry of child widgets
for (auto widget : content) {
widget->procWrapper(events, mContentWidget.mVisibleArea);
}
updateContents(content);
updateContentSize(content);
const auto padding = mPadding;
mScroller.mSizeFraction = mContentWidget.mArea.w / mContentSize;
mScroller.setArea(this->mArea);
mContentWidget.setArea(mScroller.getViewport());
if (mScroller.mSizeFraction > 1.f) {
setOffset(content, 0);
} else {
setOffset(content, (-mScroller.mPositionFraction) * mContentSize);
}
for (auto widget : content) {
widget->setArea({ mContentWidget.mArea.x + padding,
mContentWidget.mArea.y + widget->mArea.y,
mScroller.getViewport().z - padding * 2,
widget->mArea.w });
}
}
void ScrollableWindow::addWidget(Widget* widget) {
mContentWidget.mChildWidgets.pushBack(widget);
List<Widget*>& content = mContentWidget.mChildWidgets;
updateContents(content);
updateContentSize(content);
}
void ScrollableWindow::clearContent() { mContentWidget.mChildWidgets.removeAll(); }
List<Widget*>& ScrollableWindow::getContent() { return mContentWidget.mChildWidgets; }
void ScrollableWindow::eventUpdateConfiguration(WidgetManager& wm) {
wm.setActiveId("ScrollableWidget");
mPadding = wm.getNumber("Padding", "Padding");
}
[[nodiscard]] halnf ScrollableWindow::getContentSize() const { return mContentSize; }
void ScrollableWindow::updateContents(List<Widget*>& contentWidgets) {
if (!contentWidgets.size()) {
return;
}
const halnf offset = contentWidgets.first()->mArea.y + mPadding;
halnf start = 0;
for (auto widget : contentWidgets) {
widget->mArea.y = start;
start += widget->mArea.w + mPadding;
}
for (auto widget : contentWidgets) {
widget->mArea.y += offset;
}
}
void ScrollableWindow::updateContentSize(List<Widget*>& contentWidgets) {
mContentSize = 0;
if (contentWidgets.size()) {
mContentSize = contentWidgets.last()->mArea.y - contentWidgets.first()->mArea.y;
mContentSize += contentWidgets.last()->mArea.w;
mContentSize += 2 * mPadding;
}
}
void ScrollableWindow::setOffset(List<Widget*>& contentWidgets, const halnf offset) {
if (!contentWidgets.size()) return;
auto newOffset = offset - contentWidgets.first()->mArea.y + mPadding;
for (auto widget : contentWidgets) {
widget->mArea.y += newOffset;
}
}

View file

@ -1,62 +0,0 @@
#include "SliderWidget.hpp"
using namespace tp;
SliderWidget::SliderWidget() = default;
void SliderWidget::eventProcess(const Events& events) {
if (this->isPressed()) {
mIsSliding = true;
} else if (events.isReleased(InputID::MOUSE1)) {
mIsSliding = false;
}
if (mIsSliding) {
mFactor = (events.getPointer().x - this->mArea.x - handleSize / 2.f) / (this->mArea.z - handleSize);
}
mFactor = tp::clamp(mFactor, 0.f, 1.f);
}
void SliderWidget::eventDraw(Canvas& canvas) {
canvas.rect(this->mArea, handleColor, rounding);
canvas.rect(this->mArea.shrink(borderSize), defaultColor, rounding);
canvas.rect(getHandle(), handleColor, rounding);
}
RectF SliderWidget::getHandle() const {
const auto left = this->mArea.x + (this->mArea.z - handleSize) * mFactor;
return { left, this->mArea.y, handleSize, this->mArea.w };
}
void SliderWidget::eventUpdateConfiguration(WidgetManager& wm) {
wm.setActiveId("Slider");
defaultColor = wm.getColor("Default", "Base");
handleColor = wm.getColor("Handle", RGBA(0.3f, 0.3f, 0.3f, 1.f));
borderSize = wm.getNumber("BorderSize", 2);
handleSize = wm.getNumber("HandleSize", 15.f);
rounding = wm.getNumber("Rounding", "Rounding");
}
NamedSliderWidget::NamedSliderWidget(const char* name) {
mLabel.mLabel = name;
this->mArea = { 0, 0, 100, 30 };
this->mChildWidgets.pushBack(&mSlider);
this->mChildWidgets.pushBack(&mLabel);
}
void NamedSliderWidget::eventProcess(const Events& events) {
const auto widthFirst = this->mArea.z * mFactor;
const auto widthSecond = this->mArea.z * (1.f - mFactor);
RectF rec = this->mArea;
rec.size.x = widthFirst;
mLabel.setArea(rec);
rec.pos.x += widthFirst;
rec.size.x = widthSecond;
mSlider.setArea(rec);
}

View file

@ -1,57 +0,0 @@
#include "TextInputWidget.hpp"
#include "imgui.h"
#include "imgui_internal.h"
using namespace tp;
TextInputWidget::TextInputWidget() = default;
void TextInputWidget::eventDraw(Canvas&) {
nChanged = false;
const auto col = mAccentColor;
const auto colSel = mHoveredColor;
ImGui::GetStyle().Colors[ImGuiCol_FrameBg] = { col.r, col.g, col.b, col.a };
ImGui::GetStyle().Colors[ImGuiCol_TextSelectedBg] = { colSel.r, colSel.g, colSel.b, colSel.a };
ImGui::SetNextWindowPos({ this->mArea.x, this->mArea.y });
ImGui::SetNextWindowSize({ this->mArea.z, this->mArea.w });
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0, 0 });
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, mRounding * 1.5f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { mPadding, mPadding });
// ImGui::PushID((int) alni(this));
ImGui::Begin(
mId.c_str(),
0,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoResize
);
if (mMultiline) {
if (ImGui::InputTextMultiline("input", mBuff, mMaxBufferSize, { this->mArea.z, this->mArea.w })) {
mValue = mBuff;
nChanged = true;
}
} else {
if (ImGui::InputTextEx("input", mId.c_str(), mBuff, mMaxBufferSize, { this->mArea.z, this->mArea.w }, 0)) {
mValue = mBuff;
nChanged = true;
}
}
ImGui::End();
ImGui::PopStyleVar(3);
}
void TextInputWidget::eventUpdateConfiguration(WidgetManager& wm) {
wm.setActiveId("TextInput");
mAccentColor = wm.getColor("Accent", "Accent");
mBaseColor = wm.getColor("Base", "Base");
mRounding = wm.getNumber("Rounding", "Rounding");
mHoveredColor = wm.getColor("Hovered", "Accent");
mPadding = wm.getNumber("Padding", "Padding");
}

152
Widgets/private/Widget.cpp Normal file
View file

@ -0,0 +1,152 @@
#include "Widget.hpp"
#include <algorithm>
using namespace tp;
Widget::Widget() {
mArea.setTargetRect({ 100, 100, 10, 10 });
setLayout(WidgetManagerInterface::defaultLayout(this));
mArea.endAnimation();
mFlags.set(ENABLED, true);
}
Widget::~Widget() {
delete mLayout;
}
void Widget::addChild(Widget* child, bool front) {
if (auto node = mDepthOrder.find(child)) {
node->data->bringToFront();
return;
}
if (child->mParent) {
child->mParent->removeChild(child);
}
mChildren.push_back(child);
if (front) {
mDepthOrder.pushFront(child);
} else {
mDepthOrder.pushBack(child);
}
child->mParent = this;
triggerWidgetUpdate("add child");
child->triggerWidgetUpdate("new parent");
}
void Widget::removeChild(Widget* child) {
auto node = mDepthOrder.find(child);
if (!node) return;
mDepthOrder.removeNode(node);
mChildren.erase(std::remove(mChildren.begin(), mChildren.end(), child), mChildren.end());
triggerWidgetUpdate("removed child");
child->triggerWidgetUpdate("parent changed");
}
void Widget::clear() {
mChildren.clear();
mDepthOrder.removeAll();
}
void Widget::endAnimations() { mArea.endAnimation(); }
bool Widget::processesEvents() const { return false; }
void Widget::updateAnimations() { mArea.updateCurrentRect(); }
bool Widget::needsNextFrame() const { return !mArea.shouldEndTransition(); }
void Widget::bringToFront() {
if (!mParent) return;
auto& order = mParent->mDepthOrder;
auto node = order.find(this);
DEBUG_ASSERT(node)
order.detach(node);
order.pushFront(node);
}
void Widget::bringToBack() {
if (!mParent) return;
auto& order = mParent->mDepthOrder;
auto node = order.find(this);
DEBUG_ASSERT(node)
order.detach(node);
order.pushBack(node);
}
void Widget::mouseEnter() { mFlags.set(IN_FOCUS, true); }
void Widget::mouseLeave() { mFlags.set(IN_FOCUS, false); }
bool Widget::propagateEventsToChildren() const { return true; }
void Widget::setLayout(tp::WidgetLayout* layout) {
delete mLayout;
mLayout = layout;
triggerWidgetUpdate("chane layout");
}
WidgetLayout* Widget::getLayout() { return mLayout; }
const WidgetLayout* Widget::getLayout() const { return mLayout; }
WidgetManagerInterface* Widget::getRoot() {
Widget* iter = mParent;
while (iter && iter->mParent) {
iter = iter->mParent;
}
return dynamic_cast<WidgetManagerInterface*>(iter);
}
void Widget::setAreaCache(const tp::RectF& area) { mAreaCache = area; }
void Widget::setArea(const RectF& area) {
if (mArea.getTargetRect() == area) return;
mArea.setTargetRect(area);
triggerWidgetUpdate("new area");
}
RectF Widget::getArea() const { return mArea.getCurrentRect(); }
RectF Widget::getAreaT() const { return mArea.getTargetRect(); }
RectF Widget::getRelativeArea() const { return { {}, mArea.getCurrentRect().size }; }
RectF Widget::getRelativeAreaT() const { return { {}, mArea.getTargetRect().size }; }
void Widget::setDebug(const char* name, RGBA col) {
mDebug.id = name;
mDebug.col = col;
}
void Widget::setSizePolicy(SizePolicy x, SizePolicy y) {
getLayout()->setSizePolicy(x, y);
}
void Widget::triggerWidgetUpdate(const char* reason) {
if (auto root = getRoot()) {
root->updateWidget(this, reason);
}
}
void Widget::openPopup(Widget* widget) {
addChild(widget);
getRoot()->openPopup(widget);
}
void Widget::closePopup(Widget* widget) {
getRoot()->closePopup(widget);
}
void Widget::lockFocus() {
getRoot()->lockFocus(this);
}
void Widget::freeFocus() {
getRoot()->freeFocus(this);
}

View file

@ -0,0 +1,10 @@
#include "WidgetApplication.hpp"
#include "imgui.h"
using namespace tp;
void WidgetApplication::debugUI() {
ImGui::Text("Proc ms : %i", (int) mProcTime);
ImGui::SameLine(); ImGui::Text("Draw ms : %i", (int) mDrawTime);
}

View file

@ -1,140 +0,0 @@
#include "WidgetBase.hpp"
using namespace tp;
Widget::Widget() { mArea = { 0, 0, 100, 100 }; }
Widget::~Widget() = default;
void Widget::procWrapper(const Events& events, const RectF& parentArea) {
if (!mEnable) return;
checkVisibility(events, parentArea);
if (!mVisible) return;
checkFocus(events);
checkClicked(events);
if (mHandlesEvents) {
for (auto child : mChildWidgets) {
child->procWrapper(events, mVisibleArea);
}
eventProcess(events);
}
}
void Widget::drawWrapper(Canvas& canvas) {
if (!mEnable || !mVisible) return;
eventDraw(canvas);
// draw child widgets
canvas.pushClamp(this->mArea);
for (auto child = mChildWidgets.lastNode(); child; child = child->prev) {
child->data->drawWrapper(canvas);
}
canvas.popClamp();
eventDrawOver(canvas);
}
void Widget::updateConfigWrapper(WidgetManager& wm) {
wm.setActiveId("Global");
eventUpdateConfiguration(wm);
for (auto child : mChildWidgets) {
child->updateConfigWrapper(wm);
}
}
void Widget::eventProcess(const Events& events) {}
void Widget::eventDraw(Canvas& canvas) {}
void Widget::eventDrawOver(Canvas& canvas) {}
void Widget::eventUpdateConfiguration(WidgetManager& wm) {}
void Widget::eventVisible(const Events& events) {}
void Widget::eventNotVisible(const Events& events) {}
void Widget::eventFocusEnter(const Events& events) {}
void Widget::eventFocusLeave(const Events& events) {}
void Widget::eventPressed(const Events& events) {}
void Widget::eventReleased(const Events& events) {}
void Widget::checkVisibility(const Events& events, const RectF& parentArea) {
const bool currentVisibility = parentArea.isOverlap(getArea());
parentArea.calcIntersection(mArea, mVisibleArea);
if (currentVisibility != mVisible) {
if (currentVisibility) eventVisible(events);
else eventNotVisible(events);
}
mVisible = currentVisibility;
if (!mVisible) {
mHolding = false;
mPressed = false;
mReleased = false;
}
}
void Widget::checkFocus(const Events& events) {
const bool currentFocus = mVisibleArea.isInside(events.getPointerPrev());
if (currentFocus != mInFocus) {
if (currentFocus) eventFocusEnter(events);
else eventFocusLeave(events);
}
mInFocus = currentFocus;
if (!mInFocus) {
mHolding = false;
mPressed = false;
mReleased = false;
}
}
void Widget::checkClicked(const Events& events) {
mPressed = false;
mReleased = false;
if (!mInFocus) return;
if (mHolding) {
if (events.isReleased(InputID::MOUSE1)) {
eventReleased(events);
mReleased = true;
mHolding = false;
}
} else {
if (events.isPressed(InputID::MOUSE1) && mVisibleArea.isInside(events.getPointer())) {
eventPressed(events);
mHolding = true;
mPressed = true;
}
}
}
void Widget::setEnable(bool enable) { mEnable = enable; }
void Widget::setVisible(bool visible) { mVisible = visible; }
void Widget::setArea(const RectF& area) { mArea = area; }
const RectF& Widget::getArea() const { return mArea; }
bool Widget::isFocus() const { return mInFocus; }
bool Widget::isPressed() const { return mPressed; }
bool Widget::isReleased() const { return mReleased; }
bool Widget::isHolding() const { return mHolding; }
void Widget::clearEvents() {
mReleased = false;
mPressed = false;
mHolding = false;
}

View file

@ -1,74 +0,0 @@
#include "WidgetManager.hpp"
using namespace tp;
WidgetManager::WidgetManager() { initGlobalParameters(); }
WidgetManager::~WidgetManager() { mConfigurations.removeAll(); }
const RGBA& WidgetManager::getColor(const std::string& parameterId, const RGBA& defaultValue) {
WidgetConfig& config = getWidgetConfig(mActiveId);
Parameter& parameter = getParameter(config, parameterId, Parameter(defaultValue));
return parameter.color;
}
const RGBA& WidgetManager::getColor(const std::string& parameterId, const char* globalRef) {
WidgetConfig& config = getWidgetConfig(mActiveId);
Parameter& parameter = getParameter(config, parameterId, Parameter(globalRef, Parameter::COL));
return parameter.color;
}
halnf WidgetManager::getNumber(const std::string& parameterId, halnf defaultValue) {
WidgetConfig& config = getWidgetConfig(mActiveId);
Parameter& parameter = getParameter(config, parameterId, Parameter(defaultValue));
return parameter.value;
}
halnf WidgetManager::getNumber(const std::string& parameterId, const char* globalRef) {
WidgetConfig& config = getWidgetConfig(mActiveId);
Parameter& parameter = getParameter(config, parameterId, Parameter(globalRef, Parameter::VAL));
return parameter.value;
}
void WidgetManager::setActiveId(const std::string& id) { mActiveId = id; }
WidgetConfig& WidgetManager::getWidgetConfig(const std::string& id) {
auto idx = mConfigurations.presents(id);
if (idx) return mConfigurations.getSlotVal(idx);
mConfigurations.put(id, {});
return mConfigurations.get(id);
}
WidgetManager::Parameter& WidgetManager::getParameter(WidgetConfig& config, const std::string& id, const Parameter& defaultValue) {
auto idx = config.mParameters.presents(id);
if (!idx) {
config.mParameters.put(id, defaultValue);
}
Parameter& parameter = config.mParameters.get(id);
if (parameter.globalReference) {
return mGlobalConfig.mParameters.get(parameter.globalId);
} else {
return parameter;
}
}
void WidgetManager::initGlobalParameters() {
auto& params = mGlobalConfig.mParameters;
params.put("FontSize", Parameter(15.f));
params.put("FontSizeDim", Parameter(12.f));
params.put("Rounding", Parameter(5.f));
params.put("Padding", Parameter(5.f));
params.put("HandleSize", Parameter(5.f));
params.put("Background", Parameter(RGBA{ 0.03f, 0.03f, 0.03f, 1.f }));
params.put("Base", Parameter(RGBA{ 0.07f, 0.07f, 0.07f, 1.f }));
params.put("Accent", Parameter(RGBA{ 0.13f, 0.13f, 0.13f, 1.f }));
params.put("Interaction", Parameter(RGBA{ 0.33f, 0.33f, 0.3f, 1.f }));
params.put("Action", Parameter(RGBA{ 0.44f, 0.44f, 0.4f, 1.f }));
params.put("Front", Parameter(RGBA{ 1.f, 1.f, 1.f, 1.f }));
params.put("FrontDim", Parameter(RGBA{ 0.7f, 0.7f, 0.7f, 1.f }));
}

View file

@ -1,73 +0,0 @@
#include "WorkspaceWidget.hpp"
using namespace tp;
WorkspaceWidget::WorkspaceWidget() {
this->mChildWidgets.pushBack(&mFloatingLayer);
this->mChildWidgets.pushBack(&mDockSpace);
}
void WorkspaceWidget::eventProcess(const Events& events) {
mDockSpace.setArea(this->mArea);
mFloatingLayer.setArea(this->mArea);
mDockSpace.mPreview = false;
mDockSpace.mHandlesEvents = !mFloatingLayer.handlesEvent();
for (auto floatingChild : mFloatingLayer.mChildWidgets) {
auto widget = dynamic_cast<FloatingWidget*>(floatingChild.data());
if (!widget) continue;
if (widget->mDropped) {
auto side = mDockSpace.getPreviewSide();
if (side != DockWidget::NONE) {
mFloatingLayer.mChildWidgets.removeNode(mFloatingLayer.mChildWidgets.find(widget));
widget->setCollapsed(false);
widget->stopFloating();
mDockSpace.addSideWidget(widget, side);
}
}
if (widget->isFloating()) {
mDockSpace.mHandlesEvents = true;
mDockSpace.mPreview = true;
}
}
for (auto& dockedChild : mDockSpace.mSideWidgets) {
auto widget = dynamic_cast<FloatingWidget*>(dockedChild.widget);
if (!widget) continue;
if (widget->isFloating()) {
mDockSpace.removeSideWidget(dockedChild.side);
mFloatingLayer.mChildWidgets.pushBack(widget);
widget->mArea.pos = events.getPointer() - 20;
widget->mArea.size = mDefaultFloatSize;
widget->mResizable = true;
// widget->mBorders = true;
}
}
// toggle hide state
if (events.isDown(InputID::LEFT_ALT)) {
if (events.isPressed(InputID::N1)) {
mDockSpace.toggleHiddenState(DockWidget::LEFT);
} else if (events.isPressed(InputID::N2)) {
mDockSpace.toggleHiddenState(DockWidget::TOP);
} else if (events.isPressed(InputID::N3)) {
mDockSpace.toggleHiddenState(DockWidget::RIGHT);
} else if (events.isPressed(InputID::N4)) {
mDockSpace.toggleHiddenState(DockWidget::BOTTOM);
}
}
for (auto& dockedChild : mDockSpace.mSideWidgets) {
auto widget = dynamic_cast<FloatingWidget*>(dockedChild.widget);
if (!widget) continue;
widget->setCollapsed(false);
widget->mResizable = false;
// widget->mBorders = false;
}
}

View file

@ -0,0 +1,149 @@
#include "BasicLayout.hpp"
#include "Widget.hpp"
#include <algorithm>
using namespace tp;
void BasicLayout::pickRect(bool vertical) {
auto current = getArea();
auto children = getChildrenEnclosure();
auto parent = getParentEnclosure();
children.shrinkFromCenter(-mLayoutMargin, true);
RectF newArea;
if (vertical) {
auto rangeY = pickRange(current.getRangeY(), children.getRangeY(), parent.getRangeY(), true);
newArea = RectF(current.getRangeX(), rangeY);
} else {
auto rangeX = pickRange(current.getRangeX(), children.getRangeX(), parent.getRangeX(), false);
newArea = RectF(rangeX, current.getRangeY());
}
setArea(newArea);
}
void BasicLayout::clampRect() {
DEBUG_ASSERT(0)
auto current = getArea();
auto children = getChildrenEnclosure();
auto parent = getParentEnclosure();
auto rangeX = clampRange(current.getRangeX(), children.getRangeX(), parent.getRangeX(), false);
auto rangeY = clampRange(current.getRangeY(), children.getRangeY(), parent.getRangeY(), true);
setArea(RectF(rangeX, rangeY));
}
void BasicLayout::updateLayout(bool vertical) {
if (children().empty()) return;
if (vertical) {
switch (mLayoutPolicy) {
case LayoutPolicy::Vertical: adjustLayout(true); break;
case LayoutPolicy::Passive:
case LayoutPolicy::Horizontal: break;
}
} else {
switch (mLayoutPolicy) {
case LayoutPolicy::Horizontal: adjustLayout(false); break;
case LayoutPolicy::Passive:
case LayoutPolicy::Vertical: break;
}
}
}
halnf BasicLayout::changeChildSize(tp::Widget* widget, halnf diff, bool vertical) {
auto prevSize = widget->getLayout()->getArea().size[vertical];
{
auto area = widget->getLayout()->getArea();
area.size[vertical] += diff;
widget->setAreaCache(area);
widget->getLayout()->clampMinMaxSize();
}
auto newSize = widget->getLayout()->getArea().size[vertical];
return newSize - prevSize;
}
void BasicLayout::adjustLayout(bool vertical) {
std::vector<std::pair<Widget*, bool>> contributors;
Vec2F contentSize = { 0, 0 };
Vec2F availableSize = getArea().size;
for (auto child : children()) {
child->getLayout()->pickRect(vertical);
if (child->getLayout()->getSizePolicy()[vertical] == SizePolicy::Expanding) {
contributors.emplace_back( child, true );
auto area = child->getLayout()->getArea();
area.size[vertical] = 0;
child->setAreaCache(area);
child->getLayout()->clampMinMaxSize();
}
contentSize += child->getLayout()->getArea().size;
}
availableSize -= mLayoutGap * ((halnf) children().size() - 1) + mLayoutMargin * 2;
auto diff = availableSize - contentSize;
// expand or contract as much as possible
while (!contributors.empty() && (diff[vertical] != 0)) {
auto quota = diff / (halnf) contributors.size();
for (auto& contributor : contributors) {
if (!contributor.second) continue;
// contributor.first->endAnimations();
auto contribution = changeChildSize(contributor.first, quota[vertical], vertical);
if (contribution == 0) {
contributor.second = false;
}
diff[vertical] -= contribution;
}
contributors.erase(
std::remove_if(contributors.begin(), contributors.end(), [](auto node) { return !node.second; }),
contributors.end()
);
}
// set opposite direction size
for (auto child : children()) {
// if (child->getLayout()->getSizePolicy()[!vertical] != SizePolicy::Minimal) {
auto area = child->getLayout()->getArea();
area.size[!vertical] = getArea().size[!vertical] - mLayoutMargin * 2;
child->setAreaCache(area);
// }
}
// set pos
halnf iterPos = mLayoutMargin;
for (auto child : children()) {
auto area = child->getLayout()->getArea();
area.pos[vertical] = iterPos;
area.pos[!vertical] = mLayoutMargin;
iterPos += area.size[vertical] + mLayoutGap;
child->setAreaCache(area);
// child->updateAnimations();
// child->triggerWidgetUpdate("layout changed");
}
}
RectF BasicLayout::getAvailableChildArea() const {
auto out = WidgetLayout::getAvailableChildArea();
return out.shrinkFromCenter(mLayoutMargin, true);
}

View file

@ -0,0 +1,290 @@
#include "DockLayout.hpp"
using namespace tp;
DockLayout::DockLayout(Widget* widget) :
WidgetLayout(widget) {
mSideWidgets[0].side = LEFT;
mSideWidgets[1].side = TOP;
mSideWidgets[2].side = RIGHT;
mSideWidgets[3].side = BOTTOM;
}
bool DockLayout::setCenterWidget(Widget* widget) {
if (mCenterWidget) return false;
mCenterWidget = widget;
return true;
}
bool DockLayout::dockWidget(Widget* widget, Side side) {
if (sideExists(side) || side == Side::NONE) return false;
auto& sideWidget = mSideWidgets[side];
sideWidget.widget = widget;
for (auto& order : mSideWidgets) {
if (order.order == -1) {
order.order = side;
break;
}
}
sideWidget.hidden = false;
sideWidget.areaBeforeDocking = widget->getArea();
return true;
}
bool DockLayout::undockWidget(Side side) {
if (!sideExists(side) || (side == Side::NONE)) return false;
bool removed = false;
for (ualni i = 0; i < 3; i++) {
if (mSideWidgets[i].order == side) {
removed = true;
}
if (removed) {
swapV(mSideWidgets[i].order, mSideWidgets[i + 1].order);
}
}
mSideWidgets[3].order = -1;
mSideWidgets[side].widget = nullptr;
return true;
}
void DockLayout::toggleWidgetVisibility(Side side) {
DEBUG_ASSERT(sideExists(side))
mSideWidgets[side].hidden = !mSideWidgets[side].hidden;
}
void DockLayout::calculateSideAreas() {
auto startArea = getArea().relative();
for (auto& sideWidget : mSideWidgets) {
const auto side = sideWidget.order;
if (side == -1) break;
if (!isSideVisible(Side(side))) continue;
bool vertical = side == TOP || side == BOTTOM;
bool opposite = side == BOTTOM || side == RIGHT;
auto& sideSize = mSideWidgets[side].absoluteSize;
auto factor = sideSize / startArea.size[vertical];
if (opposite) factor = factor * -1 + 1;
auto& area = mSideWidgets[side].area;
if (!vertical) {
const auto first = startArea.splitByFactorHL(factor);
const auto second = startArea.splitByFactorHR(factor);
area = side == LEFT ? first : second;
startArea = side == LEFT ? second : first;
} else {
const auto first = startArea.splitByFactorVT(factor);
const auto second = startArea.splitByFactorVB(factor);
area = side == TOP ? first : second;
startArea = side == TOP ? second : first;
}
area = area.shrink(mPadding);
}
mCenterArea = startArea.shrink(mPadding);
}
void DockLayout::calculateResizeHandles() {
RectF rec;
RectF area = getArea().relative();
for (auto& sideWidget : mSideWidgets) {
auto& sideSize = sideWidget.absoluteSize;
auto& resizeHandle = sideWidget.resizeHandle;
if (resizeHandle.end < mSideSizePadding * 2) {
sideSize = resizeHandle.end / 2.f;
} else {
sideSize = clamp(sideSize, resizeHandle.start + mSideSizePadding, resizeHandle.end - mSideSizePadding);
}
}
if (isSideVisible(LEFT)) {
auto& side = mSideWidgets[LEFT];
rec = { side.area.p4(), { mPadding * 2, side.area.size.y } };
side.resizeHandle.area = rec;
side.resizeHandle.start = 0;
side.resizeHandle.end = mCenterArea.p3().x;
}
if (isSideVisible(RIGHT)) {
auto& side = mSideWidgets[RIGHT];
rec = { side.area.p1(), { mPadding * 2, side.area.size.y } };
rec.x -= mPadding * 2;
side.resizeHandle.area = rec;
side.resizeHandle.start = 0;
side.resizeHandle.end = (area.p3() - mCenterArea.p1()).x;
}
if (isSideVisible(TOP)) {
auto& side = mSideWidgets[TOP];
rec = { side.area.p2(), { side.area.size.x, mPadding * 2 } };
side.resizeHandle.area = rec;
side.resizeHandle.start = 0;
side.resizeHandle.end = mCenterArea.p2().y;
}
if (isSideVisible(BOTTOM)) {
auto& side = mSideWidgets[BOTTOM];
rec = { side.area.p1(), { side.area.size.x, mPadding * 2 } };
rec.y -= mPadding * 2;
side.resizeHandle.area = rec;
side.resizeHandle.start = 0;
side.resizeHandle.end = (area.p3() - mCenterArea.p1()).y;
}
}
void DockLayout::updateChildSideWidgets() {
for (ualni i = 0; i < 4; i++) {
if (!sideExists(Side(i))) continue;
auto widget = mSideWidgets[i].widget;
if (!isSideVisible(Side(i))) {
// FIXME : widget->mEnable = false;
} else {
widget->setAreaCache(mSideWidgets[i].area);
// FIXME : widget->mEnable = true;
}
}
if (mCenterWidget) mCenterWidget->setAreaCache(mCenterArea);
}
void DockLayout::calculateHeaderAreas() {
for (ualni i = 0; i < 4; i++) {
if (!isSideVisible(Side(i))) continue;
auto& area = mSideWidgets[i].area;
const auto factor = mHeaderSize / area.size.y;
mSideWidgets[i].headerArea = area.splitByFactorVT(factor);
area = area.splitByFactorVB(factor);
mSideWidgets[i].headerArea.size.y -= mPadding;
}
}
ualni DockLayout::getVisibleSidesSize() const {
ualni out = 0;
for (ualni i = 0; i < 4; i++) {
if (isSideVisible(Side(i))) out++;
}
return out;
}
auto DockLayout::getSideFromWidget(Widget* widget) -> Side {
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.widget == widget) return sideWidget.side;
}
return DockLayout::NONE;
}
void DockLayout::updateLayout(bool vertical) {
for (auto child : children()) {
child->getLayout()->pickRect(vertical);
}
if (vertical) return;
adjustChildrenRect();
}
void DockLayout::adjustChildrenRect() {
calculateSideAreas();
calculateResizeHandles();
updateChildSideWidgets();
}
void DockLayout::updatePreviewSide(const Vec2F& pointer) {
const auto handleSize = min(mCenterArea.size.x, mCenterArea.size.y) * mHandleFactor;
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.widget) continue;
switch (sideWidget.side) {
case TOP: sideWidget.area = mCenterArea.splitByFactorVT(mHandleSplitFactor); break;
case BOTTOM: sideWidget.area = mCenterArea.splitByFactorVB(1 - mHandleSplitFactor); break;
case LEFT: sideWidget.area = mCenterArea.splitByFactorHL(mHandleSplitFactor); break;
case RIGHT: sideWidget.area = mCenterArea.splitByFactorHR(1 - mHandleSplitFactor); break;
default: break;
}
sideWidget.area = sideWidget.area.shrink(mPadding * 2);
sideWidget.previewHandleArea = sideWidget.area.getSizedFromCenter({ handleSize, handleSize });
}
mPreviewArea = {};
mPreviewSide = NONE;
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.widget) continue;
if (sideWidget.previewHandleArea.isInside(pointer)) {
mPreviewArea = sideWidget.area;
mPreviewSide = sideWidget.side;
}
}
}
DockLayout::Side DockLayout::getPreviewSide() {
return mPreviewSide;
}
void DockLayout::updateResizeHover(const Vec2F& pointer) {
for (auto& sideWidget : mSideWidgets) {
sideWidget.resizeHandle.hover = sideWidget.resizeHandle.area.isInside(pointer);
}
}
void DockLayout::updateResize(const Vec2F& pointerDelta) {
if (!mResizing) return;
// do the resizing
for (auto& sideWidget : mSideWidgets) {
if (!sideWidget.resizeHandle.active) continue;
halnf delta = pointerDelta[(sideWidget.side == TOP || sideWidget.side == BOTTOM)];
if (sideWidget.side == BOTTOM || sideWidget.side == RIGHT) {
delta *= -1;
}
sideWidget.absoluteSize += delta;
}
}
bool DockLayout::startResize(const Vec2F& pointer) {
for (auto& sideWidget : mSideWidgets) {
if (sideWidget.resizeHandle.hover) {
sideWidget.resizeHandle.active = true;
mResizing = true;
return true;
}
}
return false;
}
void DockLayout::endResize() {
for (auto& sideWidget : mSideWidgets) {
sideWidget.resizeHandle.active = false;
}
mResizing = false;
}
bool DockLayout::isSideVisible(DockLayout::Side side) const {
return sideExists(side) && !mSideWidgets[side].hidden;
}
bool DockLayout::sideExists(DockLayout::Side side) const {
return mSideWidgets[side].widget;
}
const std::vector<Widget*>& DockLayout::getDockedWidgets() {
mDockedWidgets.resize(5);
for (auto i = 0; i < 4; i++) {
mDockedWidgets[i] = mSideWidgets[i].widget;
}
mDockedWidgets[4] = mCenterWidget;
return mDockedWidgets;
}

View file

@ -0,0 +1,52 @@
#include "FloatingLayout.hpp"
#include "Widget.hpp"
using namespace tp;
void FloatingLayout::pickRect(bool vertical) {
if (mIsFloating) {
auto area = getArea();
if (mIsResizing) {
mPointerCurrent.clamp(mMinSize, mMaxSize);
area.size[vertical] = (mPointerCurrent + mHandleSize / 2.f)[vertical];
for (auto child : children()) {
child->triggerWidgetUpdate("floating menu resized");
}
} else if (mIsFloating) {
area.pos[vertical] += (mPointerCurrent - mPointerStart)[vertical];
}
setArea(area);
}
clampMinMaxSize();
}
RectF FloatingLayout::resizeHandleRect() {
auto area = getAnimatedArea().relative();
area.pos = area.p3() - mHandleSize;
area.size = mHandleSize;
area.shrink(mHandlePadding);
return area;
}
void FloatingLayout::startAction(const Vec2F& pointer) {
mPointerStart = pointer;
mIsFloating = true;
if (resizeHandleRect().isInside(mPointerStart)) {
mIsResizing = true;
}
}
void FloatingLayout::updateAction(const Vec2F& pointer) {
mPointerCurrent = pointer;
}
void FloatingLayout::endAction() {
mIsResizing = mIsFloating = false;
}

View file

@ -0,0 +1,29 @@
#include "OverlayLayout.hpp"
#include "Widget.hpp"
using namespace tp;
void OverlayLayout::updateLayout(bool vertical) {
if (vertical) return;
if (children().empty()) return;
for (auto child : children()) {
child->getLayout()->setArea(getArea().relative());
}
/*
if (children().empty()) return;
auto first = children().front();
first->getLayout()->setArea(getArea().relative());
for (auto child : children()) {
if (child == first) continue;
child->getLayout()->setArea(getArea().relative());
}
*/
}

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().relative(), 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

@ -0,0 +1,177 @@
#include "DebugManager.hpp"
#include "RootWidget.hpp"
#include "BasicLayout.hpp"
#include "imgui.h"
#include <sstream>
using namespace tp;
DebugManager tp::gDebugWidget;
#define LIST_SIZE { -FLT_MIN, 150 }
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 };
rootWidget->mRoot.setArea(area);
events.setEnableKeyEvents(true);
if (events.isPressed(InputID::K)) mDebugStopProcessing = !mDebugStopProcessing;
if (mDebugStopProcessing) return false;
if (auto widget = rootWidget->mUpdateManager.mInFocusWidget) {
if (auto lay = dynamic_cast<BasicLayout*>(widget->getLayout())) {
if (events.isPressed(InputID::V)) lay->setLayoutPolicy(LayoutPolicy::Vertical);
if (events.isPressed(InputID::H)) lay->setLayoutPolicy(LayoutPolicy::Horizontal);
auto sizing = lay->getSizePolicy();
if (events.isDown(InputID::X)) {
if (events.isPressed(InputID::S)) lay->setSizePolicy(SizePolicy::Minimal, sizing.y);
if (events.isPressed(InputID::E)) lay->setSizePolicy(SizePolicy::Expanding, sizing.y);
}
if (events.isDown(InputID::Y)) {
if (events.isPressed(InputID::S)) lay->setSizePolicy(sizing.x, SizePolicy::Minimal);
if (events.isPressed(InputID::E)) lay->setSizePolicy(sizing.x, SizePolicy::Expanding);
}
}
}
if (auto breakWidget = rootWidget->mUpdateManager.mInFocusWidget) {
if (events.isPressed(InputID::P)) mProcBreakpoints.insert(breakWidget);
if (events.isPressed(InputID::L)) mLayBreakpoints.insert(breakWidget);
}
}
return true;
}
void DebugManager::drawDebug(RootWidget* rootWidget, Canvas& canvas) {
mRootWidget = rootWidget;
if (!mDebug) {
return;
}
// ImGui::Checkbox("Draw debug", &mDebug);
ImGui::SameLine(); ImGui::Text("To Toggle processing press k");
auto& upd = rootWidget->mUpdateManager;
recursiveDraw(canvas, &rootWidget->mRoot, { 0, 0 }, 0);
ImGui::Text("Triggered: %i", (int) upd.mTriggeredWidgets.size());
ImGui::SameLine(); ImGui::Text("Processing: %i", upd.mDebugWidgetsToProcess);
ImGui::Checkbox("Stop processing", &mDebugStopProcessing);
ImGui::SameLine(); ImGui::Checkbox("Force new frames", &mDebugRedrawAlways);
ImGui::SameLine(); ImGui::Checkbox("Slow-mo", &mSlowMotion);
if (upd.mInFocusWidget) {
ImGui::Text("Under cursor");
{
if (ImGui::BeginListBox("##under_cursor", LIST_SIZE)) {
for (auto widget = upd.mInFocusWidget; widget && widget->mParent; widget = widget->mParent) {
widgetMenu(widget);
}
ImGui::EndListBox();
}
}
}
ImGui::Text("Triggered");
{
if (ImGui::BeginListBox("##triggered", LIST_SIZE)) {
for (auto widget : upd.mTriggeredWidgets) {
widgetMenu(widget.first);
}
ImGui::EndListBox();
}
}
drawLayoutOrder();
}
void DebugManager::recursiveDraw(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder) {
auto area = RectF{ pos, active->getAreaT().size };
if (active->isUpdate()) {
RGBA color = { 1, 0, 0, 1 };
canvas.frame(area, color);
canvas.text((active->mDebug.id + ":" + std::to_string(depthOrder)).c_str(), area, 22, Canvas::Align::LC, 2, color);
}
if (active->mFlags.get(Widget::IN_FOCUS)) {
if (active->isUpdate()) {
canvas.circle(pos + active->mDebug.pLocal, 5, active->mDebug.col);
canvas.circle(active->mDebug.pGlobal, 15, active->mDebug.col);
}
RGBA color = { 1, 0, 0, 0.3f };
canvas.debugCross(area, color);
}
int orderIdx = 0;
for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) {
recursiveDraw(canvas, child->data, pos + child->data->getAreaT().pos, orderIdx++);
}
}
void DebugManager::widgetMenu(Widget* widget) {
ImGui::PushID(widget);
if (ImGui::CollapsingHeader((widget->mDebug.id + std::to_string((long) widget)).c_str())) {
ImGui::Text("trigger reason: %s", widget->mDebug.triggerReason.c_str());
auto area = widget->getAreaT();
if (ImGui::InputFloat4("rect", &area.x)) {
widget->setArea(area);
}
auto layout = widget->getLayout();
ImGui::InputFloat2("min size", &layout->mMinSize.x);
ImGui::InputFloat2("max size", &layout->mMaxSize.x);
{
int sizePolicyX = int(widget->getLayout()->getSizePolicy().x);
int sizePolicyY = int(widget->getLayout()->getSizePolicy().y);
if (ImGui::Combo("Size Policy X", &sizePolicyX, "Fixed\0Expanding\0Minimal\0")) {
widget->setSizePolicy(SizePolicy(sizePolicyX), SizePolicy(sizePolicyY));
}
if (ImGui::Combo("Size Policy Y", &sizePolicyY, "Fixed\0Expanding\0Minimal\0")) {
widget->setSizePolicy(SizePolicy(sizePolicyX), SizePolicy(sizePolicyY));
}
}
if (auto pBasicLayout = dynamic_cast<BasicLayout*>(widget->getLayout())) {
int policy = int(pBasicLayout->mLayoutPolicy);
if (ImGui::Combo("Layout", &policy, "Passive\0Vertical\0Horizontal\0")) {
pBasicLayout->mLayoutPolicy = LayoutPolicy(policy);
}
}
}
ImGui::PopID();
}
void DebugManager::drawLayoutOrder() {
ImGui::Text("Layout Processing Order");
if (ImGui::BeginListBox("##layout_order", LIST_SIZE)) {
for (auto iter : mRootWidget->mLayoutManager.mLayOrder) {
ImGui::Text("%i %s (%s)", iter.second, iter.first->mDebug.id.c_str(), std::to_string((long) iter.first).c_str());
}
ImGui::EndListBox();
}
}

View file

@ -0,0 +1,98 @@
#include "LayoutManager.hpp"
#include "DebugManager.hpp"
#include "DockLayout.hpp"
#include "ScrollableLayout.hpp"
#include <algorithm>
using namespace tp;
void LayoutManager::adjust(Widget* root) {
for (auto i : IterRange(2)) {
mVertical = bool(i);
mDepGraph.clear();
mLayOrder.clear();
mRoots.clear();
findDependencies(root);
// TODO : implement better topological-sort
for (auto iterRoot : mRoots) {
topologicalSort(iterRoot);
}
adjustLayouts();
}
}
void LayoutManager::findDependencies(Widget* root) {
mDepGraph.insert({ root, {} });
Widget::dfs(root, [this](Widget* widget) { mDepGraph.insert({ widget, {} }); });
for (auto& [widget, deps] : mDepGraph) {
for (auto child : widget->mChildren) {
mDepGraph.insert({ child, {} });
if (auto depOrder = getLayoutOrder(widget->getLayout(), child->getLayout())) {
if (depOrder > 0) {
deps.depends.push_back(child);
mDepGraph[child].references++;
} else {
mDepGraph[child].depends.push_back(widget);
mDepGraph[widget].references++;
}
}
}
}
// find root
for (auto& node : mDepGraph) {
if (node.second.references == 0) {
mRoots.push_back(node.first);
}
}
}
void LayoutManager::topologicalSort(Widget* root, int depth) {
mDepGraph.at(root).depth = max(mDepGraph.at(root).depth, depth);
for (auto& dep : mDepGraph.at(root).depends) {
topologicalSort(dep, depth + 1);
}
}
void LayoutManager::adjustLayouts() {
for (auto& iter : mDepGraph) {
mLayOrder.emplace_back(iter.first, iter.second.depth);
}
std::sort(mLayOrder.begin(), mLayOrder.end(), [](auto first, auto second){
return first.second > second.second;
});
for (auto& [iter, _] : mLayOrder) {
iter->getLayout()->updateLayout(mVertical);
}
}
static int sizePolicyDep[3][3] = {
// f s e child
{ 1, 1, 1 }, // parent fixed
{ 1, 1, 1 }, // parent shrink
{ 1, 1, 1 }, // parent expand
};
int LayoutManager::getLayoutOrder(WidgetLayout* parent, WidgetLayout* child) const {
if (!parent || !child) return 0;
auto policyParent = parent->getSizePolicy()[mVertical];
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

@ -0,0 +1,211 @@
#include "UpdateManager.hpp"
#include "DebugManager.hpp"
using namespace tp;
void UpdateManager::processWidgets(Widget* root, EventHandler& events) {
events.setEnableKeyEvents(true);
events.setCursorOrigin({ 0, 0 });
processFocusItems(events);
events.setEnableKeyEvents(false);
events.setCursorOrigin({ 0, 0 });
processActiveTree(root, events, { 0, 0 });
}
void UpdateManager::clean() {
erase_if(mTriggeredWidgets, [](auto iter) {
auto widget = iter.first;
auto flag = iter.second;
if (!flag) return false;
// if (mWidgetsToProcess.find(widget) == mWidgetsToProcess.end()) return false;
widget->updateAnimations();
auto end = !widget->needsNextFrame();
if (end) {
widget->endAnimations();
widget->mDebug.triggerReason = "del";
}
return end;
});
}
void UpdateManager::scheduleUpdate(Widget* widget, const char* reason) {
widget->mDebug.triggerReason = reason;
mTriggeredWidgets.insert({ widget, false });
}
void UpdateManager::updateTreeToProcess(Widget* root) {
Widget::dfs(root, [](Widget* widget) {
widget->mFlags.set(Widget::NEEDS_UPDATE, false);
});
for (auto& [widget, flag] : mTriggeredWidgets) {
flag = true;
for (auto iter = widget; iter && iter->mParent; iter = iter->mParent) {
iter->mFlags.set(Widget::NEEDS_UPDATE, true);
}
}
mDebugWidgetsToProcess = 0;
Widget::dfs(root, [this](Widget*) {
mDebugWidgetsToProcess++;
});
}
void UpdateManager::getWidgetPath(Widget* widget, std::vector<Widget*>& out) {
if (!widget) return;
for (auto iter = widget; iter && iter->mParent; iter = iter->mParent) {
out.push_back(iter);
}
for (auto i = 0; i < out.size() / 2; i++) {
swapV(out[i], out[out.size() - i - 1]);
}
}
void UpdateManager::handleFocusChanges(Widget* root, EventHandler& events) {
auto prevFocus = mInFocusWidget;
events.setCursorOrigin({ 0, 0 });
mInFocusWidget = nullptr;
if (!mFocusLockWidget) {
findFocusWidget(root, &mInFocusWidget, events.getPointer());
} else {
mInFocusWidget = mFocusLockWidget;
}
// if (mInFocusWidget == prevFocus) return;
if (mInFocusWidget) scheduleUpdate(mInFocusWidget, "focus entered");
if (!mInFocusWidget && !prevFocus) return;
std::vector<Widget*> path2;
getWidgetPath(mInFocusWidget, path2);
size_t propLen2 = path2.size();
for (auto i = 0; i < path2.size(); i++) {
if (!path2[i]->propagateEventsToChildren()) {
propLen2 = i + 1;
break;
}
}
std::vector<Widget*> path1;
getWidgetPath(prevFocus, path1);
size_t propLen1 = path1.size();
for (auto i = 0; i < path1.size(); i++) {
if (!path1[i]->propagateEventsToChildren()) {
propLen1 = i + 1;
break;
}
}
int mostCommonIdx = 0;
if (!(path1.empty() || path2.empty())) {
while (path1[mostCommonIdx] == path2[mostCommonIdx] && mostCommonIdx < min(path1.size(), path2.size())) {
mostCommonIdx++;
}
}
mostCommonIdx--;
for (auto i = 0; i < path1.size(); i++) {
path1[i]->mFlags.set(Widget::IN_FOCUS, false);
if (i > mostCommonIdx && i < propLen1) path1[i]->mouseLeave();
}
for (auto i = 0; i < path2.size(); i++) {
path2[i]->mFlags.set(Widget::IN_FOCUS, true);
if (i > mostCommonIdx && i < propLen2) path2[i]->mouseEnter();
}
}
void UpdateManager::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer) {
if (!iter->mArea.getTargetRect().isInside(pointer) || !iter->mFlags.get(Widget::ENABLED)) return;
if (iter->processesEvents()) {
*focus = iter;
}
for (auto child = iter->mDepthOrder.lastNode(); child; child = child->prev) {
findFocusWidget(child->data, focus, pointer - iter->mArea.getTargetRect().pos);
}
}
void UpdateManager::processActiveTree(Widget* iter, EventHandler& events, Vec2F parent) {
if (!iter || !iter->isUpdate()) return;
auto current = parent + iter->getAreaT().pos;
if (!iter->mFlags.get(Widget::IN_FOCUS)) {
iter->mDebug.pGlobal = current;
events.setCursorOrigin(current);
procWidget(iter, events, false);
}
for (auto child : iter->mDepthOrder) {
processActiveTree(child.data(), events, current);
}
}
void UpdateManager::processFocusItems(EventHandler& events) {
if (!mInFocusWidget) return;
std::vector<Widget*> path;
getWidgetPath(mInFocusWidget, path);
size_t len = path.size();
for (auto i = 0; i < len; i++) {
if (!path[i]->propagateEventsToChildren()) {
len = i + 1;
break;
}
}
std::vector<Vec2F> widgetGlobalPos(path.size());
widgetGlobalPos[0] = 0;
for (auto widget = 0; widget < path.size() - 1; widget++) {
widgetGlobalPos[widget + 1] = widgetGlobalPos[widget] + path[widget + 1]->getAreaT().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]);
widget->mDebug.pGlobal = widgetGlobalPos[iter];
if (!eventsProcessed && widget->processesEvents()) {
procWidget(widget, events, true);
eventsProcessed = true;
} else {
procWidget(widget, events, false);
}
}
}
void UpdateManager::procWidget(Widget* widget, EventHandler& events, bool withEvents) {
events.setEnableKeyEvents(withEvents);
gDebugWidget.checkProcBreakPoints(widget);
widget->process(events);
}
void UpdateManager::lockFocus(tp::Widget* widget) {
mFocusLockWidget = widget;
}
void UpdateManager::freeFocus(tp::Widget* widget) {
// DEBUG_ASSERT(mFocusLockWidget == widget)
mFocusLockWidget = nullptr;
}

View file

@ -0,0 +1,28 @@
#include "AnimationTestWidget.hpp"
using namespace tp;
void AnimationTestWidget::process(const EventHandler& events) {
if (events.isDown(tp::InputID::MOUSE1)) {
mTestSpring.getStart().setTargetPosition(events.getPointer());
}
if (events.isDown(tp::InputID::MOUSE2)) {
mTestSpring.getEnd().setTargetPosition(events.getPointer());
}
mTestSpring.updateCurrentRect();
if (mTestSpring.shouldEndTransition()) {
mTestSpring.endAnimation();
}
}
void AnimationTestWidget::draw(Canvas& canvas) {
canvas.rect(getRelativeArea(), { 0, 0, 0, 1 }, 10);
canvas.rect(mTestSpring.getCurrentRect(), RGBA(1.f), 10);
}
bool AnimationTestWidget::needsNextFrame() const {
return Widget::needsNextFrame() || !mTestSpring.shouldEndTransition();
}

View file

@ -0,0 +1,138 @@
#include "DockWidget.hpp"
#include "FloatingWidget.hpp"
using namespace tp;
DockWidget::DockWidget() : Widget() {
setDebug("dock", { 1, 1, 1, 1 });
setLayout(new DockLayout(this));
}
void DockWidget::dockWidget(Widget* widget, DockLayout::Side side) {
if (layout()->dockWidget(widget, side)) {
addChild(widget);
for (auto child : layout()->getDockedWidgets()) {
if (child) child->bringToBack();
}
}
}
void DockWidget::undockWidget(DockLayout::Side side, bool restoreArea) {
if (layout()->sideExists(side) && side != DockLayout::NONE) {
auto widget = layout()->getSideWidget(side);
widget->bringToFront();
if (restoreArea) {
widget->setArea(layout()->getRectBeforeDocked(side));
} else {
// FIXME : widget->endAnimations();
}
layout()->undockWidget(side);
}
}
void DockWidget::setCenterWidget(Widget* widget) {
if (layout()->setCenterWidget(widget)) {
addChild(widget);
}
}
void DockWidget::toggleWidgetVisibility(DockLayout::Side side) {
if (layout()->sideExists(side)) {
auto widget = layout()->getSideWidget(side);
widget->setEnabled(!layout()->isSideVisible(side));
widget->bringToBack();
layout()->toggleWidgetVisibility(side);
}
}
void DockWidget::process(const EventHandler& events) {
// calculateHeaderAreas();
Widget* floater = nullptr;
for (auto child : mChildren) {
if (auto iter = dynamic_cast<FloatingWidget*>(child)) {
if (iter->isFloating()) {
floater = iter;
break;
}
}
}
if (floater) undockWidget(layout()->getSideFromWidget(floater), false);
if (floater) {
layout()->updatePreviewSide(events.getPointer());
}
if (mPreviewWidget && !floater) {
dockWidget(mPreviewWidget, layout()->getPreviewSide());
}
mPreviewWidget = floater;
if (!mPreviewWidget) {
layout()->updateResizeHover(events.getPointer());
if (events.isPressed(InputID::MOUSE1)) {
if (layout()->startResize(events.getPointer())) {
triggerWidgetUpdate("dock layout resizing");
}
} else if (events.isReleased(InputID::MOUSE1)) {
layout()->endResize();
}
if (layout()->isResizing()) {
layout()->updateResize(events.getPointerDelta());
}
}
}
bool DockWidget::propagateEventsToChildren() const {
return !layout()->isResizing();
}
bool DockWidget::needsNextFrame() const {
return Widget::needsNextFrame() || layout()->isResizing();
}
void DockWidget::draw(Canvas& canvas) {
canvas.rect(getRelativeArea(), mBackgroundColor, 0);
}
void DockWidget::drawSide(DockLayout::Side side, tp::Canvas& canvas) {
auto lay = layout();
if (lay->isSideVisible(side)) {
// header
canvas.rect(lay->getHeaderArea(side), mResizeHandleColorActive, 0);
// resize
if (lay->isResizing(side)) {
canvas.rect(lay->getResizeHandleArea(side).shrink(mPadding / 1.5f), mResizeHandleColorActive, 0);
} else if (lay->isResizeHandleHover(side)) {
canvas.rect(lay->getResizeHandleArea(side).shrink(mPadding / 1.5f), mResizeHandleColorHovered, 0);
}
} else {
// preview handles
if (mPreviewWidget) {
// preview active
if (lay->getPreviewSide() == side) {
canvas.rect(lay->getPreviewArea().shrink(mPadding * 2), mPreviewColor, mRounding);
} else {
canvas.rect(lay->getPreviewHandleArea(side), mPreviewColor, mRounding);
}
}
}
}
void DockWidget::drawOverlay(Canvas& canvas) {
drawSide(DockLayout::Side::LEFT, canvas);
drawSide(DockLayout::Side::RIGHT, canvas);
drawSide(DockLayout::Side::TOP, canvas);
drawSide(DockLayout::Side::BOTTOM, canvas);
}

View file

@ -0,0 +1,76 @@
#include "FloatingWidget.hpp"
#include "ScrollableLayout.hpp"
using namespace tp;
void FloatingWidget::process(const EventHandler& events) {
const auto pointer = events.getPointer();
if (getRelativeAreaT().isInside(pointer) && events.isPressed(InputID::MOUSE1)) {
layout()->startAction(pointer);
bringToFront();
lockFocus();
}
if (layout()->isFloating() && events.isReleased(InputID::MOUSE1)) {
layout()->endAction();
freeFocus();
}
layout()->updateAction(pointer);
}
void FloatingWidget::draw(Canvas& canvas) {
canvas.rect(layout()->resizeHandleRect(), RGBA(0.7f), 2);
canvas.rect(getRelativeArea(), RGBA(0.5f), 10);
}
bool FloatingWidget::processesEvents() const {
return true;
}
bool FloatingWidget::propagateEventsToChildren() const {
return !layout()->isFloating();
}
bool FloatingWidget::isFloating() const {
return layout()->isFloating();
}
bool FloatingWidget::needsNextFrame() const {
return Widget::needsNextFrame() || isFloating();
}
FloatingLayout* FloatingWidget::layout() { return dynamic_cast<FloatingLayout*>(Widget::getLayout()); }
const FloatingLayout* FloatingWidget::layout() const {
return dynamic_cast<const FloatingLayout*>(Widget::getLayout());
}
FloatingMenu::FloatingMenu() : FloatingWidget() {
setDebug("float menu", { 0.0, 0.9, 0.1, 0.7 });
// addChild(&mMenuLayout);
addChild(&mHeader);
addChild(&mBodyLayout);
mHeader.setText("Menu");
mHeader.setSizePolicy(SizePolicy::Expanding, SizePolicy::Minimal);
mBodyLayout.setSizePolicy(SizePolicy::Expanding, SizePolicy::Expanding);
mBodyLayout.setLayout(new ScrollableLayout(&mBodyLayout));
mBodyLayout.addChild(&mScrollBar);
mBodyLayout.addChild(&mContentWidget);
mContentWidget.setSizePolicy(SizePolicy::Minimal, SizePolicy::Minimal);
// getLayout()->setLayoutPolicy(LayoutPolicy::Vertical);
// mBodyLayout.getLayout()->setLayoutPolicy(LayoutPolicy::Vertical);
}
void FloatingMenu::setText(const std::string& text) {
mHeader.setText(text);
}

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

@ -0,0 +1,130 @@
#include "SimpleWidgets.hpp"
using namespace tp;
void LabelWidget::setText(const std::string& text) {
mText = text;
}
const std::string& LabelWidget::getText() const {
return mText;
}
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<void()>& action) {
mAction = action;
}
void ButtonWidget::setColor(const RGBA& in) {
mColor = in;
mColorAnimated.setTargetColor(mColor);
triggerWidgetUpdate("color changed");
}
void ButtonWidget::process(const EventHandler& eventHandler) {
if (getRelativeArea().isInside(eventHandler.getPointer())) {
if (eventHandler.isPressed(InputID::MOUSE1)) {
mAction();
}
}
}
void ButtonWidget::draw(Canvas& canvas) {
canvas.rect(getRelativeArea(), mColorAnimated.getCurrentColor(), mRounding);
LabelWidget::draw(canvas);
}
bool ButtonWidget::needsNextFrame() const {
return LabelWidget::needsNextFrame() || !mColorAnimated.shouldEndTransition();
}
void ButtonWidget::endAnimations() {
mColorAnimated.endAnimation();
LabelWidget::endAnimations();
}
void ButtonWidget::updateAnimations() {
mColorAnimated.updateCurrentRect();
LabelWidget::updateAnimations();
}
void ButtonWidget::mouseEnter() {
mColorAnimated.setTargetColor(mColor);
mColorAnimated.endAnimation();
mColorAnimated.setTargetColor(mColorHovered);
// mColorAnimated.updateCurrentRect();
triggerWidgetUpdate("button hovered");
}
void ButtonWidget::mouseLeave() {
mColorAnimated.setTargetColor(mColor);
//mColorAnimated.updateCurrentRect();
triggerWidgetUpdate("button out of focus");
}
void SliderWidget::process(const EventHandler& events) {
const auto pointer = events.getPointer();
switch (mState) {
case SLIDING:
if (events.isReleased(InputID::MOUSE1)) {
mState = IDLE;
freeFocus();
}
mFactor = ((pointer - mHandleSize / 2) / (getArea().relative().size - mHandleSize)).x;
mFactor = clamp(mFactor, 0.f, 1.f);
break;
case IDLE:
case HOVER:
mState = getHandleArea().isInside(pointer) ? HOVER : IDLE;
if (getRelativeAreaT().isInside(pointer) && events.isPressed(InputID::MOUSE1)) {
mState = SLIDING;
lockFocus();
}
break;
}
}
void SliderWidget::draw(Canvas& canvas) {
canvas.rect(getArea().relative(), mColorBG, mRounding);
switch (mState) {
case IDLE: canvas.rect(getHandleArea(), mColorIdle, mRounding); break;
case HOVER: canvas.rect(getHandleArea(), mColorHovered, mRounding); break;
case SLIDING: canvas.rect(getHandleArea(), mColorActive, mRounding); break;
}
}
RectF SliderWidget::getHandleArea() const {
auto area = getArea().relative();
const auto& size = area.size;
auto center = area.center();
auto tilt = -(mFactor - 0.5f) * 2;
area.pos.x = center.x - (tilt * ((size.x - mHandleSize) / 2)) - mHandleSize / 2;
area.size.x = mHandleSize;
return area;
}