2026-02-11 19:40:26 +08:00
|
|
|
|
#include <algorithm>
|
2026-02-25 06:23:53 +08:00
|
|
|
|
#include <app/application.h>
|
2026-02-26 00:55:13 +08:00
|
|
|
|
#include <cmath>
|
2026-02-25 06:23:53 +08:00
|
|
|
|
#include <core/string.h>
|
|
|
|
|
|
#include <graphics/render_backend.h>
|
|
|
|
|
|
#include <ui/button.h>
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
namespace extra2d {
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Button 实现
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 默认构造函数
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Button::Button() {
|
|
|
|
|
|
setAnchor(0.0f, 0.0f);
|
|
|
|
|
|
setSpatialIndexed(false);
|
2026-02-13 13:56:18 +08:00
|
|
|
|
|
2026-02-11 19:40:26 +08:00
|
|
|
|
auto &dispatcher = getEventDispatcher();
|
|
|
|
|
|
dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) {
|
|
|
|
|
|
hovered_ = true;
|
|
|
|
|
|
auto &app = Application::instance();
|
|
|
|
|
|
app.window().setCursor(hoverCursor_);
|
|
|
|
|
|
cursorChanged_ = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
dispatcher.addListener(EventType::UIHoverExit, [this](Event &) {
|
|
|
|
|
|
hovered_ = false;
|
|
|
|
|
|
pressed_ = false;
|
|
|
|
|
|
if (cursorChanged_) {
|
|
|
|
|
|
auto &app = Application::instance();
|
|
|
|
|
|
app.window().resetCursor();
|
|
|
|
|
|
cursorChanged_ = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
dispatcher.addListener(EventType::UIPressed,
|
|
|
|
|
|
[this](Event &) { pressed_ = true; });
|
|
|
|
|
|
dispatcher.addListener(EventType::UIReleased,
|
|
|
|
|
|
[this](Event &) { pressed_ = false; });
|
|
|
|
|
|
dispatcher.addListener(EventType::UIClicked, [this](Event &) {
|
2026-02-13 13:56:18 +08:00
|
|
|
|
if (toggleMode_) {
|
|
|
|
|
|
toggle();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (onClick_) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
onClick_();
|
2026-02-13 13:56:18 +08:00
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 带文本的构造函数
|
|
|
|
|
|
* @param text 按钮文本
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
Button::Button(const std::string &text) : Button() { text_ = text; }
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
// 静态创建方法
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 创建空按钮对象
|
|
|
|
|
|
* @return 按钮对象指针
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
Ptr<Button> Button::create() { return makePtr<Button>(); }
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 创建带文本的按钮对象
|
|
|
|
|
|
* @param text 按钮文本
|
|
|
|
|
|
* @return 按钮对象指针
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
Ptr<Button> Button::create(const std::string &text) {
|
|
|
|
|
|
return makePtr<Button>(text);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 创建带文本和字体的按钮对象
|
|
|
|
|
|
* @param text 按钮文本
|
|
|
|
|
|
* @param font 字体图集
|
|
|
|
|
|
* @return 按钮对象指针
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Ptr<Button> Button::create(const std::string &text, Ptr<FontAtlas> font) {
|
|
|
|
|
|
auto btn = makePtr<Button>(text);
|
|
|
|
|
|
btn->setFont(font);
|
|
|
|
|
|
return btn;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
// 普通设置方法
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置按钮文本
|
|
|
|
|
|
* @param text 文本内容
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setText(const std::string &text) {
|
|
|
|
|
|
text_ = text;
|
2026-02-26 00:55:13 +08:00
|
|
|
|
if (font_ && size().empty()) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Vec2 textSize = font_->measureText(text_);
|
|
|
|
|
|
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置字体
|
|
|
|
|
|
* @param font 字体图集指针
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setFont(Ptr<FontAtlas> font) {
|
|
|
|
|
|
font_ = font;
|
2026-02-26 00:55:13 +08:00
|
|
|
|
if (font_ && size().empty() && !text_.empty()) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Vec2 textSize = font_->measureText(text_);
|
|
|
|
|
|
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置内边距(Vec2版本)
|
|
|
|
|
|
* @param padding 内边距向量
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setPadding(const Vec2 &padding) {
|
|
|
|
|
|
padding_ = padding;
|
2026-02-26 00:55:13 +08:00
|
|
|
|
if (font_ && size().empty() && !text_.empty()) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Vec2 textSize = font_->measureText(text_);
|
|
|
|
|
|
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置内边距(分量版本)
|
|
|
|
|
|
* @param x X方向内边距
|
|
|
|
|
|
* @param y Y方向内边距
|
|
|
|
|
|
*/
|
2026-02-26 00:55:13 +08:00
|
|
|
|
void Button::setPadding(float x, float y) { setPadding(Vec2(x, y)); }
|
2026-02-13 16:52:57 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置文本颜色
|
|
|
|
|
|
* @param color 文本颜色
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setTextColor(const Color &color) { textColor_ = color; }
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置背景颜色
|
|
|
|
|
|
* @param normal 普通状态颜色
|
|
|
|
|
|
* @param hover 悬停状态颜色
|
|
|
|
|
|
* @param pressed 按下状态颜色
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
|
|
|
|
|
const Color &pressed) {
|
|
|
|
|
|
bgNormal_ = normal;
|
|
|
|
|
|
bgHover_ = hover;
|
|
|
|
|
|
bgPressed_ = pressed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置边框
|
|
|
|
|
|
* @param color 边框颜色
|
|
|
|
|
|
* @param width 边框宽度
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setBorder(const Color &color, float width) {
|
|
|
|
|
|
borderColor_ = color;
|
|
|
|
|
|
borderWidth_ = width;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置圆角半径
|
|
|
|
|
|
* @param radius 圆角半径
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setCornerRadius(float radius) {
|
|
|
|
|
|
cornerRadius_ = std::max(0.0f, radius);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置是否启用圆角
|
|
|
|
|
|
* @param enabled 是否启用
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setRoundedCornersEnabled(bool enabled) {
|
|
|
|
|
|
roundedCornersEnabled_ = enabled;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置是否使用Alpha遮罩进行点击检测
|
|
|
|
|
|
* @param enabled 是否启用
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setUseAlphaMaskForHitTest(bool enabled) {
|
|
|
|
|
|
useAlphaMaskForHitTest_ = enabled;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置点击回调
|
|
|
|
|
|
* @param callback 回调函数
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setOnClick(Function<void()> callback) {
|
|
|
|
|
|
onClick_ = std::move(callback);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置是否为切换模式
|
|
|
|
|
|
* @param enabled 是否启用
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setToggleMode(bool enabled) { toggleMode_ = enabled; }
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置开关状态
|
|
|
|
|
|
* @param on 是否开启
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setOn(bool on) {
|
|
|
|
|
|
if (isOn_ != on) {
|
|
|
|
|
|
isOn_ = on;
|
|
|
|
|
|
if (onStateChange_) {
|
|
|
|
|
|
onStateChange_(isOn_);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 切换状态
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::toggle() { setOn(!isOn_); }
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置状态改变回调
|
|
|
|
|
|
* @param callback 回调函数
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setOnStateChange(Function<void(bool)> callback) {
|
|
|
|
|
|
onStateChange_ = std::move(callback);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置状态文字
|
|
|
|
|
|
* @param textOff 关闭状态文字
|
|
|
|
|
|
* @param textOn 开启状态文字
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setStateText(const std::string &textOff,
|
|
|
|
|
|
const std::string &textOn) {
|
|
|
|
|
|
textOff_ = textOff;
|
|
|
|
|
|
textOn_ = textOn;
|
|
|
|
|
|
useStateText_ = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置状态文字颜色
|
|
|
|
|
|
* @param colorOff 关闭状态颜色
|
|
|
|
|
|
* @param colorOn 开启状态颜色
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setStateTextColor(const Color &colorOff, const Color &colorOn) {
|
|
|
|
|
|
textColorOff_ = colorOff;
|
|
|
|
|
|
textColorOn_ = colorOn;
|
|
|
|
|
|
useStateTextColor_ = true;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置悬停光标
|
|
|
|
|
|
* @param cursor 光标形状
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setHoverCursor(CursorShape cursor) { hoverCursor_ = cursor; }
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置背景图片
|
|
|
|
|
|
* @param normal 普通状态图片
|
|
|
|
|
|
* @param hover 悬停状态图片
|
|
|
|
|
|
* @param pressed 按下状态图片
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
|
|
|
|
|
|
Ptr<Texture> pressed) {
|
|
|
|
|
|
imgNormal_ = normal;
|
|
|
|
|
|
imgHover_ = hover ? hover : normal;
|
|
|
|
|
|
imgPressed_ = pressed ? pressed : (hover ? hover : normal);
|
|
|
|
|
|
useImageBackground_ = (normal != nullptr);
|
2026-02-12 21:50:21 +08:00
|
|
|
|
useTextureRect_ = false;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original && normal) {
|
2026-02-26 00:59:16 +08:00
|
|
|
|
setSize(static_cast<float>(normal->width()),
|
|
|
|
|
|
static_cast<float>(normal->height()));
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置背景图片(带矩形区域)
|
|
|
|
|
|
* @param texture 纹理
|
|
|
|
|
|
* @param rect 矩形区域
|
|
|
|
|
|
*/
|
2026-02-12 21:50:21 +08:00
|
|
|
|
void Button::setBackgroundImage(Ptr<Texture> texture, const Rect &rect) {
|
|
|
|
|
|
imgNormal_ = texture;
|
|
|
|
|
|
imgHover_ = texture;
|
|
|
|
|
|
imgPressed_ = texture;
|
|
|
|
|
|
imgNormalRect_ = rect;
|
|
|
|
|
|
imgHoverRect_ = rect;
|
|
|
|
|
|
imgPressedRect_ = rect;
|
|
|
|
|
|
useImageBackground_ = (texture != nullptr);
|
|
|
|
|
|
useTextureRect_ = true;
|
2026-02-13 13:56:18 +08:00
|
|
|
|
useStateImages_ = false;
|
2026-02-12 21:50:21 +08:00
|
|
|
|
|
|
|
|
|
|
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original) {
|
|
|
|
|
|
setSize(rect.size.width, rect.size.height);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置状态背景图片
|
|
|
|
|
|
* @param offNormal 关闭状态普通图片
|
|
|
|
|
|
* @param onNormal 开启状态普通图片
|
|
|
|
|
|
* @param offHover 关闭状态悬停图片
|
|
|
|
|
|
* @param onHover 开启状态悬停图片
|
|
|
|
|
|
* @param offPressed 关闭状态按下图片
|
|
|
|
|
|
* @param onPressed 开启状态按下图片
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setStateBackgroundImage(
|
|
|
|
|
|
Ptr<Texture> offNormal, Ptr<Texture> onNormal, Ptr<Texture> offHover,
|
|
|
|
|
|
Ptr<Texture> onHover, Ptr<Texture> offPressed, Ptr<Texture> onPressed) {
|
|
|
|
|
|
imgOffNormal_ = offNormal;
|
|
|
|
|
|
imgOnNormal_ = onNormal;
|
|
|
|
|
|
imgOffHover_ = offHover ? offHover : offNormal;
|
|
|
|
|
|
imgOnHover_ = onHover ? onHover : onNormal;
|
|
|
|
|
|
imgOffPressed_ = offPressed ? offPressed : offNormal;
|
|
|
|
|
|
imgOnPressed_ = onPressed ? onPressed : onNormal;
|
|
|
|
|
|
|
|
|
|
|
|
useStateImages_ = (offNormal != nullptr && onNormal != nullptr);
|
|
|
|
|
|
useImageBackground_ = useStateImages_;
|
|
|
|
|
|
useTextureRect_ = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (useStateImages_ && scaleMode_ == ImageScaleMode::Original && offNormal) {
|
2026-02-26 00:59:16 +08:00
|
|
|
|
setSize(static_cast<float>(offNormal->width()),
|
|
|
|
|
|
static_cast<float>(offNormal->height()));
|
2026-02-13 13:56:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置背景图片缩放模式
|
|
|
|
|
|
* @param mode 缩放模式
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
|
|
|
|
|
scaleMode_ = mode;
|
|
|
|
|
|
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
|
|
|
|
|
|
imgNormal_) {
|
2026-02-26 00:59:16 +08:00
|
|
|
|
setSize(static_cast<float>(imgNormal_->width()),
|
|
|
|
|
|
static_cast<float>(imgNormal_->height()));
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置自定义尺寸(Vec2版本)
|
|
|
|
|
|
* @param size 尺寸向量
|
|
|
|
|
|
*/
|
2026-02-13 13:56:18 +08:00
|
|
|
|
void Button::setCustomSize(const Vec2 &size) { setSize(size.x, size.y); }
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 设置自定义尺寸(分量版本)
|
|
|
|
|
|
* @param width 宽度
|
|
|
|
|
|
* @param height 高度
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
void Button::setCustomSize(float width, float height) {
|
|
|
|
|
|
setSize(width, height);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取边界框
|
|
|
|
|
|
* @return 边界矩形
|
|
|
|
|
|
*/
|
2026-02-26 00:55:13 +08:00
|
|
|
|
Rect Button::boundingBox() const {
|
2026-02-26 00:38:31 +08:00
|
|
|
|
auto position = convertToWorldSpace(extra2d::Vec2::Zero());
|
|
|
|
|
|
auto anchorPt = anchor();
|
|
|
|
|
|
auto scaleVal = scale();
|
2026-02-26 00:55:13 +08:00
|
|
|
|
auto widgetSize = size();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-26 00:55:13 +08:00
|
|
|
|
if (widgetSize.empty()) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
return Rect();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 00:55:13 +08:00
|
|
|
|
float w = widgetSize.width * scaleVal.x;
|
|
|
|
|
|
float h = widgetSize.height * scaleVal.y;
|
|
|
|
|
|
float x0 = position.x - widgetSize.width * anchorPt.x * scaleVal.x;
|
|
|
|
|
|
float y0 = position.y - widgetSize.height * anchorPt.y * scaleVal.y;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
return Rect(x0, y0, w, h);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 计算图片尺寸
|
|
|
|
|
|
* @param buttonSize 按钮尺寸
|
|
|
|
|
|
* @param imageSize 图片尺寸
|
|
|
|
|
|
* @return 计算后的图片尺寸
|
|
|
|
|
|
*/
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Vec2 Button::calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize) {
|
|
|
|
|
|
switch (scaleMode_) {
|
|
|
|
|
|
case ImageScaleMode::Original:
|
|
|
|
|
|
return imageSize;
|
|
|
|
|
|
|
|
|
|
|
|
case ImageScaleMode::Stretch:
|
|
|
|
|
|
return buttonSize;
|
|
|
|
|
|
|
|
|
|
|
|
case ImageScaleMode::ScaleFit: {
|
|
|
|
|
|
float scaleX = buttonSize.x / imageSize.x;
|
|
|
|
|
|
float scaleY = buttonSize.y / imageSize.y;
|
|
|
|
|
|
float scale = std::min(scaleX, scaleY);
|
|
|
|
|
|
return Vec2(imageSize.x * scale, imageSize.y * scale);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case ImageScaleMode::ScaleFill: {
|
|
|
|
|
|
float scaleX = buttonSize.x / imageSize.x;
|
|
|
|
|
|
float scaleY = buttonSize.y / imageSize.y;
|
|
|
|
|
|
float scale = std::max(scaleX, scaleY);
|
|
|
|
|
|
return Vec2(imageSize.x * scale, imageSize.y * scale);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return imageSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 绘制背景图片
|
|
|
|
|
|
* @param renderer 渲染后端
|
|
|
|
|
|
* @param rect 绘制区域
|
|
|
|
|
|
*/
|
2026-02-26 00:59:16 +08:00
|
|
|
|
void Button::drawBackgroundImage(Renderer &renderer, const Rect &rect) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Texture *texture = nullptr;
|
2026-02-12 21:50:21 +08:00
|
|
|
|
Rect srcRect;
|
|
|
|
|
|
|
2026-02-13 13:56:18 +08:00
|
|
|
|
if (useStateImages_) {
|
|
|
|
|
|
if (isOn_) {
|
|
|
|
|
|
if (pressed_ && imgOnPressed_) {
|
|
|
|
|
|
texture = imgOnPressed_.get();
|
|
|
|
|
|
} else if (hovered_ && imgOnHover_) {
|
|
|
|
|
|
texture = imgOnHover_.get();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
texture = imgOnNormal_.get();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (pressed_ && imgOffPressed_) {
|
|
|
|
|
|
texture = imgOffPressed_.get();
|
|
|
|
|
|
} else if (hovered_ && imgOffHover_) {
|
|
|
|
|
|
texture = imgOffHover_.get();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
texture = imgOffNormal_.get();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (texture) {
|
2026-02-26 00:59:16 +08:00
|
|
|
|
srcRect = Rect(0, 0, static_cast<float>(texture->width()),
|
|
|
|
|
|
static_cast<float>(texture->height()));
|
2026-02-13 13:56:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (pressed_ && imgPressed_) {
|
|
|
|
|
|
texture = imgPressed_.get();
|
|
|
|
|
|
srcRect = useTextureRect_
|
|
|
|
|
|
? imgPressedRect_
|
2026-02-26 00:59:16 +08:00
|
|
|
|
: Rect(0, 0, static_cast<float>(imgPressed_->width()),
|
|
|
|
|
|
static_cast<float>(imgPressed_->height()));
|
2026-02-13 13:56:18 +08:00
|
|
|
|
} else if (hovered_ && imgHover_) {
|
|
|
|
|
|
texture = imgHover_.get();
|
|
|
|
|
|
srcRect = useTextureRect_
|
|
|
|
|
|
? imgHoverRect_
|
2026-02-26 00:59:16 +08:00
|
|
|
|
: Rect(0, 0, static_cast<float>(imgHover_->width()),
|
|
|
|
|
|
static_cast<float>(imgHover_->height()));
|
2026-02-13 13:56:18 +08:00
|
|
|
|
} else if (imgNormal_) {
|
|
|
|
|
|
texture = imgNormal_.get();
|
|
|
|
|
|
srcRect = useTextureRect_
|
|
|
|
|
|
? imgNormalRect_
|
2026-02-26 00:59:16 +08:00
|
|
|
|
: Rect(0, 0, static_cast<float>(imgNormal_->width()),
|
|
|
|
|
|
static_cast<float>(imgNormal_->height()));
|
2026-02-13 13:56:18 +08:00
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!texture)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2026-02-12 21:50:21 +08:00
|
|
|
|
Vec2 imageSize(srcRect.size.width, srcRect.size.height);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2026-02-13 13:56:18 +08:00
|
|
|
|
renderer.drawSprite(*texture, destRect, srcRect, Colors::White, 0.0f,
|
|
|
|
|
|
Vec2::Zero());
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 绘制圆角矩形边框
|
|
|
|
|
|
* @param renderer 渲染后端
|
|
|
|
|
|
* @param rect 矩形区域
|
|
|
|
|
|
* @param color 颜色
|
|
|
|
|
|
* @param radius 圆角半径
|
|
|
|
|
|
*/
|
2026-02-26 00:59:16 +08:00
|
|
|
|
void Button::drawRoundedRect(Renderer &renderer, const Rect &rect,
|
2026-02-11 19:40:26 +08:00
|
|
|
|
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) {
|
|
|
|
|
|
renderer.drawRect(rect, color, borderWidth_);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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_);
|
2026-02-13 13:56:18 +08:00
|
|
|
|
renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color,
|
|
|
|
|
|
borderWidth_);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
renderer.drawLine(Vec2(x, y + r), Vec2(x, y + h - r), color, borderWidth_);
|
2026-02-13 13:56:18 +08:00
|
|
|
|
renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color,
|
|
|
|
|
|
borderWidth_);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
Vec2 p1(x + r + r * cosf(angle1), y + r + r * sinf(angle1));
|
|
|
|
|
|
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;
|
2026-02-13 13:56:18 +08:00
|
|
|
|
float angle2 =
|
|
|
|
|
|
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
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;
|
|
|
|
|
|
Vec2 p1(x + w - r + r * cosf(angle1), y + h - r + r * sinf(angle1));
|
|
|
|
|
|
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;
|
2026-02-13 13:56:18 +08:00
|
|
|
|
float angle2 =
|
|
|
|
|
|
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
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_);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 填充圆角矩形
|
|
|
|
|
|
* @param renderer 渲染后端
|
|
|
|
|
|
* @param rect 矩形区域
|
|
|
|
|
|
* @param color 颜色
|
|
|
|
|
|
* @param radius 圆角半径
|
|
|
|
|
|
*/
|
2026-02-26 00:59:16 +08:00
|
|
|
|
void Button::fillRoundedRect(Renderer &renderer, const Rect &rect,
|
2026-02-11 19:40:26 +08:00
|
|
|
|
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) {
|
|
|
|
|
|
renderer.fillRect(rect, color);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
|
|
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++) {
|
|
|
|
|
|
float angle = 3.14159f + 3.14159f * 0.5f * (float)i / segments;
|
|
|
|
|
|
vertices.push_back(Vec2(x + r + r * cosf(angle), y + r + r * sinf(angle)));
|
|
|
|
|
|
}
|
|
|
|
|
|
renderer.fillPolygon(vertices, color);
|
|
|
|
|
|
|
|
|
|
|
|
vertices.clear();
|
|
|
|
|
|
vertices.push_back(Vec2(x + w - r, y + r));
|
|
|
|
|
|
for (int i = 0; i <= segments; i++) {
|
|
|
|
|
|
float angle = 3.14159f * 1.5f + 3.14159f * 0.5f * (float)i / segments;
|
|
|
|
|
|
vertices.push_back(
|
|
|
|
|
|
Vec2(x + w - r + r * cosf(angle), y + r + r * sinf(angle)));
|
|
|
|
|
|
}
|
|
|
|
|
|
renderer.fillPolygon(vertices, color);
|
|
|
|
|
|
|
|
|
|
|
|
vertices.clear();
|
|
|
|
|
|
vertices.push_back(Vec2(x + w - r, y + h - r));
|
|
|
|
|
|
for (int i = 0; i <= segments; i++) {
|
|
|
|
|
|
float angle = 0 + 3.14159f * 0.5f * (float)i / segments;
|
|
|
|
|
|
vertices.push_back(
|
|
|
|
|
|
Vec2(x + w - r + r * cosf(angle), y + h - r + r * sinf(angle)));
|
|
|
|
|
|
}
|
|
|
|
|
|
renderer.fillPolygon(vertices, color);
|
|
|
|
|
|
|
|
|
|
|
|
vertices.clear();
|
|
|
|
|
|
vertices.push_back(Vec2(x + r, y + h - r));
|
|
|
|
|
|
for (int i = 0; i <= segments; i++) {
|
|
|
|
|
|
float angle = 3.14159f * 0.5f + 3.14159f * 0.5f * (float)i / segments;
|
|
|
|
|
|
vertices.push_back(
|
|
|
|
|
|
Vec2(x + r + r * cosf(angle), y + h - r + r * sinf(angle)));
|
|
|
|
|
|
}
|
|
|
|
|
|
renderer.fillPolygon(vertices, color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 16:52:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 绘制组件
|
|
|
|
|
|
* @param renderer 渲染后端
|
|
|
|
|
|
*/
|
2026-02-26 00:59:16 +08:00
|
|
|
|
void Button::onDrawWidget(Renderer &renderer) {
|
2026-02-26 00:55:13 +08:00
|
|
|
|
Rect rect = boundingBox();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
if (rect.empty()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (useImageBackground_) {
|
|
|
|
|
|
drawBackgroundImage(renderer, rect);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
renderer.endSpriteBatch();
|
|
|
|
|
|
|
|
|
|
|
|
Color bg = bgNormal_;
|
|
|
|
|
|
if (pressed_) {
|
|
|
|
|
|
bg = bgPressed_;
|
|
|
|
|
|
} else if (hovered_) {
|
|
|
|
|
|
bg = bgHover_;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (roundedCornersEnabled_) {
|
|
|
|
|
|
fillRoundedRect(renderer, rect, bg, cornerRadius_);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
renderer.fillRect(rect, bg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
renderer.beginSpriteBatch();
|
|
|
|
|
|
|
2026-02-12 21:50:21 +08:00
|
|
|
|
renderer.endSpriteBatch();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-12 21:50:21 +08:00
|
|
|
|
if (borderWidth_ > 0.0f) {
|
|
|
|
|
|
if (roundedCornersEnabled_) {
|
|
|
|
|
|
drawRoundedRect(renderer, rect, borderColor_, cornerRadius_);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
renderer.drawRect(rect, borderColor_, borderWidth_);
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 21:50:21 +08:00
|
|
|
|
renderer.beginSpriteBatch();
|
|
|
|
|
|
}
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-13 13:56:18 +08:00
|
|
|
|
if (font_) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
std::string textToDraw;
|
|
|
|
|
|
if (useStateText_) {
|
|
|
|
|
|
textToDraw = isOn_ ? textOn_ : textOff_;
|
|
|
|
|
|
} else {
|
2026-02-13 13:56:18 +08:00
|
|
|
|
textToDraw = text_;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Color colorToUse;
|
|
|
|
|
|
if (useStateTextColor_) {
|
|
|
|
|
|
colorToUse = isOn_ ? textColorOn_ : textColorOff_;
|
|
|
|
|
|
} else {
|
2026-02-13 13:56:18 +08:00
|
|
|
|
colorToUse = textColor_;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!textToDraw.empty()) {
|
2026-02-13 13:56:18 +08:00
|
|
|
|
Vec2 textSize = font_->measureText(textToDraw);
|
|
|
|
|
|
|
2026-02-11 19:40:26 +08:00
|
|
|
|
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
|
|
|
|
|
|
rect.center().y - textSize.y * 0.5f);
|
|
|
|
|
|
|
2026-02-13 13:56:18 +08:00
|
|
|
|
float minX = rect.left() + padding_.x;
|
|
|
|
|
|
float minY = rect.top() + padding_.y;
|
|
|
|
|
|
float maxX = rect.right() - padding_.x - textSize.x;
|
|
|
|
|
|
float maxY = rect.bottom() - padding_.y - textSize.y;
|
|
|
|
|
|
|
|
|
|
|
|
textPos.x = std::max(minX, std::min(textPos.x, maxX));
|
|
|
|
|
|
textPos.y = std::max(minY, std::min(textPos.y, maxY));
|
|
|
|
|
|
|
2026-02-11 19:40:26 +08:00
|
|
|
|
colorToUse.a = 1.0f;
|
|
|
|
|
|
|
2026-02-13 13:56:18 +08:00
|
|
|
|
renderer.drawText(*font_, textToDraw, textPos, colorToUse);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace extra2d
|