refactor(ui): decouple ChatBox from image dependencies, allow custom TextField backgrounds

TextField's background is now a generic `Widget` pointer, enabling use of any widget (e.g., `Rect`, `Image`) as background. `ChatBox` no longer creates its own `TextField`; it accepts one via `set_text_field`. Removed `set_d_image` and `set_default_image` in favor of `set_background`. Added a shared default image constant in `default_image.hpp`. Updated `host_game_ui.cpp`, `join_game_ui.cpp`, and `world_ui_manager.cpp` to construct backgrounds accordingly.
This commit is contained in:
2026-07-19 20:15:21 +08:00
parent dae57c9163
commit 2fd9f0b854
8 changed files with 93 additions and 42 deletions

View File

@@ -24,9 +24,8 @@ public:
std::string& get_input_text();
float width() const override;
float height() const override;
void set_d_image(TextureManager& m);
void set_text_field(std::unique_ptr<TextField> text_field);
void set_typing(bool typing, bool finished);
void set_app(App* app);
template <typename F> ChatBox& set_on_finish(F&& f) {
m_text_field->set_on_finish(std::forward<F>(f));
return *this;

View File

@@ -0,0 +1,5 @@
#pragma once
namespace Cubed {
static constexpr const char* DEFAULT_TEXT_FIELD_IMAGE =
"texture/ui/textfield001.png";
}

View File

@@ -1,6 +1,5 @@
#pragma once
#include "Cubed/ui/image.hpp"
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/rect.hpp"
#include "Cubed/ui/widget.hpp"
@@ -22,13 +21,13 @@ public:
TextField& set_width(float width);
TextField& set_height(float height);
TextField& set_show_text(const std::string& text);
TextField& set_background_image(const std::string& path,
TextureManager& texture_manager);
TextField& set_default_image(TextureManager& texture_manager);
TextField& set_background(std::unique_ptr<Widget> background);
TextField& set_auto_scale(bool auto_scale);
TextField& set_app(App* app);
TextField& set_typing(bool typing, bool finished);
TextField& clear_input();
TextField& set_fill_width(bool fill);
TextField& set_fill_height(bool fill);
bool handle_mouse_move_event(const MouseMoveEvent& e) override;
bool handle_mouse_button_event(const MouseButtonEvent& e) override;
bool handle_text_input_event(const TextInputEvent& e) override;
@@ -46,11 +45,9 @@ private:
static constexpr float DEFAULT_SCALE = 3.0f;
static constexpr float TEXT_SCALE = 0.6f;
static constexpr float CURSOR_INTERVAL = 0.5f;
static constexpr const char* DEFAULT_TEXT_FIELD_IMAGE =
"texture/ui/textfield001.png";
App* m_app = nullptr;
std::unique_ptr<Image> m_background;
std::unique_ptr<Widget> m_background;
std::unique_ptr<Label> m_foreground;
std::unique_ptr<Rect> m_cursor;
float m_cursor_timer = 0.0f;
@@ -62,6 +59,8 @@ private:
float m_width = NORMAL_TEXTFIELD_WIDTH;
float m_height = NORMAL_TEXTFIELD_HEIGHT;
float m_scale = DEFAULT_SCALE;
bool m_fill_width = false;
bool m_fill_height = false;
std::string m_input_text;
std::string m_show_text;

View File

@@ -2,11 +2,7 @@
#include "Cubed/tools/text_tools.hpp"
namespace Cubed {
ChatBox::ChatBox(Widget* parent) : Widget(parent) {
m_text_field = std::make_unique<TextField>(this);
m_text_field->set_anchor(Anchor::BOTTOM_LEFT);
}
ChatBox::ChatBox(Widget* parent) : Widget(parent) {}
void ChatBox::add_message(ChatMessage& message) {
while (m_messages.size() > MAX_MESSGAES_SUM) {
@@ -27,7 +23,10 @@ void ChatBox::add_message(ChatMessage& message) {
ChatBox& ChatBox::set_scale(float scale) {
m_scale = scale;
if (m_text_field) {
m_text_field->set_scale(scale);
}
return *this;
}
ChatBox& ChatBox::set_text_scale(float scale) {
@@ -41,7 +40,6 @@ ChatBox& ChatBox::set_text_scale(float scale) {
}
ChatBox& ChatBox::set_width(float width) {
m_width = width;
m_text_field->set_width(width);
return *this;
}
ChatBox& ChatBox::set_show_lines(int lines) {
@@ -63,17 +61,25 @@ float ChatBox::height() const {
// Height is dynamically calculated, no scaling needed
return m_height;
}
void ChatBox::set_d_image(TextureManager& m) {
m_text_field->set_default_image(m);
void ChatBox::set_text_field(std::unique_ptr<TextField> text_field) {
m_text_field = std::move(text_field);
}
void ChatBox::set_typing(bool typing, bool finished) {
if (m_text_field) {
m_text_field->set_typing(typing, finished);
m_text_field->set_visible(typing);
}
}
void ChatBox::set_app(App* app) { m_text_field->set_app(app); }
void ChatBox::layout() {
int y = m_text_field->height() + m_spacing;
int y;
if (m_text_field) {
y = m_text_field->height() + m_spacing;
} else {
y = m_spacing;
}
int line = 0;
// Shift upward sequentially starting from the latest
for (auto it = m_messages.rbegin(); it != m_messages.rend(); ++it) {
@@ -100,7 +106,10 @@ void ChatBox::on_update(float dt) {
}
void ChatBox::on_render(Renderer& renderer) {
if (m_text_field) {
m_text_field->render(renderer);
}
for (auto it = m_messages.rbegin(); it != m_messages.rend(); ++it) {
if (!it->label) {
continue;
@@ -111,7 +120,7 @@ void ChatBox::on_render(Renderer& renderer) {
}
}
bool ChatBox::handle_text_input_event(const TextInputEvent& e) {
if (m_text_field->handle_text_input_event(e)) {
if (m_text_field && m_text_field->handle_text_input_event(e)) {
return true;
}
return Widget::handle_text_input_event(e);

View File

@@ -6,6 +6,7 @@
#include "Cubed/scene/scene_manager.hpp"
#include "Cubed/ui/button.hpp"
#include "Cubed/ui/column_layout.hpp"
#include "Cubed/ui/default_image.hpp"
#include "Cubed/ui/image.hpp"
#include "Cubed/ui/text_field.hpp"
namespace Cubed {
@@ -48,7 +49,10 @@ void HostGameUI::init() {
auto& text_seed = layout.add_child<TextField>();
text_seed.set_show_text(tr("hostgame.world_seed"));
text_seed.set_app(&m_scene.scene_manager().app());
text_seed.set_default_image(texture_manager);
std::unique_ptr<Image> back = std::make_unique<Image>(&text_seed);
back->set_fill(true).set_image(DEFAULT_TEXT_FIELD_IMAGE,
texture_manager);
text_seed.set_background(std::move(back));
text_seed.set_on_finish([this, &text_seed]() {
unsigned seed = 0;
auto& text = text_seed.input_text();
@@ -67,7 +71,10 @@ void HostGameUI::init() {
}
{
auto& text_port = layout.add_child<TextField>();
text_port.set_default_image(texture_manager);
std::unique_ptr<Image> back = std::make_unique<Image>(&text_port);
back->set_fill(true).set_image(DEFAULT_TEXT_FIELD_IMAGE,
texture_manager);
text_port.set_background(std::move(back));
text_port.set_show_text(tr("hostgame.port"));
text_port.set_app(&m_scene.scene_manager().app());
text_port.set_on_finish([this, &text_port]() {

View File

@@ -5,6 +5,7 @@
#include "Cubed/scene/join_game_scene.hpp"
#include "Cubed/ui/button.hpp"
#include "Cubed/ui/column_layout.hpp"
#include "Cubed/ui/default_image.hpp"
#include "Cubed/ui/text_field.hpp"
namespace Cubed {
JoinGameUI::JoinGameUI(JoinGameScene& scene) : m_scene(scene) {}
@@ -45,7 +46,10 @@ void JoinGameUI::init() {
{
auto& text_ip = layout.add_child<TextField>();
text_ip.set_show_text(tr("joingame.server_ip"));
text_ip.set_default_image(texture_manager);
std::unique_ptr<Image> back = std::make_unique<Image>(&text_ip);
back->set_fill(true).set_image(DEFAULT_TEXT_FIELD_IMAGE,
texture_manager);
text_ip.set_background(std::move(back));
text_ip.set_app(&m_scene.scene_manager().app());
text_ip.set_on_finish([this, &text_ip]() {
auto& ip = text_ip.input_text();

View File

@@ -11,11 +11,6 @@ constexpr float DELTA_CUROUR_HEIGHT = 10.0f;
namespace Cubed {
TextField::TextField(Widget* parent) : Widget(parent) {
m_background = std::make_unique<Image>(this);
m_background->set_fill(true);
m_background->set_anchor(Anchor::TOP_LEFT);
m_foreground = std::make_unique<Label>(this);
m_foreground->set_anchor(Anchor::CENTER_LEFT);
m_foreground->set_offset({10, 0});
@@ -62,6 +57,14 @@ void TextField::on_render(Renderer& renderer) {
}
}
void TextField::on_update(float dt) {
if (m_fill_height) {
m_height = m_parent ? m_parent->height() : m_window_height;
}
if (m_fill_width) {
m_width = m_parent ? m_parent->width() : m_window_width;
}
m_cursor_timer += dt;
if (m_cursor_timer >= CURSOR_INTERVAL) {
m_cursor_timer = 0.0f;
@@ -105,11 +108,24 @@ void TextField::update_input_area() {
TextField& TextField::set_scale(float scale) {
m_scale = scale;
m_cursor->set_height(std::max(1.0f, height() - DELTA_CUROUR_HEIGHT));
return *this;
}
float TextField::width() const { return m_width * m_scale; }
float TextField::height() const { return m_height * m_scale; }
float TextField::width() const {
if (m_fill_width) {
return m_width;
}
return m_width * m_scale;
}
float TextField::height() const {
if (m_fill_height) {
return m_height;
}
return m_height * m_scale;
}
TextField& TextField::set_width(float width) {
m_width = width;
@@ -127,14 +143,10 @@ TextField& TextField::set_show_text(const std::string& text) {
update_show_text();
return *this;
}
TextField& TextField::set_background_image(const std::string& path,
TextureManager& texture_manager) {
m_background->set_image(path, texture_manager);
return *this;
}
TextField& TextField::set_default_image(TextureManager& texture_manager) {
return set_background_image(DEFAULT_TEXT_FIELD_IMAGE, texture_manager);
TextField& TextField::set_background(std::unique_ptr<Widget> background) {
m_background = std::move(background);
return *this;
}
TextField& TextField::set_auto_scale(bool auto_scale) {
@@ -164,6 +176,15 @@ TextField& TextField::clear_input() {
return *this;
}
TextField& TextField::set_fill_width(bool fill) {
m_fill_width = fill;
return *this;
}
TextField& TextField::set_fill_height(bool fill) {
m_fill_height = fill;
return *this;
}
void TextField::start_typing() {
if (m_app) {
m_app->start_text_input();

View File

@@ -31,14 +31,21 @@ void WorldUIManager::init() {
m_widgets.try_emplace("crosshair", &crosshair);
auto& chat_box = m_root_widget->add_child<ChatBox>();
auto text_field = std::make_unique<TextField>(&chat_box);
text_field->set_anchor(Anchor::BOTTOM_LEFT);
auto rect = std::make_unique<Rect>(text_field.get());
rect->set_color(Color::GRAY).set_alpha(0.6f).set_fill(true);
text_field->set_background(std::move(rect))
.set_app(&m_scene.scene_manager().app())
.set_fill_width(true)
.set_visible(false);
chat_box.set_text_field(std::move(text_field));
chat_box.set_spacing(15);
chat_box.set_anchor(Anchor::BOTTOM_LEFT);
chat_box.set_offset({0, -10});
chat_box.set_width(200);
chat_box.set_scale(2.0f);
chat_box.set_text_scale(0.6f);
chat_box.set_d_image(m_scene.scene_manager().app().texture_manager());
chat_box.set_app(&m_scene.scene_manager().app());
chat_box.set_on_finish([this, &chat_box]() {
ChatMessage message{m_scene.client_world().get_player().get_name(),
std::move(chat_box.get_input_text()), 0};