Reorganize code in widgets
This commit is contained in:
parent
1508a51cde
commit
7cf3328a40
31 changed files with 652 additions and 481 deletions
|
|
@ -34,13 +34,13 @@ namespace tp {
|
|||
}
|
||||
}
|
||||
|
||||
bool get(int1 idx) { return mFlags & (1l << idx); }
|
||||
[[nodiscard]] bool get(int1 idx) const { return mFlags & (1l << idx); }
|
||||
|
||||
void set(int1 idx, bool val) {
|
||||
if (val) {
|
||||
mFlags |= (1l << idx);
|
||||
mFlags |= (1 << idx);
|
||||
} else {
|
||||
mFlags &= ~(1l << idx);
|
||||
mFlags &= ~(1 << idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -241,4 +241,23 @@ SUITE(BaseModule) {
|
|||
}
|
||||
}
|
||||
|
||||
SUITE(BitFields) {
|
||||
TEST(Basic) {
|
||||
Bits<ualni> bits;
|
||||
|
||||
for (auto i = 0; i < 8; i ++) {
|
||||
bits.set((int1) i, true);
|
||||
CHECK(bits.get(i));
|
||||
}
|
||||
|
||||
bits.set(1, false);
|
||||
CHECK(!bits.get(1));
|
||||
|
||||
bits.set(3, false);
|
||||
CHECK(!bits.get(3));
|
||||
|
||||
bits.set(4, false);
|
||||
CHECK(!bits.get(4));
|
||||
}
|
||||
}
|
||||
int main() { return UnitTest::RunAllTests(); }
|
||||
|
|
@ -5,7 +5,7 @@ file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp")
|
|||
file(GLOB HEADERS "./public/*.hpp")
|
||||
|
||||
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ./public/layouts ./public/mangers ./public/widgets)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC Math Graphics Imgui)
|
||||
|
||||
### -------------------------- Applications -------------------------- ###
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ public:
|
|||
mButton5.setAction([this]() { mDockLayout.dockWidget(&mFloatingMenu, DockLayout::LEFT); });
|
||||
mButton6.setAction([this]() { mDockLayout.undockWidget(DockLayout::LEFT); });
|
||||
|
||||
mButton3.setAction([this]() { mDockLayout.toggleWidgetVisibility(DockLayout::LEFT); });
|
||||
// mButton4.setAction([this]() { mDockLayout.undockWidget(DockLayout::LEFT); });
|
||||
|
||||
mButton3.setText("toggle");
|
||||
mButton5.setText("dock");
|
||||
mButton6.setText("undock");
|
||||
|
||||
|
|
|
|||
96
WidgetsNew/private/Layout.cpp
Normal file
96
WidgetsNew/private/Layout.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include <algorithm>
|
||||
#include "Layout.hpp"
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
const RectF& WidgetLayout::getArea() const {
|
||||
return mWidget->mAreaCache;
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getAnimatedArea() const {
|
||||
return mWidget->getArea();
|
||||
}
|
||||
|
||||
void WidgetLayout::setArea(const RectF& area) {
|
||||
mWidget->setAreaCache(area);
|
||||
}
|
||||
|
||||
Widget* WidgetLayout::parent() const { return mWidget->mParent; }
|
||||
|
||||
const std::vector<Widget*>& WidgetLayout::children() const { return mWidget->mChildren; }
|
||||
|
||||
const Vec2F& WidgetLayout::getMinSize() { return mMinSize; }
|
||||
|
||||
void WidgetLayout::setMinSize(const Vec2F& size) { mMinSize = size; }
|
||||
|
||||
const Vec2<SizePolicy>& WidgetLayout::getSizePolicy() const { return mSizePolicy; }
|
||||
|
||||
void WidgetLayout::setSizePolicy(SizePolicy x, SizePolicy y) { mSizePolicy = { x, y }; }
|
||||
|
||||
RangeF WidgetLayout::pickRange(const RangeF& current, const RangeF& children, const RangeF& parent, bool vertical) const {
|
||||
RangeF out;
|
||||
|
||||
switch (mSizePolicy[vertical]) {
|
||||
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 WidgetLayout::clampMinMaxSize() {
|
||||
auto current = getArea();
|
||||
current.size.clamp(mMinSize, mMaxSize);
|
||||
setArea(current);
|
||||
}
|
||||
|
||||
RangeF WidgetLayout::clampRange(const RangeF& current, const RangeF& child, const RangeF& parent, bool vertical) const {
|
||||
auto out = current;
|
||||
|
||||
if (!children().empty()) {
|
||||
auto clampedChild = child;
|
||||
clampedChild.clamp(parent);
|
||||
out.clamp(clampedChild, parent);
|
||||
} else {
|
||||
out.clamp(parent);
|
||||
}
|
||||
|
||||
// clamp min max sizes
|
||||
auto len = clamp(current.size(), mMinSize[vertical], mMaxSize[vertical]);
|
||||
if (len != current.size()) {
|
||||
out.resizeFromCenter(len);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getChildrenEnclosure() const {
|
||||
RectF out;
|
||||
|
||||
if (children().empty()) {
|
||||
out = { getArea().center(), { 0, 0 } };
|
||||
} else {
|
||||
out = children().front()->getLayout()->getArea();
|
||||
for (auto child : children()) {
|
||||
out.expand(child->getLayout()->getArea());
|
||||
}
|
||||
out.pos += getArea().pos;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getAvailableChildArea() const {
|
||||
return getArea().relative();
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getParentEnclosure() const {
|
||||
DEBUG_ASSERT(parent())
|
||||
if (!parent()) return { { 0, 0 }, mMaxSize };
|
||||
auto out = parent()->getLayout()->getAvailableChildArea();
|
||||
return out;
|
||||
}
|
||||
|
|
@ -1,169 +1,70 @@
|
|||
|
||||
#include "RootWidget.hpp"
|
||||
#include "BasicLayout.hpp"
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include "RootWidget.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
WidgetLayout* WidgetManagerInterface::defaultLayout(Widget* widget) {
|
||||
return new BasicLayout(widget);
|
||||
}
|
||||
|
||||
RootWidget::RootWidget() {
|
||||
setDebug("root", RGBA(1));
|
||||
}
|
||||
|
||||
void RootWidget::setRootWidget(Widget* widget) {
|
||||
mRoot = widget;
|
||||
widget->mParent = this;
|
||||
}
|
||||
|
||||
void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) {
|
||||
mScreenArea = screenArea;
|
||||
mRoot->setArea(screenArea);
|
||||
|
||||
if (mDebug) {
|
||||
mScreenArea.size -= { 400, 0 };
|
||||
events->setEnableKeyEvents(true);
|
||||
if (events->isPressed(InputID::K)) mDebugStopProcessing = !mDebugStopProcessing;
|
||||
if (mDebugStopProcessing) return;
|
||||
}
|
||||
|
||||
mRoot->setArea(mScreenArea);
|
||||
if (!mDebugManager.update(this, *events)) return;
|
||||
|
||||
// construct hierarchy tree of widgets to process
|
||||
updateTreeToProcess();
|
||||
mUpdateManager.updateTreeToProcess(mRoot);
|
||||
|
||||
updateAnimations();
|
||||
updateAreaCache(mRoot, true);
|
||||
|
||||
// update all events and call all event processing callbacks
|
||||
events->setEnableKeyEvents(true);
|
||||
events->setCursorOrigin({ 0, 0 });
|
||||
processFocusItems(*events);
|
||||
|
||||
events->setEnableKeyEvents(false);
|
||||
events->setCursorOrigin({ 0, 0 });
|
||||
processActiveTree(mRoot, *events, { 0, 0 });
|
||||
mUpdateManager.processWidgets(mRoot, *events);
|
||||
|
||||
updateAreaCache(mRoot, true);
|
||||
|
||||
// update widget sizes base on individual size policies
|
||||
processLayout();
|
||||
mLayoutManager.adjust(mRoot);
|
||||
|
||||
updateAreaCache(mRoot, false);
|
||||
|
||||
// trigger some widgets by moise pointer
|
||||
handleFocusChanges(*events);
|
||||
mUpdateManager.handleFocusChanges(mRoot, *events);
|
||||
|
||||
// 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();
|
||||
auto end = !widget->needsNextFrame();
|
||||
if (end) {
|
||||
widget->endAnimations();
|
||||
widget->mDebug.triggerReason = "del";
|
||||
}
|
||||
return end;
|
||||
});
|
||||
mUpdateManager.clean();
|
||||
}
|
||||
|
||||
bool RootWidget::needsUpdate() const { return !mTriggeredWidgets.empty() || mDebugRedrawAlways; }
|
||||
|
||||
void RootWidget::debugDrawWidget(Widget* widget) {
|
||||
|
||||
ImGui::PushID(widget);
|
||||
if (ImGui::CollapsingHeader(widget->mDebug.id.c_str())) {
|
||||
|
||||
ImGui::Text("trigger reason: %s", widget->mDebug.triggerReason.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();
|
||||
bool RootWidget::needsUpdate() const {
|
||||
if (mDebugManager.isRedrawAlways()) return true;
|
||||
return mUpdateManager.isPendingUpdates();
|
||||
}
|
||||
|
||||
void RootWidget::drawFrame(Canvas& canvas) {
|
||||
// draw from top to bottom
|
||||
canvas.rect(mScreenArea, RGBA(0, 0, 0, 1));
|
||||
canvas.rect(mRoot->getAreaT(), RGBA(0, 0, 0, 1));
|
||||
|
||||
// draw all tree from top to bottom
|
||||
drawRecursion(canvas, mRoot, { 0, 0 });
|
||||
|
||||
ImGui::Checkbox("Draw debug", &mDebug);
|
||||
|
||||
if (mDebug) {
|
||||
drawDebug(canvas, mRoot, { 0, 0 }, 0);
|
||||
|
||||
ImGui::Text("Triggered Widgets: %i", (int) mTriggeredWidgets.size());
|
||||
ImGui::Text("Widgets To Process: %i", mDebugWidgetsToProcess);
|
||||
|
||||
ImGui::Text("To Toggle processing press k");
|
||||
ImGui::Checkbox("Stop processing", &mDebugStopProcessing);
|
||||
ImGui::Checkbox("Force new frames", &mDebugRedrawAlways);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mDebugManager.drawDebug(this, canvas);
|
||||
}
|
||||
|
||||
|
||||
void RootWidget::updateTreeToProcess() {
|
||||
dfs(mRoot, [](Widget* widget) {
|
||||
widget->mNeedsProcessing = false;
|
||||
});
|
||||
|
||||
for (auto& [widget, flag] : mTriggeredWidgets) {
|
||||
flag = true;
|
||||
|
||||
for (auto iter = widget; iter && iter != this; iter = iter->mParent) {
|
||||
iter->mNeedsProcessing = true;
|
||||
}
|
||||
}
|
||||
|
||||
mDebugWidgetsToProcess = 0;
|
||||
dfs(mRoot, [this](Widget*) {
|
||||
mDebugWidgetsToProcess++;
|
||||
});
|
||||
}
|
||||
|
||||
void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) {
|
||||
if (!active->mFlags.get(ENABLED)) return;
|
||||
|
||||
canvas.setOrigin(pos);
|
||||
canvas.pushClamp({ pos, active->getArea().size });
|
||||
|
||||
|
|
@ -179,31 +80,6 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos)
|
|||
active->drawOverlay(canvas);
|
||||
}
|
||||
|
||||
void RootWidget::drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder) {
|
||||
auto area = RectF{ pos, active->getAreaT().size };
|
||||
|
||||
if (active->mNeedsProcessing) {
|
||||
RGBA color = { 1, 0, 0, 1};
|
||||
canvas.frame(area, color);
|
||||
canvas.text((active->mDebug.id + ":" + std::to_string(depthOrder)).c_str(), area, 22, Canvas::Align::LC, 2, color);
|
||||
}
|
||||
|
||||
if (active->mNeedsProcessing || active->mInFocus) {
|
||||
canvas.circle(pos + active->mDebug.pLocal, 5, active->mDebug.col);
|
||||
canvas.circle(active->mDebug.pGlobal, 15, active->mDebug.col);
|
||||
}
|
||||
|
||||
if (active->mInFocus) {
|
||||
RGBA color = { 1, 0, 0, 0.3f};
|
||||
canvas.debugCross(area, color);
|
||||
}
|
||||
|
||||
int orderIdx = 0;
|
||||
for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) {
|
||||
drawDebug(canvas, child->data, pos + child->data->getAreaT().pos, orderIdx++);
|
||||
}
|
||||
}
|
||||
|
||||
void RootWidget::setWidgetArea(Widget& widget, const RectF& rect) {
|
||||
widget.mArea.setTargetRect(rect);
|
||||
widget.mArea.endAnimation();
|
||||
|
|
@ -215,85 +91,14 @@ void RootWidget::updateAnimations() {
|
|||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
events.setCursorOrigin({ 0, 0 });
|
||||
|
||||
findFocusWidget(mRoot, &mInFocusWidget, events.getPointer());
|
||||
|
||||
// if (mInFocusWidget == prevFocus) return;
|
||||
if (mInFocusWidget) updateWidget(mInFocusWidget, "focus entered");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
size_t mostCommonIdx = 0;
|
||||
if (!(path1.empty() || path2.empty())) {
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!iter->mArea.getTargetRect().isInside(pointer)) return;
|
||||
|
||||
*focus = iter;
|
||||
|
||||
for (auto child = iter->mDepthOrder.lastNode(); child; child = child->prev) {
|
||||
findFocusWidget(child->data, focus, pointer - iter->mArea.getTargetRect().pos);
|
||||
}
|
||||
}
|
||||
|
||||
void RootWidget::updateWidget(Widget* widget, const char* reason) {
|
||||
DEBUG_ASSERT(reason)
|
||||
widget->mDebug.triggerReason = reason;
|
||||
mTriggeredWidgets.insert({ widget, false });
|
||||
mUpdateManager.scheduleUpdate(widget, reason);
|
||||
}
|
||||
|
||||
void RootWidget::updateAreaCache(Widget* iter, bool read) {
|
||||
if (!iter || !iter->mNeedsProcessing) return;
|
||||
if (!iter || !iter->isUpdate()) return;
|
||||
|
||||
if (read) {
|
||||
iter->mAreaCache = iter->getAreaT();
|
||||
|
|
@ -315,69 +120,3 @@ void RootWidget::updateAreaCache(Widget* iter, bool read) {
|
|||
updateAreaCache(child.data(), read);
|
||||
}
|
||||
}
|
||||
|
||||
void RootWidget::processLayout() {
|
||||
mLayoutManager.adjust(mRoot);
|
||||
}
|
||||
|
||||
void RootWidget::processActiveTree(Widget* iter, EventHandler& events, Vec2F parent) {
|
||||
if (!iter || !iter->mNeedsProcessing) return;
|
||||
|
||||
auto current = parent + iter->getAreaT().pos;
|
||||
|
||||
if (!iter->mInFocus) {
|
||||
iter->mDebug.pGlobal = current;
|
||||
|
||||
events.setCursorOrigin(current);
|
||||
iter->process(events);
|
||||
}
|
||||
|
||||
for (auto child : iter->mDepthOrder) {
|
||||
processActiveTree(child.data(), events, current);
|
||||
}
|
||||
}
|
||||
|
||||
void RootWidget::processFocusItems(EventHandler& events) {
|
||||
if (!mInFocusWidget) return;
|
||||
|
||||
// FIXME : cringe
|
||||
|
||||
std::vector<Widget*> path;
|
||||
getWidgetPath(mInFocusWidget, path);
|
||||
|
||||
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]->getAreaT().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]);
|
||||
|
||||
widget->mDebug.pGlobal = widgetGlobalPos[iter];
|
||||
|
||||
if (!eventsProcessed && widget->processesEvents()) {
|
||||
events.setEnableKeyEvents(true);
|
||||
widget->process(events);
|
||||
|
||||
events.setEnableKeyEvents(false);
|
||||
eventsProcessed = true;
|
||||
} else {
|
||||
widget->process(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ using namespace tp;
|
|||
|
||||
Widget::Widget() {
|
||||
mArea.setTargetRect({ 100, 100, 10, 10 });
|
||||
setLayout(new BasicLayout(this));
|
||||
setLayout(WidgetManagerInterface::defaultLayout(this));
|
||||
mArea.endAnimation();
|
||||
|
||||
mFlags.set(ENABLED, true);
|
||||
}
|
||||
|
||||
Widget::~Widget() {
|
||||
|
|
@ -72,11 +74,13 @@ void Widget::bringToBack() {
|
|||
order.pushBack(node);
|
||||
}
|
||||
|
||||
void Widget::mouseEnter() { mInFocus = true; }
|
||||
void Widget::mouseLeave() { mInFocus = false; }
|
||||
void Widget::mouseEnter() { mFlags.set(IN_FOCUS, true); }
|
||||
void Widget::mouseLeave() { mFlags.set(IN_FOCUS, false); }
|
||||
|
||||
bool Widget::propagateEventsToChildren() const { return true; }
|
||||
|
||||
void Widget::setLayout(tp::WidgetLayout* layout) {
|
||||
delete mLayout;
|
||||
mLayout = layout;
|
||||
triggerWidgetUpdate("chane layout");
|
||||
}
|
||||
|
|
@ -112,15 +116,15 @@ RectF Widget::getArea() const { return mArea.getCurrentRect(); }
|
|||
|
||||
RectF Widget::getAreaT() const { return mArea.getTargetRect(); }
|
||||
|
||||
// const RectF& Widget::getAreaCache() const { return mAreaCache; }
|
||||
|
||||
RectF Widget::getRelativeArea() const { return { {}, mArea.getCurrentRect().size }; }
|
||||
|
||||
RectF Widget::getRelativeAreaT() const { return { {}, mArea.getTargetRect().size }; }
|
||||
|
||||
// RectF Widget::getRelativeAreaCache() const { return { {}, mAreaCache.size }; }
|
||||
|
||||
void Widget::setDebug(const char* name, RGBA col) {
|
||||
mDebug.id = name;
|
||||
mDebug.col = col;
|
||||
}
|
||||
|
||||
void Widget::setSizePolicy(SizePolicy x, SizePolicy y) {
|
||||
getLayout()->setSizePolicy(x, y);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,99 +1,11 @@
|
|||
#include <algorithm>
|
||||
#include "LayoutManager.hpp"
|
||||
|
||||
#include "BasicLayout.hpp"
|
||||
#include "Widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace tp;
|
||||
|
||||
const RectF& WidgetLayout::getArea() const {
|
||||
return mWidget->mAreaCache;
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getAnimatedArea() const {
|
||||
return mWidget->getArea();
|
||||
}
|
||||
|
||||
void WidgetLayout::setArea(const RectF& area) {
|
||||
mWidget->setAreaCache(area);
|
||||
}
|
||||
|
||||
Widget* WidgetLayout::parent() const { return mWidget->mParent; }
|
||||
|
||||
const std::vector<Widget*>& WidgetLayout::children() const { return mWidget->mChildren; }
|
||||
|
||||
const Vec2F& WidgetLayout::getMinSize() { return mMinSize; }
|
||||
|
||||
void WidgetLayout::setMinSize(const Vec2F& size) { mMinSize = size; }
|
||||
|
||||
const Vec2<SizePolicy>& WidgetLayout::getSizePolicy() const { return mSizePolicy; }
|
||||
|
||||
void WidgetLayout::setSizePolicy(SizePolicy x, SizePolicy y) { mSizePolicy = { x, y }; }
|
||||
|
||||
RangeF WidgetLayout::pickRange(const RangeF& current, const RangeF& children, const RangeF& parent, bool vertical) const {
|
||||
RangeF out;
|
||||
|
||||
switch (mSizePolicy[vertical]) {
|
||||
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 WidgetLayout::clampMinMaxSize() {
|
||||
auto current = getArea();
|
||||
current.size.clamp(mMinSize, mMaxSize);
|
||||
setArea(current);
|
||||
}
|
||||
|
||||
RangeF WidgetLayout::clampRange(const RangeF& current, const RangeF& child, const RangeF& parent, bool vertical) const {
|
||||
auto out = current;
|
||||
|
||||
if (!children().empty()) {
|
||||
auto clampedChild = child;
|
||||
clampedChild.clamp(parent);
|
||||
out.clamp(clampedChild, parent);
|
||||
} else {
|
||||
out.clamp(parent);
|
||||
}
|
||||
|
||||
// clamp min max sizes
|
||||
auto len = clamp(current.size(), mMinSize[vertical], mMaxSize[vertical]);
|
||||
if (len != current.size()) {
|
||||
out.resizeFromCenter(len);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getChildrenEnclosure() const {
|
||||
RectF out;
|
||||
|
||||
if (children().empty()) {
|
||||
out = { getArea().center(), { 0, 0 } };
|
||||
} else {
|
||||
out = children().front()->getLayout()->getArea();
|
||||
for (auto child : children()) {
|
||||
out.expand(child->getLayout()->getArea());
|
||||
}
|
||||
out.pos += getArea().pos;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getAvailableChildArea() const {
|
||||
return getArea().relative();
|
||||
}
|
||||
|
||||
RectF WidgetLayout::getParentEnclosure() const {
|
||||
DEBUG_ASSERT(parent())
|
||||
if (!parent()) return { { 0, 0 }, mMaxSize };
|
||||
auto out = parent()->getLayout()->getAvailableChildArea();
|
||||
return out;
|
||||
}
|
||||
|
||||
void BasicLayout::pickRect() {
|
||||
auto current = getArea();
|
||||
|
|
@ -210,15 +122,3 @@ RectF BasicLayout::getAvailableChildArea() const {
|
|||
auto out = WidgetLayout::getAvailableChildArea();
|
||||
return out.scaleFromCenter(mLayoutMargin, true);
|
||||
}
|
||||
|
||||
void LayoutManager::adjust(Widget* root) {
|
||||
Widget::dfs(
|
||||
root,
|
||||
[](Widget* widget) {
|
||||
widget->getLayout()->pickRect();
|
||||
},
|
||||
[](Widget* widget) {
|
||||
widget->getLayout()->adjustChildrenRect();
|
||||
}
|
||||
);
|
||||
}
|
||||
116
WidgetsNew/private/managers/DebugManager.cpp
Normal file
116
WidgetsNew/private/managers/DebugManager.cpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#include "DebugManager.hpp"
|
||||
#include "RootWidget.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
bool DebugManager::update(RootWidget* rootWidget, EventHandler& events) {
|
||||
if (mDebug) {
|
||||
auto area = rootWidget->mRoot->getAreaT();
|
||||
area.size -= { 400, 0 };
|
||||
rootWidget->mRoot->setArea(area);
|
||||
|
||||
events.setEnableKeyEvents(true);
|
||||
if (events.isPressed(InputID::K)) mDebugStopProcessing = !mDebugStopProcessing;
|
||||
if (mDebugStopProcessing) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebugManager::drawDebug(RootWidget* rootWidget, Canvas& canvas) {
|
||||
ImGui::Checkbox("Draw debug", &mDebug);
|
||||
|
||||
auto& upd = rootWidget->mUpdateManager;
|
||||
|
||||
if (mDebug) {
|
||||
recursiveDraw(canvas, rootWidget->mRoot, { 0, 0 }, 0);
|
||||
|
||||
ImGui::Text("Triggered Widgets: %i", (int) upd.mTriggeredWidgets.size());
|
||||
ImGui::Text("Widgets To Process: %i", upd.mDebugWidgetsToProcess);
|
||||
|
||||
ImGui::Text("To Toggle processing press k");
|
||||
ImGui::Checkbox("Stop processing", &mDebugStopProcessing);
|
||||
ImGui::Checkbox("Force new frames", &mDebugRedrawAlways);
|
||||
|
||||
if (ImGui::CollapsingHeader("Triggered")) {
|
||||
if (ImGui::BeginListBox("##triggered", { -FLT_MIN, 300 })) {
|
||||
for (auto widget : upd.mTriggeredWidgets) {
|
||||
widgetMenu(widget.first);
|
||||
}
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
}
|
||||
|
||||
if (upd.mInFocusWidget) {
|
||||
if (ImGui::CollapsingHeader("Under cursor")) {
|
||||
if (ImGui::BeginListBox("##under_cursor", { -FLT_MIN, 300 })) {
|
||||
for (auto widget = upd.mInFocusWidget; widget && widget->mParent; widget = widget->mParent) {
|
||||
widgetMenu(widget);
|
||||
}
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebugManager::recursiveDraw(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder) {
|
||||
auto area = RectF{ pos, active->getAreaT().size };
|
||||
|
||||
if (active->isUpdate()) {
|
||||
RGBA color = { 1, 0, 0, 1};
|
||||
canvas.frame(area, color);
|
||||
canvas.text((active->mDebug.id + ":" + std::to_string(depthOrder)).c_str(), area, 22, Canvas::Align::LC, 2, color);
|
||||
}
|
||||
|
||||
if (active->mFlags.get(Widget::IN_FOCUS)) {
|
||||
if (active->isUpdate()) {
|
||||
canvas.circle(pos + active->mDebug.pLocal, 5, active->mDebug.col);
|
||||
canvas.circle(active->mDebug.pGlobal, 15, active->mDebug.col);
|
||||
}
|
||||
|
||||
RGBA color = { 1, 0, 0, 0.3f};
|
||||
canvas.debugCross(area, color);
|
||||
}
|
||||
|
||||
int orderIdx = 0;
|
||||
for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) {
|
||||
recursiveDraw(canvas, child->data, pos + child->data->getAreaT().pos, orderIdx++);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugManager::widgetMenu(Widget* widget) {
|
||||
|
||||
ImGui::PushID(widget);
|
||||
if (ImGui::CollapsingHeader(widget->mDebug.id.c_str())) {
|
||||
|
||||
ImGui::Text("trigger reason: %s", widget->mDebug.triggerReason.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();
|
||||
}
|
||||
17
WidgetsNew/private/managers/LayoutManager.cpp
Normal file
17
WidgetsNew/private/managers/LayoutManager.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#include "LayoutManager.hpp"
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
void LayoutManager::adjust(Widget* root) {
|
||||
Widget::dfs(
|
||||
root,
|
||||
[](Widget* widget) {
|
||||
widget->getLayout()->pickRect();
|
||||
},
|
||||
[](Widget* widget) {
|
||||
widget->getLayout()->adjustChildrenRect();
|
||||
}
|
||||
);
|
||||
}
|
||||
191
WidgetsNew/private/managers/UpdateManager.cpp
Normal file
191
WidgetsNew/private/managers/UpdateManager.cpp
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#include "UpdateManager.hpp"
|
||||
|
||||
using namespace tp;
|
||||
|
||||
void UpdateManager::processWidgets(Widget* root, EventHandler& events) {
|
||||
events.setEnableKeyEvents(true);
|
||||
events.setCursorOrigin({ 0, 0 });
|
||||
|
||||
processFocusItems(events);
|
||||
|
||||
events.setEnableKeyEvents(false);
|
||||
events.setCursorOrigin({ 0, 0 });
|
||||
|
||||
processActiveTree(root, events, { 0, 0 });
|
||||
}
|
||||
|
||||
void UpdateManager::clean() {
|
||||
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();
|
||||
auto end = !widget->needsNextFrame();
|
||||
if (end) {
|
||||
widget->endAnimations();
|
||||
widget->mDebug.triggerReason = "del";
|
||||
}
|
||||
return end;
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateManager::scheduleUpdate(Widget* widget, const char* reason) {
|
||||
widget->mDebug.triggerReason = reason;
|
||||
mTriggeredWidgets.insert({ widget, false });
|
||||
}
|
||||
|
||||
void UpdateManager::updateTreeToProcess(Widget* root) {
|
||||
Widget::dfs(root, [](Widget* widget) {
|
||||
widget->mFlags.set(Widget::NEEDS_UPDATE, false);
|
||||
});
|
||||
|
||||
for (auto& [widget, flag] : mTriggeredWidgets) {
|
||||
flag = true;
|
||||
|
||||
for (auto iter = widget; iter && iter->mParent; iter = iter->mParent) {
|
||||
iter->mFlags.set(Widget::NEEDS_UPDATE, true);
|
||||
}
|
||||
}
|
||||
|
||||
mDebugWidgetsToProcess = 0;
|
||||
Widget::dfs(root, [this](Widget*) {
|
||||
mDebugWidgetsToProcess++;
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateManager::getWidgetPath(Widget* widget, std::vector<Widget*>& out) {
|
||||
if (!widget) return;
|
||||
|
||||
for (auto iter = widget; iter && iter->mParent; 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 UpdateManager::handleFocusChanges(Widget* root, EventHandler& events) {
|
||||
auto prevFocus = mInFocusWidget;
|
||||
|
||||
events.setCursorOrigin({ 0, 0 });
|
||||
|
||||
findFocusWidget(root, &mInFocusWidget, events.getPointer());
|
||||
|
||||
// if (mInFocusWidget == prevFocus) return;
|
||||
if (mInFocusWidget) scheduleUpdate(mInFocusWidget, "focus entered");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
size_t mostCommonIdx = 0;
|
||||
if (!(path1.empty() || path2.empty())) {
|
||||
while (path1[mostCommonIdx] == path2[mostCommonIdx] && mostCommonIdx < min(path1.size(), path2.size())) {
|
||||
mostCommonIdx++;
|
||||
}
|
||||
}
|
||||
mostCommonIdx--;
|
||||
|
||||
for (auto i = 0; i < path1.size(); i++) {
|
||||
path1[i]->mFlags.set(Widget::IN_FOCUS, false);
|
||||
if (i > mostCommonIdx && i < propLen1) path1[i]->mouseLeave();
|
||||
}
|
||||
|
||||
for (auto i = 0; i < path2.size(); i++) {
|
||||
path2[i]->mFlags.set(Widget::IN_FOCUS, true);
|
||||
if (i > mostCommonIdx && i < propLen2) path2[i]->mouseEnter();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateManager::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer) {
|
||||
if (!iter->mArea.getTargetRect().isInside(pointer) || !iter->mFlags.get(Widget::ENABLED)) return;
|
||||
|
||||
*focus = iter;
|
||||
|
||||
for (auto child = iter->mDepthOrder.lastNode(); child; child = child->prev) {
|
||||
findFocusWidget(child->data, focus, pointer - iter->mArea.getTargetRect().pos);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateManager::processActiveTree(Widget* iter, EventHandler& events, Vec2F parent) {
|
||||
if (!iter || !iter->isUpdate()) return;
|
||||
|
||||
auto current = parent + iter->getAreaT().pos;
|
||||
|
||||
if (!iter->mFlags.get(Widget::IN_FOCUS)) {
|
||||
iter->mDebug.pGlobal = current;
|
||||
|
||||
events.setCursorOrigin(current);
|
||||
iter->process(events);
|
||||
}
|
||||
|
||||
for (auto child : iter->mDepthOrder) {
|
||||
processActiveTree(child.data(), events, current);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateManager::processFocusItems(EventHandler& events) {
|
||||
if (!mInFocusWidget) return;
|
||||
|
||||
// FIXME : cringe
|
||||
|
||||
std::vector<Widget*> path;
|
||||
getWidgetPath(mInFocusWidget, path);
|
||||
|
||||
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]->getAreaT().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]);
|
||||
|
||||
widget->mDebug.pGlobal = widgetGlobalPos[iter];
|
||||
|
||||
if (!eventsProcessed && widget->processesEvents()) {
|
||||
events.setEnableKeyEvents(true);
|
||||
widget->process(events);
|
||||
|
||||
events.setEnableKeyEvents(false);
|
||||
eventsProcessed = true;
|
||||
} else {
|
||||
widget->process(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
using namespace tp;
|
||||
|
||||
DockWidget::DockWidget() {
|
||||
DockWidget::DockWidget() : Widget() {
|
||||
setDebug("dock", {});
|
||||
setLayout(new DockLayout(this));
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +40,13 @@ void DockWidget::setCenterWidget(Widget* widget) {
|
|||
}
|
||||
|
||||
void DockWidget::toggleWidgetVisibility(DockLayout::Side side) {
|
||||
if (layout()->sideExists(side)) {
|
||||
auto widget = layout()->getSideWidget(side);
|
||||
widget->setEnabled(!layout()->isSideVisible(side));
|
||||
widget->bringToBack();
|
||||
|
||||
layout()->toggleWidgetVisibility(side);
|
||||
}
|
||||
}
|
||||
|
||||
void DockWidget::process(const EventHandler& events) {
|
||||
|
|
@ -26,18 +26,18 @@ namespace tp {
|
|||
virtual void pickRect() = 0;
|
||||
virtual void clampRect() = 0;
|
||||
virtual void adjustChildrenRect() = 0;
|
||||
virtual RectF getAvailableChildArea() const;
|
||||
[[nodiscard]] virtual RectF getAvailableChildArea() const;
|
||||
|
||||
public:
|
||||
const Vec2F& getMinSize();
|
||||
void setMinSize(const Vec2F& size);
|
||||
|
||||
const Vec2<SizePolicy>& getSizePolicy() const;
|
||||
[[nodiscard]] const Vec2<SizePolicy>& getSizePolicy() const;
|
||||
void setSizePolicy(SizePolicy x, SizePolicy y);
|
||||
|
||||
public:
|
||||
[[nodiscard]] const RectF& getArea() const;
|
||||
RectF getAnimatedArea() const;
|
||||
[[nodiscard]] RectF getAnimatedArea() const;
|
||||
|
||||
void setArea(const RectF& area);
|
||||
[[nodiscard]] Widget* parent() const;
|
||||
|
|
@ -60,30 +60,4 @@ namespace tp {
|
|||
Vec2F mMinSize = { 50, 50 };
|
||||
Vec2F mMaxSize = { FLT_MAX / 2, FLT_MAX / 2 };
|
||||
};
|
||||
|
||||
class BasicLayout : public WidgetLayout {
|
||||
public:
|
||||
explicit BasicLayout(Widget* widget) : WidgetLayout(widget) {}
|
||||
|
||||
void pickRect() override;
|
||||
void clampRect() override;
|
||||
void adjustChildrenRect() override;
|
||||
RectF getAvailableChildArea() const override;
|
||||
|
||||
private:
|
||||
void adjustLayout(bool vertical);
|
||||
static halnf changeChildSize(Widget*, halnf diff, bool vertical);
|
||||
|
||||
private:
|
||||
LayoutPolicy mLayoutPolicy = LayoutPolicy::Vertical;
|
||||
halnf mLayoutGap = 5;
|
||||
halnf mLayoutMargin = 10;
|
||||
};
|
||||
|
||||
class LayoutManager {
|
||||
public:
|
||||
LayoutManager() = default;
|
||||
|
||||
void adjust(Widget* root);
|
||||
};
|
||||
}
|
||||
|
|
@ -2,54 +2,41 @@
|
|||
|
||||
#include "Widget.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include "UpdateManager.hpp"
|
||||
#include "LayoutManager.hpp"
|
||||
#include "DebugManager.hpp"
|
||||
|
||||
namespace tp {
|
||||
class RootWidget : public WidgetManagerInterface {
|
||||
friend DebugManager;
|
||||
|
||||
public:
|
||||
RootWidget() { setDebug("root", RGBA(1)); }
|
||||
RootWidget();
|
||||
|
||||
// User Interface
|
||||
public:
|
||||
void setRootWidget(Widget* widget);
|
||||
void updateWidget(Widget*, const char* reason = nullptr) override;
|
||||
|
||||
static void setWidgetArea(Widget& widget, const RectF& rect);
|
||||
|
||||
// Graphic Application Interface
|
||||
public:
|
||||
void processFrame(EventHandler* events, const RectF& screenArea);
|
||||
void drawFrame(Canvas& canvas);
|
||||
|
||||
[[nodiscard]] bool needsUpdate() const;
|
||||
|
||||
// Internals
|
||||
private:
|
||||
void updateTreeToProcess();
|
||||
void updateAnimations();
|
||||
void processLayout();
|
||||
void drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos);
|
||||
void drawDebug(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder);
|
||||
void findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer);
|
||||
void handleFocusChanges(EventHandler& events);
|
||||
void getWidgetPath(Widget* widget, std::vector<Widget*>& out);
|
||||
void processActiveTree(Widget* iter, EventHandler& events, Vec2F pos);
|
||||
void processFocusItems(EventHandler& events);
|
||||
|
||||
void updateWidget(Widget*, const char* reason = nullptr) override;
|
||||
void updateAnimations();
|
||||
void updateAreaCache(Widget* iter, bool read);
|
||||
static void debugDrawWidget(Widget* widget);
|
||||
|
||||
private:
|
||||
LayoutManager mLayoutManager;
|
||||
|
||||
RectF mScreenArea;
|
||||
Widget* mRoot = nullptr;
|
||||
|
||||
// frame to frame changes
|
||||
std::map<Widget*, bool> mTriggeredWidgets;
|
||||
Widget* mInFocusWidget = nullptr;
|
||||
|
||||
bool mDebug = true;
|
||||
bool mDebugStopProcessing = false;
|
||||
bool mDebugRedrawAlways = false;
|
||||
int mDebugWidgetsToProcess = 0;
|
||||
LayoutManager mLayoutManager;
|
||||
UpdateManager mUpdateManager;
|
||||
DebugManager mDebugManager;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "SpringAnimations.hpp"
|
||||
#include "LayoutManager.hpp"
|
||||
|
||||
#include "Layout.hpp"
|
||||
|
||||
#include "EventHandler.hpp"
|
||||
#include "Graphics.hpp"
|
||||
|
|
@ -10,14 +11,32 @@
|
|||
#include <functional>
|
||||
|
||||
namespace tp {
|
||||
class WidgetManagerInterface;
|
||||
class WidgetLayout;
|
||||
|
||||
class LayoutManager;
|
||||
class UpdateManager;
|
||||
class DebugManager;
|
||||
|
||||
class WidgetManagerInterface;
|
||||
class RootWidget;
|
||||
|
||||
class Widget {
|
||||
friend RootWidget;
|
||||
|
||||
friend LayoutManager;
|
||||
friend UpdateManager;
|
||||
friend DebugManager;
|
||||
|
||||
using BitField = Bits<ualni>;
|
||||
|
||||
using DFSAction = std::function<void(Widget*)>;
|
||||
|
||||
enum Flags : int1 {
|
||||
ENABLED = 0,
|
||||
NEEDS_UPDATE,
|
||||
IN_FOCUS,
|
||||
TRIGGERED,
|
||||
};
|
||||
|
||||
public:
|
||||
Widget();
|
||||
|
|
@ -30,10 +49,12 @@ namespace tp {
|
|||
void bringToBack();
|
||||
|
||||
void setLayout(WidgetLayout* layout);
|
||||
void setSizePolicy(SizePolicy x, SizePolicy y) { getLayout()->setSizePolicy(x, y); }
|
||||
void setSizePolicy(SizePolicy x, SizePolicy y);
|
||||
|
||||
void setEnabled(bool val) { mFlags.set(ENABLED, val); }
|
||||
|
||||
WidgetLayout* getLayout();
|
||||
const WidgetLayout* getLayout() const;
|
||||
[[nodiscard]] const WidgetLayout* getLayout() const;
|
||||
|
||||
void triggerWidgetUpdate(const char* reason = nullptr);
|
||||
|
||||
|
|
@ -58,20 +79,19 @@ namespace tp {
|
|||
public:
|
||||
[[nodiscard]] RectF getArea() const;
|
||||
[[nodiscard]] RectF getAreaT() const;
|
||||
// [[nodiscard]] const 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);
|
||||
|
||||
private:
|
||||
using DFSAction = std::function<void(Widget*)>;
|
||||
[[nodiscard]] bool isUpdate() const { return mFlags.get(ENABLED) && mFlags.get(NEEDS_UPDATE); }
|
||||
[[nodiscard]] bool isDraw() const { return mFlags.get(ENABLED); }
|
||||
|
||||
static void dfs(Widget* iter, const DFSAction& before, const DFSAction& after = [](auto){}) {
|
||||
if (!iter->mNeedsProcessing) return;
|
||||
if (!iter->isUpdate()) return;
|
||||
|
||||
before(iter);
|
||||
|
||||
|
|
@ -96,10 +116,7 @@ namespace tp {
|
|||
|
||||
WidgetLayout* mLayout = nullptr;
|
||||
|
||||
// TODO : make bitfield processing flags
|
||||
bool mInFocus = false;
|
||||
bool mNeedsProcessing = false;
|
||||
bool mTriggered = false;
|
||||
BitField mFlags;
|
||||
|
||||
// debug
|
||||
struct {
|
||||
|
|
@ -113,5 +130,6 @@ namespace tp {
|
|||
|
||||
struct WidgetManagerInterface : public Widget {
|
||||
virtual void updateWidget(Widget*, const char* reason) = 0;
|
||||
static WidgetLayout* defaultLayout(Widget* widget);
|
||||
};
|
||||
}
|
||||
25
WidgetsNew/public/layouts/BasicLayout.hpp
Normal file
25
WidgetsNew/public/layouts/BasicLayout.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "Layout.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
class BasicLayout : public WidgetLayout {
|
||||
public:
|
||||
explicit BasicLayout(Widget* widget) : WidgetLayout(widget) {}
|
||||
|
||||
void pickRect() override;
|
||||
void clampRect() override;
|
||||
void adjustChildrenRect() override;
|
||||
[[nodiscard]] RectF getAvailableChildArea() const override;
|
||||
|
||||
private:
|
||||
void adjustLayout(bool vertical);
|
||||
static halnf changeChildSize(Widget*, halnf diff, bool vertical);
|
||||
|
||||
private:
|
||||
LayoutPolicy mLayoutPolicy = LayoutPolicy::Vertical;
|
||||
halnf mLayoutGap = 5;
|
||||
halnf mLayoutMargin = 10;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "LayoutManager.hpp"
|
||||
#include "BasicLayout.hpp"
|
||||
|
||||
namespace tp {
|
||||
class FloatingLayout : public BasicLayout {
|
||||
public:
|
||||
FloatingLayout(Widget* widget) :
|
||||
explicit FloatingLayout(Widget* widget) :
|
||||
BasicLayout(widget) {}
|
||||
|
||||
public:
|
||||
25
WidgetsNew/public/mangers/DebugManager.hpp
Normal file
25
WidgetsNew/public/mangers/DebugManager.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
namespace tp {
|
||||
class DebugManager {
|
||||
public:
|
||||
DebugManager() = default;
|
||||
|
||||
bool isRedrawAlways() const { return mDebugRedrawAlways; }
|
||||
bool update(RootWidget* rootWidget, EventHandler& events);
|
||||
void drawDebug(RootWidget* rootWidget, Canvas& canvas);
|
||||
|
||||
private:
|
||||
void recursiveDraw(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder);
|
||||
|
||||
static void widgetMenu(Widget*);
|
||||
|
||||
private:
|
||||
// debug
|
||||
bool mDebug = true;
|
||||
bool mDebugStopProcessing = false;
|
||||
bool mDebugRedrawAlways = false;
|
||||
};
|
||||
}
|
||||
11
WidgetsNew/public/mangers/LayoutManager.hpp
Normal file
11
WidgetsNew/public/mangers/LayoutManager.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "Layout.hpp"
|
||||
|
||||
namespace tp {
|
||||
class LayoutManager {
|
||||
public:
|
||||
LayoutManager() = default;
|
||||
void adjust(Widget* root);
|
||||
};
|
||||
}
|
||||
38
WidgetsNew/public/mangers/UpdateManager.hpp
Normal file
38
WidgetsNew/public/mangers/UpdateManager.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace tp {
|
||||
class UpdateManager {
|
||||
friend DebugManager;
|
||||
|
||||
public:
|
||||
UpdateManager() = default;
|
||||
|
||||
void scheduleUpdate(Widget* widget, const char* reason);
|
||||
|
||||
[[nodiscard]] bool isPendingUpdates() const {
|
||||
return !mTriggeredWidgets.empty();
|
||||
}
|
||||
|
||||
void processWidgets(Widget* root, EventHandler& eventHandler);
|
||||
void clean();
|
||||
|
||||
void updateTreeToProcess(Widget* root);
|
||||
void handleFocusChanges(Widget* root, EventHandler& events);
|
||||
|
||||
void findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer);
|
||||
static void getWidgetPath(Widget* widget, std::vector<Widget*>& out);
|
||||
void processActiveTree(Widget* iter, EventHandler& events, Vec2F pos);
|
||||
void processFocusItems(EventHandler& events);
|
||||
|
||||
private:
|
||||
std::map<Widget*, bool> mTriggeredWidgets;
|
||||
Widget* mInFocusWidget = nullptr;
|
||||
|
||||
private:
|
||||
int mDebugWidgetsToProcess = 0;
|
||||
};
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
namespace tp {
|
||||
class FloatingWidget : public Widget {
|
||||
public:
|
||||
FloatingWidget() {
|
||||
FloatingWidget() : Widget() {
|
||||
setDebug("float", { 0.0, 0.9, 0.1, 1 });
|
||||
setLayout(new FloatingLayout(this));
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ namespace tp {
|
|||
|
||||
class FloatingMenu : public FloatingWidget {
|
||||
public:
|
||||
FloatingMenu() {
|
||||
FloatingMenu() : FloatingWidget() {
|
||||
setDebug("float menu", { 0.0, 0.9, 0.1, 0.7 });
|
||||
|
||||
// addChild(&mMenuLayout);
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
namespace tp {
|
||||
class LabelWidget : public Widget {
|
||||
public:
|
||||
LabelWidget() {
|
||||
LabelWidget() : Widget() {
|
||||
setDebug("label", { 0.1, 0.1, 0.1, 0.1 });
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue