From 9e7704a446899533369dd555176a64ec3fa39992 Mon Sep 17 00:00:00 2001 From: IlyaShurupov Date: Tue, 25 Jun 2024 23:27:21 +0300 Subject: [PATCH] New DockspaceWidget Stable --- Containers/public/List.hpp | 1 + Math/public/Rect.hpp | 21 +++ Widgets/examples/SimpleGUI.cpp | 25 +++- Widgets/private/ButtonWidget.cpp | 8 +- Widgets/private/DockspaceWidget.cpp | 223 +++++++++++++++++++++++++++- Widgets/public/ButtonWidget.hpp | 4 + Widgets/public/DockspaceWidget.hpp | 60 +++++++- Widgets/public/WidgetBase.hpp | 4 + 8 files changed, 339 insertions(+), 7 deletions(-) diff --git a/Containers/public/List.hpp b/Containers/public/List.hpp index fafad37..4d2e70e 100644 --- a/Containers/public/List.hpp +++ b/Containers/public/List.hpp @@ -207,6 +207,7 @@ namespace tp { void insert(TypeArg data, Index idx) { insert(newNode(data), idx); } void removeNode(Node* node) { + if (!node) return; detach(node); deleteNode(node); } diff --git a/Math/public/Rect.hpp b/Math/public/Rect.hpp index c1045fb..4705736 100644 --- a/Math/public/Rect.hpp +++ b/Math/public/Rect.hpp @@ -119,8 +119,10 @@ namespace tp { return *this; } + // pos Vec2 p1() { return pos; } + // pos + size Vec2 p3() { return pos + size; } Vec2 p2() { return { pos.x, pos.y + size.y }; } @@ -224,6 +226,25 @@ namespace tp { Vec2 center() { return pos + size / 2.f; } + // splits by Factor Horizontally returning Left rect + Rect splitByFactorHL(halnf factor) { + return { x, y, size.x * factor, size.y }; + } + + Rect splitByFactorHR(halnf factor) { + const auto abs = size.x * factor; + return { x + abs, y, size.x - abs, size.y }; + } + + Rect splitByFactorVT(halnf factor) { + return { x, y, size.x, size.y * factor }; + } + + Rect splitByFactorVB(halnf factor) { + const auto abs = size.y * factor; + return { x, y + abs, size.x, size.y - abs }; + } + public: union { Vec2 v1; diff --git a/Widgets/examples/SimpleGUI.cpp b/Widgets/examples/SimpleGUI.cpp index 5760da3..1eb39de 100644 --- a/Widgets/examples/SimpleGUI.cpp +++ b/Widgets/examples/SimpleGUI.cpp @@ -10,6 +10,27 @@ class SimpleGUI : public Application { public: SimpleGUI() { + // debug + + mGui.addSideWidget(&mButtons[0], DockSpaceWidget::BOTTOM); + mGui.addSideWidget(&mButtons[1], DockSpaceWidget::RIGHT); + + mGui.removeSideWidget(DockSpaceWidget::BOTTOM); + + mGui.addSideWidget(&mButtons[0], DockSpaceWidget::TOP); + mGui.addSideWidget(&mButtons[2], DockSpaceWidget::LEFT); + + mGui.removeSideWidget(DockSpaceWidget::TOP); + + mGui.addSideWidget(&mButtons[0], DockSpaceWidget::BOTTOM); + + mGui.addSideWidget(&mButtons[3], DockSpaceWidget::TOP); + mGui.setCenterWidget(&mButtons[4]); + + mButtons[4].mCallback = [&]() { mGui.toggleHiddenState(DockSpaceWidget::BOTTOM); }; + mButtons[0].mCallback = [&]() { mGui.toggleHiddenState(DockSpaceWidget::TOP); }; + mButtons[2].mCallback = [&]() { mGui.toggleHiddenState(DockSpaceWidget::RIGHT); }; + mButtons[3].mCallback = [&]() { mGui.toggleHiddenState(DockSpaceWidget::LEFT); }; } void processFrame(EventHandler* eventHandler) override { @@ -28,7 +49,9 @@ public: private: WidgetManager mWidgetManager; - SimpleWidget2 mGui; + DockSpaceWidget mGui; + + ButtonWidget mButtons[5]; }; int main() { diff --git a/Widgets/private/ButtonWidget.cpp b/Widgets/private/ButtonWidget.cpp index 35ba117..cc71543 100644 --- a/Widgets/private/ButtonWidget.cpp +++ b/Widgets/private/ButtonWidget.cpp @@ -16,7 +16,13 @@ ButtonWidget::ButtonWidget(const std::string& label, const tp::RectF& aArea) { bool ButtonWidget::isFired() { return this->isReleased(); } -void ButtonWidget::eventProcess(const Events&) { mLabel.setArea(this->mArea); } +void ButtonWidget::eventProcess(const Events&) { + mLabel.setArea(this->mArea); + + if (isFired()) { + mCallback(); + } +} void ButtonWidget::eventDraw(Canvas& canvas) { if (this->isHolding()) { diff --git a/Widgets/private/DockspaceWidget.cpp b/Widgets/private/DockspaceWidget.cpp index 25619c9..4518606 100644 --- a/Widgets/private/DockspaceWidget.cpp +++ b/Widgets/private/DockspaceWidget.cpp @@ -2,14 +2,90 @@ using namespace tp; -void DockSpaceWidget::eventProcess(const tp::Events& events) { +DockSpaceWidget::DockSpaceWidget() = default; +void DockSpaceWidget::addSideWidget(Widget* widget, Side side) { + if (mSideWidgets[side]) return; + mSideWidgets[side] = widget; + for (auto& order : mSideSplitOrder) { + if (order == -1) { + order = side; + break; + } + } + mChildWidgets.pushBack(widget); + widget->mIsDocked = true; + mSideHidden[side] = false; +} + +void DockSpaceWidget::removeSideWidget(Side side) { + if (!mSideWidgets[side]) return; + + bool removed = false; + for (ualni i = 0; i < 3; i++) { + if (mSideSplitOrder[i] == side) { + removed = true; + } + if (removed) { + mSideSplitOrder[i] = mSideSplitOrder[i + 1]; + } + } + mSideSplitOrder[3] = -1; + + auto widget = mSideWidgets[side]; + mSideWidgets[side] = nullptr; + widget->mIsDocked = false; + + mChildWidgets.removeNode(mChildWidgets.find(widget)); +} + +void DockSpaceWidget::setCenterWidget(Widget* widget) { + mChildWidgets.removeNode(mChildWidgets.find(mCenterWidget)); + mCenterWidget = widget; + mChildWidgets.pushBack(mCenterWidget); +} + +void DockSpaceWidget::toggleHiddenState(DockSpaceWidget::Side side) { + if (!isSideExists(side)) return; + mSideHidden[side] = !mSideHidden[side]; +} + +void DockSpaceWidget::eventProcess(const tp::Events& events) { + updateActiveWindow(events); + + calculateSideAreas(); + calculateResizeHandles(); + // calculateHeaderAreas(); + handleResizeEvents(events); + + updateChildSideWidgets(); +} + +void DockSpaceWidget::eventDraw(Canvas& canvas) { + canvas.rect(this->mArea, mBackgroundColor, 0); + + for (auto sizeHandle : mResizeHandles) { + if (sizeHandle->active) { + canvas.rect(sizeHandle->area.shrink(mPadding / 1.5f), mResizeHandleColorActive, 0); + } else if (sizeHandle->hover) { + canvas.rect(sizeHandle->area.shrink(mPadding / 1.5f), mResizeHandleColorHovered, 0); + } + } + + for (auto& header : mSideHeaderAreas) { + canvas.rect(header, mResizeHandleColorActive, 0); + } +} + +void DockSpaceWidget::updateActiveWindow(const tp::Events& events) { if (this->mChildWidgets.size() > 1 && events.isPressed(InputID::MOUSE1)) { Widget* activeChild = nullptr; for (auto childNode = this->mChildWidgets.firstNode(); childNode; childNode = childNode->next) { auto child = childNode->data; + + if (child->mIsDocked) continue; if (child->mArea.isInside(events.getPointer())) { mChildWidgets.detach(childNode); @@ -23,12 +99,151 @@ void DockSpaceWidget::eventProcess(const tp::Events& events) { if (activeChild) { for (auto child : this->mChildWidgets) { - if (activeChild != child.data()) child->mHandlesEvents = false; + if (activeChild != child.data() && !child->mIsDocked) child->mHandlesEvents = false; } } } } -void DockSpaceWidget::eventDraw(Canvas& canvas) { - canvas.rect(this->mArea, RGBA(0, 0, 0, 1), 0); +void DockSpaceWidget::calculateSideAreas() { + auto startArea = this->mArea; + for (ualni i = 0; i < 4; i++) { + const auto side = mSideSplitOrder[i]; + + if (side == -1) break; + if (mSideHidden[side]) continue; + + bool vertical = side == TOP || side == BOTTOM; + bool opposite = side == BOTTOM || side == RIGHT; + + const auto maxAbsoluteSize = startArea.size[vertical]; + const auto paddingFactor = mSideSizePadding / maxAbsoluteSize; + + mSideAbsoluteSize[side] = + clamp(mSideAbsoluteSize[side], maxAbsoluteSize * paddingFactor, maxAbsoluteSize * (1.f - paddingFactor)); + + auto factor = mSideAbsoluteSize[side] / startArea.size[vertical]; + if (opposite) factor = factor * -1 + 1; + + if (!vertical) { + const auto first = startArea.splitByFactorHL(factor); + const auto second = startArea.splitByFactorHR(factor); + mSideAreas[side] = side == LEFT ? first : second; + startArea = side == LEFT ? second : first; + } else { + const auto first = startArea.splitByFactorVT(factor); + const auto second = startArea.splitByFactorVB(factor); + mSideAreas[side] = side == TOP ? first : second; + startArea = side == TOP ? second : first; + } + + mSideAreas[side] = mSideAreas[side].shrink(mPadding); + } + + mCenterArea = startArea.shrink(mPadding); +} + +void DockSpaceWidget::calculateResizeHandles() { + mResizeHandles.clear(); + RectF rec; + + if (isSideVisible(LEFT)) { + rec = { mSideAreas[LEFT].p4(), { mPadding * 2, mSideAreas[LEFT].size.y } }; + mResizeHandles.append({ rec, LEFT, 0, mCenterArea.p3().x }); + } + if (isSideVisible(RIGHT)) { + rec = { mSideAreas[RIGHT].p1(), { mPadding * 2, mSideAreas[RIGHT].size.y } }; + rec.x -= mPadding * 2; + mResizeHandles.append({ rec, RIGHT, mCenterArea.p1().x, rec.p3().x }); + } + if (isSideVisible(TOP)) { + rec = { mSideAreas[TOP].p2(), { mSideAreas[TOP].size.x, mPadding * 2 } }; + mResizeHandles.append({ rec, TOP, 0, mCenterArea.p2().y }); + } + if (isSideVisible(BOTTOM)) { + rec = { mSideAreas[BOTTOM].p1(), { mSideAreas[BOTTOM].size.x, mPadding * 2 } }; + rec.y -= mPadding * 2; + mResizeHandles.append({ rec, BOTTOM, mCenterArea.p1().y, rec.p2().y }); + } +} + +void DockSpaceWidget::handleResizeEvents(const Events& events) { + for (auto resizeHandle : mResizeHandles) { + resizeHandle->hover = false; + if (resizeHandle->area.isInside(events.getPointerPrev())) { + resizeHandle->hover = true; + } + } + + if (events.isDown(InputID::MOUSE1)) { + for (auto resizeHandle : mResizeHandles) { + resizeHandle->active = false; + if (resizeHandle->area.isInside(events.getPointerPrev())) { + auto factorDeltaVec = events.getPointerDelta(); + halnf factorDelta = factorDeltaVec[(resizeHandle->side == TOP || resizeHandle->side == BOTTOM)]; + if (resizeHandle->side == BOTTOM || resizeHandle->side == RIGHT) factorDelta *= -1; + mSideAbsoluteSize[resizeHandle->side] += factorDelta; + resizeHandle->active = true; + } + } + } +} + +void DockSpaceWidget::updateChildSideWidgets() { + // Update Child Widgets + { + for (ualni i = 0; i < 4; i++) { + if (!isSideExists(Side(i))) continue; + + if (!isSideVisible(Side(i))) { + mSideWidgets[i]->mEnable = false; + } else { + mSideWidgets[i]->setArea(mSideAreas[i]); + mSideWidgets[i]->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 DockSpaceWidget::calculateHeaderAreas() { + for (ualni i = 0; i < 4; i++) { + if (!isSideVisible(Side(i))) continue; + const auto factor = mHeaderSize / mSideAreas[i].size.y; + mSideHeaderAreas[i] = mSideAreas[i].splitByFactorVT(factor); + mSideAreas[i] = mSideAreas[i].splitByFactorVB(factor); + + mSideHeaderAreas[i].size.y -= mPadding; + } +} + +bool DockSpaceWidget::isSideVisible(DockSpaceWidget::Side side) { return mSideWidgets[side] && !mSideHidden[side]; } + +bool DockSpaceWidget::isSideExists(DockSpaceWidget::Side side) { return mSideWidgets[side]; } + + +ualni DockSpaceWidget::getVisibleSidesSize() { + ualni out = 0; + for (ualni i = 0; i < 4; i++) { + if (isSideVisible(Side(i))) out++; + } + return out; } diff --git a/Widgets/public/ButtonWidget.hpp b/Widgets/public/ButtonWidget.hpp index de2a98a..deeee4e 100644 --- a/Widgets/public/ButtonWidget.hpp +++ b/Widgets/public/ButtonWidget.hpp @@ -2,6 +2,8 @@ #include "LabelWidget.hpp" +#include + namespace tp { class ButtonWidget : public Widget { @@ -23,5 +25,7 @@ namespace tp { RGBA hoveredColor; RGBA accentColor; halnf rounding = 0; + + std::function mCallback = [](){}; }; } \ No newline at end of file diff --git a/Widgets/public/DockspaceWidget.hpp b/Widgets/public/DockspaceWidget.hpp index bc152e3..5eba3d0 100644 --- a/Widgets/public/DockspaceWidget.hpp +++ b/Widgets/public/DockspaceWidget.hpp @@ -3,9 +3,67 @@ namespace tp { class DockSpaceWidget : public Widget { public: - DockSpaceWidget() = default; + enum Side { LEFT, TOP, RIGHT, BOTTOM }; + + public: + DockSpaceWidget(); void eventProcess(const Events& events) override; void eventDraw(Canvas& canvas) override; + + void addSideWidget(Widget* widget, Side side); + void removeSideWidget(Side side); + + void toggleHiddenState(DockSpaceWidget::Side side); + + void setCenterWidget(Widget* widget); + + private: + void updateActiveWindow(const tp::Events& events); + void calculateSideAreas(); + void calculateResizeHandles(); + void handleResizeEvents(const Events& events); + void updateChildSideWidgets(); + + void calculateHeaderAreas(); + + + bool isSideVisible(Side side); + bool isSideExists(Side side); + ualni getVisibleSidesSize(); + + private: + // todo : make better grouping into structs + + Widget* mSideWidgets[4] { nullptr, nullptr, nullptr, nullptr }; + Widget* mCenterWidget = nullptr; + + alni mSideSplitOrder[4] = { -1, -1, -1, -1 }; + halnf mSideAbsoluteSize[4] { 200, 100, 200, 100 }; + bool mSideHidden[4] { true, false, false, false }; + halnf mSideSizePadding = 50.f; + + // Geometry cache + struct ResizeHandle { + RectF area{ 0, 0, 0, 0 }; + Side side{ TOP }; + halnf start{ 0 }; + halnf end{ 0 }; + bool active = false; + bool hover = false; + }; + + Buffer mResizeHandles; + RectF mSideAreas[4]; + RectF mSideHeaderAreas[4]; + RectF mCenterArea {}; + + // View Parameters + halnf mPadding = 4; + halnf mHeaderSize = 27; + + RGBA mResizeHandleColorHovered = RGBA(0.3, 0.3, 0.3, 1); + RGBA mResizeHandleColorActive = RGBA(0.6, 0.6, 0.6, 1); + RGBA mBackgroundColor = RGBA(0, 0, 0, 1); }; } \ No newline at end of file diff --git a/Widgets/public/WidgetBase.hpp b/Widgets/public/WidgetBase.hpp index 2e3d7af..950bd10 100644 --- a/Widgets/public/WidgetBase.hpp +++ b/Widgets/public/WidgetBase.hpp @@ -63,5 +63,9 @@ namespace tp { bool mHolding = false; bool mPressed = false; bool mReleased = false; + + + // docking + bool mIsDocked = false; }; } \ No newline at end of file