This commit is contained in:
IlyaShurupov 2024-10-08 14:23:49 +03:00 committed by Ilya Shurupov
parent 279cf58dff
commit 4b6e7da994
18 changed files with 592 additions and 203 deletions

View file

@ -52,6 +52,36 @@ void Canvas::rect(RectF rec, const RGBA& col, halnf round) {
nvgFill(mContext->vg); nvgFill(mContext->vg);
} }
void Canvas::debugCross(RectF rec, const RGBA& col) {
nvgBeginPath(mContext->vg);
nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y);
nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y);
nvgMoveTo(mContext->vg, rec.p2().x, rec.p2().y);
nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y);
nvgStrokeWidth(mContext->vg, 2);
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
nvgStroke(mContext->vg);
// nvgFill(mContext->vg);
}
void Canvas::frame(RectF rec, const RGBA& col, halnf round) {
nvgBeginPath(mContext->vg);
nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y);
nvgLineTo(mContext->vg, rec.p2().x, rec.p2().y);
nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y);
nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y);
nvgLineTo(mContext->vg, rec.p1().x, rec.p1().y);
nvgStrokeWidth(mContext->vg, 2);
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
nvgStroke(mContext->vg);
// nvgFill(mContext->vg);
}
void Canvas::circle(Vec2F pos, halnf size, const RGBA& col) { void Canvas::circle(Vec2F pos, halnf size, const RGBA& col) {
pos += mOrigin; pos += mOrigin;

View file

@ -122,16 +122,22 @@ Vec2F EventHandler::getPointer() const { return mPointer - mPointerOrigin; }
Vec2F EventHandler::getPointerPrev() const { return mPointerPrev - mPointerOrigin; } Vec2F EventHandler::getPointerPrev() const { return mPointerPrev - mPointerOrigin; }
bool EventHandler::isPressed(InputID id) const { bool EventHandler::isPressed(InputID id) const {
if (!mEnableKeyEvents) return false;
return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED; return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED;
} }
bool EventHandler::isReleased(InputID id) const { bool EventHandler::isReleased(InputID id) const {
if (!mEnableKeyEvents) return false;
return mInputStates[(int) id].mCurrentState == InputState::State::RELEASED; return mInputStates[(int) id].mCurrentState == InputState::State::RELEASED;
} }
halnf EventHandler::getPointerPressure() const { return mPointerPressure; } halnf EventHandler::getPointerPressure() const {
if (!mEnableKeyEvents) return 0;
return mPointerPressure;
}
bool EventHandler::isDown(InputID id) const { bool EventHandler::isDown(InputID id) const {
if (!mEnableKeyEvents) return false;
return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED || return mInputStates[(int) id].mCurrentState == InputState::State::PRESSED ||
mInputStates[(int) id].mCurrentState == InputState::State::HOLD; mInputStates[(int) id].mCurrentState == InputState::State::HOLD;
} }
@ -139,3 +145,7 @@ bool EventHandler::isDown(InputID id) const {
halnf EventHandler::getScrollY() const { return mScrollDelta.y; } halnf EventHandler::getScrollY() const { return mScrollDelta.y; }
Vec2F EventHandler::getPointerDelta() const { return mPointer - mPointerPrev; } Vec2F EventHandler::getPointerDelta() const { return mPointer - mPointerPrev; }
void EventHandler::setEnableKeyEvents(bool enable) {
mEnableKeyEvents = enable;
}

View file

@ -73,7 +73,6 @@ namespace tp {
void processEvent(); void processEvent();
void processAllEvent(); void processAllEvent();
void setCursorOrigin(const Vec2F& origin); void setCursorOrigin(const Vec2F& origin);
[[nodiscard]] Vec2F getPointer() const; [[nodiscard]] Vec2F getPointer() const;
@ -87,6 +86,8 @@ namespace tp {
[[nodiscard]] halnf getPointerPressure() const; [[nodiscard]] halnf getPointerPressure() const;
void setEnableKeyEvents(bool);
private: private:
void processEventUnguarded(); void processEventUnguarded();
@ -108,6 +109,8 @@ namespace tp {
InputState mInputStates[(int) InputID::LAST_KEY_CODE]{}; InputState mInputStates[(int) InputID::LAST_KEY_CODE]{};
Timer mTimerEvent = Timer(1000); Timer mTimerEvent = Timer(1000);
bool mEnableKeyEvents = true;
}; };
} }

View file

@ -60,7 +60,9 @@ namespace tp {
void pushClamp(const RectF& rec); void pushClamp(const RectF& rec);
void popClamp(); void popClamp();
void debugCross(RectF rec, const RGBA& col);
void rect(RectF rec, const RGBA& col, halnf round = 0); void rect(RectF rec, const RGBA& col, halnf round = 0);
void frame(RectF rec, const RGBA& col, halnf round = 0);
void circle(Vec2F pos, halnf size, const RGBA& col); void circle(Vec2F pos, halnf size, const RGBA& col);
void text(const char*, const RectF&, halnf size, Align, halnf padding, const RGBA&); void text(const char*, const RectF&, halnf size, Align, halnf padding, const RGBA&);

View file

@ -122,11 +122,11 @@ namespace tp {
// pos // pos
Vec2<Type> p1() const { return pos; } Vec2<Type> p1() const { return pos; }
Vec2<Type> p2() const { return { pos.x, pos.y + size.y }; }
// pos + size // pos + size
Vec2<Type> p3() const { return pos + size; } Vec2<Type> p3() const { return pos + size; }
Vec2<Type> p2() const { return { pos.x, pos.y + size.y }; }
Vec2<Type> p4() const { return { pos.x + size.x, pos.y }; } Vec2<Type> p4() const { return { pos.x + size.x, pos.y }; }
inline bool isAbove(const Rect<Type>& rect) const { return (pos.y + size.y < rect.pos.y); } inline bool isAbove(const Rect<Type>& rect) const { return (pos.y + size.y < rect.pos.y); }
@ -165,6 +165,21 @@ namespace tp {
return { pos + val, size - val * 2 }; return { pos + val, size - val * 2 };
} }
void expand(const Vec2<Type>& point) {
pos.x = min(point.x, pos.x);
pos.y = min(point.y, pos.y);
auto p = pos + size;
p.x = max(point.x, p.x);
p.y = max(point.y, p.y);
size = p - pos;
}
void expand(const Rect<Type>& rect) {
expand(rect.pos);
expand(rect.pos + rect.size);
}
// if only one point isInside // if only one point isInside
bool clampOutside(Vec2<Type>& v1, Vec2<Type>& v2) { bool clampOutside(Vec2<Type>& v1, Vec2<Type>& v2) {
bool const in1 = isInside(v1); bool const in1 = isInside(v1);
@ -268,6 +283,10 @@ namespace tp {
Type z; Type z;
Type w; Type w;
}; };
struct {
Type width;
Type height;
};
}; };
}; };
} }

View file

@ -5,19 +5,33 @@
#include "AnimationTestWidget.hpp" #include "AnimationTestWidget.hpp"
#include "FloatingWidget.hpp" #include "FloatingWidget.hpp"
#include "SimpleWidgets.hpp" #include "SimpleWidgets.hpp"
#include "LayoutWidget.hpp"
using namespace tp; using namespace tp;
class WidgetApplication : public Application { class WidgetApplication : public Application {
public: public:
WidgetApplication() { WidgetApplication() {
mRootWidget.setRootWidget(&mWidget); mRootWidget.setRootWidget(&mLayoutWidget);
mWidget.addChild(&mWidgetFloating); mLayoutWidget.addChild(&mFloatingMenu);
// mLayoutWidget.addChild(&mButton2);
RootWidget::setWidgetArea(mWidgetFloating, { 100, 100, 150, 200 }); // mWidgetFloating.addChild(&mButton);
mWidgetFloating.addChild(&mButton); // mRootWidget.setRootWidget(&mAnimationTestWidget);
// mAnimationTestWidget.addChild(&mWidgetFloating);
// mAnimationTestWidget.addChild(&mLayoutWidget);
// RootWidget::setWidgetArea(mButton, { 100, 100, 150, 200 });
RootWidget::setWidgetArea(mFloatingMenu, { 100, 100, 150, 200 });
// mWidgetFloating.addChild(&mLayoutWidget);
// mLayoutWidget.addChild(&mButton);
// mLayoutWidget.addChild(&mLabel);
} }
void processFrame(EventHandler* eventHandler, halnf deltaTime) override { void processFrame(EventHandler* eventHandler, halnf deltaTime) override {
@ -38,10 +52,12 @@ public:
private: private:
LabelWidget mLabel; LabelWidget mLabel;
ButtonWidget mButton; ButtonWidget mButton;
ButtonWidget mButton2;
FloatingWidget mWidgetFloating; FloatingWidget mWidgetFloating;
AnimationTestWidget mAnimationTestWidget;
DesktopLayout mLayoutWidget;
FloatingMenu mFloatingMenu;
AnimationTestWidget mWidget;
RootWidget mRootWidget; RootWidget mRootWidget;
}; };

View file

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

View file

@ -12,21 +12,45 @@ void FloatingWidget::process(const EventHandler& events) {
mIsFloating = true; mIsFloating = true;
} }
if (mIsFloating && resizeHandleRect().isInside(relativePointer)) {
mIsResizing = true;
}
if (mIsFloating && events.isReleased(InputID::MOUSE1)) { if (mIsFloating && events.isReleased(InputID::MOUSE1)) {
mIsFloating = false; mIsFloating = false;
mIsResizing = false;
} }
mPointerCurrent = relativePointer; mPointerCurrent = relativePointer;
setActive(mIsFloating || needUpdate()); // triggerWidgetUpdate();
} }
void FloatingWidget::updateArea(RectF& area) const { void FloatingWidget::adjustRect() {
if (mIsFloating) { auto area = getArea();
if (mIsResizing) {
area.size = mPointerCurrent + mHandleSize / 2.f;
} else if (mIsFloating) {
area.pos += mPointerCurrent - mPointerStart; area.pos += mPointerCurrent - mPointerStart;
} }
setArea(area);
} }
void FloatingWidget::draw(Canvas& canvas) { void FloatingWidget::draw(Canvas& canvas) {
canvas.rect(resizeHandleRect(), RGBA(0.7f), 2);
canvas.rect(getRelativeArea(), RGBA(0.5f), 10); 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 "RootWidget.hpp"
#include "imgui.h"
using namespace tp; 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) { void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
mNeedUpdate = false; mScreenArea = screenArea;
auto currentPaths = &mActivePaths[mFlipFlop];
auto prevPaths = &mActivePaths[!mFlipFlop];
updateActivePaths(currentPaths, events->getPointer());
mRoot->setArea(screenArea); mRoot->setArea(screenArea);
clearProcessedFlag(*currentPaths); // construct hierarchy tree of widgets to process
clearProcessedFlag(*prevPaths); updateTreeToProcess();
processPaths(*currentPaths, *events); // update active tree animations
processPaths(*prevPaths, *events); 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) { void RootWidget::drawFrame(Canvas& canvas) {
// draw from top to bottom // 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 }); 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) { 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 }); canvas.pushClamp({ pos, active->getArea().size });
active->draw(canvas); 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); canvas.setOrigin(pos);
@ -47,57 +90,25 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos)
active->drawOverlay(canvas); active->drawOverlay(canvas);
} }
void RootWidget::updateActivePaths(Buffer<ActivePath>* activePaths, const Vec2F& pointer) { void RootWidget::drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos) {
activePaths->clear(); auto area = RectF{ pos, active->getArea().size };
// based on pointer position
Widget* pointedWidget = nullptr;
{ if (mWidgetsToProcess.find(active) != mWidgetsToProcess.end()) {
ActivePath& activePath = activePaths->append(ActivePath{}); RGBA color = { 1, 0, 0, 1};
activePath.path.append({ mRoot, {} }); canvas.frame(area, color);
canvas.text(active->mName.c_str(), area, 12, Canvas::Align::LC, 2, color);
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 (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);
} }
// based on individual widget requirements for (auto child : active->mChildren) {
{ drawDebug(canvas, child, pos + child->getArea().pos);
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();
}
}
// 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;
}
} }
} }
@ -106,50 +117,135 @@ void RootWidget::setWidgetArea(Widget& widget, const RectF& rect) {
widget.mArea.endAnimation(); widget.mArea.endAnimation();
} }
void RootWidget::processPaths(const Buffer<ActivePath>& paths, EventHandler& events) { void RootWidget::updateAnimations(ActiveTreeNode* iter) {
EventHandler dummyEvents; if (!iter) return;
bool eventsHandled = false; iter->widget->updateAnimations();
for (auto child : iter->children) {
for (auto path : paths) { updateAnimations(child);
}
for (alni widgetIdx = (alni) path->path.size() - 1; widgetIdx >= 0; widgetIdx--) {
auto activeWidgetNode = path->path[widgetIdx];
auto activeWidget = activeWidgetNode.widget;
activeWidget->mArea.updateCurrentRect();
events.setCursorOrigin(activeWidgetNode.globalPos);
bool useEvents = !eventsHandled || activeWidget->mIsActive;
if (!activeWidget->mProcessedFlag) {
activeWidget->process(useEvents ? events : dummyEvents);
activeWidget->mProcessedFlag = true;
} }
eventsHandled = !activeWidget->isPassThroughEvents(); void RootWidget::getWidgetPath(Widget* widget, std::vector<Widget*>& out) {
for (auto iter = widget; iter && iter != this; iter = iter->mParent) {
out.push_back(iter);
}
}
auto area = activeWidget->mArea.getCurrentRect(); void RootWidget::handleFocusChanges(EventHandler& events) {
activeWidget->updateArea(area); auto prevFocus = mInFocusWidget;
activeWidget->setArea(area); findFocusWidget(mRoot, &mInFocusWidget, events.getPointer());
mNeedUpdate |= activeWidget->needUpdate(); if (prevFocus) {
std::vector<Widget*> path1;
std::vector<Widget*> path2;
if (!activeWidget->needUpdate()) activeWidget->finishAnimations(); getWidgetPath(prevFocus, path1);
getWidgetPath(mInFocusWidget, path2);
if (activeWidget->mIsActive) { size_t iter = 0;
if (mActiveWidgets.find(activeWidget) == -1) { while (path1[iter] == path2[iter]) {
mActiveWidgets.append(activeWidget); iter++;
}
for (auto i = iter; i < path1.size(); i++) {
path1[i]->mouseLeave();
}
for (auto i = iter; i < path2.size(); i++) {
path2[i]->mouseEnter();
}
} else {
for (auto iter = mInFocusWidget; iter; iter = iter->mParent) {
iter->mouseEnter();
} }
} }
} }
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::clearProcessedFlag(const Buffer<ActivePath>& paths) { void RootWidget::updateWidget(Widget* widget) {
for (auto path : paths) { mTriggeredWidgets.insert(widget);
for (auto widget : path->path) { }
widget->widget->mProcessedFlag = false;
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); canvas.text(mText.c_str(), getRelativeArea(), mSize, Canvas::LC, mPadding, mColor);
} }
ButtonWidget::ButtonWidget() { ButtonWidget::ButtonWidget() {
mAction = []() { mAction = []() {
printf("Button Pressed!\n"); 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) { 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) { void ButtonWidget::process(const EventHandler& eventHandler) {
if (getRelativeArea().isInside(eventHandler.getPointer())) { if (getArea().isInside(eventHandler.getPointer())) {
if (eventHandler.isPressed(InputID::MOUSE1)) { if (eventHandler.isPressed(InputID::MOUSE1)) {
mAction(); mAction();
} }
mColorAnimated.setTargetColor(mColor);
} else {
mColorAnimated.setTargetColor(mColorHovered);
} }
mColorAnimated.updateCurrentRect();
setActive(needUpdate());
} }
void ButtonWidget::draw(Canvas& canvas) { void ButtonWidget::draw(Canvas& canvas) {
@ -47,10 +43,31 @@ void ButtonWidget::draw(Canvas& canvas) {
LabelWidget::draw(canvas); LabelWidget::draw(canvas);
} }
bool ButtonWidget::needUpdate() const { bool ButtonWidget::needsNextFrame() const {
return !mColorAnimated.checkAnimationShouldEnd(); return LabelWidget::needsNextFrame() || !mColorAnimated.shouldEndTransition();
} }
void ButtonWidget::finishAnimations() { void ButtonWidget::endAnimations() {
mColorAnimated.endAnimation(); 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; 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() { Widget::Widget() {
mArea.setTargetRect({ 10, 10, 100, 40, }); mArea.setTargetRect({ 10, 10, 100, 40, });
mArea.endAnimation(); mArea.endAnimation();
@ -9,10 +23,10 @@ Widget::Widget() {
void Widget::setArea(const RectF& area) { void Widget::setArea(const RectF& area) {
mArea.setTargetRect(area); mArea.setTargetRect(area);
}
void Widget::draw(Canvas& canvas) { if (!mArea.shouldEndTransition()) {
canvas.rect(mArea.getCurrentRect(), RGBA(1.f), 10); triggerWidgetUpdate();
}
} }
RectF Widget::getArea() const { RectF Widget::getArea() const {
@ -23,28 +37,47 @@ RectF Widget::getRelativeArea() const {
return { {}, mArea.getCurrentRect().size }; return { {}, mArea.getCurrentRect().size };
} }
void Widget::setActive(bool active) { void Widget::endAnimations() {
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() {
mArea.endAnimation(); 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;
}

View file

@ -12,9 +12,9 @@ namespace tp {
void draw(Canvas& canvas) override; void draw(Canvas& canvas) override;
void process(const EventHandler& events) override; void process(const EventHandler& events) override;
[[nodiscard]] bool needUpdate() const override; [[nodiscard]] bool needsNextFrame() const override;
private: private:
SpringRect mTestSpring; mutable SpringRect mTestSpring;
}; };
} }

View file

@ -1,22 +1,61 @@
#pragma once #pragma once
#include "Widget.hpp" #include "SimpleWidgets.hpp"
#include "LayoutWidget.hpp"
namespace tp { namespace tp {
class FloatingWidget : public Widget { class FloatingWidget : public Widget {
public: public:
FloatingWidget() = default; FloatingWidget() {
setDebug("float", { 0.0, 0.9, 0.1, 0.7 });
}
void process(const EventHandler& events) override; void process(const EventHandler& events) override;
void updateArea(RectF& area) const override; void adjustRect() override;
void draw(Canvas& canvas) override; void draw(Canvas& canvas) override;
RectF resizeHandleRect();
bool propagateEventsToChildren() const override;
private: private:
bool mIsFloating = false; bool mIsFloating = false;
bool mIsResizing = false;
halnf mHandleSize = 10;
halnf mHandlePadding = 2;
Vec2F mPointerStart; Vec2F mPointerStart;
Vec2F mPointerCurrent; Vec2F mPointerCurrent;
}; };
class FloatingMenu : public FloatingWidget {
public:
FloatingMenu() {
setDebug("float menu", { 0.0, 0.9, 0.1, 0.7 });
addChild(&mMenuLayout);
mMenuLayout.addChild(&mHeader);
mMenuLayout.addChild(&mBodyLayout);
addToMenu(&mTestButton);
mHeader.setText("Menu");
}
public:
void addToMenu(Widget* widget) {
mBodyLayout.addChild(widget);
}
private:
VerticalLayout mMenuLayout;
VerticalLayout mBodyLayout;
LabelWidget mHeader;
ButtonWidget mTestButton;
};
} }

View file

@ -0,0 +1,56 @@
#pragma once
#include "Widget.hpp"
namespace tp {
class DesktopLayout : public Widget {
public:
DesktopLayout() { setDebug("desktop layout", { 0, 0, 0.9, 0.9 }); }
void draw(Canvas& canvas) override {
// canvas.rect(getRelativeArea(), RGBA(0, 0, 0, 1));
}
void adjustRect() override {
// do nothing
}
void adjustChildrenRect() override {
// todo : adjust by viewport pos
}
[[nodiscard]] bool processesEvents() const override { return false; }
};
class VerticalLayout : public Widget {
public:
VerticalLayout() {
setDebug("vertical", { 0.8, 0.1, 0.1, 0.9 });
}
void draw(Canvas& canvas) override {
// canvas.frame(getRelativeArea(), RGBA(1));
}
void adjustChildrenRect() override {
auto content = &mChildren;
auto area = getRelativeArea();
auto iterRect = area.shrink(mPadding);
iterRect.height = ((iterRect.height + mGap) / (float) content->size()) - mGap;
for (auto widget :(*content)) {
widget->setArea(iterRect);
iterRect.y += iterRect.height + mGap;
}
}
[[nodiscard]] bool processesEvents() const override { return false; }
private:
halnf mGap = 10;
halnf mPadding = 10;
};
}

View file

@ -2,46 +2,54 @@
#include "Widget.hpp" #include "Widget.hpp"
#include <map>
#include <vector>
#include <set>
namespace tp { namespace tp {
class RootWidget { class RootWidget : public WidgetManagerInterface {
// path from leaf to root of widgets that need to be updated struct ActiveTreeNode {
struct ActivePath { ActiveTreeNode* parent = nullptr;
struct ActiveNode { std::set<ActiveTreeNode*> children;
Widget* widget = nullptr; Widget* widget = nullptr;
Vec2F globalPos;
}; };
Buffer<ActiveNode> path;
};
public: public:
RootWidget() = default; RootWidget() { setDebug("root", RGBA(1)); }
void setRootWidget(Widget* widget); void setRootWidget(Widget* widget);
void updateWidget(Widget*);
static void setWidgetArea(Widget& widget, const RectF& rect);
public:
void processFrame(EventHandler* events, const RectF& screenArea); void processFrame(EventHandler* events, const RectF& screenArea);
void drawFrame(Canvas& canvas); void drawFrame(Canvas& canvas);
[[nodiscard]] bool needsUpdate() const; [[nodiscard]] bool needsUpdate() const;
static void setWidgetArea(Widget& widget, const RectF& rect);
private: private:
void updateActivePaths(Buffer<ActivePath>* activePaths, const Vec2F& pointer); void updateTreeToProcess();
void updateAnimations(ActiveTreeNode* iter);
void drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos); void drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos);
void drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos);
void processPaths(const Buffer<ActivePath>& path, EventHandler& events); void findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer);
void clearProcessedFlag(const Buffer<ActivePath>& paths); void handleFocusChanges(EventHandler& events);
void getWidgetPath(Widget* widget, std::vector<Widget*>& out);
void processActiveTree(ActiveTreeNode* iter, EventHandler& events);
void processFocusItems(EventHandler& events);
void adjustSizes(ActiveTreeNode* iter);
private: private:
RectF mScreenArea;
Widget* mRoot = nullptr; Widget* mRoot = nullptr;
Buffer<Widget*> mActiveWidgets; // frame to frame changes
Buffer<ActivePath> mActivePaths[2]; // current and prev std::set<Widget*> mTriggeredWidgets;
bool mFlipFlop = false; std::map<Widget*, ActiveTreeNode> mWidgetsToProcess;
Widget* mInFocusWidget = nullptr;
bool mNeedUpdate = false; bool mDebug = false;
}; };
} }

View file

@ -7,14 +7,17 @@
namespace tp { namespace tp {
class LabelWidget : public Widget { class LabelWidget : public Widget {
public: public:
LabelWidget() = default; LabelWidget() {
setDebug("label", { 0.1, 0.1, 0.1, 0.1 });
}
void setText(const std::string& text); void setText(const std::string& text);
[[nodiscard]] const std::string& getText() const; [[nodiscard]] const std::string& getText() const;
void draw(Canvas& canvas) override; void draw(Canvas& canvas) override;
[[nodiscard]] bool isPassThroughEvents() const override { return true; } [[nodiscard]] bool processesEvents() const override { return false; }
private: private:
std::string mText = "Text"; std::string mText = "Text";
@ -33,11 +36,14 @@ namespace tp {
void process(const EventHandler& eventHandler) override; void process(const EventHandler& eventHandler) override;
void draw(Canvas& canvas) override; void draw(Canvas& canvas) override;
[[nodiscard]] bool isPassThroughEvents() const override { return false; } void mouseEnter() override;
void mouseLeave() override;
[[nodiscard]] bool needUpdate() const override; [[nodiscard]] bool processesEvents() const override { return true; }
[[nodiscard]] bool needsNextFrame() const override;
void finishAnimations() override; void endAnimations() override;
void updateAnimations() override;
private: private:
std::function<void()> mAction; std::function<void()> mAction;
@ -45,7 +51,7 @@ namespace tp {
SpringRect mColorAnimated; SpringRect mColorAnimated;
halnf mRounding = 5; halnf mRounding = 5;
RGBA mColor = { 1.0f, 0.0f, 0.0f, 1.f }; RGBA mColorHovered = { 0.0f, 0.4f, 0.4f, 1.f };
RGBA mColorHovered = { 0.0f, 0.0f, 0.0f, 1.f }; RGBA mColor = { 0.0f, 0.0f, 0.0f, 1.f };
}; };
} }

View file

@ -139,7 +139,7 @@ namespace tp {
mEndPos.updateCurrentPosition(); mEndPos.updateCurrentPosition();
} }
[[nodiscard]] bool checkAnimationShouldEnd() const { [[nodiscard]] bool shouldEndTransition() const {
return mStartPos.checkAnimationShouldEnd() && mEndPos.checkAnimationShouldEnd(); return mStartPos.checkAnimationShouldEnd() && mEndPos.checkAnimationShouldEnd();
} }

View file

@ -5,7 +5,11 @@
#include "EventHandler.hpp" #include "EventHandler.hpp"
#include "Graphics.hpp" #include "Graphics.hpp"
#include <vector>
namespace tp { namespace tp {
class WidgetManagerInterface;
class Widget { class Widget {
friend class RootWidget; friend class RootWidget;
@ -13,41 +17,68 @@ namespace tp {
Widget(); Widget();
virtual ~Widget() = default; virtual ~Widget() = default;
virtual void process(const EventHandler& events) {} void setDebug(const char* name, RGBA col) {
virtual void updateArea(RectF& area) const {} mName = name;
mDebugColor = col;
}
virtual void draw(Canvas& canvas); void addChild(Widget* child) {
virtual void drawOverlay(Canvas& canvas) {} mChildren.push_back(child);
child->mParent = this;
virtual bool isPassThroughEvents() const { return false; } triggerWidgetUpdate();
child->triggerWidgetUpdate();
}
[[nodiscard]] virtual bool needUpdate() const; WidgetManagerInterface* getRoot();
virtual void finishAnimations(); void triggerWidgetUpdate();
public: public:
[[nodiscard]] RectF getRelativeArea() const; virtual void process(const EventHandler& events) {}
void setActive(bool active); virtual void draw(Canvas& canvas) { canvas.rect(mArea.getCurrentRect(), RGBA(1.f), 10); }
virtual void drawOverlay(Canvas& canvas) {}
void addChild(Widget* widget); virtual void mouseEnter();
virtual void mouseLeave();
private: // size policies
virtual void adjustRect();
virtual void adjustChildrenRect();
[[nodiscard]] virtual bool processesEvents() const;
[[nodiscard]] virtual bool propagateEventsToChildren() const;
[[nodiscard]] virtual bool needsNextFrame() const;
virtual void endAnimations();
virtual void updateAnimations();
public:
[[nodiscard]] RectF getArea() const; [[nodiscard]] RectF getArea() const;
[[nodiscard]] RectF getRelativeArea() const;
void setArea(const RectF& area); void setArea(const RectF& area);
void removeChild(Widget* widget);
private: protected:
Widget* mParent = nullptr;
std::vector<Widget*> mChildren;
// relative to the parent // relative to the parent
SpringRect mArea; SpringRect mArea;
// tree structure of widgets ualni mZValue = 0;
List<Widget*> mChildWidgets;
// if needed updates even though pointer is not in area bool mInFocus = false;
bool mIsActive = false;
Widget* mParent = nullptr; // debug
std::string mName = "widget base";
Vec2F mLocalPoint;
Vec2F mGlobalPoint;
RGBA mDebugColor = { 1, 1, 1, 0.3 };
};
bool mProcessedFlag = false; struct WidgetManagerInterface : public Widget {
virtual void updateWidget(Widget*) = 0;
}; };
} }