Added quad rendering and a (WIP) button type.

This commit is contained in:
Patrick 2024-09-19 16:11:58 +02:00
parent c469772afa
commit eb47d92099
9 changed files with 300 additions and 12 deletions

BIN
assets/bitmaps/button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

View File

@ -1,2 +1,2 @@
symtex_glyphs ../fonts/symtex.png
sdl_logo sdl.png
button button.png

View File

@ -78,6 +78,11 @@ void UIApp::init(const AppInitArgs& args)
.posX = 100,
.posY = 100
});
mButton = mWidgetTree.getRootWidget().emplaceChild<Button>({
.text = "Click Me!",
.posX = 100,
.posY = 500
});
}
void UIApp::update(const AppUpdateArgs& args)

View File

@ -4,9 +4,8 @@
#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_6_UI_APP_HPP_INCLUDED)
#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_6_UI_APP_HPP_INCLUDED 1
#include <glm/vec2.hpp>
#include "../application.hpp"
#include "../gui/button.hpp"
#include "../gui/label.hpp"
#include "../gui/widget.hpp"
#include "../gui/ui_renderer.hpp"
@ -27,6 +26,7 @@ private:
WidgetTree mWidgetTree;
Label* mLabel;
Button* mButton;
sdlpp::Gamepad mGamepad;
Uint32 mNumVertices = 0;

View File

@ -5,6 +5,7 @@ src_files = Split("""
main.cpp
application.cpp
gui/button.cpp
gui/label.cpp
gui/ui_renderer.cpp
gui/widget.cpp

View File

@ -0,0 +1,93 @@
#include "./button.hpp"
namespace sdl_gpu_test
{
Button::Button(ButtonCreateArgs args) : mText(std::move(args.text)), mTextColor(args.textColor),
mBackgroundColor(args.backgroundColor), mPosX(args.posX), mPosY(args.posY), mWidth(args.width), mHeight(args.height)
{
}
void Button::setText(std::string text)
{
if (text != mText)
{
mText = std::move(text);
invalidate();
}
}
void Button::setTextColor(const glm::vec4& color)
{
if (color != mTextColor)
{
mTextColor = color;
invalidate();
}
}
void Button::setBackgroundColor(const glm::vec4& color)
{
if (color != mBackgroundColor)
{
mBackgroundColor = color;
invalidate();
}
}
void Button::setPosX(int posX)
{
if (posX != mPosX)
{
mPosX = posX;
invalidate();
}
}
void Button::setPosY(int posY)
{
if (posY != mPosY)
{
mPosY = posY;
invalidate();
}
}
void Button::handleEnteredTree()
{
update();
}
void Button::revalidate()
{
update();
}
void Button::update()
{
if (mTree == nullptr)
{
return;
}
if (mPrimitiveID != UIRenderer::UNSET_PRIMITIVE_ID)
{
mTree->getRenderer().removePrimitive(mPrimitiveID);
}
mTree->getRenderer().drawQuad({
.texture = "button",
.x = mPosX,
.y = mPosY,
.width = mWidth,
.height = mHeight,
.color = mBackgroundColor
}, &mPrimitiveID);
mTree->getRenderer().drawTextCentered({
.x = mPosX + (mWidth / 2),
.y = mPosY + (mHeight / 2),
.text = mText,
.color = mTextColor
}, &mPrimitiveID);
}
}

View File

@ -0,0 +1,68 @@
#pragma once
#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_BUTTON_HPP_INCLUDED)
#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GUI_BUTTON_HPP_INCLUDED 1
#include <glm/vec4.hpp>
#include "./widget.hpp"
namespace sdl_gpu_test
{
struct ButtonCreateArgs
{
std::string text;
glm::vec4 textColor = {1.f, 1.f, 1.f, 1.f};
glm::vec4 backgroundColor = {1.f, 0.f, 0.f, 1.f};
int posX = 0;
int posY = 0;
int width = 256;
int height = 84;
};
class Button : public Widget
{
public:
using create_args_t = ButtonCreateArgs;
private:
std::string mText;
glm::vec4 mTextColor = {1.f, 1.f, 1.f, 1.f};
glm::vec4 mBackgroundColor = {1.f, 1.f, 1.f, 1.f};
int mPosX = 0;
int mPosY = 0;
int mWidth = 0;
int mHeight = 0;
UIRenderer::primitive_id_t mPrimitiveID = UIRenderer::UNSET_PRIMITIVE_ID;
public:
explicit Button(ButtonCreateArgs args);
[[nodiscard]]
const std::string& getText() const noexcept { return mText; }
[[nodiscard]]
const glm::vec4& getTextColor() const noexcept { return mTextColor; }
[[nodiscard]]
const glm::vec4& getBackgroundTextColor() const noexcept { return mBackgroundColor; }
[[nodiscard]]
int getPosX() const noexcept { return mPosX; }
[[nodiscard]]
int getPosY() const noexcept { return mPosY; }
void setText(std::string text);
void setTextColor(const glm::vec4& color);
void setBackgroundColor(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_BUTTON_HPP_INCLUDED)

View File

@ -3,6 +3,8 @@
#include <mijin/util/string.hpp>
#include "../util/spdlog_wrapper.hpp"
namespace sdl_gpu_test::inline app6
{
namespace
@ -151,6 +153,69 @@ void UIRenderer::render(const UIRendererRenderArgs& args)
}
}
std::pair<unsigned, unsigned> UIRenderer::measureText(const MeasureTextArgs& args) const
{
unsigned width = 0;
unsigned height = mFontMap.lineHeight;
unsigned lineWidth = 0;
for (std::size_t pos = 0; pos < args.text.size(); ++pos)
{
char chr = args.text[pos];
switch (chr)
{
case '\n':
width = std::max(lineWidth, width);
lineWidth = 0;
height += mFontMap.lineHeight;
break;
case '$':
++pos;
if (pos >= args.text.size())
{
break;
}
chr = args.text[pos];
switch (chr)
{
case 'r':
break;
default:
if (pos < args.text.size() - 2
&& mijin::isHexadecimalChar(chr)
&& mijin::isHexadecimalChar(args.text[pos + 1])
&& mijin::isHexadecimalChar(args.text[pos + 2]))
{
pos += 2;
}
else
{
// what?
}
break;
}
break;
case '\\':
++pos;
if (pos >= args.text.size())
{
break;
}
chr = args.text[pos];
[[fallthrough]];
default:
{
const UVFontMapEntry& entry = mFontMap.entries[chr < 0 ? '_' : chr]; // TODO: more chars
lineWidth += entry.xAdvance;
break;
}
}
}
return {std::max(width, lineWidth), height};
}
void UIRenderer::drawText(const DrawTextArgs& args, primitive_id_t* outPrimitiveId)
{
glm::vec4 color = args.color;
@ -169,7 +234,7 @@ void UIRenderer::drawText(const DrawTextArgs& args, primitive_id_t* outPrimitive
{
case '\n':
posX = args.x;
posY += mFontMap.lineHeight;
posY += static_cast<int>(mFontMap.lineHeight);
break;
case '$':
++pos;
@ -215,17 +280,28 @@ void UIRenderer::drawText(const DrawTextArgs& args, primitive_id_t* outPrimitive
chr = args.text[pos];
[[fallthrough]];
default:
posX += drawChar({
posX += static_cast<int>(drawChar({
.x = posX,
.y = posY,
.chr = chr,
.color = color
}, outPrimitiveId);
}, outPrimitiveId));
break;
}
}
}
void UIRenderer::drawTextCentered(DrawTextArgs args, primitive_id_t* outPrimitiveId)
{
const auto [width, height] = measureText({
.text = args.text
});
args.x -= static_cast<int>(width / 2);
args.y -= static_cast<int>(height + mFontMap.lineHeight - mFontMap.base) / 2;
drawText(args, outPrimitiveId);
}
unsigned UIRenderer::drawChar(const DrawCharArgs& args, primitive_id_t* outPrimitiveId)
{
if (outPrimitiveId != nullptr && *outPrimitiveId == UNSET_PRIMITIVE_ID)
@ -246,6 +322,31 @@ unsigned UIRenderer::drawChar(const DrawCharArgs& args, primitive_id_t* outPrimi
return entry.xAdvance;
}
void UIRenderer::drawQuad(const DrawQuadArgs& args, primitive_id_t* outPrimitiveId)
{
auto itEntry = mTextureAtlas.entries.find(args.texture);
if (itEntry == mTextureAtlas.entries.end())
{
spdlog::warn("Attempt to draw quad with unknown texture: {}.", args.texture);
return;
}
const UVTextureAtlasEntry& entry = itEntry->second;
if (outPrimitiveId != nullptr && *outPrimitiveId == UNSET_PRIMITIVE_ID)
{
*outPrimitiveId = mNextOwner++;
}
drawQuadInternal({
.topLeft = {args.x, args.y},
.bottomRight = {args.x + args.width, args.y + args.height},
.uvTopLeft = {entry.uvX, entry.uvY},
.uvBottomRight = {entry.uvX + entry.uvWidth, entry.uvY + entry.uvHeight},
.color = args.color
}, outPrimitiveId == nullptr ? UNSET_PRIMITIVE_ID : *outPrimitiveId);
}
bool UIRenderer::removePrimitive(primitive_id_t primitiveId)
{
bool didRemove = false;
@ -254,7 +355,8 @@ bool UIRenderer::removePrimitive(primitive_id_t primitiveId)
if (mTriangleOwners[triangleIdx] == primitiveId)
{
mTriangleOwners.erase(mTriangleOwners.begin() + triangleIdx);
mVertices.erase(mVertices.begin() + 3 * triangleIdx, mVertices.begin() + 3 * (triangleIdx + 1));
mVertices.erase(mVertices.begin() + static_cast<std::ptrdiff_t>(3) * triangleIdx,
mVertices.begin() + static_cast<std::ptrdiff_t>(3) * (triangleIdx + 1));
didRemove = true;
}
}
@ -294,11 +396,11 @@ void UIRenderer::drawQuadInternal(const DrawQuadInternalArgs& args, primitive_id
mVerticesDirty = true;
}
void UIRenderer::addTriangleInternal(const UIVertex& v0, const UIVertex& v1, const UIVertex& v2, UIRenderer::primitive_id_t primitiveId)
void UIRenderer::addTriangleInternal(const UIVertex& vtx0, const UIVertex& vtx1, const UIVertex& vtx2, primitive_id_t primitiveId)
{
mVertices.push_back(v0);
mVertices.push_back(v1);
mVertices.push_back(v2);
mVertices.push_back(vtx0);
mVertices.push_back(vtx1);
mVertices.push_back(vtx2);
mTriangleOwners.push_back(primitiveId);
}

View File

@ -20,6 +20,11 @@ struct UIVertex
glm::vec4 color = glm::vec4(1.f);
};
struct MeasureTextArgs
{
std::string_view text;
};
struct DrawTextArgs
{
int x = 0;
@ -36,6 +41,16 @@ struct DrawCharArgs
glm::vec4 color = glm::vec4(1.f);
};
struct DrawQuadArgs
{
std::string texture;
int x = 0;
int y = 0;
int width;
int height;
glm::vec4 color = glm::vec4(1.f);
};
struct UIRendererRenderArgs
{
const sdlpp::GPUCommandBuffer* cmdBuffer;
@ -67,8 +82,12 @@ public:
void init(Application& application);
void render(const UIRendererRenderArgs& args);
[[nodiscard]]
std::pair<unsigned, unsigned> measureText(const MeasureTextArgs& args) const;
void drawText(const DrawTextArgs& args, primitive_id_t* outPrimitiveId = nullptr);
void drawTextCentered(DrawTextArgs args, primitive_id_t* outPrimitiveId = nullptr);
unsigned drawChar(const DrawCharArgs& args, primitive_id_t* outPrimitiveId = nullptr);
void drawQuad(const DrawQuadArgs& args, primitive_id_t* outPrimitiveId = nullptr);
bool removePrimitive(primitive_id_t primitiveId);
private:
struct DrawQuadInternalArgs
@ -80,7 +99,7 @@ private:
glm::vec4 color = glm::vec4(1.f);
};
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 addTriangleInternal(const UIVertex& vtx0, const UIVertex& vtx1, const UIVertex& vtx2, primitive_id_t primitiveId);
void uploadVertices();
};
} // namespace sdl_gpu_test::inline app6