Began implementing some little widget tree system.
This commit is contained in:
parent
d7c15660e5
commit
b29fefa6ec
@ -69,6 +69,15 @@ void UIApp::init(const AppInitArgs& args)
|
||||
{
|
||||
mGamepad.open(gamepads[0]);
|
||||
}
|
||||
|
||||
// init the UI
|
||||
mWidgetTree.init({.renderer = &mUIRenderer});
|
||||
mLabel = mWidgetTree.getRootWidget().emplaceChild<Label>({
|
||||
.text = "Test-Text!\nSecond $f00line$fff?",
|
||||
.color = glm::vec4(0.f, 1.f, 0.f, 1.f),
|
||||
.posX = 100,
|
||||
.posY = 100
|
||||
});
|
||||
}
|
||||
|
||||
void UIApp::update(const AppUpdateArgs& args)
|
||||
@ -76,6 +85,8 @@ void UIApp::update(const AppUpdateArgs& args)
|
||||
Application::update(args);
|
||||
|
||||
processInput(args);
|
||||
mLabel->setText(std::format("Rotation: {}{}\n$rUI vertices: $f00{}", mRotation > 180.f ? "$00f": "$fff" , mRotation, mNumVertices));
|
||||
mWidgetTree.revalidateWidgets();
|
||||
|
||||
// begin rendering
|
||||
sdlpp::GPUCommandBuffer cmdBuffer = mDevice.acquireCommandBuffer();
|
||||
|
@ -6,8 +6,10 @@
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include "./ui/ui_renderer.hpp"
|
||||
#include "../application.hpp"
|
||||
#include "../gui/label.hpp"
|
||||
#include "../gui/widget.hpp"
|
||||
#include "../gui/ui_renderer.hpp"
|
||||
#include "../sdlpp/gamepad.hpp"
|
||||
|
||||
namespace sdl_gpu_test
|
||||
@ -22,6 +24,9 @@ private:
|
||||
sdlpp::GPUSampler mSampler;
|
||||
|
||||
UIRenderer mUIRenderer;
|
||||
WidgetTree mWidgetTree;
|
||||
|
||||
Label* mLabel;
|
||||
|
||||
sdlpp::Gamepad mGamepad;
|
||||
Uint32 mNumVertices = 0;
|
||||
|
@ -5,6 +5,9 @@ src_files = Split("""
|
||||
main.cpp
|
||||
|
||||
application.cpp
|
||||
gui/label.cpp
|
||||
gui/ui_renderer.cpp
|
||||
gui/widget.cpp
|
||||
util/bitmap.cpp
|
||||
util/font_map.cpp
|
||||
util/mesh.cpp
|
||||
@ -16,7 +19,6 @@ src_files = Split("""
|
||||
4_textured_cube/app.cpp
|
||||
5_input/app.cpp
|
||||
6_ui/app.cpp
|
||||
6_ui/ui/ui_renderer.cpp
|
||||
""")
|
||||
|
||||
shader_files = env.Glob("#assets/shaders/glsl/*.frag") \
|
||||
|
@ -76,6 +76,7 @@ void Application::run(std::span<const char*> args)
|
||||
.secondsSinceStart = std::chrono::duration_cast<std::chrono::duration<float>>(frameTime - startTime).count(),
|
||||
.tickSeconds = std::chrono::duration_cast<std::chrono::duration<float>>(frameTime - lastFrameTime).count()
|
||||
});
|
||||
mTaskLoop.tick();
|
||||
lastFrameTime = frameTime;
|
||||
}
|
||||
cleanup({});
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <mijin/async/coroutine.hpp>
|
||||
#include <mijin/virtual_filesystem/stacked.hpp>
|
||||
|
||||
#include "./sdlpp/event.hpp"
|
||||
@ -36,6 +37,7 @@ protected:
|
||||
sdlpp::Window mWindow;
|
||||
sdlpp::GPUDevice mDevice;
|
||||
mijin::StackedFileSystemAdapter mFileSystem;
|
||||
mijin::SimpleTaskLoop mTaskLoop;
|
||||
bool mRunning = true;
|
||||
public:
|
||||
virtual ~Application() noexcept = default;
|
||||
@ -49,6 +51,9 @@ public:
|
||||
[[nodiscard]]
|
||||
mijin::StackedFileSystemAdapter& getFileSystem() noexcept { return mFileSystem; }
|
||||
|
||||
[[nodiscard]]
|
||||
mijin::SimpleTaskLoop& getTaskLoop() noexcept { return mTaskLoop; }
|
||||
|
||||
virtual void init(const AppInitArgs& args);
|
||||
virtual void cleanup(const AppCleanupArgs& args);
|
||||
virtual void update(const AppUpdateArgs& args);
|
||||
|
75
private/sdl_gpu_test/gui/label.cpp
Normal file
75
private/sdl_gpu_test/gui/label.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
|
||||
#include "./label.hpp"
|
||||
|
||||
namespace sdl_gpu_test
|
||||
{
|
||||
Label::Label(LabelCreateArgs args) : mText(std::move(args.text)), mColor(args.color), mPosX(args.posX), mPosY(args.posY)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Label::setText(std::string text)
|
||||
{
|
||||
if (text != mText)
|
||||
{
|
||||
mText = std::move(text);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::setColor(const glm::vec4& color)
|
||||
{
|
||||
if (color != mColor)
|
||||
{
|
||||
mColor = color;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::setPosX(int posX)
|
||||
{
|
||||
if (posX != mPosX)
|
||||
{
|
||||
mPosX = posX;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::setPosY(int posY)
|
||||
{
|
||||
if (posY != mPosY)
|
||||
{
|
||||
mPosY = posY;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::handleEnteredTree()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void Label::revalidate()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void Label::update()
|
||||
{
|
||||
if (mTree == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPrimitiveID != UIRenderer::UNSET_PRIMITIVE_ID)
|
||||
{
|
||||
mTree->getRenderer().removePrimitive(mPrimitiveID);
|
||||
}
|
||||
mTree->getRenderer().drawText({
|
||||
.x = mPosX,
|
||||
.y = mPosY,
|
||||
.text = mText,
|
||||
.color = mColor
|
||||
}, &mPrimitiveID);
|
||||
}
|
||||
}
|
58
private/sdl_gpu_test/gui/label.hpp
Normal file
58
private/sdl_gpu_test/gui/label.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_LABEL_HPP_INCLUDED)
|
||||
#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_LABEL_HPP_INCLUDED 1
|
||||
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#include "./widget.hpp"
|
||||
|
||||
namespace sdl_gpu_test
|
||||
{
|
||||
struct LabelCreateArgs
|
||||
{
|
||||
std::string text;
|
||||
glm::vec4 color = {1.f, 1.f, 1.f, 1.f};
|
||||
int posX = 0;
|
||||
int posY = 0;
|
||||
};
|
||||
|
||||
class Label : public Widget
|
||||
{
|
||||
public:
|
||||
using create_args_t = LabelCreateArgs;
|
||||
private:
|
||||
std::string mText;
|
||||
glm::vec4 mColor = {1.f, 1.f, 1.f, 1.f};
|
||||
int mPosX = 0;
|
||||
int mPosY = 0;
|
||||
UIRenderer::primitive_id_t mPrimitiveID = UIRenderer::UNSET_PRIMITIVE_ID;
|
||||
public:
|
||||
explicit Label(LabelCreateArgs args);
|
||||
|
||||
[[nodiscard]]
|
||||
const std::string& getText() const noexcept { return mText; }
|
||||
|
||||
[[nodiscard]]
|
||||
const glm::vec4& getColor() const noexcept { return mColor; }
|
||||
|
||||
[[nodiscard]]
|
||||
int getPosX() const noexcept { return mPosX; }
|
||||
|
||||
[[nodiscard]]
|
||||
int getPosY() const noexcept { return mPosY; }
|
||||
|
||||
void setText(std::string text);
|
||||
void setColor(const glm::vec4& color);
|
||||
void setPosX(int posX);
|
||||
void setPosY(int posY);
|
||||
|
||||
void handleEnteredTree() override;
|
||||
void revalidate() override;
|
||||
private:
|
||||
void update();
|
||||
};
|
||||
} // namespace sdl_gpu_test
|
||||
|
||||
#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_LABEL_HPP_INCLUDED)
|
@ -72,7 +72,7 @@ void UIRenderer::init(Application& application)
|
||||
.offset = offsetof(UIVertex, color)
|
||||
}
|
||||
};
|
||||
mUIPipeline.create(device, {
|
||||
mPipeline.create(device, {
|
||||
.vertexShader = vertexShader,
|
||||
.fragmentShader = fragmentShader,
|
||||
.vertexInputState = {
|
||||
@ -92,34 +92,24 @@ void UIRenderer::init(Application& application)
|
||||
.textureHeight = 256
|
||||
});
|
||||
|
||||
// render some test text
|
||||
drawText({
|
||||
.x = 100,
|
||||
.y = 100,
|
||||
.text = "Test-Text!\nSecond $f00line$fff?"
|
||||
});
|
||||
|
||||
// create UI vertex buffer
|
||||
mUIVertexBuffer.create(device, {
|
||||
.usage = {.vertex = true},
|
||||
.size = static_cast<Uint32>(mUIVertices.size() * sizeof(UIVertex))
|
||||
});
|
||||
application.uploadVertexData(mUIVertexBuffer, std::span(mUIVertices));
|
||||
|
||||
// create texture and sampler
|
||||
sdlpp::GPUTextureCreateArgs textureArgs = {
|
||||
.format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB,
|
||||
.usage = {.sampler = true}
|
||||
};
|
||||
mUITexture = application.loadTexture("bitmaps/ui.png", textureArgs);
|
||||
mTexture = application.loadTexture("bitmaps/ui.png", textureArgs);
|
||||
|
||||
mUISampler.create(device, {});
|
||||
mSampler.create(device, {});
|
||||
}
|
||||
|
||||
void UIRenderer::render(const UIRendererRenderArgs& args)
|
||||
{
|
||||
if (!mUIVertices.empty())
|
||||
if (!mVertices.empty())
|
||||
{
|
||||
if (mVerticesDirty)
|
||||
{
|
||||
uploadVertices();
|
||||
}
|
||||
const UIVertexShaderParameters uiVertexShaderParameters = {
|
||||
.windowSize = {
|
||||
static_cast<float>(args.targetTextureWidth), static_cast<float>(args.targetTextureHeight)
|
||||
@ -137,19 +127,24 @@ void UIRenderer::render(const UIRendererRenderArgs& args)
|
||||
static const glm::vec4 WHITE(1.f, 1.f, 1.f, 1.f);
|
||||
args.cmdBuffer->pushFragmentUniformData(0, std::span(&WHITE, 1));
|
||||
args.cmdBuffer->pushVertexUniformData(0, std::span(&uiVertexShaderParameters, 1));
|
||||
renderPass.bindFragmentSampler({.texture = mUITexture, .sampler = mUISampler});
|
||||
renderPass.bindGraphicsPipeline(mUIPipeline);
|
||||
renderPass.bindVertexBuffer({.buffer = mUIVertexBuffer});
|
||||
renderPass.drawPrimitives({.numVertices = static_cast<Uint32>(mUIVertices.size())});
|
||||
renderPass.bindFragmentSampler({.texture = mTexture, .sampler = mSampler});
|
||||
renderPass.bindGraphicsPipeline(mPipeline);
|
||||
renderPass.bindVertexBuffer({.buffer = mVertexBuffer});
|
||||
renderPass.drawPrimitives({.numVertices = static_cast<Uint32>(mVertices.size())});
|
||||
renderPass.end();
|
||||
}
|
||||
}
|
||||
|
||||
void UIRenderer::drawText(const DrawTextArgs& args)
|
||||
void UIRenderer::drawText(const DrawTextArgs& args, primitive_id_t* outPrimitiveId)
|
||||
{
|
||||
glm::vec4 color = args.color;
|
||||
unsigned posX = args.x;
|
||||
unsigned posY = args.y;
|
||||
int posX = args.x;
|
||||
int posY = args.y;
|
||||
|
||||
if (outPrimitiveId != nullptr && *outPrimitiveId == UNSET_PRIMITIVE_ID)
|
||||
{
|
||||
*outPrimitiveId = mNextOwner++;
|
||||
}
|
||||
|
||||
for (std::size_t pos = 0; pos < args.text.size(); ++pos)
|
||||
{
|
||||
@ -167,20 +162,32 @@ void UIRenderer::drawText(const DrawTextArgs& args)
|
||||
break;
|
||||
}
|
||||
chr = args.text[pos];
|
||||
if (pos < args.text.size() - 2
|
||||
&& mijin::isHexadecimalChar(chr)
|
||||
&& mijin::isHexadecimalChar(args.text[pos + 1])
|
||||
&& mijin::isHexadecimalChar(args.text[pos + 2]))
|
||||
switch (chr)
|
||||
{
|
||||
unsigned num = 0;
|
||||
(void) mijin::toNumber(args.text.substr(pos, 3), num, 16);
|
||||
const std::uint8_t red = static_cast<std::uint8_t>(num >> 8);
|
||||
const std::uint8_t green = static_cast<std::uint8_t>((num >> 4) & 0xF);
|
||||
const std::uint8_t blue = static_cast<std::uint8_t>(num & 0xF);
|
||||
color.r = static_cast<float>(red) / 15.f;
|
||||
color.g = static_cast<float>(green) / 15.f;
|
||||
color.b = static_cast<float>(blue) / 15.f;
|
||||
pos += 2;
|
||||
case 'r':
|
||||
color = args.color;
|
||||
break;
|
||||
default:
|
||||
if (pos < args.text.size() - 2
|
||||
&& mijin::isHexadecimalChar(chr)
|
||||
&& mijin::isHexadecimalChar(args.text[pos + 1])
|
||||
&& mijin::isHexadecimalChar(args.text[pos + 2]))
|
||||
{
|
||||
unsigned num = 0;
|
||||
(void) mijin::toNumber(args.text.substr(pos, 3), num, 16);
|
||||
const std::uint8_t red = static_cast<std::uint8_t>(num >> 8);
|
||||
const std::uint8_t green = static_cast<std::uint8_t>((num >> 4) & 0xF);
|
||||
const std::uint8_t blue = static_cast<std::uint8_t>(num & 0xF);
|
||||
color.r = static_cast<float>(red) / 15.f;
|
||||
color.g = static_cast<float>(green) / 15.f;
|
||||
color.b = static_cast<float>(blue) / 15.f;
|
||||
pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// what?
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case '\\':
|
||||
@ -197,14 +204,19 @@ void UIRenderer::drawText(const DrawTextArgs& args)
|
||||
.y = posY,
|
||||
.chr = chr,
|
||||
.color = color
|
||||
});
|
||||
}, outPrimitiveId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned UIRenderer::drawChar(const DrawCharArgs& args)
|
||||
unsigned UIRenderer::drawChar(const DrawCharArgs& args, primitive_id_t* outPrimitiveId)
|
||||
{
|
||||
if (outPrimitiveId != nullptr && *outPrimitiveId == UNSET_PRIMITIVE_ID)
|
||||
{
|
||||
*outPrimitiveId = mNextOwner++;
|
||||
}
|
||||
|
||||
const UVFontMapEntry& entry = mFontMap.entries[args.chr < 0 ? '_' : args.chr]; // TODO: more chars
|
||||
const glm::vec2 topLeft = {args.x + entry.xOffset, args.y + entry.yOffset};
|
||||
drawQuadInternal({
|
||||
@ -213,12 +225,31 @@ unsigned UIRenderer::drawChar(const DrawCharArgs& args)
|
||||
.uvTopLeft = {entry.uvX, entry.uvY},
|
||||
.uvBottomRight = {entry.uvX + entry.uvWidth, entry.uvY + entry.uvHeight},
|
||||
.color = args.color
|
||||
});
|
||||
}, outPrimitiveId == nullptr ? UNSET_PRIMITIVE_ID : *outPrimitiveId);
|
||||
|
||||
return entry.xAdvance;
|
||||
}
|
||||
|
||||
void UIRenderer::drawQuadInternal(const DrawQuadInternalArgs& args)
|
||||
bool UIRenderer::removePrimitive(primitive_id_t primitiveId)
|
||||
{
|
||||
bool didRemove = false;
|
||||
for (int triangleIdx = static_cast<int>(mTriangleOwners.size()) - 1; triangleIdx >= 0; --triangleIdx)
|
||||
{
|
||||
if (mTriangleOwners[triangleIdx] == primitiveId)
|
||||
{
|
||||
mTriangleOwners.erase(mTriangleOwners.begin() + triangleIdx);
|
||||
mVertices.erase(mVertices.begin() + 3 * triangleIdx, mVertices.begin() + 3 * (triangleIdx + 1));
|
||||
didRemove = true;
|
||||
}
|
||||
}
|
||||
if (didRemove)
|
||||
{
|
||||
mVerticesDirty = true;
|
||||
}
|
||||
return didRemove;
|
||||
}
|
||||
|
||||
void UIRenderer::drawQuadInternal(const DrawQuadInternalArgs& args, primitive_id_t primitiveId)
|
||||
{
|
||||
const UIVertex topLeft = {
|
||||
.pos = args.topLeft,
|
||||
@ -241,11 +272,41 @@ void UIRenderer::drawQuadInternal(const DrawQuadInternalArgs& args)
|
||||
.color = args.color
|
||||
};
|
||||
|
||||
mUIVertices.push_back(topLeft);
|
||||
mUIVertices.push_back(bottomLeft);
|
||||
mUIVertices.push_back(topRight);
|
||||
mUIVertices.push_back(bottomRight);
|
||||
mUIVertices.push_back(topRight);
|
||||
mUIVertices.push_back(bottomLeft);
|
||||
addTriangleInternal(topLeft, bottomLeft, topRight, primitiveId);
|
||||
addTriangleInternal(bottomRight, topRight, bottomLeft, primitiveId);
|
||||
|
||||
mVerticesDirty = true;
|
||||
}
|
||||
|
||||
void UIRenderer::addTriangleInternal(const UIVertex& v0, const UIVertex& v1, const UIVertex& v2, UIRenderer::primitive_id_t primitiveId)
|
||||
{
|
||||
mVertices.push_back(v0);
|
||||
mVertices.push_back(v1);
|
||||
mVertices.push_back(v2);
|
||||
mTriangleOwners.push_back(primitiveId);
|
||||
}
|
||||
|
||||
void UIRenderer::uploadVertices()
|
||||
{
|
||||
if (mVertexBufferSize < mVertices.size())
|
||||
{
|
||||
if (mVertexBufferSize > 0)
|
||||
{
|
||||
mVertexBuffer.destroy();
|
||||
}
|
||||
|
||||
mVertexBuffer.create(mApplication->getDevice(), {
|
||||
.usage = {.vertex = true},
|
||||
.size = static_cast<Uint32>(mVertices.size() * sizeof(UIVertex))
|
||||
});
|
||||
|
||||
mVertexBufferSize = mVertices.size();
|
||||
}
|
||||
|
||||
if (!mVertices.empty())
|
||||
{
|
||||
mApplication->uploadVertexData(mVertexBuffer, std::span(mVertices));
|
||||
}
|
||||
mVerticesDirty = false;
|
||||
}
|
||||
}
|
@ -6,9 +6,9 @@
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
#include "../../application.hpp"
|
||||
#include "../../sdlpp/gpu.hpp"
|
||||
#include "../../util/font_map.hpp"
|
||||
#include "../application.hpp"
|
||||
#include "../sdlpp/gpu.hpp"
|
||||
#include "../util/font_map.hpp"
|
||||
|
||||
namespace sdl_gpu_test::inline app6
|
||||
{
|
||||
@ -21,16 +21,16 @@ struct UIVertex
|
||||
|
||||
struct DrawTextArgs
|
||||
{
|
||||
unsigned x = 0;
|
||||
unsigned y = 0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
std::string_view text;
|
||||
glm::vec4 color = glm::vec4(1.f);
|
||||
};
|
||||
|
||||
struct DrawCharArgs
|
||||
{
|
||||
unsigned x = 0;
|
||||
unsigned y = 0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
char chr;
|
||||
glm::vec4 color = glm::vec4(1.f);
|
||||
};
|
||||
@ -45,20 +45,29 @@ struct UIRendererRenderArgs
|
||||
|
||||
class UIRenderer
|
||||
{
|
||||
public:
|
||||
using primitive_id_t = std::uint64_t;
|
||||
|
||||
static constexpr std::uint64_t UNSET_PRIMITIVE_ID = 0;
|
||||
private:
|
||||
Application* mApplication = nullptr;
|
||||
sdlpp::GPUBuffer mUIVertexBuffer;
|
||||
sdlpp::GPUGraphicsPipeline mUIPipeline;
|
||||
std::vector<UIVertex> mUIVertices;
|
||||
sdlpp::GPUTexture mUITexture;
|
||||
sdlpp::GPUSampler mUISampler;
|
||||
sdlpp::GPUBuffer mVertexBuffer;
|
||||
sdlpp::GPUGraphicsPipeline mPipeline;
|
||||
sdlpp::GPUTexture mTexture;
|
||||
sdlpp::GPUSampler mSampler;
|
||||
std::vector<UIVertex> mVertices;
|
||||
std::vector<primitive_id_t> mTriangleOwners;
|
||||
primitive_id_t mNextOwner = 1;
|
||||
UVFontMap mFontMap;
|
||||
bool mVerticesDirty = true;
|
||||
std::size_t mVertexBufferSize = 0;
|
||||
public:
|
||||
void init(Application& application);
|
||||
void render(const UIRendererRenderArgs& args);
|
||||
|
||||
void drawText(const DrawTextArgs& args);
|
||||
unsigned drawChar(const DrawCharArgs& args);
|
||||
void drawText(const DrawTextArgs& args, primitive_id_t* outPrimitiveId = nullptr);
|
||||
unsigned drawChar(const DrawCharArgs& args, primitive_id_t* outPrimitiveId = nullptr);
|
||||
bool removePrimitive(primitive_id_t primitiveId);
|
||||
private:
|
||||
struct DrawQuadInternalArgs
|
||||
{
|
||||
@ -68,7 +77,9 @@ private:
|
||||
glm::vec2 uvBottomRight;
|
||||
glm::vec4 color = glm::vec4(1.f);
|
||||
};
|
||||
void drawQuadInternal(const DrawQuadInternalArgs& args);
|
||||
void drawQuadInternal(const DrawQuadInternalArgs& args, primitive_id_t primitiveId);
|
||||
void addTriangleInternal(const UIVertex& v0, const UIVertex& v1, const UIVertex& v2, primitive_id_t primitiveId);
|
||||
void uploadVertices();
|
||||
};
|
||||
} // namespace sdl_gpu_test::inline app6
|
||||
|
67
private/sdl_gpu_test/gui/widget.cpp
Normal file
67
private/sdl_gpu_test/gui/widget.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
#include "./widget.hpp"
|
||||
|
||||
#include <mijin/debug/assert.hpp>
|
||||
|
||||
namespace sdl_gpu_test
|
||||
{
|
||||
void Widget::enterTree(WidgetTree* tree, ParentWidget* parent)
|
||||
{
|
||||
MIJIN_ASSERT(mTree == nullptr && mParent == nullptr, "Widget is already in a tree.");
|
||||
MIJIN_ASSERT_FATAL(tree != nullptr, "Missing argument: tree");
|
||||
|
||||
mTree = tree;
|
||||
mParent = parent;
|
||||
handleEnteredTree();
|
||||
}
|
||||
|
||||
void Widget::invalidate()
|
||||
{
|
||||
if (mTree != nullptr)
|
||||
{
|
||||
mTree->invalidateWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ParentWidget::handleEnteredTree()
|
||||
{
|
||||
for (widget_ptr_t& child : mChildren)
|
||||
{
|
||||
child->enterTree(mTree, this);
|
||||
}
|
||||
}
|
||||
|
||||
Widget* ParentWidget::addChild(widget_ptr_t&& child)
|
||||
{
|
||||
mChildren.push_back(std::move(child));
|
||||
|
||||
if (mTree != nullptr)
|
||||
{
|
||||
mChildren.back()->enterTree(mTree, this);
|
||||
}
|
||||
|
||||
return mChildren.back().get();
|
||||
}
|
||||
|
||||
void WidgetTree::init(const WidgetTreeInitArgs& args)
|
||||
{
|
||||
MIJIN_ASSERT_FATAL(args.renderer != nullptr, "Missing argument: renderer.");
|
||||
mRenderer = args.renderer;
|
||||
|
||||
mRootWidget.enterTree(this, nullptr);
|
||||
}
|
||||
|
||||
void WidgetTree::revalidateWidgets()
|
||||
{
|
||||
for (Widget* widget : mInvalidWidgets)
|
||||
{
|
||||
widget->revalidate();
|
||||
}
|
||||
mInvalidWidgets.clear();
|
||||
}
|
||||
|
||||
void WidgetTree::invalidateWidget(Widget* widget) noexcept
|
||||
{
|
||||
mInvalidWidgets.insert(widget);
|
||||
}
|
||||
}
|
76
private/sdl_gpu_test/gui/widget.hpp
Normal file
76
private/sdl_gpu_test/gui/widget.hpp
Normal file
@ -0,0 +1,76 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_WIDGET_HPP_INCLUDED)
|
||||
#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_WIDGET_HPP_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "./ui_renderer.hpp"
|
||||
|
||||
namespace sdl_gpu_test
|
||||
{
|
||||
class Widget
|
||||
{
|
||||
protected:
|
||||
class WidgetTree* mTree = nullptr;
|
||||
class ParentWidget* mParent = nullptr;
|
||||
public:
|
||||
virtual ~Widget() noexcept = default;
|
||||
|
||||
virtual void handleEnteredTree() {}
|
||||
virtual void revalidate() {}
|
||||
|
||||
void invalidate();
|
||||
private:
|
||||
void enterTree(class WidgetTree* tree, class ParentWidget* parent);
|
||||
|
||||
friend class ParentWidget;
|
||||
friend class WidgetTree;
|
||||
};
|
||||
using widget_ptr_t = std::unique_ptr<Widget>;
|
||||
|
||||
class ParentWidget : public Widget
|
||||
{
|
||||
private:
|
||||
std::vector<widget_ptr_t> mChildren;
|
||||
public:
|
||||
void handleEnteredTree() override;
|
||||
|
||||
Widget* addChild(widget_ptr_t&& child);
|
||||
|
||||
template<typename TChild>
|
||||
TChild* emplaceChild(typename TChild::create_args_t args)
|
||||
{
|
||||
return static_cast<TChild*>(addChild(std::make_unique<TChild>(args)));
|
||||
}
|
||||
};
|
||||
|
||||
struct WidgetTreeInitArgs
|
||||
{
|
||||
UIRenderer* renderer = nullptr;
|
||||
};
|
||||
|
||||
class WidgetTree
|
||||
{
|
||||
private:
|
||||
UIRenderer* mRenderer = nullptr;
|
||||
ParentWidget mRootWidget;
|
||||
std::unordered_set<Widget*> mInvalidWidgets;
|
||||
public:
|
||||
void init(const WidgetTreeInitArgs& args);
|
||||
void revalidateWidgets();
|
||||
|
||||
[[nodiscard]]
|
||||
UIRenderer& getRenderer() const noexcept { return *mRenderer; }
|
||||
|
||||
[[nodiscard]]
|
||||
ParentWidget& getRootWidget() noexcept { return mRootWidget; }
|
||||
|
||||
void invalidateWidget(Widget* widget) noexcept;
|
||||
};
|
||||
} // namespace sdl_gpu_test
|
||||
|
||||
#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_WIDGET_HPP_INCLUDED)
|
Loading…
x
Reference in New Issue
Block a user