uitextbox update
This commit is contained in:
@@ -33,7 +33,7 @@ namespace Game::Object {
|
||||
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 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
|
||||
// Called once when a collision begins
|
||||
|
||||
@@ -4,16 +4,40 @@
|
||||
#include <renderer/font.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <game/input.hpp>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
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 {
|
||||
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;
|
||||
|
||||
void start() override;
|
||||
void update(float deltaTime) override;
|
||||
void render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) override;
|
||||
|
||||
void setText(const std::string& text);
|
||||
std::string getText() const;
|
||||
@@ -33,5 +57,9 @@ namespace Game::Object {
|
||||
float mBoxWidth = 0.f;
|
||||
float mBoxHeight = 0.f;
|
||||
bool mNeedsTextRefresh = true;
|
||||
UITextboxConfig mConfig;
|
||||
|
||||
float mCursorTimer = 0.f;
|
||||
bool mCursorVisible = true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <renderer/renderer.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
#include <renderer/font.hpp>
|
||||
#include <object/ui/uitextbox.hpp>
|
||||
#include <game/agame/sampletextbox.hpp>
|
||||
#include <object/components/boxcollider.hpp>
|
||||
|
||||
@@ -27,6 +28,9 @@ int main() {
|
||||
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));
|
||||
|
||||
// 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();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
#include <object/ui/uitextbox.hpp>
|
||||
#include <renderer/renderer.hpp>
|
||||
|
||||
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() {
|
||||
// Center the text box on the position
|
||||
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 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();
|
||||
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();
|
||||
}
|
||||
@@ -26,11 +28,13 @@ namespace Game::Object {
|
||||
mNeedsTextRefresh = true;
|
||||
}
|
||||
|
||||
if (!mIsFocused) {
|
||||
if (mNeedsTextRefresh) {
|
||||
refreshVisualText();
|
||||
}
|
||||
return;
|
||||
if (mIsFocused) {
|
||||
// Cursor blink
|
||||
mCursorTimer += deltaTime;
|
||||
if (mCursorTimer >= mConfig.cursorBlinkRate) {
|
||||
mCursorTimer = 0.f;
|
||||
mCursorVisible = !mCursorVisible;
|
||||
mNeedsTextRefresh = true;
|
||||
}
|
||||
|
||||
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)) {
|
||||
mIsFocused = false;
|
||||
mCursorTimer = 0.f;
|
||||
mCursorVisible = true;
|
||||
mNeedsTextRefresh = true;
|
||||
}
|
||||
|
||||
for (int key = 0; key < SDL_SCANCODE_COUNT; ++key) {
|
||||
SDL_Scancode scancode = static_cast<SDL_Scancode>(key);
|
||||
if (!Input::isKeyJustPressed(scancode)) {
|
||||
continue;
|
||||
}
|
||||
if (!Input::isKeyJustPressed(scancode)) continue;
|
||||
|
||||
SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_GetModState(), true);
|
||||
if (keycode >= 32 && keycode <= 126) {
|
||||
if (mConfig.maxLength == 0 || static_cast<int>(mText.size()) < mConfig.maxLength) {
|
||||
mText.push_back(static_cast<char>(keycode));
|
||||
mNeedsTextRefresh = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mNeedsTextRefresh) {
|
||||
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) {
|
||||
mText = text;
|
||||
mNeedsTextRefresh = true;
|
||||
refreshVisualText();
|
||||
}
|
||||
|
||||
std::string UITextBox::getText() const {
|
||||
return mText;
|
||||
}
|
||||
|
||||
std::string UITextBox::getValue() const {
|
||||
return mText;
|
||||
}
|
||||
|
||||
bool UITextBox::isFocused() const {
|
||||
return mIsFocused;
|
||||
}
|
||||
std::string UITextBox::getText() const { return mText; }
|
||||
std::string UITextBox::getValue() const { return mText; }
|
||||
bool UITextBox::isFocused() const { return mIsFocused; }
|
||||
|
||||
bool UITextBox::isMouseInsideBox() const {
|
||||
const float mouseX = Input::getMouseX();
|
||||
const float mouseY = Input::getMouseY();
|
||||
|
||||
const float left = mTransform.x;
|
||||
const float right = mTransform.x + mBoxWidth;
|
||||
const float top = mTransform.y;
|
||||
const float bottom = mTransform.y + mBoxHeight;
|
||||
const float left = mTransform.x - mConfig.paddingX;
|
||||
const float right = left + mBoxWidth + 2.f * mConfig.paddingX;
|
||||
const float top = mTransform.y - mConfig.paddingY;
|
||||
const float bottom = top + mBoxHeight + 2.f * mConfig.paddingY;
|
||||
|
||||
return mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom;
|
||||
}
|
||||
|
||||
void UITextBox::refreshVisualText() {
|
||||
SDL_Color color = mIsFocused ? SDL_Color{255, 240, 180, 255} : SDL_Color{255, 255, 255, 255};
|
||||
std::string rendered = mText.empty() ? " " : mText;
|
||||
if (mIsFocused) {
|
||||
const bool showPlaceholder = mText.empty() && !mIsFocused && !mConfig.placeholder.empty();
|
||||
|
||||
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 += "_";
|
||||
}
|
||||
}
|
||||
|
||||
std::dynamic_pointer_cast<Renderer::Font>(mTex)->build(color, rendered);
|
||||
mNeedsTextRefresh = false;
|
||||
|
||||
Reference in New Issue
Block a user