Refactor in a good state

This commit is contained in:
IlyaShurupov 2024-10-09 14:31:23 +03:00 committed by Ilya Shurupov
parent 4b6e7da994
commit 164359f6aa
21 changed files with 606 additions and 105 deletions

View file

@ -0,0 +1,324 @@
#include "DockLayoutWidget.hpp"
using namespace tp;
DockLayoutWidget::DockLayoutWidget() {
mSideWidgets[0].side = LEFT;
mSideWidgets[1].side = TOP;
mSideWidgets[2].side = RIGHT;
mSideWidgets[3].side = BOTTOM;
}
void DockLayoutWidget::process(const EventHandler& events) {
// calculateHeaderAreas();
// handlePreview(events);
handleResizeEvents(events);
calculateSideAreas();
calculateResizeHandles();
updateChildSideWidgets();
}
void DockLayoutWidget::draw(Canvas& canvas) {
canvas.rect(getRelativeArea(), mBackgroundColor, 0);
}
void DockLayoutWidget::drawOverlay(Canvas& canvas) {
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);
}
if (mPreview) {
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);
}
}
}
bool DockLayoutWidget::propagateEventsToChildren() const {
return !mResizing;
}
bool DockLayoutWidget::needsNextFrame() const {
return Widget::needsNextFrame() || mResizing;
}
void DockLayoutWidget::setCenterWidget(Widget* widget) {
mCenterWidget = widget;
addChild(widget);
}
void DockLayoutWidget::dockWidget(Widget* widget, Side side) {
DEBUG_ASSERT(!sideExists(side))
addChild(widget);
auto& sideWidget = mSideWidgets[side];
sideWidget.widget = widget;
for (auto& order : mSideWidgets) {
if (order.order == -1) {
order.order = side;
break;
}
}
sideWidget.hidden = false;
}
void DockLayoutWidget::undockWidget(Side side) {
DEBUG_ASSERT(sideExists(side))
bool removed = false;
for (ualni i = 0; i < 3; i++) {
if (mSideWidgets[i].order == side) {
removed = true;
}
if (removed) {
swap(mSideWidgets[i].order, mSideWidgets[i + 1].order);
}
}
mSideWidgets[3].order = -1;
mSideWidgets[side].widget = nullptr;
}
void DockLayoutWidget::toggleWidgetVisibility(Side side) {
DEBUG_ASSERT(sideExists(side))
mSideWidgets[side].hidden = !mSideWidgets[side].hidden;
}
void DockLayoutWidget::calculateSideAreas() {
auto startArea = getRelativeArea();
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 DockLayoutWidget::calculateResizeHandles() {
RectF rec;
RectF area = getRelativeArea();
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 DockLayoutWidget::handleResizeEvents(const EventHandler& events) {
auto pointer = events.getPointer();
auto pointerDelta = events.getPointerDelta();
// check triggers
if (events.isReleased(InputID::MOUSE1)) {
for (auto& sideWidget : mSideWidgets) {
sideWidget.resizeHandle.active = false;
}
mResizing = false;
} else {
for (auto& sideWidget : mSideWidgets) {
sideWidget.resizeHandle.hover = sideWidget.resizeHandle.area.isInside(pointer);
if (sideWidget.resizeHandle.hover && events.isPressed(InputID::MOUSE1)) {
sideWidget.resizeHandle.active = true;
mResizing = true;
triggerWidgetUpdate();
}
}
}
// 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;
}
}
void DockLayoutWidget::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 DockLayoutWidget::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 DockLayoutWidget::isSideVisible(DockLayoutWidget::Side side) {
return sideExists(side) && !mSideWidgets[side].hidden;
}
bool DockLayoutWidget::sideExists(DockLayoutWidget::Side side) { return mSideWidgets[side].widget; }
ualni DockLayoutWidget::getVisibleSidesSize() {
ualni out = 0;
for (ualni i = 0; i < 4; i++) {
if (isSideVisible(Side(i))) out++;
}
return out;
}
void DockLayoutWidget::handlePreview(const EventHandler& 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

@ -10,6 +10,8 @@ void FloatingWidget::process(const EventHandler& events) {
if (inside && events.isPressed(InputID::MOUSE1)) {
mPointerStart = relativePointer;
mIsFloating = true;
bringToFront();
}
if (mIsFloating && resizeHandleRect().isInside(relativePointer)) {

View file

@ -19,23 +19,26 @@ void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
// construct hierarchy tree of widgets to process
updateTreeToProcess();
auto root = mWidgetsToProcess.find(mRoot) == mWidgetsToProcess.end() ? nullptr : &mWidgetsToProcess[mRoot];
// update active tree animations
updateAnimations(&mWidgetsToProcess[mRoot]);
updateAnimations(root);
// update all events and call all event processing callbacks
EventHandler dummyEvents;
processFocusItems(*events);
processActiveTree(&mWidgetsToProcess[mRoot], dummyEvents);
processActiveTree(root, dummyEvents);
// update widget sizes base on individual size policies
adjustSizes(&mWidgetsToProcess[mRoot]);
adjustSizes(root);
// trigger some widgets by moise pointer
handleFocusChanges(*events);
// check triggered widgets for removal
erase_if(mTriggeredWidgets, [](auto widget) {
// check triggered widgets for removal
erase_if(mTriggeredWidgets, [this](auto widget) {
if (mWidgetsToProcess.find(widget) == mWidgetsToProcess.end()) return false;
auto end = !widget->needsNextFrame();
if (end) {
widget->endAnimations();
@ -53,8 +56,10 @@ void RootWidget::drawFrame(Canvas& canvas) {
// draw all tree from top to bottom
drawRecursion(canvas, mRoot, { 0, 0 });
ImGui::Checkbox("Draw debug", &mDebug);
if (mDebug) {
drawDebug(canvas, mRoot, { 0, 0 });
drawDebug(canvas, mRoot, { 0, 0 }, 0);
ImGui::Text("Triggered Widgets: %i", (int) mTriggeredWidgets.size());
ImGui::Text("Widgets To Process: %i", (int) mWidgetsToProcess.size());
}
@ -80,8 +85,8 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos)
active->draw(canvas);
for (auto child : active->mChildren) {
drawRecursion(canvas, child, pos + child->getArea().pos);
for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) {
drawRecursion(canvas, child->data, pos + child->data->getArea().pos);
}
canvas.setOrigin(pos);
@ -90,14 +95,13 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos)
active->drawOverlay(canvas);
}
void RootWidget::drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos) {
void RootWidget::drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder) {
auto area = RectF{ pos, active->getArea().size };
if (mWidgetsToProcess.find(active) != mWidgetsToProcess.end()) {
RGBA color = { 1, 0, 0, 1};
canvas.frame(area, color);
canvas.text(active->mName.c_str(), area, 12, Canvas::Align::LC, 2, color);
canvas.text((active->mName + ":" + std::to_string(depthOrder)).c_str(), area, 22, Canvas::Align::LC, 2, color);
}
if (active->mInFocus) {
@ -107,8 +111,9 @@ void RootWidget::drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos) {
canvas.circle(active->mGlobalPoint, 10, active->mDebugColor);
}
for (auto child : active->mChildren) {
drawDebug(canvas, child, pos + child->getArea().pos);
int orderIdx = 0;
for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) {
drawDebug(canvas, child->data, pos + child->data->getArea().pos, orderIdx++);
}
}
@ -126,40 +131,61 @@ void RootWidget::updateAnimations(ActiveTreeNode* iter) {
}
void RootWidget::getWidgetPath(Widget* widget, std::vector<Widget*>& out) {
if (!widget) return;
for (auto iter = widget; iter && iter != this; iter = iter->mParent) {
out.push_back(iter);
}
for (auto i = 0; i < out.size() / 2; i++) {
swap(out[i], out[out.size() - i - 1]);
}
}
void RootWidget::handleFocusChanges(EventHandler& events) {
auto prevFocus = mInFocusWidget;
findFocusWidget(mRoot, &mInFocusWidget, events.getPointer());
if (prevFocus) {
std::vector<Widget*> path1;
std::vector<Widget*> path2;
if (mInFocusWidget) updateWidget(mInFocusWidget);
getWidgetPath(prevFocus, path1);
getWidgetPath(mInFocusWidget, path2);
size_t iter = 0;
while (path1[iter] == path2[iter]) {
iter++;
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;
}
}
for (auto i = iter; i < path1.size(); i++) {
path1[i]->mouseLeave();
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;
}
}
for (auto i = iter; i < path2.size(); i++) {
path2[i]->mouseEnter();
}
size_t mostCommonIdx = 0;
} else {
for (auto iter = mInFocusWidget; iter; iter = iter->mParent) {
iter->mouseEnter();
if (!(path1.empty() || path2.empty())) {
while (path1[mostCommonIdx] == path2[mostCommonIdx]) {
mostCommonIdx++;
}
}
for (auto i = 0; i < path1.size(); i++) {
path1[i]->mInFocus = false;
if (i >= mostCommonIdx && i < propLen1) path1[i]->mouseLeave();
}
for (auto i = 0; i < path2.size(); i++) {
path2[i]->mInFocus = true;
if (i >= mostCommonIdx && i < propLen2) path2[i]->mouseEnter();
}
}
void RootWidget::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer) {
@ -167,8 +193,8 @@ void RootWidget::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& poin
*focus = iter;
for (auto child : iter->mChildren) {
findFocusWidget(child, focus, pointer - iter->mArea.getTargetRect().pos);
for (auto child = iter->mDepthOrder.lastNode(); child; child = child->prev) {
findFocusWidget(child->data, focus, pointer - iter->mArea.getTargetRect().pos);
}
}
@ -208,10 +234,6 @@ void RootWidget::processFocusItems(EventHandler& events) {
std::vector<Widget*> path;
getWidgetPath(mInFocusWidget, path);
for (auto i = 0; i < path.size() / 2; i++) {
swap(path[i], path[path.size() - i - 1]);
}
size_t len = path.size();
for (auto i = 0; i < len; i++) {
if (!path[i]->propagateEventsToChildren()) {

View file

@ -30,6 +30,12 @@ void ButtonWidget::setAction(const std::function<void()>& action) {
mAction = action;
}
void ButtonWidget::setColor(const RGBA& in) {
mColor = in;
mColorAnimated.setTargetColor(mColor);
triggerWidgetUpdate();
}
void ButtonWidget::process(const EventHandler& eventHandler) {
if (getArea().isInside(eventHandler.getPointer())) {
if (eventHandler.isPressed(InputID::MOUSE1)) {

View file

@ -62,6 +62,24 @@ void Widget::adjustRect() {
}
}
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::adjustChildrenRect() {
auto area = RectF({}, getArea().size);
for (auto widget : mChildren) {
@ -71,7 +89,6 @@ void Widget::adjustChildrenRect() {
void Widget::mouseEnter() {
mInFocus = true;
triggerWidgetUpdate();
}
void Widget::mouseLeave() {
@ -80,4 +97,14 @@ void Widget::mouseLeave() {
bool Widget::propagateEventsToChildren() const {
return true;
}
}
void Widget::addChild(Widget* child) {
mChildren.push_back(child);
mDepthOrder.pushBack(child);
child->mParent = this;
triggerWidgetUpdate();
child->triggerWidgetUpdate();
}