uitextbox update

This commit is contained in:
2026-04-22 08:41:29 +02:00
parent a7a7f9f4e9
commit 8be2cea49a
4 changed files with 133 additions and 59 deletions

View File

@@ -33,7 +33,7 @@ namespace Game::Object {
virtual void update(float deltaTime) = 0; virtual void update(float deltaTime) = 0;
virtual void onWindowResized(int newWidth, int newHeight) {} // Called when the window is resized, with the new width and height in pixels virtual void onWindowResized(int newWidth, int newHeight) {} // Called when the window is resized, with the new width and height in pixels
virtual void destroyed() {}; // Pre-destruction call virtual void destroyed() {}; // Pre-destruction call
void render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config); virtual void render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config);
// Collision calls // Collision calls
// Called once when a collision begins // Called once when a collision begins

View File

@@ -4,16 +4,40 @@
#include <renderer/font.hpp> #include <renderer/font.hpp>
#include <renderer/texture.hpp> #include <renderer/texture.hpp>
#include <utility> #include <utility>
#include <string>
#include <game/input.hpp> #include <game/input.hpp>
#include <SDL3/SDL.h>
namespace Game::Object { namespace Game::Object {
struct UITextboxConfig {
SDL_Color bgColor = {20, 20, 20, 210};
SDL_Color borderColor = {110, 110, 110, 255};
SDL_Color focusedBorderColor = {200, 175, 70, 255};
SDL_Color textColor = {255, 255, 255, 255};
SDL_Color focusedTextColor = {255, 240, 180, 255};
SDL_Color placeholderColor = {120, 120, 120, 200};
float borderThickness = 2.f;
float paddingX = 8.f;
float paddingY = 4.f;
float minWidth = 160.f;
float minHeight = 32.f;
int maxLength = 0; // 0 = unlimited
std::string placeholder = "";
float cursorBlinkRate = 0.53f; // seconds per blink phase
};
class UITextBox : public Entity { class UITextBox : public Entity {
public: public:
UITextBox(const std::string& name, std::shared_ptr<Renderer::Font> font, const Transform& transform, float x = 0.f, float y = 0.f); UITextBox(const std::string& name, std::shared_ptr<Renderer::Font> font,
const Transform& transform,
float x = 0.f, float y = 0.f,
UITextboxConfig config = {});
~UITextBox() override = default; ~UITextBox() override = default;
void start() override; void start() override;
void update(float deltaTime) override; void update(float deltaTime) override;
void render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) override;
void setText(const std::string& text); void setText(const std::string& text);
std::string getText() const; std::string getText() const;
@@ -33,5 +57,9 @@ namespace Game::Object {
float mBoxWidth = 0.f; float mBoxWidth = 0.f;
float mBoxHeight = 0.f; float mBoxHeight = 0.f;
bool mNeedsTextRefresh = true; bool mNeedsTextRefresh = true;
UITextboxConfig mConfig;
float mCursorTimer = 0.f;
bool mCursorVisible = true;
}; };
} }

View File

@@ -8,6 +8,7 @@
#include <renderer/renderer.hpp> #include <renderer/renderer.hpp>
#include <renderer/texture.hpp> #include <renderer/texture.hpp>
#include <renderer/font.hpp> #include <renderer/font.hpp>
#include <object/ui/uitextbox.hpp>
#include <game/agame/sampletextbox.hpp> #include <game/agame/sampletextbox.hpp>
#include <object/components/boxcollider.hpp> #include <object/components/boxcollider.hpp>
@@ -27,6 +28,9 @@ int main() {
player->addComponent<Object::Components::BoxCollider>(); player->addComponent<Object::Components::BoxCollider>();
//State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player2", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 128, "Roboto"), t1)); //State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player2", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 128, "Roboto"), t1));
// Sample textbox
State::GameState::getInstance().addEntity(std::make_unique<AGame::SampleTextBox>("Sample Text Box", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 48, "Roboto"), Object::DEFAULT_TRANSFORM, 640.f, 360.f));
window.run(); window.run();
return 0; return 0;

View File

@@ -1,19 +1,21 @@
#include <object/ui/uitextbox.hpp> #include <object/ui/uitextbox.hpp>
#include <renderer/renderer.hpp>
namespace Game::Object { namespace Game::Object {
UITextBox::UITextBox(const std::string& name, std::shared_ptr<Renderer::Font> font, const Transform& transform, float x, float y)
: Entity(name, font, transform), mX(x), mY(y) { } UITextBox::UITextBox(const std::string& name, std::shared_ptr<Renderer::Font> font,
const Transform& transform, float x, float y, UITextboxConfig config)
: Entity(name, font, transform), mX(x), mY(y), mConfig(config) { }
void UITextBox::start() { void UITextBox::start() {
// Center the text box on the position
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f; mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f; mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
// Keep a stable interaction area even when current text is empty.
mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX(); mBoxWidth = static_cast<float>(mTex->getWidth()) * mTransform.adjustedScaleX();
mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY(); mBoxHeight = static_cast<float>(mTex->getHeight()) * mTransform.adjustedScaleY();
if (mBoxWidth <= 0.f) mBoxWidth = 180.f;
if (mBoxHeight <= 0.f) mBoxHeight = 36.f; if (mBoxWidth < mConfig.minWidth) mBoxWidth = mConfig.minWidth;
if (mBoxHeight < mConfig.minHeight) mBoxHeight = mConfig.minHeight;
refreshVisualText(); refreshVisualText();
} }
@@ -26,11 +28,13 @@ namespace Game::Object {
mNeedsTextRefresh = true; mNeedsTextRefresh = true;
} }
if (!mIsFocused) { if (mIsFocused) {
if (mNeedsTextRefresh) { // Cursor blink
refreshVisualText(); mCursorTimer += deltaTime;
} if (mCursorTimer >= mConfig.cursorBlinkRate) {
return; mCursorTimer = 0.f;
mCursorVisible = !mCursorVisible;
mNeedsTextRefresh = true;
} }
if (Input::isKeyJustPressed(SDL_SCANCODE_BACKSPACE) && !mText.empty()) { if (Input::isKeyJustPressed(SDL_SCANCODE_BACKSPACE) && !mText.empty()) {
@@ -40,63 +44,101 @@ namespace Game::Object {
if (Input::isKeyJustPressed(SDL_SCANCODE_RETURN) || Input::isKeyJustPressed(SDL_SCANCODE_KP_ENTER)) { if (Input::isKeyJustPressed(SDL_SCANCODE_RETURN) || Input::isKeyJustPressed(SDL_SCANCODE_KP_ENTER)) {
mIsFocused = false; mIsFocused = false;
mCursorTimer = 0.f;
mCursorVisible = true;
mNeedsTextRefresh = true; mNeedsTextRefresh = true;
} }
for (int key = 0; key < SDL_SCANCODE_COUNT; ++key) { for (int key = 0; key < SDL_SCANCODE_COUNT; ++key) {
SDL_Scancode scancode = static_cast<SDL_Scancode>(key); SDL_Scancode scancode = static_cast<SDL_Scancode>(key);
if (!Input::isKeyJustPressed(scancode)) { if (!Input::isKeyJustPressed(scancode)) continue;
continue;
}
SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_GetModState(), true); SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_GetModState(), true);
if (keycode >= 32 && keycode <= 126) { if (keycode >= 32 && keycode <= 126) {
if (mConfig.maxLength == 0 || static_cast<int>(mText.size()) < mConfig.maxLength) {
mText.push_back(static_cast<char>(keycode)); mText.push_back(static_cast<char>(keycode));
mNeedsTextRefresh = true; mNeedsTextRefresh = true;
} }
} }
}
}
if (mNeedsTextRefresh) { if (mNeedsTextRefresh) {
refreshVisualText(); refreshVisualText();
} }
} }
void UITextBox::render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) {
if (!mIsVisible) return;
SDL_Renderer* r = renderer->getSDLRenderer();
const float bx = mTransform.x - mConfig.paddingX - config.camX + config.screenW / 2.f;
const float by = mTransform.y - mConfig.paddingY - config.camY + config.screenH / 2.f;
const float bw = mBoxWidth + 2.f * mConfig.paddingX;
const float bh = mBoxHeight + 2.f * mConfig.paddingY;
const float t = mConfig.borderThickness;
// Background
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
const SDL_Color& bg = mConfig.bgColor;
SDL_SetRenderDrawColor(r, bg.r, bg.g, bg.b, bg.a);
const SDL_FRect bgRect = {bx, by, bw, bh};
SDL_RenderFillRect(r, &bgRect);
// Border (4 filled rects for configurable thickness)
const SDL_Color& bc = mIsFocused ? mConfig.focusedBorderColor : mConfig.borderColor;
SDL_SetRenderDrawColor(r, bc.r, bc.g, bc.b, bc.a);
const SDL_FRect borders[4] = {
{bx, by, bw, t }, // top
{bx, by + bh - t, bw, t }, // bottom
{bx, by, t, bh}, // left
{bx + bw - t, by, t, bh}, // right
};
SDL_RenderFillRects(r, borders, 4);
// Text (or placeholder) via base render
Entity::render(renderer, config);
}
void UITextBox::setText(const std::string& text) { void UITextBox::setText(const std::string& text) {
mText = text; mText = text;
mNeedsTextRefresh = true; mNeedsTextRefresh = true;
refreshVisualText(); refreshVisualText();
} }
std::string UITextBox::getText() const { std::string UITextBox::getText() const { return mText; }
return mText; std::string UITextBox::getValue() const { return mText; }
} bool UITextBox::isFocused() const { return mIsFocused; }
std::string UITextBox::getValue() const {
return mText;
}
bool UITextBox::isFocused() const {
return mIsFocused;
}
bool UITextBox::isMouseInsideBox() const { bool UITextBox::isMouseInsideBox() const {
const float mouseX = Input::getMouseX(); const float mouseX = Input::getMouseX();
const float mouseY = Input::getMouseY(); const float mouseY = Input::getMouseY();
const float left = mTransform.x; const float left = mTransform.x - mConfig.paddingX;
const float right = mTransform.x + mBoxWidth; const float right = left + mBoxWidth + 2.f * mConfig.paddingX;
const float top = mTransform.y; const float top = mTransform.y - mConfig.paddingY;
const float bottom = mTransform.y + mBoxHeight; const float bottom = top + mBoxHeight + 2.f * mConfig.paddingY;
return mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom; return mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom;
} }
void UITextBox::refreshVisualText() { void UITextBox::refreshVisualText() {
SDL_Color color = mIsFocused ? SDL_Color{255, 240, 180, 255} : SDL_Color{255, 255, 255, 255}; const bool showPlaceholder = mText.empty() && !mIsFocused && !mConfig.placeholder.empty();
std::string rendered = mText.empty() ? " " : mText;
if (mIsFocused) { SDL_Color color;
std::string rendered;
if (showPlaceholder) {
color = mConfig.placeholderColor;
rendered = mConfig.placeholder;
} else {
color = mIsFocused ? mConfig.focusedTextColor : mConfig.textColor;
rendered = mText.empty() ? " " : mText;
if (mIsFocused && mCursorVisible) {
rendered += "_"; rendered += "_";
} }
}
std::dynamic_pointer_cast<Renderer::Font>(mTex)->build(color, rendered); std::dynamic_pointer_cast<Renderer::Font>(mTex)->build(color, rendered);
mNeedsTextRefresh = false; mNeedsTextRefresh = false;