feat(ui): 为UI组件添加链式调用构建器方法和坐标空间支持

为ProgressBar、Slider、RadioButton、Label、CheckBox、Button等UI组件添加链式调用构建器方法,简化组件创建和配置流程。
新增CoordinateSpace枚举支持三种坐标空间:Screen(屏幕空间)、World(世界空间)和Camera(相机空间)。
重构onDraw方法为onDrawWidget,统一处理不同坐标空间的渲染逻辑。
更新示例代码展示如何使用链式调用和坐标空间功能。
This commit is contained in:
ChestnutYueyue 2026-02-11 13:10:47 +08:00
parent d0314447ee
commit 2751b27d90
17 changed files with 1091 additions and 286 deletions

View File

@ -22,52 +22,118 @@ enum class ImageScaleMode {
class Button : public Widget {
public:
Button();
explicit Button(const String &text);
~Button() override = default;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<Button> create();
static Ptr<Button> create(const String &text);
static Ptr<Button> create(const String &text, Ptr<FontAtlas> font);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Button *withPosition(float x, float y);
Button *withPosition(const Vec2 &pos);
Button *withAnchor(float x, float y);
Button *withAnchor(const Vec2 &anchor);
Button *withText(const String &text);
Button *withFont(Ptr<FontAtlas> font);
Button *withTextColor(const Color &color);
Button *withBackgroundColor(const Color &normal, const Color &hover,
const Color &pressed);
Button *withSize(float width, float height);
Button *withPadding(const Vec2 &padding);
Button *withPadding(float x, float y);
Button *withBorder(const Color &color, float width);
Button *withCornerRadius(float radius);
Button *withRoundedCornersEnabled(bool enabled);
Button *withHoverCursor(CursorShape cursor);
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
Button *withCoordinateSpace(CoordinateSpace space);
Button *withScreenPosition(float x, float y);
Button *withScreenPosition(const Vec2 &pos);
Button *withCameraOffset(float x, float y);
Button *withCameraOffset(const Vec2 &offset);
// ------------------------------------------------------------------------
// 文字内容
// ------------------------------------------------------------------------
void setText(const String &text);
const String &getText() const { return text_; }
// ------------------------------------------------------------------------
// 字体
// ------------------------------------------------------------------------
void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; }
// ------------------------------------------------------------------------
// 内边距
// ------------------------------------------------------------------------
void setPadding(const Vec2 &padding);
Vec2 getPadding() const { return padding_; }
// ------------------------------------------------------------------------
// 文字颜色
// ------------------------------------------------------------------------
void setTextColor(const Color &color);
Color getTextColor() const { return textColor_; }
// ------------------------------------------------------------------------
// 纯色背景设置
// ------------------------------------------------------------------------
void setBackgroundColor(const Color &normal, const Color &hover,
const Color &pressed);
// ------------------------------------------------------------------------
// 边框设置
// ------------------------------------------------------------------------
void setBorder(const Color &color, float width);
// ------------------------------------------------------------------------
// 图片背景设置
// ------------------------------------------------------------------------
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
Ptr<Texture> pressed = nullptr);
void setBackgroundImageScaleMode(ImageScaleMode mode);
void setCustomSize(const Vec2 &size);
void setCustomSize(float width, float height);
// ------------------------------------------------------------------------
// 圆角矩形设置
// ------------------------------------------------------------------------
void setCornerRadius(float radius);
float getCornerRadius() const { return cornerRadius_; }
void setRoundedCornersEnabled(bool enabled);
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
// 鼠标光标设置(悬停时显示的光标形状)
// ------------------------------------------------------------------------
// 鼠标光标设置
// ------------------------------------------------------------------------
void setHoverCursor(CursorShape cursor);
CursorShape getHoverCursor() const { return hoverCursor_; }
// Alpha遮罩点击检测用于不规则形状按钮
// ------------------------------------------------------------------------
// Alpha遮罩点击检测
// ------------------------------------------------------------------------
void setUseAlphaMaskForHitTest(bool enabled);
bool isUseAlphaMaskForHitTest() const { return useAlphaMaskForHitTest_; }
// ------------------------------------------------------------------------
// 点击回调
// ------------------------------------------------------------------------
void setOnClick(Function<void()> callback);
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
void drawBackgroundImage(RenderBackend &renderer, const Rect &rect);
void drawRoundedRect(RenderBackend &renderer, const Rect &rect,
const Color &color, float radius);
@ -104,15 +170,15 @@ private:
float borderWidth_ = 1.0f;
// 圆角矩形
float cornerRadius_ = 8.0f; // 圆角半径
bool roundedCornersEnabled_ = false; // 默认关闭
float cornerRadius_ = 8.0f;
bool roundedCornersEnabled_ = false;
// 鼠标光标
CursorShape hoverCursor_ = CursorShape::Hand; // 悬停时默认显示手型光标
bool cursorChanged_ = false; // 标记是否已改变光标
CursorShape hoverCursor_ = CursorShape::Hand;
bool cursorChanged_ = false;
// Alpha遮罩点击检测(用于不规则形状按钮)
bool useAlphaMaskForHitTest_ = false; // 默认关闭
// Alpha遮罩点击检测
bool useAlphaMaskForHitTest_ = false;
bool hovered_ = false;
bool pressed_ = false;
@ -152,7 +218,7 @@ public:
void setOnStateChange(Function<void(bool)> callback);
protected:
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
private:
// 状态图片

View File

@ -17,6 +17,27 @@ public:
static Ptr<CheckBox> create();
static Ptr<CheckBox> create(const String &label);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
CheckBox *withPosition(float x, float y);
CheckBox *withPosition(const Vec2 &pos);
CheckBox *withAnchor(float x, float y);
CheckBox *withAnchor(const Vec2 &anchor);
CheckBox *withText(const String &text);
CheckBox *withFont(Ptr<FontAtlas> font);
CheckBox *withTextColor(const Color &color);
CheckBox *withSize(float width, float height);
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
CheckBox *withCoordinateSpace(CoordinateSpace space);
CheckBox *withScreenPosition(float x, float y);
CheckBox *withScreenPosition(const Vec2 &pos);
CheckBox *withCameraOffset(float x, float y);
CheckBox *withCameraOffset(const Vec2 &offset);
void setChecked(bool checked);
bool isChecked() const { return checked_; }
void toggle();
@ -50,7 +71,7 @@ public:
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
bool onMousePress(const MouseEvent &event) override;
bool onMouseRelease(const MouseEvent &event) override;

View File

@ -23,6 +23,27 @@ public:
static Ptr<Label> create(const String &text);
static Ptr<Label> create(const String &text, Ptr<FontAtlas> font);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Label *withPosition(float x, float y);
Label *withPosition(const Vec2 &pos);
Label *withAnchor(float x, float y);
Label *withAnchor(const Vec2 &anchor);
Label *withText(const String &text);
Label *withFont(Ptr<FontAtlas> font);
Label *withTextColor(const Color &color);
Label *withFontSize(int size);
// ------------------------------------------------------------------------
// 坐标空间设置(链式调用)
// ------------------------------------------------------------------------
Label *withCoordinateSpace(CoordinateSpace space);
Label *withScreenPosition(float x, float y);
Label *withScreenPosition(const Vec2 &pos);
Label *withCameraOffset(float x, float y);
Label *withCameraOffset(const Vec2 &offset);
// ------------------------------------------------------------------------
// 文本内容
// ------------------------------------------------------------------------
@ -111,7 +132,7 @@ public:
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
private:
String text_;

View File

@ -21,6 +21,21 @@ public:
static Ptr<ProgressBar> create();
static Ptr<ProgressBar> create(float min, float max, float value);
// ------------------------------------------------------------------------
// 链式调用构建器方法 - 坐标空间支持
// ------------------------------------------------------------------------
ProgressBar *withPosition(float x, float y);
ProgressBar *withPosition(const Vec2 &pos);
ProgressBar *withAnchor(float x, float y);
ProgressBar *withAnchor(const Vec2 &anchor);
ProgressBar *withSize(float width, float height);
ProgressBar *withProgress(float progress);
ProgressBar *withCoordinateSpace(CoordinateSpace space);
ProgressBar *withScreenPosition(float x, float y);
ProgressBar *withScreenPosition(const Vec2 &pos);
ProgressBar *withCameraOffset(float x, float y);
ProgressBar *withCameraOffset(const Vec2 &offset);
// ------------------------------------------------------------------------
// 数值范围
// ------------------------------------------------------------------------
@ -151,7 +166,7 @@ public:
protected:
void onUpdate(float deltaTime) override;
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
private:
// 数值

View File

@ -17,6 +17,23 @@ public:
static Ptr<RadioButton> create();
static Ptr<RadioButton> create(const String &label);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
RadioButton *withPosition(float x, float y);
RadioButton *withPosition(const Vec2 &pos);
RadioButton *withAnchor(float x, float y);
RadioButton *withAnchor(const Vec2 &anchor);
RadioButton *withText(const String &text);
RadioButton *withFont(Ptr<FontAtlas> font);
RadioButton *withTextColor(const Color &color);
RadioButton *withSize(float width, float height);
RadioButton *withCoordinateSpace(CoordinateSpace space);
RadioButton *withScreenPosition(float x, float y);
RadioButton *withScreenPosition(const Vec2 &pos);
RadioButton *withCameraOffset(float x, float y);
RadioButton *withCameraOffset(const Vec2 &offset);
void setSelected(bool selected);
bool isSelected() const { return selected_; }
@ -52,7 +69,7 @@ public:
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
bool onMousePress(const MouseEvent &event) override;
bool onMouseRelease(const MouseEvent &event) override;

View File

@ -17,6 +17,23 @@ public:
static Ptr<Slider> create();
static Ptr<Slider> create(float min, float max, float value);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Slider *withPosition(float x, float y);
Slider *withPosition(const Vec2 &pos);
Slider *withAnchor(float x, float y);
Slider *withAnchor(const Vec2 &anchor);
Slider *withSize(float width, float height);
Slider *withMinValue(float min);
Slider *withMaxValue(float max);
Slider *withValue(float value);
Slider *withCoordinateSpace(CoordinateSpace space);
Slider *withScreenPosition(float x, float y);
Slider *withScreenPosition(const Vec2 &pos);
Slider *withCameraOffset(float x, float y);
Slider *withCameraOffset(const Vec2 &offset);
void setRange(float min, float max);
float getMin() const { return min_; }
float getMax() const { return max_; }
@ -76,7 +93,7 @@ public:
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
bool onMousePress(const MouseEvent &event) override;
bool onMouseRelease(const MouseEvent &event) override;
bool onMouseMove(const MouseEvent &event) override;

View File

@ -12,6 +12,12 @@ namespace extra2d {
// ============================================================================
class Text : public Widget {
public:
// ------------------------------------------------------------------------
// 对齐方式枚举
// ------------------------------------------------------------------------
enum class Alignment { Left, Center, Right };
enum class VerticalAlignment { Top, Middle, Bottom };
Text();
explicit Text(const String &text);
~Text() override = default;
@ -23,6 +29,19 @@ public:
static Ptr<Text> create(const String &text);
static Ptr<Text> create(const String &text, Ptr<FontAtlas> font);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Text *withPosition(float x, float y);
Text *withPosition(const Vec2 &pos);
Text *withAnchor(float x, float y);
Text *withAnchor(const Vec2 &anchor);
Text *withTextColor(const Color &color);
Text *withFont(Ptr<FontAtlas> font);
Text *withFontSize(int size);
Text *withAlignment(Alignment align);
Text *withVerticalAlignment(VerticalAlignment align);
// ------------------------------------------------------------------------
// 文字内容
// ------------------------------------------------------------------------
@ -47,16 +66,12 @@ public:
// ------------------------------------------------------------------------
// 对齐方式
// ------------------------------------------------------------------------
enum class Alignment { Left, Center, Right };
void setAlignment(Alignment align);
Alignment getAlignment() const { return alignment_; }
// ------------------------------------------------------------------------
// 垂直对齐方式
// ------------------------------------------------------------------------
enum class VerticalAlignment { Top, Middle, Bottom };
void setVerticalAlignment(VerticalAlignment align);
VerticalAlignment getVerticalAlignment() const { return verticalAlignment_; }
@ -68,8 +83,17 @@ public:
Rect getBoundingBox() const override;
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
Text *withCoordinateSpace(CoordinateSpace space);
Text *withScreenPosition(float x, float y);
Text *withScreenPosition(const Vec2 &pos);
Text *withCameraOffset(float x, float y);
Text *withCameraOffset(const Vec2 &offset);
protected:
void onDraw(RenderBackend &renderer) override;
void onDrawWidget(RenderBackend &renderer) override;
private:
String text_;

View File

@ -17,6 +17,15 @@ struct MouseEvent {
int mods; // 修饰键状态
};
// ============================================================================
// 坐标空间枚举 - 定义 UI 组件的渲染坐标空间
// ============================================================================
enum class CoordinateSpace {
Screen, // 屏幕空间 - 固定位置,不随相机移动
World, // 世界空间 - 随相机移动(默认行为)
Camera, // 相机空间 - 相对于相机位置的偏移
};
// ============================================================================
// Widget 基类 - UI 组件的基础
// ============================================================================
@ -31,6 +40,26 @@ public:
Rect getBoundingBox() const override;
// ------------------------------------------------------------------------
// 坐标空间设置
// ------------------------------------------------------------------------
void setCoordinateSpace(CoordinateSpace space);
CoordinateSpace getCoordinateSpace() const { return coordinateSpace_; }
// ------------------------------------------------------------------------
// 屏幕空间位置设置(仅在 Screen 空间下有效)
// ------------------------------------------------------------------------
void setScreenPosition(const Vec2 &pos);
void setScreenPosition(float x, float y);
Vec2 getScreenPosition() const { return screenPosition_; }
// ------------------------------------------------------------------------
// 相机空间偏移设置(仅在 Camera 空间下有效)
// ------------------------------------------------------------------------
void setCameraOffset(const Vec2 &offset);
void setCameraOffset(float x, float y);
Vec2 getCameraOffset() const { return cameraOffset_; }
// ------------------------------------------------------------------------
// 鼠标事件处理(子类可重写)
// ------------------------------------------------------------------------
@ -58,11 +87,25 @@ protected:
return getBoundingBox().containsPoint(Point(x, y));
}
// 获取实际渲染位置(根据坐标空间计算)
Vec2 getRenderPosition() const;
// 子类重写此方法以支持自定义渲染
virtual void onDrawWidget(RenderBackend &renderer) {}
// 重写 Node 的 onDraw 以处理坐标空间
void onDraw(RenderBackend &renderer) override;
private:
Size size_ = Size::Zero();
bool enabled_ = true;
bool focused_ = false;
bool hovered_ = false;
// 坐标空间相关
CoordinateSpace coordinateSpace_ = CoordinateSpace::World;
Vec2 screenPosition_ = Vec2::Zero(); // 屏幕空间位置
Vec2 cameraOffset_ = Vec2::Zero(); // 相机空间偏移
};
} // namespace extra2d

View File

@ -7,15 +7,18 @@
namespace extra2d {
/**
* @brief
*/
// ============================================================================
// Button 实现
// ============================================================================
Button::Button() {
// 按钮默认锚点为左上角这样setPosition(0, 0)会在左上角显示
setAnchor(0.0f, 0.0f);
setSpatialIndexed(false);
auto &dispatcher = getEventDispatcher();
dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) {
hovered_ = true;
// 鼠标进入按钮区域,改变光标为手型
auto &app = Application::instance();
app.window().setCursor(hoverCursor_);
cursorChanged_ = true;
@ -23,7 +26,6 @@ Button::Button() {
dispatcher.addListener(EventType::UIHoverExit, [this](Event &) {
hovered_ = false;
pressed_ = false;
// 鼠标离开按钮区域,恢复默认光标
if (cursorChanged_) {
auto &app = Application::instance();
app.window().resetCursor();
@ -40,16 +42,137 @@ Button::Button() {
});
}
/**
* @brief
* @return
*/
Ptr<Button> Button::create() { return makePtr<Button>(); }
Button::Button(const String &text) : Button() {
text_ = text;
}
/**
* @brief
* @param text
*/
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
Ptr<Button> Button::create() {
return makePtr<Button>();
}
Ptr<Button> Button::create(const String &text) {
return makePtr<Button>(text);
}
Ptr<Button> Button::create(const String &text, Ptr<FontAtlas> font) {
auto btn = makePtr<Button>(text);
btn->setFont(font);
return btn;
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Button *Button::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
Button *Button::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
Button *Button::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
Button *Button::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
Button *Button::withText(const String &text) {
setText(text);
return this;
}
Button *Button::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
Button *Button::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
Button *Button::withBackgroundColor(const Color &normal, const Color &hover,
const Color &pressed) {
setBackgroundColor(normal, hover, pressed);
return this;
}
Button *Button::withSize(float width, float height) {
setSize(width, height);
return this;
}
Button *Button::withPadding(const Vec2 &padding) {
setPadding(padding);
return this;
}
Button *Button::withPadding(float x, float y) {
setPadding(Vec2(x, y));
return this;
}
Button *Button::withBorder(const Color &color, float width) {
setBorder(color, width);
return this;
}
Button *Button::withCornerRadius(float radius) {
setCornerRadius(radius);
return this;
}
Button *Button::withRoundedCornersEnabled(bool enabled) {
setRoundedCornersEnabled(enabled);
return this;
}
Button *Button::withHoverCursor(CursorShape cursor) {
setHoverCursor(cursor);
return this;
}
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
Button *Button::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
Button *Button::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
Button *Button::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
Button *Button::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
Button *Button::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
// ------------------------------------------------------------------------
// 普通设置方法
// ------------------------------------------------------------------------
void Button::setText(const String &text) {
text_ = text;
if (font_ && getSize().empty()) {
@ -58,10 +181,6 @@ void Button::setText(const String &text) {
}
}
/**
* @brief
* @param font
*/
void Button::setFont(Ptr<FontAtlas> font) {
font_ = font;
if (font_ && getSize().empty() && !text_.empty()) {
@ -70,10 +189,6 @@ void Button::setFont(Ptr<FontAtlas> font) {
}
}
/**
* @brief
* @param padding
*/
void Button::setPadding(const Vec2 &padding) {
padding_ = padding;
if (font_ && getSize().empty() && !text_.empty()) {
@ -82,18 +197,10 @@ void Button::setPadding(const Vec2 &padding) {
}
}
/**
* @brief
* @param color
*/
void Button::setTextColor(const Color &color) { textColor_ = color; }
void Button::setTextColor(const Color &color) {
textColor_ = color;
}
/**
* @brief
* @param normal
* @param hover
* @param pressed
*/
void Button::setBackgroundColor(const Color &normal, const Color &hover,
const Color &pressed) {
bgNormal_ = normal;
@ -101,60 +208,31 @@ void Button::setBackgroundColor(const Color &normal, const Color &hover,
bgPressed_ = pressed;
}
/**
* @brief
* @param color
* @param width
*/
void Button::setBorder(const Color &color, float width) {
borderColor_ = color;
borderWidth_ = width;
}
/**
* @brief
* @param radius
*/
void Button::setCornerRadius(float radius) {
cornerRadius_ = std::max(0.0f, radius);
}
/**
* @brief
* @param enabled
*/
void Button::setRoundedCornersEnabled(bool enabled) {
roundedCornersEnabled_ = enabled;
}
/**
* @brief 使Alpha遮罩进行点击检测
* @param enabled
*/
void Button::setUseAlphaMaskForHitTest(bool enabled) {
useAlphaMaskForHitTest_ = enabled;
}
/**
* @brief
* @param callback
*/
void Button::setOnClick(Function<void()> callback) {
onClick_ = std::move(callback);
}
/**
* @brief
* @param cursor
*/
void Button::setHoverCursor(CursorShape cursor) { hoverCursor_ = cursor; }
void Button::setHoverCursor(CursorShape cursor) {
hoverCursor_ = cursor;
}
/**
* @brief
* @param normal
* @param hover
* @param pressed
*/
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
Ptr<Texture> pressed) {
imgNormal_ = normal;
@ -162,20 +240,14 @@ void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
imgPressed_ = pressed ? pressed : (hover ? hover : normal);
useImageBackground_ = (normal != nullptr);
// 如果使用原图大小模式,设置按钮大小为图片大小
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original && normal) {
setSize(static_cast<float>(normal->getWidth()),
static_cast<float>(normal->getHeight()));
}
}
/**
* @brief
* @param mode
*/
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
scaleMode_ = mode;
// 如果切换到原图大小模式,更新按钮大小
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
imgNormal_) {
setSize(static_cast<float>(imgNormal_->getWidth()),
@ -183,27 +255,32 @@ void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
}
}
/**
* @brief
* @param size
*/
void Button::setCustomSize(const Vec2 &size) { setSize(size.x, size.y); }
void Button::setCustomSize(const Vec2 &size) {
setSize(size.x, size.y);
}
/**
* @brief
* @param width
* @param height
*/
void Button::setCustomSize(float width, float height) {
setSize(width, height);
}
/**
* @brief
* @param buttonSize
* @param imageSize
* @return
*/
Rect Button::getBoundingBox() const {
auto pos = getRenderPosition();
auto anchor = getAnchor();
auto scale = getScale();
auto size = getSize();
if (size.empty()) {
return Rect();
}
float w = size.width * scale.x;
float h = size.height * scale.y;
float x0 = pos.x - size.width * anchor.x * scale.x;
float y0 = pos.y - size.height * anchor.y * scale.y;
return Rect(x0, y0, w, h);
}
Vec2 Button::calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize) {
switch (scaleMode_) {
case ImageScaleMode::Original:
@ -229,11 +306,6 @@ Vec2 Button::calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize) {
return imageSize;
}
/**
* @brief
* @param renderer
* @param rect
*/
void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
Texture *texture = nullptr;
if (pressed_ && imgPressed_) {
@ -252,57 +324,37 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
Vec2 buttonSize(rect.size.width, rect.size.height);
Vec2 drawSize = calculateImageSize(buttonSize, imageSize);
// 计算绘制位置(居中)
Vec2 drawPos(rect.origin.x + (rect.size.width - drawSize.x) * 0.5f,
rect.origin.y + (rect.size.height - drawSize.y) * 0.5f);
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
// 绘制图片使用Alpha混合
renderer.drawSprite(*texture, destRect, Rect(0, 0, imageSize.x, imageSize.y),
Colors::White, 0.0f, Vec2::Zero());
}
/**
* @brief
* @param renderer
* @param rect
* @param color
* @param radius
*/
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
const Color &color, float radius) {
// 限制圆角半径不超过矩形尺寸的一半
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
radius = std::min(radius, maxRadius);
if (radius <= 0.0f) {
// 圆角为0使用普通矩形
renderer.drawRect(rect, color, borderWidth_);
return;
}
const int segments = 8; // 每个圆角的线段数
const int segments = 8;
float x = rect.origin.x;
float y = rect.origin.y;
float w = rect.size.width;
float h = rect.size.height;
float r = radius;
// 绘制四条直线边
// 上边
renderer.drawLine(Vec2(x + r, y), Vec2(x + w - r, y), color, borderWidth_);
// 下边
renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color,
borderWidth_);
// 左边
renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color, borderWidth_);
renderer.drawLine(Vec2(x, y + r), Vec2(x, y + h - r), color, borderWidth_);
// 右边
renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color,
borderWidth_);
renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color, borderWidth_);
// 绘制四个圆角(使用线段近似)
// 左上角
for (int i = 0; i < segments; i++) {
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f;
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f;
@ -310,16 +362,13 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
Vec2 p2(x + r + r * cosf(angle2), y + r + r * sinf(angle2));
renderer.drawLine(p1, p2, color, borderWidth_);
}
// 右上角
for (int i = 0; i < segments; i++) {
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f;
float angle2 =
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
Vec2 p1(x + w - r + r * cosf(angle1), y + r + r * sinf(angle1));
Vec2 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2));
renderer.drawLine(p1, p2, color, borderWidth_);
}
// 右下角
for (int i = 0; i < segments; i++) {
float angle1 = 3.14159f * 0.5f * (float)i / segments;
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments;
@ -327,66 +376,46 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
Vec2 p2(x + w - r + r * cosf(angle2), y + h - r + r * sinf(angle2));
renderer.drawLine(p1, p2, color, borderWidth_);
}
// 左下角
for (int i = 0; i < segments; i++) {
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f;
float angle2 =
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
Vec2 p1(x + r + r * cosf(angle1), y + h - r + r * sinf(angle1));
Vec2 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2));
renderer.drawLine(p1, p2, color, borderWidth_);
}
}
/**
* @brief
* @param renderer
* @param rect
* @param color
* @param radius
*/
void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
const Color &color, float radius) {
// 限制圆角半径不超过矩形尺寸的一半
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
radius = std::min(radius, maxRadius);
if (radius <= 0.0f) {
// 圆角为0使用普通矩形填充
renderer.fillRect(rect, color);
return;
}
const int segments = 8; // 每个圆角的线段数
const int segments = 8;
float x = rect.origin.x;
float y = rect.origin.y;
float w = rect.size.width;
float h = rect.size.height;
float r = radius;
// 构建圆角矩形的顶点(使用三角形扇形填充)
std::vector<Vec2> vertices;
// 中心矩形区域
vertices.push_back(Vec2(x + r, y + r)); // 左上内角
vertices.push_back(Vec2(x + w - r, y + r)); // 右上内角
vertices.push_back(Vec2(x + w - r, y + h - r)); // 右下内角
vertices.push_back(Vec2(x + r, y + h - r)); // 左下内角
vertices.push_back(Vec2(x + r, y + r));
vertices.push_back(Vec2(x + w - r, y + r));
vertices.push_back(Vec2(x + w - r, y + h - r));
vertices.push_back(Vec2(x + r, y + h - r));
renderer.fillPolygon(vertices, color);
// 填充四个侧边矩形
// 上边
renderer.fillRect(Rect(x + r, y, w - 2 * r, r), color);
// 下边
renderer.fillRect(Rect(x + r, y + h - r, w - 2 * r, r), color);
// 左边
renderer.fillRect(Rect(x, y + r, r, h - 2 * r), color);
// 右边
renderer.fillRect(Rect(x + w - r, y + r, r, h - 2 * r), color);
// 填充四个圆角(使用扇形)
// 左上角
vertices.clear();
vertices.push_back(Vec2(x + r, y + r));
for (int i = 0; i <= segments; i++) {
@ -395,7 +424,6 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
}
renderer.fillPolygon(vertices, color);
// 右上角
vertices.clear();
vertices.push_back(Vec2(x + w - r, y + r));
for (int i = 0; i <= segments; i++) {
@ -405,7 +433,6 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
}
renderer.fillPolygon(vertices, color);
// 右下角
vertices.clear();
vertices.push_back(Vec2(x + w - r, y + h - r));
for (int i = 0; i <= segments; i++) {
@ -415,7 +442,6 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
}
renderer.fillPolygon(vertices, color);
// 左下角
vertices.clear();
vertices.push_back(Vec2(x + r, y + h - r));
for (int i = 0; i <= segments; i++) {
@ -426,30 +452,15 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
renderer.fillPolygon(vertices, color);
}
/**
* @brief
*
*
* 1. -
* 2. -
* 3. -
*
*
* fillRect drawRect 使线
* drawText
*/
void Button::onDraw(RenderBackend &renderer) {
void Button::onDrawWidget(RenderBackend &renderer) {
Rect rect = getBoundingBox();
if (rect.empty()) {
return;
}
// ========== 第1层绘制背景图片或纯色==========
if (useImageBackground_) {
// 图片背景使用精灵批次绘制
drawBackgroundImage(renderer, rect);
} else {
// 纯色背景使用 fillRect 或 fillRoundedRect 绘制
renderer.endSpriteBatch();
Color bg = bgNormal_;
@ -468,7 +479,6 @@ void Button::onDraw(RenderBackend &renderer) {
renderer.beginSpriteBatch();
}
// ========== 第2层绘制边框 ==========
renderer.endSpriteBatch();
if (borderWidth_ > 0.0f) {
@ -481,7 +491,6 @@ void Button::onDraw(RenderBackend &renderer) {
renderer.beginSpriteBatch();
// ========== 第3层绘制文字 ==========
if (font_ && !text_.empty()) {
Vec2 textSize = font_->measureText(text_);
@ -507,30 +516,14 @@ void Button::onDraw(RenderBackend &renderer) {
// ToggleImageButton 实现
// ============================================================================
/**
* @brief
*/
ToggleImageButton::ToggleImageButton() {
setOnClick([this]() { toggle(); });
}
/**
* @brief
* @return
*/
Ptr<ToggleImageButton> ToggleImageButton::create() {
return makePtr<ToggleImageButton>();
}
/**
* @brief
* @param stateOffNormal -
* @param stateOnNormal -
* @param stateOffHover -
* @param stateOnHover -
* @param stateOffPressed -
* @param stateOnPressed -
*/
void ToggleImageButton::setStateImages(Ptr<Texture> stateOffNormal,
Ptr<Texture> stateOnNormal,
Ptr<Texture> stateOffHover,
@ -550,10 +543,6 @@ void ToggleImageButton::setStateImages(Ptr<Texture> stateOffNormal,
}
}
/**
* @brief
* @param on
*/
void ToggleImageButton::setOn(bool on) {
if (isOn_ != on) {
isOn_ = on;
@ -563,24 +552,14 @@ void ToggleImageButton::setOn(bool on) {
}
}
/**
* @brief
*/
void ToggleImageButton::toggle() { setOn(!isOn_); }
void ToggleImageButton::toggle() {
setOn(!isOn_);
}
/**
* @brief
* @param callback
*/
void ToggleImageButton::setOnStateChange(Function<void(bool)> callback) {
onStateChange_ = std::move(callback);
}
/**
* @brief
* @param textOff
* @param textOn
*/
void ToggleImageButton::setStateText(const String &textOff,
const String &textOn) {
textOff_ = textOff;
@ -588,11 +567,6 @@ void ToggleImageButton::setStateText(const String &textOff,
useStateText_ = true;
}
/**
* @brief
* @param colorOff
* @param colorOn
*/
void ToggleImageButton::setStateTextColor(const Color &colorOff,
const Color &colorOn) {
textColorOff_ = colorOff;
@ -600,21 +574,12 @@ void ToggleImageButton::setStateTextColor(const Color &colorOff,
useStateTextColor_ = true;
}
/**
* @brief
*
*
* 1. -
* 2. -
* 3. -
*/
void ToggleImageButton::onDraw(RenderBackend &renderer) {
void ToggleImageButton::onDrawWidget(RenderBackend &renderer) {
Rect rect = getBoundingBox();
if (rect.empty()) {
return;
}
// ========== 第1层根据当前状态和交互状态选择并绘制图片 ==========
Ptr<Texture> texture = nullptr;
if (isOn_) {
@ -650,7 +615,6 @@ void ToggleImageButton::onDraw(RenderBackend &renderer) {
0.0f, Vec2::Zero());
}
// ========== 第2层绘制边框 ==========
renderer.endSpriteBatch();
float borderWidth = 1.0f;
@ -666,7 +630,6 @@ void ToggleImageButton::onDraw(RenderBackend &renderer) {
renderer.beginSpriteBatch();
// ========== 第3层绘制状态文字 ==========
auto font = getFont();
if (font) {
String textToDraw;

View File

@ -19,6 +19,77 @@ Ptr<CheckBox> CheckBox::create(const String &label) {
return cb;
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
CheckBox *CheckBox::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
CheckBox *CheckBox::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
CheckBox *CheckBox::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
CheckBox *CheckBox::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
CheckBox *CheckBox::withText(const String &text) {
setLabel(text);
return this;
}
CheckBox *CheckBox::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
CheckBox *CheckBox::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
CheckBox *CheckBox::withSize(float width, float height) {
setSize(width, height);
return this;
}
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
CheckBox *CheckBox::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
CheckBox *CheckBox::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
CheckBox *CheckBox::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
CheckBox *CheckBox::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
CheckBox *CheckBox::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void CheckBox::setChecked(bool checked) {
if (checked_ != checked) {
checked_ = checked;
@ -80,7 +151,7 @@ Rect CheckBox::getBoundingBox() const {
return Rect(pos.x, pos.y, width, boxSize_);
}
void CheckBox::onDraw(RenderBackend &renderer) {
void CheckBox::onDrawWidget(RenderBackend &renderer) {
Vec2 pos = getPosition();
// 绘制复选框

View File

@ -28,6 +28,77 @@ Ptr<Label> Label::create(const String &text, Ptr<FontAtlas> font) {
return label;
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Label *Label::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
Label *Label::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
Label *Label::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
Label *Label::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
Label *Label::withText(const String &text) {
setText(text);
return this;
}
Label *Label::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
Label *Label::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
Label *Label::withFontSize(int size) {
setFontSize(size);
return this;
}
// ------------------------------------------------------------------------
// 坐标空间设置(链式调用)
// ------------------------------------------------------------------------
Label *Label::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
Label *Label::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
Label *Label::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
Label *Label::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
Label *Label::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void Label::setText(const String &text) {
text_ = text;
sizeDirty_ = true;
@ -280,7 +351,7 @@ Rect Label::getBoundingBox() const {
return Rect(pos.x, pos.y, size.x, size.y);
}
void Label::onDraw(RenderBackend &renderer) {
void Label::onDrawWidget(RenderBackend &renderer) {
if (!font_ || text_.empty()) {
return;
}

View File

@ -21,6 +21,65 @@ Ptr<ProgressBar> ProgressBar::create(float min, float max, float value) {
return bar;
}
// ============================================================================
// 链式调用构建器方法实现
// ============================================================================
ProgressBar *ProgressBar::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
ProgressBar *ProgressBar::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
ProgressBar *ProgressBar::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
ProgressBar *ProgressBar::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
ProgressBar *ProgressBar::withSize(float width, float height) {
setSize(width, height);
return this;
}
ProgressBar *ProgressBar::withProgress(float progress) {
setValue(min_ + progress * (max_ - min_));
return this;
}
ProgressBar *ProgressBar::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
ProgressBar *ProgressBar::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
ProgressBar *ProgressBar::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
ProgressBar *ProgressBar::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
ProgressBar *ProgressBar::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void ProgressBar::setRange(float min, float max) {
min_ = min;
max_ = max;
@ -226,7 +285,7 @@ void ProgressBar::onUpdate(float deltaTime) {
}
}
void ProgressBar::onDraw(RenderBackend &renderer) {
void ProgressBar::onDrawWidget(RenderBackend &renderer) {
Vec2 pos = getPosition();
Size size = getSize();

View File

@ -19,6 +19,80 @@ Ptr<RadioButton> RadioButton::create(const String &label) {
return rb;
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
RadioButton *RadioButton::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
RadioButton *RadioButton::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
RadioButton *RadioButton::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
RadioButton *RadioButton::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
RadioButton *RadioButton::withText(const String &text) {
setLabel(text);
return this;
}
RadioButton *RadioButton::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
RadioButton *RadioButton::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
RadioButton *RadioButton::withSize(float width, float height) {
setSize(width, height);
return this;
}
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
RadioButton *RadioButton::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
RadioButton *RadioButton::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
RadioButton *RadioButton::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
RadioButton *RadioButton::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
RadioButton *RadioButton::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
// ------------------------------------------------------------------------
// 普通设置方法
// ------------------------------------------------------------------------
void RadioButton::setSelected(bool selected) {
if (selected_ != selected) {
selected_ = selected;
@ -80,7 +154,7 @@ Rect RadioButton::getBoundingBox() const {
return Rect(pos.x, pos.y, width, circleSize_);
}
void RadioButton::onDraw(RenderBackend &renderer) {
void RadioButton::onDrawWidget(RenderBackend &renderer) {
Vec2 pos = getPosition();
float centerX = pos.x + circleSize_ * 0.5f;
float centerY = pos.y + getSize().height * 0.5f;

View File

@ -21,6 +21,147 @@ Ptr<Slider> Slider::create(float min, float max, float value) {
return slider;
}
// ============================================================================
// 链式调用构建器方法实现
// ============================================================================
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return this指针
*/
Slider *Slider::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param pos
* @return this指针
*/
Slider *Slider::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
/**
* @brief
* @param x X锚点0-1
* @param y Y锚点0-1
* @return this指针
*/
Slider *Slider::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param anchor
* @return this指针
*/
Slider *Slider::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
/**
* @brief
* @param width
* @param height
* @return this指针
*/
Slider *Slider::withSize(float width, float height) {
setSize(width, height);
return this;
}
/**
* @brief
* @param min
* @return this指针
*/
Slider *Slider::withMinValue(float min) {
min_ = min;
setValue(value_);
return this;
}
/**
* @brief
* @param max
* @return this指针
*/
Slider *Slider::withMaxValue(float max) {
max_ = max;
setValue(value_);
return this;
}
/**
* @brief
* @param value
* @return this指针
*/
Slider *Slider::withValue(float value) {
setValue(value);
return this;
}
/**
* @brief
* @param space
* @return this指针
*/
Slider *Slider::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
/**
* @brief
* @param x X屏幕坐标
* @param y Y屏幕坐标
* @return this指针
*/
Slider *Slider::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param pos
* @return this指针
*/
Slider *Slider::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
/**
* @brief
* @param x X偏移量
* @param y Y偏移量
* @return this指针
*/
Slider *Slider::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param offset
* @return this指针
*/
Slider *Slider::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void Slider::setRange(float min, float max) {
min_ = min;
max_ = max;
@ -217,7 +358,7 @@ float Slider::snapToStep(float value) const {
return min_ + steps * step_;
}
void Slider::onDraw(RenderBackend &renderer) {
void Slider::onDrawWidget(RenderBackend &renderer) {
Rect trackRect = getTrackRect();
// 绘制轨道背景

View File

@ -15,6 +15,85 @@ Text::Text(const String &text) : text_(text) {
setAnchor(0.0f, 0.0f);
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Text *Text::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
Text *Text::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
Text *Text::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
Text *Text::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
Text *Text::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
Text *Text::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
Text *Text::withFontSize(int size) {
setFontSize(size);
return this;
}
Text *Text::withAlignment(Alignment align) {
setAlignment(align);
return this;
}
Text *Text::withVerticalAlignment(VerticalAlignment align) {
setVerticalAlignment(align);
return this;
}
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
Text *Text::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
Text *Text::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
Text *Text::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
Text *Text::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
Text *Text::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
// ------------------------------------------------------------------------
// 普通设置方法
// ------------------------------------------------------------------------
void Text::setText(const String &text) {
text_ = text;
sizeDirty_ = true;
@ -70,35 +149,45 @@ Vec2 Text::calculateDrawPosition() const {
Vec2 pos = getPosition();
Vec2 textSize = getTextSize();
Size widgetSize = getSize();
Vec2 anchor = getAnchor();
// 如果设置了控件大小,使用控件大小作为对齐参考
float refWidth = widgetSize.empty() ? textSize.x : widgetSize.width;
float refHeight = widgetSize.empty() ? textSize.y : widgetSize.height;
// 水平对齐
switch (alignment_) {
case Alignment::Center:
pos.x += (refWidth - textSize.x) * 0.5f;
break;
case Alignment::Right:
pos.x += refWidth - textSize.x;
break;
case Alignment::Left:
default:
break;
// 锚点调整:锚点(0.5, 0.5)表示文本中心在pos位置
// 需要将文本向左上方偏移锚点比例 * 文本大小
pos.x -= textSize.x * anchor.x;
pos.y -= textSize.y * anchor.y;
// 水平对齐(仅在设置了控件大小时生效)
if (!widgetSize.empty()) {
switch (alignment_) {
case Alignment::Center:
pos.x += (refWidth - textSize.x) * 0.5f;
break;
case Alignment::Right:
pos.x += refWidth - textSize.x;
break;
case Alignment::Left:
default:
break;
}
}
// 垂直对齐
switch (verticalAlignment_) {
case VerticalAlignment::Middle:
pos.y += (refHeight - textSize.y) * 0.5f;
break;
case VerticalAlignment::Bottom:
pos.y += refHeight - textSize.y;
break;
case VerticalAlignment::Top:
default:
break;
// 垂直对齐(仅在设置了控件大小时生效)
if (!widgetSize.empty()) {
switch (verticalAlignment_) {
case VerticalAlignment::Middle:
pos.y += (refHeight - textSize.y) * 0.5f;
break;
case VerticalAlignment::Bottom:
pos.y += refHeight - textSize.y;
break;
case VerticalAlignment::Top:
default:
break;
}
}
return pos;
@ -129,7 +218,7 @@ Rect Text::getBoundingBox() const {
return Rect(pos.x, pos.y, size.x, size.y);
}
void Text::onDraw(RenderBackend &renderer) {
void Text::onDrawWidget(RenderBackend &renderer) {
if (!font_ || text_.empty()) {
return;
}

View File

@ -1,4 +1,6 @@
#include <cmath>
#include <extra2d/graphics/camera.h>
#include <extra2d/scene/scene.h>
#include <extra2d/ui/widget.h>
namespace extra2d {
@ -21,7 +23,7 @@ Rect Widget::getBoundingBox() const {
return Rect();
}
auto pos = getPosition();
auto pos = getRenderPosition();
auto anchor = getAnchor();
auto scale = getScale();
@ -37,4 +39,103 @@ Rect Widget::getBoundingBox() const {
return Rect(l, t, std::abs(w), std::abs(h));
}
// ------------------------------------------------------------------------
// 坐标空间设置
// ------------------------------------------------------------------------
void Widget::setCoordinateSpace(CoordinateSpace space) {
coordinateSpace_ = space;
}
void Widget::setScreenPosition(const Vec2 &pos) {
screenPosition_ = pos;
if (coordinateSpace_ == CoordinateSpace::Screen) {
updateSpatialIndex();
}
}
void Widget::setScreenPosition(float x, float y) {
setScreenPosition(Vec2(x, y));
}
void Widget::setCameraOffset(const Vec2 &offset) {
cameraOffset_ = offset;
if (coordinateSpace_ == CoordinateSpace::Camera) {
updateSpatialIndex();
}
}
void Widget::setCameraOffset(float x, float y) {
setCameraOffset(Vec2(x, y));
}
// ------------------------------------------------------------------------
// 获取实际渲染位置(根据坐标空间计算)
// ------------------------------------------------------------------------
Vec2 Widget::getRenderPosition() const {
switch (coordinateSpace_) {
case CoordinateSpace::Screen:
// 屏幕空间:直接使用屏幕位置
return screenPosition_;
case CoordinateSpace::Camera: {
// 相机空间:相机位置 + 偏移
Scene *scene = getScene();
if (scene) {
Camera *camera = scene->getActiveCamera();
if (camera) {
return camera->getPosition() + cameraOffset_;
}
}
// 如果没有场景或相机,使用偏移作为绝对位置
return cameraOffset_;
}
case CoordinateSpace::World:
default:
// 世界空间:使用节点的世界位置
return getPosition();
}
}
// ------------------------------------------------------------------------
// 重写 onDraw 以处理坐标空间
// ------------------------------------------------------------------------
void Widget::onDraw(RenderBackend &renderer) {
// 根据坐标空间调整渲染
if (coordinateSpace_ == CoordinateSpace::Screen) {
// 屏幕空间:临时修改位置为屏幕位置,保持锚点不变
Vec2 worldPos = getPosition();
const_cast<Widget*>(this)->setPosition(screenPosition_);
// 调用子类的绘制
onDrawWidget(renderer);
// 恢复原始位置
const_cast<Widget*>(this)->setPosition(worldPos);
} else if (coordinateSpace_ == CoordinateSpace::Camera) {
// 相机空间:计算相对于相机的位置
Scene *scene = getScene();
if (scene) {
Camera *camera = scene->getActiveCamera();
if (camera) {
Vec2 worldPos = getPosition();
Vec2 cameraRelativePos = camera->getPosition() + cameraOffset_;
const_cast<Widget*>(this)->setPosition(cameraRelativePos);
// 调用子类的绘制
onDrawWidget(renderer);
// 恢复原始位置
const_cast<Widget*>(this)->setPosition(worldPos);
return;
}
}
// 如果没有场景或相机,按世界空间处理
onDrawWidget(renderer);
} else {
// 世界空间:正常渲染
onDrawWidget(renderer);
}
}
} // namespace extra2d

View File

@ -28,7 +28,40 @@ public:
if (!font_) {
E2D_LOG_ERROR("字体加载失败,文字渲染将不可用!");
return;
}
// 创建 "你好世界" 文本组件 - 使用屏幕空间(固定位置,不随相机移动)
auto text1 = Text::create("你好世界", font_);
text1->withCoordinateSpace(CoordinateSpace::Screen)
->withScreenPosition(640.0f, 360.0f) // 屏幕中心
->withAnchor(0.5f, 0.5f) // 中心锚点,让文字中心对准位置
->withTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
addChild(text1);
// 创建提示文本组件 - 使用屏幕空间,固定在屏幕底部
auto text2 = Text::create("退出按键START 按钮)", font_);
text2->withCoordinateSpace(CoordinateSpace::Screen)
->withScreenPosition(640.0f, 650.0f) // 屏幕底部
->withAnchor(0.5f, 0.5f)
->withTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f));
addChild(text2);
// 创建相机空间文本 - 跟随相机但保持相对偏移
auto text3 = Text::create("相机空间文本", font_);
text3->withCoordinateSpace(CoordinateSpace::Camera)
->withCameraOffset(50.0f, 50.0f) // 相机左上角偏移屏幕坐标系Y向下
->withAnchor(0.0f, 0.0f) // 左上角锚点,文字从指定位置开始显示
->withTextColor(Color(0.0f, 1.0f, 1.0f, 1.0f));
addChild(text3);
// 创建世界空间文本 - 随相机移动(默认行为)
auto text4 = Text::create("世界空间文本", font_);
text4->withCoordinateSpace(CoordinateSpace::World)
->withPosition(100.0f, 100.0f) // 世界坐标
->withAnchor(0.0f, 0.0f) // 左上角锚点,文字从指定位置开始显示
->withTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f));
addChild(text4);
}
/**
@ -48,27 +81,6 @@ public:
}
}
/**
* @brief
* @param renderer
*/
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
if (!font_)
return;
// 屏幕中心位置
float centerX = 640.0f; // 1280 / 2
float centerY = 360.0f; // 720 / 2
// 绘制 "你好世界" 文字(白色,居中)
renderer.drawText(*font_, "你好世界", Vec2(centerX - 100.0f, centerY),Color(1.0f, 1.0f, 1.0f, 1.0f));
// 绘制提示文字(黄色)
renderer.drawText(*font_, "退出按键START 按钮)",Vec2(centerX - 80.0f, centerY + 50.0f), Color(1.0f, 1.0f, 0.0f, 1.0f));
}
private:
Ptr<FontAtlas> font_; // 字体图集
};