This commit is contained in:
IlyaShurupov 2024-10-08 14:23:49 +03:00
parent 49cfe70c0d
commit 72c4740f82
18 changed files with 592 additions and 203 deletions

View file

@ -13,7 +13,7 @@ void AnimationTestWidget::process(const EventHandler& events) {
mTestSpring.updateCurrentRect();
if (mTestSpring.checkAnimationShouldEnd()) {
if (mTestSpring.shouldEndTransition()) {
mTestSpring.endAnimation();
}
}
@ -23,7 +23,6 @@ void AnimationTestWidget::draw(Canvas& canvas) {
canvas.rect(mTestSpring.getCurrentRect(), RGBA(1.f), 10);
}
bool AnimationTestWidget::needUpdate() const {
if (Widget::needUpdate()) return true;
return !mTestSpring.checkAnimationShouldEnd();
bool AnimationTestWidget::needsNextFrame() const {
return Widget::needsNextFrame() || !mTestSpring.shouldEndTransition();
}

View file

@ -12,21 +12,45 @@ void FloatingWidget::process(const EventHandler& events) {
mIsFloating = true;
}
if (mIsFloating && resizeHandleRect().isInside(relativePointer)) {
mIsResizing = true;
}
if (mIsFloating && events.isReleased(InputID::MOUSE1)) {
mIsFloating = false;
mIsResizing = false;
}
mPointerCurrent = relativePointer;
setActive(mIsFloating || needUpdate());
// triggerWidgetUpdate();
}
void FloatingWidget::updateArea(RectF& area) const {
if (mIsFloating) {
void FloatingWidget::adjustRect() {
auto area = getArea();
if (mIsResizing) {
area.size = mPointerCurrent + mHandleSize / 2.f;
} else if (mIsFloating) {
area.pos += mPointerCurrent - mPointerStart;
}
setArea(area);
}
void FloatingWidget::draw(Canvas& canvas) {
canvas.rect(resizeHandleRect(), RGBA(0.7f), 2);
canvas.rect(getRelativeArea(), RGBA(0.5f), 10);
}
RectF FloatingWidget::resizeHandleRect() {
auto area = getRelativeArea();
area.pos = area.p3() - mHandleSize;
area.size = mHandleSize;
area.shrink(mHandlePadding);
return area;
}
bool FloatingWidget::propagateEventsToChildren() const {
return !mIsFloating;
}

View file

@ -1,35 +1,77 @@
#include <algorithm>
#include "RootWidget.hpp"
#include "imgui.h"
using namespace tp;
void RootWidget::setRootWidget(Widget* widget) { mRoot = widget; }
void RootWidget::setRootWidget(Widget* widget) {
mRoot = widget;
widget->mParent = this;
}
void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
mNeedUpdate = false;
auto currentPaths = &mActivePaths[mFlipFlop];
auto prevPaths = &mActivePaths[!mFlipFlop];
updateActivePaths(currentPaths, events->getPointer());
mScreenArea = screenArea;
mRoot->setArea(screenArea);
clearProcessedFlag(*currentPaths);
clearProcessedFlag(*prevPaths);
// construct hierarchy tree of widgets to process
updateTreeToProcess();
processPaths(*currentPaths, *events);
processPaths(*prevPaths, *events);
// update active tree animations
updateAnimations(&mWidgetsToProcess[mRoot]);
mActiveWidgets.erase_if([](auto widget) { return !widget->mIsActive; });
// update all events and call all event processing callbacks
EventHandler dummyEvents;
mFlipFlop = !mFlipFlop;
processFocusItems(*events);
processActiveTree(&mWidgetsToProcess[mRoot], dummyEvents);
// update widget sizes base on individual size policies
adjustSizes(&mWidgetsToProcess[mRoot]);
// trigger some widgets by moise pointer
handleFocusChanges(*events);
// check triggered widgets for removal
erase_if(mTriggeredWidgets, [](auto widget) {
auto end = !widget->needsNextFrame();
if (end) {
widget->endAnimations();
}
return end;
});
}
bool RootWidget::needsUpdate() const { return mNeedUpdate; }
bool RootWidget::needsUpdate() const { return !mTriggeredWidgets.empty(); }
void RootWidget::drawFrame(Canvas& canvas) {
// draw from top to bottom
canvas.rect(mScreenArea, RGBA(0, 0, 0, 1));
// draw all tree from top to bottom
drawRecursion(canvas, mRoot, { 0, 0 });
if (mDebug) {
drawDebug(canvas, mRoot, { 0, 0 });
ImGui::Text("Triggered Widgets: %i", (int) mTriggeredWidgets.size());
ImGui::Text("Widgets To Process: %i", (int) mWidgetsToProcess.size());
}
}
void RootWidget::updateTreeToProcess() {
mWidgetsToProcess.clear();
for (auto widget : mTriggeredWidgets) {
for (auto iter = widget; iter; iter = iter->mParent) {
if (iter->mParent) {
mWidgetsToProcess[iter->mParent].children.insert(&mWidgetsToProcess[iter]);
mWidgetsToProcess[iter->mParent].widget= iter->mParent;
mWidgetsToProcess[iter].widget= iter;
}
}
}
}
void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) {
@ -37,8 +79,9 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos)
canvas.pushClamp({ pos, active->getArea().size });
active->draw(canvas);
for (auto child : active->mChildWidgets) {
drawRecursion(canvas, child.data(), pos + child->getArea().pos);
for (auto child : active->mChildren) {
drawRecursion(canvas, child, pos + child->getArea().pos);
}
canvas.setOrigin(pos);
@ -47,57 +90,25 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos)
active->drawOverlay(canvas);
}
void RootWidget::updateActivePaths(Buffer<ActivePath>* activePaths, const Vec2F& pointer) {
activePaths->clear();
void RootWidget::drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos) {
auto area = RectF{ pos, active->getArea().size };
// based on pointer position
Widget* pointedWidget = nullptr;
{
ActivePath& activePath = activePaths->append(ActivePath{});
activePath.path.append({ mRoot, {} });
auto pointerIter = pointer;
bool newActive = true;
while (newActive) {
newActive = false;
for (auto child : activePath.path.last().widget->mChildWidgets) {
const auto area = child->mArea.getCurrentRect();
if (area.isInside(pointerIter)) {
activePath.path.append({ child.data(), {} });
newActive = true;
pointerIter -= child->getArea().pos;
break;
}
}
}
pointedWidget = activePath.path.last().widget;
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);
}
// based on individual widget requirements
{
for (auto widget : mActiveWidgets) {
if (widget.data() == pointedWidget) continue;
ActivePath& activePath = activePaths->append(ActivePath{});
for (Widget* iter = widget.data(); iter; iter = iter->mParent) {
activePath.path.append({ iter, {} });
}
activePath.path.reverse();
}
if (active->mInFocus) {
RGBA color = { 1, 0, 0, 0.3f};
canvas.debugCross(area, color);
canvas.circle(pos + active->mLocalPoint, 5, active->mDebugColor);
canvas.circle(active->mGlobalPoint, 10, active->mDebugColor);
}
// update paths global pos
for (auto activePath : *activePaths) {
Vec2F iterPos = { 0, 0 };
for (auto activeWidget : activePath->path) {
iterPos += activeWidget->widget->getArea().pos;
activeWidget->globalPos = iterPos;
}
for (auto child : active->mChildren) {
drawDebug(canvas, child, pos + child->getArea().pos);
}
}
@ -106,50 +117,135 @@ void RootWidget::setWidgetArea(Widget& widget, const RectF& rect) {
widget.mArea.endAnimation();
}
void RootWidget::processPaths(const Buffer<ActivePath>& paths, EventHandler& events) {
EventHandler dummyEvents;
bool eventsHandled = false;
void RootWidget::updateAnimations(ActiveTreeNode* iter) {
if (!iter) return;
iter->widget->updateAnimations();
for (auto child : iter->children) {
updateAnimations(child);
}
}
for (auto path : paths) {
void RootWidget::getWidgetPath(Widget* widget, std::vector<Widget*>& out) {
for (auto iter = widget; iter && iter != this; iter = iter->mParent) {
out.push_back(iter);
}
}
for (alni widgetIdx = (alni) path->path.size() - 1; widgetIdx >= 0; widgetIdx--) {
auto activeWidgetNode = path->path[widgetIdx];
auto activeWidget = activeWidgetNode.widget;
void RootWidget::handleFocusChanges(EventHandler& events) {
auto prevFocus = mInFocusWidget;
findFocusWidget(mRoot, &mInFocusWidget, events.getPointer());
activeWidget->mArea.updateCurrentRect();
if (prevFocus) {
std::vector<Widget*> path1;
std::vector<Widget*> path2;
events.setCursorOrigin(activeWidgetNode.globalPos);
getWidgetPath(prevFocus, path1);
getWidgetPath(mInFocusWidget, path2);
bool useEvents = !eventsHandled || activeWidget->mIsActive;
size_t iter = 0;
while (path1[iter] == path2[iter]) {
iter++;
}
if (!activeWidget->mProcessedFlag) {
activeWidget->process(useEvents ? events : dummyEvents);
activeWidget->mProcessedFlag = true;
}
for (auto i = iter; i < path1.size(); i++) {
path1[i]->mouseLeave();
}
eventsHandled = !activeWidget->isPassThroughEvents();
for (auto i = iter; i < path2.size(); i++) {
path2[i]->mouseEnter();
}
auto area = activeWidget->mArea.getCurrentRect();
activeWidget->updateArea(area);
activeWidget->setArea(area);
mNeedUpdate |= activeWidget->needUpdate();
if (!activeWidget->needUpdate()) activeWidget->finishAnimations();
if (activeWidget->mIsActive) {
if (mActiveWidgets.find(activeWidget) == -1) {
mActiveWidgets.append(activeWidget);
}
}
} else {
for (auto iter = mInFocusWidget; iter; iter = iter->mParent) {
iter->mouseEnter();
}
}
}
void RootWidget::clearProcessedFlag(const Buffer<ActivePath>& paths) {
for (auto path : paths) {
for (auto widget : path->path) {
widget->widget->mProcessedFlag = false;
void RootWidget::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer) {
if (!iter->mArea.getTargetRect().isInside(pointer)) return;
*focus = iter;
for (auto child : iter->mChildren) {
findFocusWidget(child, focus, pointer - iter->mArea.getTargetRect().pos);
}
}
void RootWidget::updateWidget(Widget* widget) {
mTriggeredWidgets.insert(widget);
}
void RootWidget::adjustSizes(ActiveTreeNode* iter) {
if (!iter) return;
iter->widget->adjustRect();
iter->widget->adjustChildrenRect();
for (auto child : iter->children) {
adjustSizes(child);
}
}
void RootWidget::processActiveTree(ActiveTreeNode* iter, EventHandler& events) {
if (!iter) return;
if (!iter->widget->mInFocus) {
iter->widget->updateAnimations();
iter->widget->process(events);
}
for (auto child : iter->children) {
processActiveTree(child, events);
}
}
void RootWidget::processFocusItems(EventHandler& events) {
if (!mInFocusWidget) return;
// FIXME : cringe
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()) {
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]->getArea().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]);
if (!eventsProcessed && widget->processesEvents()) {
events.setEnableKeyEvents(true);
widget->updateAnimations();
widget->process(events);
events.setEnableKeyEvents(false);
eventsProcessed = true;
} else {
widget->updateAnimations();
widget->process(events);
}
}
}

View file

@ -15,11 +15,15 @@ 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) {
@ -27,19 +31,11 @@ void ButtonWidget::setAction(const std::function<void()>& action) {
}
void ButtonWidget::process(const EventHandler& eventHandler) {
if (getRelativeArea().isInside(eventHandler.getPointer())) {
if (getArea().isInside(eventHandler.getPointer())) {
if (eventHandler.isPressed(InputID::MOUSE1)) {
mAction();
}
mColorAnimated.setTargetColor(mColor);
} else {
mColorAnimated.setTargetColor(mColorHovered);
}
mColorAnimated.updateCurrentRect();
setActive(needUpdate());
}
void ButtonWidget::draw(Canvas& canvas) {
@ -47,10 +43,31 @@ void ButtonWidget::draw(Canvas& canvas) {
LabelWidget::draw(canvas);
}
bool ButtonWidget::needUpdate() const {
return !mColorAnimated.checkAnimationShouldEnd();
bool ButtonWidget::needsNextFrame() const {
return LabelWidget::needsNextFrame() || !mColorAnimated.shouldEndTransition();
}
void ButtonWidget::finishAnimations() {
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();
}
void ButtonWidget::mouseLeave() {
mColorAnimated.setTargetColor(mColor);
mColorAnimated.updateCurrentRect();
triggerWidgetUpdate();
}

View file

@ -2,6 +2,20 @@
using namespace tp;
WidgetManagerInterface* Widget::getRoot() {
Widget* iter = mParent;
while (iter && iter->mParent) {
iter = iter->mParent;
}
return dynamic_cast<WidgetManagerInterface*>(iter);
}
void Widget::triggerWidgetUpdate() {
if (auto root = getRoot()) {
root->updateWidget(this);
}
}
Widget::Widget() {
mArea.setTargetRect({ 10, 10, 100, 40, });
mArea.endAnimation();
@ -9,10 +23,10 @@ Widget::Widget() {
void Widget::setArea(const RectF& area) {
mArea.setTargetRect(area);
}
void Widget::draw(Canvas& canvas) {
canvas.rect(mArea.getCurrentRect(), RGBA(1.f), 10);
if (!mArea.shouldEndTransition()) {
triggerWidgetUpdate();
}
}
RectF Widget::getArea() const {
@ -23,28 +37,47 @@ RectF Widget::getRelativeArea() const {
return { {}, mArea.getCurrentRect().size };
}
void Widget::setActive(bool active) {
mIsActive = active;
}
void Widget::addChild(Widget* widget) {
if (widget->mParent) {
widget->mParent->removeChild(widget);
}
mChildWidgets.pushBack(widget);
widget->mParent = this;
}
void Widget::removeChild(Widget* widget) {
auto node = mChildWidgets.find(widget);
node->data->mParent = nullptr;
mChildWidgets.removeNode(node);
}
bool Widget::needUpdate() const {
return !mArea.checkAnimationShouldEnd();
}
void Widget::finishAnimations() {
void Widget::endAnimations() {
mArea.endAnimation();
}
bool Widget::processesEvents() const {
return true;
}
void Widget::updateAnimations() {
mArea.updateCurrentRect();
}
bool Widget::needsNextFrame() const {
return !mArea.shouldEndTransition() || mInFocus;
}
void Widget::adjustRect() {
if (mChildren.empty()) return;
auto area = (*mChildren.begin())->getArea();
for (auto widget : mChildren) {
area.expand(widget->getArea());
}
}
void Widget::adjustChildrenRect() {
auto area = RectF({}, getArea().size);
for (auto widget : mChildren) {
widget->setArea(area);
}
}
void Widget::mouseEnter() {
mInFocus = true;
triggerWidgetUpdate();
}
void Widget::mouseLeave() {
mInFocus = false;
}
bool Widget::propagateEventsToChildren() const {
return true;
}