Fix sizing issues and others

This commit is contained in:
IlyaShurupov 2024-10-11 01:48:21 +03:00 committed by Ilya Shurupov
parent 0b73f8037b
commit a36386cc20
12 changed files with 325 additions and 142 deletions

View file

@ -8,10 +8,20 @@
using namespace tp;
/*
* adjust child widgets in the layout view
* make scrollable area
* change Widget::clampMinMax function to include child enclosure
* refactor all sizing in Widget -> move to separate files
* refactor root widget -> reorganize code
*/
class WidgetApplication : public Application {
public:
WidgetApplication() {
setup2();
setup1();
}
void setup1() {
@ -52,6 +62,21 @@ public:
// mLayoutWidget.addChild(&mLabel);
}
void setup3() {
mRootWidget.setRootWidget(&mDesktopLayout);
mDesktopLayout.addChild(&mFloatingWidget);
mFloatingWidget.addChild(&mButton);
mFloatingWidget.addChild(&mButton2);
mFloatingWidget.addChild(&mButton3);
mButton.setSizePolicy(Widget::SizePolicy::Minimal, Widget::SizePolicy::Expanding);
mButton2.setSizePolicy(Widget::SizePolicy::Minimal, Widget::SizePolicy::Expanding);
mButton3.setSizePolicy(Widget::SizePolicy::Expanding, Widget::SizePolicy::Expanding);
RootWidget::setWidgetArea(mFloatingWidget, { 100, 100, 400, 400 });
}
void setup2() {
mRootWidget.setRootWidget(&mDesktopLayout);
mDesktopLayout.addChild(&mWidget);
@ -60,17 +85,17 @@ public:
mWidget.addChild(&mButton2);
mWidget.addChild(&mButton3);
mButton.setSizePolicy(Widget::SizePolicy::Expand, Widget::SizePolicy::Contract);
mButton2.setSizePolicy(Widget::SizePolicy::Expand, Widget::SizePolicy::Contract);
mButton3.setSizePolicy(Widget::SizePolicy::Expand, Widget::SizePolicy::Contract);
mButton.setSizePolicy(Widget::SizePolicy::Expanding, Widget::SizePolicy::Minimal);
mButton2.setSizePolicy(Widget::SizePolicy::Expanding, Widget::SizePolicy::Minimal);
mButton3.setSizePolicy(Widget::SizePolicy::Expanding, Widget::SizePolicy::Minimal);
mWidget.setSizePolicy(Widget::SizePolicy::Contract, Widget::SizePolicy::Contract);
mWidget.setSizePolicy(Widget::SizePolicy::Minimal, Widget::SizePolicy::Minimal);
mWidget.setLayoutPolicy(Widget::LayoutPolicy::Horizontally);
mButton.setAction([this]() { mButton2.setMinSize(mButton2.getMinSize() + 10); });
mButton2.setAction([this]() { mButton.setMinSize(mButton.getMinSize() + 10); } );
RootWidget::setWidgetArea(mWidget, { 300, 100, 150, 200 });
RootWidget::setWidgetArea(mWidget, { 400, 100, 150, 200 });
}
void processFrame(EventHandler* eventHandler, halnf deltaTime) override {
@ -96,6 +121,7 @@ private:
ButtonWidget mButton3;
ButtonWidget mButton4;
FloatingWidget mFloatingWidget;
FloatingMenu mFloatingMenu;
FloatingMenu mFloatingMenu2;

View file

@ -38,10 +38,6 @@ void DockLayoutWidget::process(const EventHandler& events) {
if (!mPreviewWidget) {
handleResizeEvents(events);
}
calculateSideAreas();
calculateResizeHandles();
updateChildSideWidgets();
}
void DockLayoutWidget::draw(Canvas& canvas) {
@ -146,7 +142,7 @@ void DockLayoutWidget::toggleWidgetVisibility(Side side) {
}
void DockLayoutWidget::calculateSideAreas() {
auto startArea = getRelativeArea();
auto startArea = getRelativeAreaCache();
for (auto& sideWidget : mSideWidgets) {
const auto side = sideWidget.order;
@ -246,7 +242,7 @@ void DockLayoutWidget::handleResizeEvents(const EventHandler& events) {
sideWidget.resizeHandle.active = true;
mResizing = true;
triggerWidgetUpdate();
triggerWidgetUpdate("new docked child");
}
}
}
@ -275,12 +271,12 @@ void DockLayoutWidget::updateChildSideWidgets() {
if (!isSideVisible(Side(i))) {
// widget->mEnable = false;
} else {
widget->setArea(mSideWidgets[i].area);
widget->setAreaCache(mSideWidgets[i].area);
// widget->mEnable = true;
}
}
if (mCenterWidget) mCenterWidget->setArea(mCenterArea);
if (mCenterWidget) mCenterWidget->setAreaCache(mCenterArea);
}
// update depth order
@ -369,3 +365,9 @@ auto DockLayoutWidget::getSideFromWidget(Widget* widget) -> Side {
}
return DockLayoutWidget::NONE;
}
void DockLayoutWidget::adjustChildrenRect() {
calculateSideAreas();
calculateResizeHandles();
updateChildSideWidgets();
}

View file

@ -5,18 +5,19 @@ using namespace tp;
void FloatingWidget::process(const EventHandler& events) {
const auto relativePointer = events.getPointer();
bool inside = getRelativeArea().isInside(relativePointer);
bool inside = getRelativeAreaT().isInside(relativePointer);
if (inside && events.isPressed(InputID::MOUSE1)) {
mPointerStart = relativePointer;
mIsFloating = true;
if (resizeHandleRect().isInside(relativePointer)) {
mIsResizing = true;
}
bringToFront();
}
if (mIsFloating && resizeHandleRect().isInside(relativePointer)) {
mIsResizing = true;
}
if (mIsFloating && events.isReleased(InputID::MOUSE1)) {
mIsFloating = false;
@ -30,18 +31,25 @@ void FloatingWidget::process(const EventHandler& events) {
void FloatingWidget::pickRect() {
if (mIsFloating) {
auto area = getArea();
auto area = getAreaCache();
if (mIsResizing) {
mPointerCurrent.clamp(mMinSize, mMaxSize);
area.size = mPointerCurrent + mHandleSize / 2.f;
for (auto child : mChildren) {
child->triggerWidgetUpdate("floating menu resized");
}
} else if (mIsFloating) {
area.pos += mPointerCurrent - mPointerStart;
}
setArea(area);
} else {
Widget::pickRect();
setAreaCache(area);
}
clampMinMaxSize();
}
void FloatingWidget::draw(Canvas& canvas) {
@ -63,4 +71,8 @@ bool FloatingWidget::propagateEventsToChildren() const {
bool FloatingWidget::isFloating() const {
return mIsFloating;
}
}
bool FloatingWidget::needsNextFrame() const {
return Widget::needsNextFrame() || isFloating();
}

View file

@ -12,15 +12,15 @@ void RootWidget::setRootWidget(Widget* widget) {
}
void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
mScreenArea = screenArea;
if (mDebug) {
events->setEnableKeyEvents(true);
mScreenArea.size -= { 400, 0 };
if (events->isPressed(InputID::K)) mDebugStopProcessing = !mDebugStopProcessing;
if (mDebugStopProcessing) return;
}
mScreenArea = screenArea;
mRoot->setArea(screenArea);
mRoot->setArea(mScreenArea);
// construct hierarchy tree of widgets to process
updateTreeToProcess();
@ -31,24 +31,37 @@ void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
updateAnimations(root);
// update all events and call all event processing callbacks
EventHandler dummyEvents;
events->setEnableKeyEvents(true);
events->setCursorOrigin({ 0, 0 });
processFocusItems(*events);
processActiveTree(root, dummyEvents);
events->setEnableKeyEvents(false);
events->setCursorOrigin({ 0, 0 });
processActiveTree(root, *events, { 0, 0 });
updateAreaCache(root, true);
// update widget sizes base on individual size policies
adjustSizes(root);
updateAreaCache(root, false);
// trigger some widgets by moise pointer
handleFocusChanges(*events);
// check triggered widgets for removal
erase_if(mTriggeredWidgets, [this](auto widget) {
// check triggered widgets for removal
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();
if (mWidgetsToProcess.find(widget) == mWidgetsToProcess.end()) return false;
auto end = !widget->needsNextFrame();
if (end) {
widget->endAnimations();
widget->mTriggerReason = "del";
}
return end;
});
@ -56,6 +69,40 @@ void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
bool RootWidget::needsUpdate() const { return !mTriggeredWidgets.empty() || mDebugRedrawAlways; }
void RootWidget::debugDrawWidget(Widget* widget) {
ImGui::PushID(widget);
if (ImGui::CollapsingHeader(widget->mName.c_str())) {
ImGui::Text("trigger reason: %s", widget->mTriggerReason.c_str());
auto area = widget->getAreaT();
if (ImGui::InputFloat4("rect", &area.x)) {
widget->setArea(area);
}
ImGui::InputFloat2("min size", &widget->mMinSize.x);
ImGui::InputFloat2("max size", &widget->mMaxSize.x);
int sizePolicyX = int(widget->mSizePolicy.x);
int sizePolicyY = int(widget->mSizePolicy.y);
int layout = int(widget->mLayoutPolicy);
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 (ImGui::Combo("Layout", &layout, "Passive\0Vertical\0Horizontal\0")) {
widget->setLayoutPolicy(LayoutPolicy(layout));
}
}
ImGui::PopID();
}
void RootWidget::drawFrame(Canvas& canvas) {
// draw from top to bottom
canvas.rect(mScreenArea, RGBA(0, 0, 0, 1));
@ -75,43 +122,33 @@ void RootWidget::drawFrame(Canvas& canvas) {
ImGui::Checkbox("Stop processing", &mDebugStopProcessing);
ImGui::Checkbox("Force new frames", &mDebugRedrawAlways);
if (mInFocusWidget) {
ImGui::Text("Item under cursor %s", mInFocusWidget->mName.c_str());
if (ImGui::BeginListBox("##empty", { -FLT_MIN, 0 })) {
for (auto widget = mInFocusWidget; widget && widget != this; widget = widget->mParent) {
if (ImGui::CollapsingHeader(widget->mName.c_str())) {
ImGui::InputFloat2("min size", &widget->mMinSize.x);
ImGui::InputFloat2("max size", &widget->mMaxSize.x);
int sizePolicyX = int(widget->mSizePolicy.x);
int sizePolicyY = int(widget->mSizePolicy.y);
int layout = int(widget->mLayoutPolicy);
if (ImGui::Combo("Size Policy X", &sizePolicyX, "Passive\0Expand\0Contract\0")) {
widget->setSizePolicy(SizePolicy(sizePolicyX), SizePolicy(sizePolicyY));
}
if (ImGui::Combo("Size Policy Y", &sizePolicyY, "Passive\0Expand\0Contract\0")) {
widget->setSizePolicy(SizePolicy(sizePolicyX), SizePolicy(sizePolicyY));
}
if (ImGui::Combo("Layout", &layout, "Passive\0Vertical\0Horizontal\0")) {
widget->setLayoutPolicy(LayoutPolicy(layout));
}
}
if (ImGui::CollapsingHeader("Triggered")) {
if (ImGui::BeginListBox("##triggered", { -FLT_MIN, 300 })) {
for (auto widget : mTriggeredWidgets) {
debugDrawWidget(widget.first);
}
ImGui::EndListBox();
}
}
if (mInFocusWidget) {
if (ImGui::CollapsingHeader("Under cursor")) {
if (ImGui::BeginListBox("##under_cursor", { -FLT_MIN, 300 })) {
for (auto widget = mInFocusWidget; widget && widget != this; widget = widget->mParent) {
debugDrawWidget(widget);
}
ImGui::EndListBox();
}
}
}
}
}
void RootWidget::updateTreeToProcess() {
mWidgetsToProcess.clear();
for (auto widget : mTriggeredWidgets) {
for (auto& [widget, flag] : mTriggeredWidgets) {
flag = true;
for (auto iter = widget; iter; iter = iter->mParent) {
if (iter->mParent) {
mWidgetsToProcess[iter->mParent].children.insert(&mWidgetsToProcess[iter]);
@ -139,24 +176,29 @@ void RootWidget::drawRecursion(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 };
auto area = RectF{ pos, active->getAreaT().size };
if (mWidgetsToProcess.find(active) != mWidgetsToProcess.end()) {
auto processed = mWidgetsToProcess.find(active) != mWidgetsToProcess.end();
if (processed) {
RGBA color = { 1, 0, 0, 1};
canvas.frame(area, color);
canvas.text((active->mName + ":" + std::to_string(depthOrder)).c_str(), area, 22, Canvas::Align::LC, 2, color);
}
if (processed || active->mInFocus) {
canvas.circle(pos + active->mLocalPoint, 5, active->mDebugColor);
canvas.circle(active->mGlobalPoint, 15, active->mDebugColor);
}
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);
}
int orderIdx = 0;
for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) {
drawDebug(canvas, child->data, pos + child->data->getArea().pos, orderIdx++);
drawDebug(canvas, child->data, pos + child->data->getAreaT().pos, orderIdx++);
}
}
@ -188,9 +230,12 @@ void RootWidget::getWidgetPath(Widget* widget, std::vector<Widget*>& out) {
void RootWidget::handleFocusChanges(EventHandler& events) {
auto prevFocus = mInFocusWidget;
events.setCursorOrigin({ 0, 0 });
findFocusWidget(mRoot, &mInFocusWidget, events.getPointer());
if (mInFocusWidget) updateWidget(mInFocusWidget);
// if (mInFocusWidget == prevFocus) return;
if (mInFocusWidget) updateWidget(mInFocusWidget, "focus entered");
std::vector<Widget*> path2;
getWidgetPath(mInFocusWidget, path2);
@ -213,21 +258,21 @@ void RootWidget::handleFocusChanges(EventHandler& events) {
}
size_t mostCommonIdx = 0;
if (!(path1.empty() || path2.empty())) {
while (path1[mostCommonIdx] == path2[mostCommonIdx]) {
while (path1[mostCommonIdx] == path2[mostCommonIdx] && mostCommonIdx < min(path1.size(), path2.size())) {
mostCommonIdx++;
}
}
mostCommonIdx--;
for (auto i = 0; i < path1.size(); i++) {
path1[i]->mInFocus = false;
if (i >= mostCommonIdx && i < propLen1) path1[i]->mouseLeave();
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();
if (i > mostCommonIdx && i < propLen2) path2[i]->mouseEnter();
}
}
@ -241,32 +286,62 @@ void RootWidget::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& poin
}
}
void RootWidget::updateWidget(Widget* widget) {
mTriggeredWidgets.insert(widget);
void RootWidget::updateWidget(Widget* widget, const char* reason) {
DEBUG_ASSERT(reason)
widget->mTriggerReason = reason;
mTriggeredWidgets.insert({ widget, false });
}
void RootWidget::updateAreaCache(ActiveTreeNode* iter, bool read) {
if (!iter) return;
if (read) {
iter->widget->mAreaCache = iter->widget->getAreaT();
} else {
iter->widget->setArea(iter->widget->mAreaCache);
iter->widget->mArea.updateCurrentRect();
}
for (auto child : iter->widget->mChildren) {
if (read) {
child->mAreaCache = child->getAreaT();
} else {
child->setArea(child->mAreaCache);
child->mArea.updateCurrentRect();
}
}
for (auto child : iter->children) {
updateAreaCache(child, read);
}
}
void RootWidget::adjustSizes(ActiveTreeNode* iter) {
if (!iter) return;
iter->widget->pickRect();
for (auto child : iter->children) {
adjustSizes(child);
}
iter->widget->pickRect();
iter->widget->adjustChildrenRect();
// iter->widget->pickRect();
}
void RootWidget::processActiveTree(ActiveTreeNode* iter, EventHandler& events) {
void RootWidget::processActiveTree(ActiveTreeNode* iter, EventHandler& events, Vec2F parent) {
if (!iter) return;
auto current = parent + iter->widget->getAreaT().pos;
if (!iter->widget->mInFocus) {
iter->widget->updateAnimations();
iter->widget->mGlobalPoint = current;
events.setCursorOrigin(current);
iter->widget->process(events);
}
for (auto child : iter->children) {
processActiveTree(child, events);
processActiveTree(child, events, current);
}
}
@ -290,8 +365,8 @@ void RootWidget::processFocusItems(EventHandler& events) {
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];
widgetGlobalPos[widget + 1] = widgetGlobalPos[widget] + path[widget + 1]->getAreaT().pos;
// path[widget]->mGlobalPoint = widgetGlobalPos[widget];
}
bool eventsProcessed = false;
@ -301,19 +376,15 @@ void RootWidget::processFocusItems(EventHandler& events) {
events.setCursorOrigin(widgetGlobalPos[iter]);
widget->mLocalPoint = events.getPointer();
widget->mGlobalPoint = 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

@ -33,7 +33,7 @@ void ButtonWidget::setAction(const std::function<void()>& action) {
void ButtonWidget::setColor(const RGBA& in) {
mColor = in;
mColorAnimated.setTargetColor(mColor);
triggerWidgetUpdate();
triggerWidgetUpdate("color changed");
}
void ButtonWidget::process(const EventHandler& eventHandler) {
@ -68,12 +68,14 @@ void ButtonWidget::mouseEnter() {
mColorAnimated.endAnimation();
mColorAnimated.setTargetColor(mColorHovered);
mColorAnimated.updateCurrentRect();
triggerWidgetUpdate();
// mColorAnimated.updateCurrentRect();
triggerWidgetUpdate("button hovered");
}
void ButtonWidget::mouseLeave() {
mColorAnimated.setTargetColor(mColor);
mColorAnimated.updateCurrentRect();
triggerWidgetUpdate();
//mColorAnimated.updateCurrentRect();
triggerWidgetUpdate("button out of focus");
}

View file

@ -12,9 +12,9 @@ WidgetManagerInterface* Widget::getRoot() {
return dynamic_cast<WidgetManagerInterface*>(iter);
}
void Widget::triggerWidgetUpdate() {
void Widget::triggerWidgetUpdate(const char* reason) {
if (auto root = getRoot()) {
root->updateWidget(this);
root->updateWidget(this, reason);
}
}
@ -23,11 +23,15 @@ Widget::Widget() {
mArea.endAnimation();
}
void Widget::setAreaCache(const tp::RectF& area) {
mAreaCache = area;
}
void Widget::setArea(const RectF& area) {
if (mArea.getTargetRect() == area) return;
mArea.setTargetRect(area);
triggerWidgetUpdate();
triggerWidgetUpdate("new area");
}
RectF Widget::getArea() const {
@ -38,6 +42,10 @@ RectF Widget::getAreaT() const {
return mArea.getTargetRect();
}
RectF Widget::getAreaCache() const {
return mAreaCache;
}
RectF Widget::getRelativeArea() const {
return { {}, mArea.getCurrentRect().size };
}
@ -46,12 +54,16 @@ RectF Widget::getRelativeAreaT() const {
return { {}, mArea.getTargetRect().size };
}
RectF Widget::getRelativeAreaCache() const {
return { {}, mAreaCache.size };
}
void Widget::endAnimations() {
mArea.endAnimation();
}
bool Widget::processesEvents() const {
return true;
return false;
}
void Widget::updateAnimations() {
@ -59,7 +71,7 @@ void Widget::updateAnimations() {
}
bool Widget::needsNextFrame() const {
return !mArea.shouldEndTransition();
return !mArea.shouldEndTransition(); // || mInFocus;
}
void Widget::bringToFront() {
@ -107,8 +119,8 @@ void Widget::addChild(Widget* child) {
child->mParent = this;
triggerWidgetUpdate();
child->triggerWidgetUpdate();
triggerWidgetUpdate("add child");
child->triggerWidgetUpdate("new parent");
}
void Widget::removeChild(Widget* child) {
@ -118,29 +130,29 @@ void Widget::removeChild(Widget* child) {
mDepthOrder.removeNode(node);
mChildren.erase(std::remove(mChildren.begin(), mChildren.end(), child), mChildren.end());
triggerWidgetUpdate();
child->triggerWidgetUpdate();
triggerWidgetUpdate("removed child");
child->triggerWidgetUpdate("parent changed");
}
void Widget::setSizePolicy(tp::Widget::SizePolicy x, tp::Widget::SizePolicy y) {
mSizePolicy = { x, y };
triggerWidgetUpdate();
triggerWidgetUpdate("chane size policy");
}
void Widget::setLayoutPolicy(LayoutPolicy layoutPolicy) {
mLayoutPolicy = layoutPolicy;
triggerWidgetUpdate();
triggerWidgetUpdate("new layout");
}
const Vec2F& Widget::getMinSize() { return mMinSize; }
void Widget::setMinSize(const Vec2F& size) {
mMinSize = size;
triggerWidgetUpdate();
triggerWidgetUpdate("new min size");
}
void Widget::pickRect() {
auto current = getAreaT();
auto current = getAreaCache();
auto children = getChildrenEnclosure();
auto parent = getParentEnclosure();
@ -150,21 +162,21 @@ void Widget::pickRect() {
auto newArea = RectF(rangeX, rangeY);
for (auto child : mChildren) {
child->setArea(child->getAreaT().move(current.pos - newArea.pos));
child->setAreaCache(child->getAreaCache().move(current.pos - newArea.pos));
}
setArea(newArea);
setAreaCache(newArea);
}
void Widget::clampRect() {
auto current = getAreaT();
auto current = getAreaCache();
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));
setAreaCache(RectF(rangeX, rangeY));
}
void Widget::adjustChildrenRect() {
@ -179,14 +191,14 @@ void Widget::adjustChildrenRect() {
halnf Widget::changeChildSize(tp::Widget* widget, halnf diff, bool vertical) {
auto prevSize = widget->getAreaT().size[vertical];
auto prevSize = widget->getAreaCache().size[vertical];
{
auto area = widget->getAreaT();
auto area = widget->getAreaCache();
area.size[vertical] += diff;
widget->setArea(area);
widget->clampRect();
widget->setAreaCache(area);
widget->clampMinMaxSize();
}
auto newSize = widget->getAreaT().size[vertical];
auto newSize = widget->getAreaCache().size[vertical];
return newSize - prevSize;
}
@ -197,12 +209,22 @@ void Widget::adjustLayout(bool vertical) {
Vec2F availableSize = getRelativeAreaT().size;
for (auto child : mChildren) {
contentSize += child->getAreaT().size;
if (child->mSizePolicy[vertical] == SizePolicy::Expand) {
if (child->mSizePolicy[vertical] == SizePolicy::Expanding) {
contributors.emplace_back( child, true );
auto area = child->getAreaCache();
area.size[vertical] = 0;
child->setAreaCache(area);
child->clampRect();
} else {
// child->triggerWidgetUpdate("expand child in layout");
}
contentSize += child->getAreaCache().size;
}
availableSize -= mLayoutGap * ((halnf) mChildren.size() - 1) + mLayoutMargin * 2;
auto diff = availableSize - contentSize;
// expand or contract as much as possible
@ -212,6 +234,7 @@ void Widget::adjustLayout(bool vertical) {
for (auto& contributor : contributors) {
if (!contributor.second) continue;
// contributor.first->endAnimations();
auto contribution = changeChildSize(contributor.first, quota[vertical], vertical);
if (contribution == 0) {
@ -228,12 +251,16 @@ void Widget::adjustLayout(bool vertical) {
}
// arrange
halnf iterPos = 0;
halnf iterPos = mLayoutMargin;
for (auto child : mChildren) {
auto area = child->getAreaT();
auto area = child->getAreaCache();
area.pos[vertical] = iterPos;
iterPos += area.size[vertical];
child->setArea(area);
iterPos += area.size[vertical] + mLayoutGap;
child->setAreaCache(area);
// child->updateAnimations();
// child->triggerWidgetUpdate("layout changed");
}
}
@ -241,15 +268,21 @@ RangeF Widget::pickRange(const RangeF& current, const RangeF& children, const Ra
RangeF out;
switch (mSizePolicy[vertical]) {
case SizePolicy::Passive: out = current; break;
case SizePolicy::Expand: out = parent; break;
case SizePolicy::Contract: out = children; break;
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 Widget::clampMinMaxSize() {
auto current = getAreaCache();
current.size.clamp(mMinSize, mMaxSize);
setAreaCache(current);
}
RangeF Widget::clampRange(const RangeF& current, const RangeF& children, const RangeF& parent, bool vertical) {
auto out = current;
@ -274,12 +307,13 @@ RectF Widget::getChildrenEnclosure() {
RectF out;
if (mChildren.empty()) {
out = { getAreaT().center(), { 0, 0 } };
out = { getAreaCache().center(), { 0, 0 } };
} else {
out = mChildren.front()->getAreaCache();
for (auto child : mChildren) {
out.expand(child->getAreaT());
out.expand(child->getAreaCache());
}
out.pos += getAreaT().pos;
out.pos += getAreaCache().pos;
}
return out;
@ -288,5 +322,10 @@ RectF Widget::getChildrenEnclosure() {
RectF Widget::getParentEnclosure() {
DEBUG_ASSERT(mParent);
if (!mParent) return { { 0, 0 }, mMaxSize };
return mParent->getRelativeAreaT();
auto out = mParent->getRelativeAreaT();
if (mParent->mLayoutPolicy != LayoutPolicy::Passive) {
return out.scaleFromCenter(mParent->mLayoutMargin, true);
}
return out;
}

View file

@ -36,7 +36,7 @@ namespace tp {
public:
void pickRect() override {}
void adjustChildrenRect() override {}
void adjustChildrenRect() override;
void process(const EventHandler& events) override;
void draw(Canvas& canvas) override;

View file

@ -7,7 +7,10 @@ namespace tp {
class FloatingWidget : public Widget {
public:
FloatingWidget() {
setDebug("float", { 0.0, 0.9, 0.1, 0.7 });
setDebug("float", { 0.0, 0.9, 0.1, 1 });
// mSizePolicy = { SizePolicy::Contract, SizePolicy::Contract };
mLayoutPolicy = LayoutPolicy::Horizontally;
}
void process(const EventHandler& events) override;
@ -18,7 +21,10 @@ namespace tp {
RectF resizeHandleRect();
[[nodiscard]] bool needsNextFrame() const override;
[[nodiscard]] bool propagateEventsToChildren() const override;
[[nodiscard]] bool processesEvents() const override { return true; }
[[nodiscard]] bool isFloating() const;
@ -38,24 +44,31 @@ namespace tp {
FloatingMenu() {
setDebug("float menu", { 0.0, 0.9, 0.1, 0.7 });
addChild(&mMenuLayout);
// addChild(&mMenuLayout);
mMenuLayout.addChild(&mHeader);
mMenuLayout.addChild(&mBodyLayout);
addChild(&mHeader);
addChild(&mBodyLayout);
mHeader.setText("Menu");
mHeader.setSizePolicy(SizePolicy::Expanding, SizePolicy::Minimal);
mBodyLayout.setSizePolicy(SizePolicy::Expanding, SizePolicy::Expanding);
setLayoutPolicy(LayoutPolicy::Vertically);
mBodyLayout.setLayoutPolicy(LayoutPolicy::Vertically);
}
public:
void addToMenu(Widget* widget) {
widget->setSizePolicy(SizePolicy::Expanding, SizePolicy::Minimal);
mBodyLayout.addChild(widget);
}
private:
VerticalLayout mMenuLayout;
VerticalLayout mBodyLayout;
// VerticalLayout mMenuLayout;
Widget mBodyLayout;
LabelWidget mHeader;
ButtonWidget mTestButton;
// ButtonWidget mTestButton;
};
}

View file

@ -19,7 +19,7 @@ namespace tp {
RootWidget() { setDebug("root", RGBA(1)); }
void setRootWidget(Widget* widget);
void updateWidget(Widget*);
void updateWidget(Widget*, const char* reason = nullptr) override;
static void setWidgetArea(Widget& widget, const RectF& rect);
@ -37,16 +37,18 @@ namespace tp {
void findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer);
void handleFocusChanges(EventHandler& events);
void getWidgetPath(Widget* widget, std::vector<Widget*>& out);
void processActiveTree(ActiveTreeNode* iter, EventHandler& events);
void processActiveTree(ActiveTreeNode* iter, EventHandler& events, Vec2F pos);
void processFocusItems(EventHandler& events);
void adjustSizes(ActiveTreeNode* iter);
void updateAreaCache(ActiveTreeNode* iter, bool read);
static void debugDrawWidget(Widget* widget);
private:
RectF mScreenArea;
Widget* mRoot = nullptr;
// frame to frame changes
std::set<Widget*> mTriggeredWidgets;
std::map<Widget*, bool> mTriggeredWidgets;
std::map<Widget*, ActiveTreeNode> mWidgetsToProcess;
Widget* mInFocusWidget = nullptr;

View file

@ -15,9 +15,9 @@ namespace tp {
public:
enum class SizePolicy {
Passive,
Expand,
Contract,
Fixed,
Expanding,
Minimal,
};
enum class LayoutPolicy {
@ -40,7 +40,7 @@ namespace tp {
WidgetManagerInterface* getRoot();
void triggerWidgetUpdate();
void triggerWidgetUpdate(const char* reason = nullptr);
void bringToFront();
void bringToBack();
@ -67,6 +67,9 @@ namespace tp {
// resizing
RangeF pickRange(const RangeF& current, const RangeF& children, const RangeF& parent, bool vertical);
RangeF clampRange(const RangeF& current, const RangeF& children, const RangeF& parent, bool vertical);
void clampMinMaxSize();
RectF getChildrenEnclosure();
RectF getParentEnclosure();
void adjustLayout(bool vertical);
@ -83,11 +86,14 @@ namespace tp {
public:
[[nodiscard]] RectF getArea() const;
[[nodiscard]] RectF getAreaT() const;
[[nodiscard]] RectF getAreaCache() const;
[[nodiscard]] RectF getRelativeArea() const;
[[nodiscard]] RectF getRelativeAreaT() const;
[[nodiscard]] RectF getRelativeAreaCache() const;
void setArea(const RectF& area);
void setAreaCache(const RectF& area);
protected:
Widget* mParent = nullptr;
@ -100,20 +106,28 @@ namespace tp {
// resizing
LayoutPolicy mLayoutPolicy = LayoutPolicy::Passive;
Vec2<SizePolicy> mSizePolicy = { SizePolicy::Passive, SizePolicy::Passive };
Vec2<SizePolicy> mSizePolicy = { SizePolicy::Fixed, SizePolicy::Fixed };
Vec2F mMinSize = { 50, 50 };
Vec2F mMaxSize = { FLT_MAX / 2, FLT_MAX / 2 };
bool mInFocus = false;
halnf mLayoutGap = 5;
halnf mLayoutMargin = 10;
// cache
RectF mAreaCache;
// debug
int inOutCallbacks = 0;
std::string mName = "widget base";
Vec2F mLocalPoint;
Vec2F mGlobalPoint;
RGBA mDebugColor = { 1, 1, 1, 0.3 };
std::string mTriggerReason = "none";
};
struct WidgetManagerInterface : public Widget {
virtual void updateWidget(Widget*) = 0;
virtual void updateWidget(Widget*, const char* reason = nullptr) = 0;
};
}