refactor: 统一边界框方法名称为 boundingBox

将 getBoundingBox 方法重命名为 boundingBox,以遵循更简洁的命名规范
同时更新相关文档和示例代码中的调用
This commit is contained in:
ChestnutYueyue 2026-02-26 00:55:13 +08:00
parent 377ec373b0
commit ea5ecd383f
39 changed files with 2031 additions and 2149 deletions

View File

@ -316,8 +316,8 @@ if (buttonFrame) {
**按钮使用世界坐标空间World Space** **按钮使用世界坐标空间World Space**
```cpp ```cpp
// Button::getBoundingBox() 返回世界坐标系的包围盒 // Button::boundingBox() 返回世界坐标系的包围盒
Rect Button::getBoundingBox() const { Rect Button::boundingBox() const {
auto pos = getRenderPosition(); // 获取世界坐标位置 auto pos = getRenderPosition(); // 获取世界坐标位置
// ... 计算包围盒 // ... 计算包围盒
return Rect(x0, y0, w, h); // 世界坐标 return Rect(x0, y0, w, h); // 世界坐标

View File

@ -22,7 +22,7 @@ public:
} }
// 必须实现 getBoundingBox 方法 // 必须实现 getBoundingBox 方法
Rect getBoundingBox() const override { Rect boundingBox() const override {
Vec2 pos = getPosition(); Vec2 pos = getPosition();
return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_); return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_);
} }
@ -212,7 +212,7 @@ private:
## 关键要点 ## 关键要点
1. **必须调用 `setSpatialIndexed(true)`** - 启用节点的空间索引 1. **必须调用 `setSpatialIndexed(true)`** - 启用节点的空间索引
2. **必须实现 `getBoundingBox()`** - 返回准确的边界框 2. **必须实现 `boundingBox()`** - 返回准确的边界框
3. **在 `onEnter()` 中调用 `Scene::onEnter()`** - 确保节点正确注册到空间索引 3. **在 `onEnter()` 中调用 `Scene::onEnter()`** - 确保节点正确注册到空间索引
4. **使用 `queryCollisions()`** - 自动利用空间索引优化检测 4. **使用 `queryCollisions()`** - 自动利用空间索引优化检测

View File

@ -16,10 +16,11 @@ public:
void setColliding(bool colliding) { isColliding_ = colliding; } void setColliding(bool colliding) { isColliding_ = colliding; }
Rect getBoundingBox() const override { Rect boundingBox() const override {
// 返回实际的矩形边界 // 返回实际的矩形边界
Vec2 position = pos(); Vec2 position = pos();
return Rect(position.x - width_ / 2, position.y - height_ / 2, width_, height_); return Rect(position.x - width_ / 2, position.y - height_ / 2, width_,
height_);
} }
void onRender(RenderBackend &renderer) override { void onRender(RenderBackend &renderer) override {
@ -27,17 +28,17 @@ public:
// 绘制填充矩形 // 绘制填充矩形
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_; Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_;
renderer.fillRect( renderer.fillRect(Rect(position.x - width_ / 2, position.y - height_ / 2,
Rect(position.x - width_ / 2, position.y - height_ / 2, width_, height_), width_, height_),
fillColor); fillColor);
// 绘制边框 // 绘制边框
Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f) Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f)
: Color(1.0f, 1.0f, 1.0f, 0.5f); : Color(1.0f, 1.0f, 1.0f, 0.5f);
float borderWidth = isColliding_ ? 3.0f : 2.0f; float borderWidth = isColliding_ ? 3.0f : 2.0f;
renderer.drawRect( renderer.drawRect(Rect(position.x - width_ / 2, position.y - height_ / 2,
Rect(position.x - width_ / 2, position.y - height_ / 2, width_, height_), width_, height_),
borderColor, borderWidth); borderColor, borderWidth);
} }
private: private:
@ -149,7 +150,8 @@ private:
addChild(fpsText_); addChild(fpsText_);
// 创建退出提示文本 // 创建退出提示文本
float screenHeight = static_cast<float>(Application::instance().getConfig().height); float screenHeight =
static_cast<float>(Application::instance().getConfig().height);
exitHintText_ = Text::create("按 + 键退出", infoFont_); exitHintText_ = Text::create("按 + 键退出", infoFont_);
exitHintText_->setPosition(50.0f, screenHeight - 50.0f); exitHintText_->setPosition(50.0f, screenHeight - 50.0f);
exitHintText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); exitHintText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
@ -238,8 +240,7 @@ private:
// 程序入口 // 程序入口
// ============================================================================ // ============================================================================
int main(int argc, char **argv) int main(int argc, char **argv) {
{
// 初始化日志系统 // 初始化日志系统
Logger::init(); Logger::init();
Logger::setLevel(LogLevel::Debug); Logger::setLevel(LogLevel::Debug);

View File

@ -123,8 +123,7 @@ void GameScene::onUpdate(float dt) {
} }
if (bird_->isLiving() && GAME_HEIGHT - bird_->pos().y <= 123.0f) { if (bird_->isLiving() && GAME_HEIGHT - bird_->pos().y <= 123.0f) {
bird_->setPosition( bird_->setPosition(extra2d::Vec2(bird_->pos().x, GAME_HEIGHT - 123.0f));
extra2d::Vec2(bird_->pos().x, GAME_HEIGHT - 123.0f));
bird_->setStatus(Bird::Status::Still); bird_->setStatus(Bird::Status::Still);
onHit(); onHit();
} }
@ -159,7 +158,7 @@ bool GameScene::checkCollision() {
if (!bird_ || !pipes_) if (!bird_ || !pipes_)
return false; return false;
extra2d::Rect birdBox = bird_->getBoundingBox(); extra2d::Rect birdBox = bird_->boundingBox();
// 检查与每个水管的碰撞 // 检查与每个水管的碰撞
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {

View File

@ -74,7 +74,7 @@ void StartScene::onEnter() {
playBtn_->setAnchor(0.5f, 0.5f); playBtn_->setAnchor(0.5f, 0.5f);
// PLAY 按钮在小鸟下方 // PLAY 按钮在小鸟下方
playBtn_->setPosition(screenWidth / 2.0f, playBtn_->setPosition(screenWidth / 2.0f,
screenHeight - playBtn_->getSize().height - 100.0f); screenHeight - playBtn_->size().height - 100.0f);
playBtn_->setOnClick([this]() { playBtn_->setOnClick([this]() {
ResLoader::playMusic(MusicType::Click); ResLoader::playMusic(MusicType::Click);
startGame(); startGame();
@ -95,7 +95,7 @@ void StartScene::onEnter() {
shareBtn_->setAnchor(0.5f, 0.5f); shareBtn_->setAnchor(0.5f, 0.5f);
// SHARE 按钮在 PLAY 按钮下方,靠近地面 // SHARE 按钮在 PLAY 按钮下方,靠近地面
shareBtn_->setPosition(screenWidth / 2.0f, shareBtn_->setPosition(screenWidth / 2.0f,
screenHeight - shareBtn_->getSize().height - 80.0f); screenHeight - shareBtn_->size().height - 80.0f);
shareBtn_->setOnClick([this]() { shareBtn_->setOnClick([this]() {
ResLoader::playMusic(MusicType::Click); ResLoader::playMusic(MusicType::Click);
// 分享功能暂不实现 // 分享功能暂不实现

View File

@ -7,189 +7,189 @@
namespace flappybird { namespace flappybird {
Bird::Bird() { Bird::Bird() { setStatus(Status::Idle); }
setStatus(Status::Idle);
}
Bird::~Bird() = default; Bird::~Bird() = default;
void Bird::onEnter() { void Bird::onEnter() {
Node::onEnter(); Node::onEnter();
if (!sprite_) { if (!sprite_) {
initAnimations(); initAnimations();
} }
} }
void Bird::initAnimations() { void Bird::initAnimations() {
// 随机选择小鸟颜色0-2 // 随机选择小鸟颜色0-2
int colorMode = extra2d::randomInt(0, 2); int colorMode = extra2d::randomInt(0, 2);
std::string prefix = "bird" + std::to_string(colorMode) + "_"; std::string prefix = "bird" + std::to_string(colorMode) + "_";
// 加载动画帧序列: 0 -> 1 -> 2 -> 1 // 加载动画帧序列: 0 -> 1 -> 2 -> 1
int frameSequence[] = {0, 1, 2, 1}; int frameSequence[] = {0, 1, 2, 1};
for (int frameIndex : frameSequence) { for (int frameIndex : frameSequence) {
auto frameSprite = ResLoader::getKeyFrame(prefix + std::to_string(frameIndex)); auto frameSprite =
if (frameSprite) { ResLoader::getKeyFrame(prefix + std::to_string(frameIndex));
frames_.push_back(frameSprite); if (frameSprite) {
} else { frames_.push_back(frameSprite);
E2D_LOG_WARN("无法加载动画帧: {}{}", prefix, frameIndex);
}
}
// 创建精灵
if (!frames_.empty()) {
sprite_ = extra2d::Sprite::create();
setCurrentFrame(0);
addChild(sprite_);
E2D_LOG_INFO("小鸟动画创建成功: 颜色={}, 帧数={}", colorMode, frames_.size());
} else { } else {
E2D_LOG_ERROR("小鸟动画创建失败: 没有找到任何动画帧"); E2D_LOG_WARN("无法加载动画帧: {}{}", prefix, frameIndex);
} }
}
// 创建精灵
if (!frames_.empty()) {
sprite_ = extra2d::Sprite::create();
setCurrentFrame(0);
addChild(sprite_);
E2D_LOG_INFO("小鸟动画创建成功: 颜色={}, 帧数={}", colorMode,
frames_.size());
} else {
E2D_LOG_ERROR("小鸟动画创建失败: 没有找到任何动画帧");
}
} }
void Bird::setCurrentFrame(int frameIndex) { void Bird::setCurrentFrame(int frameIndex) {
if (frames_.empty() || !sprite_) return; if (frames_.empty() || !sprite_)
return;
frameIndex = frameIndex % static_cast<int>(frames_.size());
currentFrame_ = frameIndex; frameIndex = frameIndex % static_cast<int>(frames_.size());
currentFrame_ = frameIndex;
auto& frame = frames_[frameIndex];
sprite_->setTexture(frame->getTexture()); auto &frame = frames_[frameIndex];
sprite_->setTextureRect(frame->getRect()); sprite_->setTexture(frame->getTexture());
sprite_->setTextureRect(frame->getRect());
} }
void Bird::updateFrameAnimation(float dt) { void Bird::updateFrameAnimation(float dt) {
if (frames_.empty() || status_ == Status::Still) return; if (frames_.empty() || status_ == Status::Still)
return;
frameTimer_ += dt; frameTimer_ += dt;
float interval = frameInterval_; float interval = frameInterval_;
if (status_ == Status::StartToFly) { if (status_ == Status::StartToFly) {
interval = 0.05f; // 2倍速度 interval = 0.05f; // 2倍速度
} }
while (frameTimer_ >= interval) { while (frameTimer_ >= interval) {
frameTimer_ -= interval; frameTimer_ -= interval;
setCurrentFrame((currentFrame_ + 1) % static_cast<int>(frames_.size())); setCurrentFrame((currentFrame_ + 1) % static_cast<int>(frames_.size()));
} }
} }
void Bird::onUpdate(float dt) { void Bird::onUpdate(float dt) {
extra2d::Node::onUpdate(dt); extra2d::Node::onUpdate(dt);
// 更新帧动画 // 更新帧动画
updateFrameAnimation(dt); updateFrameAnimation(dt);
// 处理闲置动画(上下浮动) // 处理闲置动画(上下浮动)
if (status_ == Status::Idle) { if (status_ == Status::Idle) {
idleTimer_ += dt; idleTimer_ += dt;
idleOffset_ = std::sin(idleTimer_ * 5.0f) * 4.0f; idleOffset_ = std::sin(idleTimer_ * 5.0f) * 4.0f;
} }
} }
void Bird::onRender(extra2d::RenderBackend& renderer) { void Bird::onRender(extra2d::RenderBackend &renderer) {
// 精灵会自动渲染,这里只需要处理旋转和偏移 // 精灵会自动渲染,这里只需要处理旋转和偏移
if (sprite_) { if (sprite_) {
sprite_->setRotation(rotation_); sprite_->setRotation(rotation_);
// 应用闲置偏移 // 应用闲置偏移
if (status_ == Status::Idle) { if (status_ == Status::Idle) {
sprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_)); sprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_));
} else { } else {
sprite_->setPosition(extra2d::Vec2(0.0f, 0.0f)); sprite_->setPosition(extra2d::Vec2(0.0f, 0.0f));
}
} }
}
// 调用父类的 onRender 来渲染子节点 // 调用父类的 onRender 来渲染子节点
Node::onRender(renderer); Node::onRender(renderer);
} }
void Bird::fall(float dt) { void Bird::fall(float dt) {
if (!living_) return; if (!living_)
return;
// 更新垂直位置 // 更新垂直位置
extra2d::Vec2 position = pos(); extra2d::Vec2 position = pos();
position.y += speed_ * dt; position.y += speed_ * dt;
setPosition(position);
// 应用重力
speed_ += gravity * dt;
// 限制顶部边界
if (position.y < 0) {
position.y = 0;
setPosition(position); setPosition(position);
speed_ = 0;
}
// 应用重力 // 根据速度计算旋转角度
speed_ += gravity * dt; // 上升时抬头(-15度),下降时低头(最大90度)
if (speed_ < 0) {
// 限制顶部边界 rotation_ = -15.0f;
if (position.y < 0) { } else {
position.y = 0; rotation_ = std::min(90.0f, speed_ * 0.15f);
setPosition(position); }
speed_ = 0;
}
// 根据速度计算旋转角度
// 上升时抬头(-15度),下降时低头(最大90度)
if (speed_ < 0) {
rotation_ = -15.0f;
} else {
rotation_ = std::min(90.0f, speed_ * 0.15f);
}
} }
void Bird::jump() { void Bird::jump() {
if (!living_) return; if (!living_)
return;
// 给小鸟向上的速度 // 给小鸟向上的速度
speed_ = -jumpSpeed; speed_ = -jumpSpeed;
// 设置状态为飞行 // 设置状态为飞行
setStatus(Status::Fly); setStatus(Status::Fly);
// 播放音效 // 播放音效
ResLoader::playMusic(MusicType::Fly); ResLoader::playMusic(MusicType::Fly);
} }
void Bird::die() { void Bird::die() {
living_ = false; living_ = false;
// 播放死亡音效 // 播放死亡音效
ResLoader::playMusic(MusicType::Hit); ResLoader::playMusic(MusicType::Hit);
} }
void Bird::setStatus(Status status) { void Bird::setStatus(Status status) {
status_ = status; status_ = status;
switch (status) { switch (status) {
case Status::Still: case Status::Still:
// 停止所有动画 // 停止所有动画
frameTimer_ = 0.0f; frameTimer_ = 0.0f;
break; break;
case Status::Idle: case Status::Idle:
// 开始闲置动画 // 开始闲置动画
frameInterval_ = 0.1f; // 正常速度 frameInterval_ = 0.1f; // 正常速度
idleTimer_ = 0.0f; idleTimer_ = 0.0f;
break; break;
case Status::StartToFly: case Status::StartToFly:
// 停止闲置动画,加速翅膀扇动 // 停止闲置动画,加速翅膀扇动
idleOffset_ = 0.0f; idleOffset_ = 0.0f;
break; break;
case Status::Fly: case Status::Fly:
// 飞行状态 // 飞行状态
break; break;
default: default:
break; break;
} }
} }
extra2d::Rect Bird::getBoundingBox() const { extra2d::Rect Bird::boundingBox() const {
extra2d::Vec2 position = pos(); extra2d::Vec2 position = pos();
// 小鸟碰撞框大小约为 24x24 // 小鸟碰撞框大小约为 24x24
float halfSize = 12.0f; float halfSize = 12.0f;
return extra2d::Rect( return extra2d::Rect(position.x - halfSize, position.y - halfSize,
position.x - halfSize, halfSize * 2.0f, halfSize * 2.0f);
position.y - halfSize,
halfSize * 2.0f,
halfSize * 2.0f
);
} }
} // namespace flappybird } // namespace flappybird

View File

@ -15,116 +15,116 @@ namespace flappybird {
*/ */
class Bird : public extra2d::Node { class Bird : public extra2d::Node {
public: public:
/** /**
* @brief * @brief
*/ */
enum class Status { enum class Status {
Still, // 静止不动 Still, // 静止不动
Idle, // 上下浮动(菜单展示) Idle, // 上下浮动(菜单展示)
StartToFly, // 开始飞行 StartToFly, // 开始飞行
Fly // 飞行中 Fly // 飞行中
}; };
/** /**
* @brief * @brief
*/ */
Bird(); Bird();
/** /**
* @brief * @brief
*/ */
~Bird(); ~Bird();
/** /**
* @brief * @brief
* @param dt * @param dt
*/ */
void onUpdate(float dt) override; void onUpdate(float dt) override;
/** /**
* @brief * @brief
* @param renderer * @param renderer
*/ */
void onRender(extra2d::RenderBackend& renderer) override; void onRender(extra2d::RenderBackend &renderer) override;
/** /**
* @brief * @brief
*/ */
void onEnter() override; void onEnter() override;
/** /**
* @brief * @brief
* @param dt * @param dt
*/ */
void fall(float dt); void fall(float dt);
/** /**
* @brief * @brief
*/ */
void jump(); void jump();
/** /**
* @brief * @brief
*/ */
void die(); void die();
/** /**
* @brief * @brief
* @param status * @param status
*/ */
void setStatus(Status status); void setStatus(Status status);
/** /**
* @brief * @brief
* @return * @return
*/ */
Status getStatus() const { return status_; } Status getStatus() const { return status_; }
/** /**
* @brief * @brief
* @return * @return
*/ */
bool isLiving() const { return living_; } bool isLiving() const { return living_; }
/** /**
* @brief * @brief
* @return * @return
*/ */
extra2d::Rect getBoundingBox() const override; extra2d::Rect boundingBox() const override;
private: private:
/** /**
* @brief * @brief
*/ */
void initAnimations(); void initAnimations();
/** /**
* @brief * @brief
*/ */
void updateFrameAnimation(float dt); void updateFrameAnimation(float dt);
/** /**
* @brief * @brief
*/ */
void setCurrentFrame(int frameIndex); void setCurrentFrame(int frameIndex);
bool living_ = true; // 是否存活 bool living_ = true; // 是否存活
float speed_ = 0.0f; // 垂直速度 float speed_ = 0.0f; // 垂直速度
float rotation_ = 0.0f; // 旋转角度 float rotation_ = 0.0f; // 旋转角度
Status status_ = Status::Idle; // 当前状态 Status status_ = Status::Idle; // 当前状态
// 动画相关 // 动画相关
extra2d::Ptr<extra2d::Sprite> sprite_; // 精灵 extra2d::Ptr<extra2d::Sprite> sprite_; // 精灵
std::vector<extra2d::Ptr<extra2d::SpriteFrame>> frames_; // 动画帧 std::vector<extra2d::Ptr<extra2d::SpriteFrame>> frames_; // 动画帧
int currentFrame_ = 0; // 当前帧索引 int currentFrame_ = 0; // 当前帧索引
float frameTimer_ = 0.0f; // 帧计时器 float frameTimer_ = 0.0f; // 帧计时器
float frameInterval_ = 0.1f; // 帧间隔(秒) float frameInterval_ = 0.1f; // 帧间隔(秒)
float idleTimer_ = 0.0f; // 闲置动画计时器 float idleTimer_ = 0.0f; // 闲置动画计时器
float idleOffset_ = 0.0f; // 闲置偏移量 float idleOffset_ = 0.0f; // 闲置偏移量
// 物理常量 // 物理常量
static constexpr float gravity = 1440.0f; // 重力加速度 static constexpr float gravity = 1440.0f; // 重力加速度
static constexpr float jumpSpeed = 432.0f; // 跳跃初速度 static constexpr float jumpSpeed = 432.0f; // 跳跃初速度
}; };
} // namespace flappybird } // namespace flappybird

View File

@ -3,111 +3,105 @@
// ============================================================================ // ============================================================================
#include "Pipe.h" #include "Pipe.h"
#include "ResLoader.h"
#include "BaseScene.h" #include "BaseScene.h"
#include "ResLoader.h"
namespace flappybird { namespace flappybird {
Pipe::Pipe() { Pipe::Pipe() {
scored = false; scored = false;
// 注意:不要在构造函数中创建子节点 // 注意:不要在构造函数中创建子节点
// 因为此时 weak_from_this() 还不能使用 // 因为此时 weak_from_this() 还不能使用
} }
void Pipe::onEnter() { void Pipe::onEnter() {
Node::onEnter(); Node::onEnter();
// 在 onEnter 中创建子节点,此时 weak_from_this() 可用
if (!topPipe_ && !bottomPipe_) {
// 使用游戏逻辑高度
float screenHeight = GAME_HEIGHT;
// 获取地面高度 // 在 onEnter 中创建子节点,此时 weak_from_this() 可用
auto landFrame = ResLoader::getKeyFrame("land"); if (!topPipe_ && !bottomPipe_) {
float landHeight = landFrame ? landFrame->getRect().size.height : 112.0f; // 使用游戏逻辑高度
float screenHeight = GAME_HEIGHT;
// 随机生成水管高度 // 获取地面高度
// 范围:与屏幕顶部最小距离不小于 100 像素 auto landFrame = ResLoader::getKeyFrame("land");
// 与屏幕底部最小距离不小于地面上方 100 像素 float landHeight = landFrame ? landFrame->getRect().size.height : 112.0f;
float minHeight = 100.0f;
float maxHeight = screenHeight - landHeight - 100.0f - gapHeight_;
float height = static_cast<float>(extra2d::randomInt(static_cast<int>(minHeight), static_cast<int>(maxHeight)));
// 创建上水管 // 随机生成水管高度
auto topFrame = ResLoader::getKeyFrame("pipe_above"); // 范围:与屏幕顶部最小距离不小于 100 像素
if (topFrame) { // 与屏幕底部最小距离不小于地面上方 100 像素
topPipe_ = extra2d::Sprite::create(topFrame->getTexture(), topFrame->getRect()); float minHeight = 100.0f;
topPipe_->setAnchor(extra2d::Vec2(0.5f, 1.0f)); // 锚点设在底部中心 float maxHeight = screenHeight - landHeight - 100.0f - gapHeight_;
topPipe_->setPosition(extra2d::Vec2(0.0f, height - gapHeight_ / 2.0f)); float height = static_cast<float>(extra2d::randomInt(
addChild(topPipe_); static_cast<int>(minHeight), static_cast<int>(maxHeight)));
}
// 创建下水管 // 创建上水管
auto bottomFrame = ResLoader::getKeyFrame("pipe_below"); auto topFrame = ResLoader::getKeyFrame("pipe_above");
if (bottomFrame) { if (topFrame) {
bottomPipe_ = extra2d::Sprite::create(bottomFrame->getTexture(), bottomFrame->getRect()); topPipe_ =
bottomPipe_->setAnchor(extra2d::Vec2(0.5f, 0.0f)); // 锚点设在顶部中心 extra2d::Sprite::create(topFrame->getTexture(), topFrame->getRect());
bottomPipe_->setPosition(extra2d::Vec2(0.0f, height + gapHeight_ / 2.0f)); topPipe_->setAnchor(extra2d::Vec2(0.5f, 1.0f)); // 锚点设在底部中心
addChild(bottomPipe_); topPipe_->setPosition(extra2d::Vec2(0.0f, height - gapHeight_ / 2.0f));
} addChild(topPipe_);
} }
// 创建下水管
auto bottomFrame = ResLoader::getKeyFrame("pipe_below");
if (bottomFrame) {
bottomPipe_ = extra2d::Sprite::create(bottomFrame->getTexture(),
bottomFrame->getRect());
bottomPipe_->setAnchor(extra2d::Vec2(0.5f, 0.0f)); // 锚点设在顶部中心
bottomPipe_->setPosition(extra2d::Vec2(0.0f, height + gapHeight_ / 2.0f));
addChild(bottomPipe_);
}
}
} }
Pipe::~Pipe() = default; Pipe::~Pipe() = default;
extra2d::Rect Pipe::getBoundingBox() const { extra2d::Rect Pipe::boundingBox() const {
// 返回整个水管的边界框(包含上下两根) // 返回整个水管的边界框(包含上下两根)
extra2d::Vec2 position = pos(); extra2d::Vec2 position = pos();
// 水管宽度约为 52 // 水管宽度约为 52
float pipeWidth = 52.0f; float pipeWidth = 52.0f;
float halfWidth = pipeWidth / 2.0f; float halfWidth = pipeWidth / 2.0f;
// 使用游戏逻辑高度 // 使用游戏逻辑高度
float screenHeight = GAME_HEIGHT; float screenHeight = GAME_HEIGHT;
return extra2d::Rect( return extra2d::Rect(position.x - halfWidth, 0.0f, pipeWidth, screenHeight);
position.x - halfWidth,
0.0f,
pipeWidth,
screenHeight
);
} }
extra2d::Rect Pipe::getTopPipeBox() const { extra2d::Rect Pipe::getTopPipeBox() const {
if (!topPipe_) return extra2d::Rect(); if (!topPipe_)
return extra2d::Rect();
extra2d::Vec2 position = pos();
extra2d::Vec2 topPos = topPipe_->pos(); extra2d::Vec2 position = pos();
extra2d::Vec2 topPos = topPipe_->pos();
// 上水管尺寸
float pipeWidth = 52.0f; // 上水管尺寸
float pipeHeight = 320.0f; float pipeWidth = 52.0f;
float pipeHeight = 320.0f;
return extra2d::Rect(
position.x - pipeWidth / 2.0f, return extra2d::Rect(position.x - pipeWidth / 2.0f,
position.y + topPos.y - pipeHeight, position.y + topPos.y - pipeHeight, pipeWidth,
pipeWidth, pipeHeight);
pipeHeight
);
} }
extra2d::Rect Pipe::getBottomPipeBox() const { extra2d::Rect Pipe::getBottomPipeBox() const {
if (!bottomPipe_) return extra2d::Rect(); if (!bottomPipe_)
return extra2d::Rect();
extra2d::Vec2 position = pos();
extra2d::Vec2 bottomPos = bottomPipe_->pos(); extra2d::Vec2 position = pos();
extra2d::Vec2 bottomPos = bottomPipe_->pos();
// 下水管尺寸
float pipeWidth = 52.0f; // 下水管尺寸
float pipeHeight = 320.0f; float pipeWidth = 52.0f;
float pipeHeight = 320.0f;
return extra2d::Rect(
position.x - pipeWidth / 2.0f, return extra2d::Rect(position.x - pipeWidth / 2.0f, position.y + bottomPos.y,
position.y + bottomPos.y, pipeWidth, pipeHeight);
pipeWidth,
pipeHeight
);
} }
} // namespace flappybird } // namespace flappybird

View File

@ -15,45 +15,45 @@ namespace flappybird {
*/ */
class Pipe : public extra2d::Node { class Pipe : public extra2d::Node {
public: public:
/** /**
* @brief * @brief
*/ */
Pipe(); Pipe();
/** /**
* @brief * @brief
*/ */
~Pipe(); ~Pipe();
/** /**
* @brief * @brief
*/ */
void onEnter() override; void onEnter() override;
/** /**
* @brief * @brief
* @return * @return
*/ */
extra2d::Rect getBoundingBox() const override; extra2d::Rect boundingBox() const override;
/** /**
* @brief * @brief
* @return * @return
*/ */
extra2d::Rect getTopPipeBox() const; extra2d::Rect getTopPipeBox() const;
/** /**
* @brief * @brief
* @return * @return
*/ */
extra2d::Rect getBottomPipeBox() const; extra2d::Rect getBottomPipeBox() const;
bool scored = false; // 是否已计分 bool scored = false; // 是否已计分
private: private:
extra2d::Ptr<extra2d::Sprite> topPipe_; // 上水管 extra2d::Ptr<extra2d::Sprite> topPipe_; // 上水管
extra2d::Ptr<extra2d::Sprite> bottomPipe_; // 下水管 extra2d::Ptr<extra2d::Sprite> bottomPipe_; // 下水管
float gapHeight_ = 120.0f; // 间隙高度 float gapHeight_ = 120.0f; // 间隙高度
}; };
} // namespace flappybird } // namespace flappybird

View File

@ -37,8 +37,8 @@ public:
bool isColliding() const { return isColliding_; } bool isColliding() const { return isColliding_; }
int getId() const { return id_; } int getId() const { return id_; }
// 必须实现 getBoundingBox() 才能参与空间索引碰撞检测 // 必须实现 boundingBox() 才能参与空间索引碰撞检测
Rect getBoundingBox() const override { Rect boundingBox() const override {
Vec2 position = pos(); Vec2 position = pos();
return Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_); return Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_);
} }
@ -65,14 +65,16 @@ public:
// 碰撞时变红色 // 碰撞时变红色
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.9f) : color_; Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.9f) : color_;
renderer.fillRect(Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_), renderer.fillRect(
fillColor); Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_),
fillColor);
// 绘制边框 // 绘制边框
Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f) Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f)
: Color(0.3f, 0.3f, 0.3f, 0.5f); : Color(0.3f, 0.3f, 0.3f, 0.5f);
renderer.drawRect(Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_), renderer.drawRect(
borderColor, 1.0f); Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_),
borderColor, 1.0f);
} }
private: private:

View File

@ -30,7 +30,7 @@ public:
/// 获取遮罩尺寸 /// 获取遮罩尺寸
int getWidth() const { return width_; } int getWidth() const { return width_; }
int getHeight() const { return height_; } int getHeight() const { return height_; }
Size getSize() const { Size size() const {
return Size(static_cast<float>(width_), static_cast<float>(height_)); return Size(static_cast<float>(width_), static_cast<float>(height_));
} }

View File

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <graphics/texture.h>
#include <graphics/alpha_mask.h> #include <graphics/alpha_mask.h>
#include <graphics/texture.h>
#include <glad/glad.h> #include <glad/glad.h>
@ -15,58 +16,64 @@ namespace extra2d {
// ============================================================================ // ============================================================================
class GLTexture : public Texture { class GLTexture : public Texture {
public: public:
GLTexture(int width, int height, const uint8_t* pixels, int channels); GLTexture(int width, int height, const uint8_t *pixels, int channels);
GLTexture(const std::string& filepath); GLTexture(const std::string &filepath);
~GLTexture(); ~GLTexture();
// Texture 接口实现 // Texture 接口实现
int getWidth() const override { return width_; } int getWidth() const override { return width_; }
int getHeight() const override { return height_; } int getHeight() const override { return height_; }
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); } Size size() const override {
int getChannels() const override { return channels_; } return Size(static_cast<float>(width_), static_cast<float>(height_));
PixelFormat getFormat() const override; }
void* getNativeHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(textureID_)); } int getChannels() const override { return channels_; }
bool isValid() const override { return textureID_ != 0; } PixelFormat getFormat() const override;
void setFilter(bool linear) override; void *getNativeHandle() const override {
void setWrap(bool repeat) override; return reinterpret_cast<void *>(static_cast<uintptr_t>(textureID_));
}
bool isValid() const override { return textureID_ != 0; }
void setFilter(bool linear) override;
void setWrap(bool repeat) override;
// 从参数创建纹理的工厂方法 // 从参数创建纹理的工厂方法
static Ptr<Texture> create(int width, int height, PixelFormat format); static Ptr<Texture> create(int width, int height, PixelFormat format);
// 加载压缩纹理KTX/DDS 格式) // 加载压缩纹理KTX/DDS 格式)
bool loadCompressed(const std::string& filepath); bool loadCompressed(const std::string &filepath);
// OpenGL 特定 // OpenGL 特定
GLuint getTextureID() const { return textureID_; } GLuint getTextureID() const { return textureID_; }
void bind(unsigned int slot = 0) const; void bind(unsigned int slot = 0) const;
void unbind() const; void unbind() const;
// 获取纹理数据大小(字节),用于 VRAM 跟踪 // 获取纹理数据大小(字节),用于 VRAM 跟踪
size_t getDataSize() const { return dataSize_; } size_t getDataSize() const { return dataSize_; }
// Alpha 遮罩 // Alpha 遮罩
bool hasAlphaMask() const { return alphaMask_ != nullptr && alphaMask_->isValid(); } bool hasAlphaMask() const {
const AlphaMask* getAlphaMask() const { return alphaMask_.get(); } return alphaMask_ != nullptr && alphaMask_->isValid();
void generateAlphaMask(); // 从当前纹理数据生成遮罩 }
const AlphaMask *getAlphaMask() const { return alphaMask_.get(); }
void generateAlphaMask(); // 从当前纹理数据生成遮罩
private: private:
GLuint textureID_; GLuint textureID_;
int width_; int width_;
int height_; int height_;
int channels_; int channels_;
PixelFormat format_; PixelFormat format_;
size_t dataSize_; size_t dataSize_;
// 原始像素数据(用于生成遮罩) // 原始像素数据(用于生成遮罩)
std::vector<uint8_t> pixelData_; std::vector<uint8_t> pixelData_;
std::unique_ptr<AlphaMask> alphaMask_; std::unique_ptr<AlphaMask> alphaMask_;
void createTexture(const uint8_t* pixels); void createTexture(const uint8_t *pixels);
// KTX 文件加载 // KTX 文件加载
bool loadKTX(const std::string& filepath); bool loadKTX(const std::string &filepath);
// DDS 文件加载 // DDS 文件加载
bool loadDDS(const std::string& filepath); bool loadDDS(const std::string &filepath);
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -78,7 +78,7 @@ public:
int getWidth() const { return width_; } int getWidth() const { return width_; }
int getHeight() const { return height_; } int getHeight() const { return height_; }
Vec2 getSize() const { Vec2 size() const {
return Vec2(static_cast<float>(width_), static_cast<float>(height_)); return Vec2(static_cast<float>(width_), static_cast<float>(height_));
} }
PixelFormat getColorFormat() const { return colorFormat_; } PixelFormat getColorFormat() const { return colorFormat_; }

View File

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <core/types.h>
#include <core/math_types.h> #include <core/math_types.h>
#include <core/types.h>
namespace extra2d { namespace extra2d {
@ -9,25 +10,25 @@ namespace extra2d {
// 像素格式枚举 // 像素格式枚举
// ============================================================================ // ============================================================================
enum class PixelFormat { enum class PixelFormat {
R8, // 单通道灰度 R8, // 单通道灰度
RG8, // 双通道 RG8, // 双通道
RGB8, // RGB 24位 RGB8, // RGB 24位
RGBA8, // RGBA 32位默认 RGBA8, // RGBA 32位默认
RGB16F, // RGB 半精度浮点 RGB16F, // RGB 半精度浮点
RGBA16F, // RGBA 半精度浮点 RGBA16F, // RGBA 半精度浮点
RGB32F, // RGB 全精度浮点 RGB32F, // RGB 全精度浮点
RGBA32F, // RGBA 全精度浮点 RGBA32F, // RGBA 全精度浮点
Depth16, // 16位深度 Depth16, // 16位深度
Depth24, // 24位深度 Depth24, // 24位深度
Depth32F, // 32位浮点深度 Depth32F, // 32位浮点深度
Depth24Stencil8, // 24位深度 + 8位模板 Depth24Stencil8, // 24位深度 + 8位模板
// 压缩纹理格式 // 压缩纹理格式
ETC2_RGB8, // ETC2 RGB 压缩 ETC2_RGB8, // ETC2 RGB 压缩
ETC2_RGBA8, // ETC2 RGBA 压缩 ETC2_RGBA8, // ETC2 RGBA 压缩
ASTC_4x4, // ASTC 4x4 压缩 ASTC_4x4, // ASTC 4x4 压缩
ASTC_6x6, // ASTC 6x6 压缩 ASTC_6x6, // ASTC 6x6 压缩
ASTC_8x8 // ASTC 8x8 压缩 ASTC_8x8 // ASTC 8x8 压缩
}; };
// ============================================================================ // ============================================================================
@ -35,30 +36,30 @@ enum class PixelFormat {
// ============================================================================ // ============================================================================
class Texture { class Texture {
public: public:
virtual ~Texture() = default; virtual ~Texture() = default;
// 获取尺寸 // 获取尺寸
virtual int getWidth() const = 0; virtual int getWidth() const = 0;
virtual int getHeight() const = 0; virtual int getHeight() const = 0;
virtual Size getSize() const = 0; virtual Size size() const = 0;
// 获取通道数 // 获取通道数
virtual int getChannels() const = 0; virtual int getChannels() const = 0;
// 获取像素格式 // 获取像素格式
virtual PixelFormat getFormat() const = 0; virtual PixelFormat getFormat() const = 0;
// 获取原始句柄(用于底层渲染) // 获取原始句柄(用于底层渲染)
virtual void* getNativeHandle() const = 0; virtual void *getNativeHandle() const = 0;
// 是否有效 // 是否有效
virtual bool isValid() const = 0; virtual bool isValid() const = 0;
// 设置过滤模式 // 设置过滤模式
virtual void setFilter(bool linear) = 0; virtual void setFilter(bool linear) = 0;
// 设置环绕模式 // 设置环绕模式
virtual void setWrap(bool repeat) = 0; virtual void setWrap(bool repeat) = 0;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,10 +1,11 @@
#pragma once #pragma once
#include <core/types.h>
#include <core/string.h>
#include <core/math_types.h> #include <core/math_types.h>
#include <core/string.h>
#include <core/types.h>
#include <functional> #include <functional>
#include <SDL.h> #include <SDL.h>
namespace extra2d { namespace extra2d {
@ -17,32 +18,33 @@ class Input;
// 窗口配置 // 窗口配置
// ============================================================================ // ============================================================================
struct WindowConfig { struct WindowConfig {
std::string title = "Extra2D Application"; std::string title = "Extra2D Application";
int width = 1280; int width = 1280;
int height = 720; int height = 720;
bool fullscreen = true; bool fullscreen = true;
bool resizable = false; bool resizable = false;
bool vsync = true; bool vsync = true;
int msaaSamples = 0; int msaaSamples = 0;
bool centerWindow = true; bool centerWindow = true;
bool enableCursors = true; bool enableCursors = true;
bool enableDpiScale = true; bool enableDpiScale = true;
bool fullscreenDesktop = true; // true: SDL_WINDOW_FULLSCREEN_DESKTOP, false: SDL_WINDOW_FULLSCREEN bool fullscreenDesktop =
true; // true: SDL_WINDOW_FULLSCREEN_DESKTOP, false: SDL_WINDOW_FULLSCREEN
}; };
// ============================================================================ // ============================================================================
// 鼠标光标形状枚举 // 鼠标光标形状枚举
// ============================================================================ // ============================================================================
enum class CursorShape { enum class CursorShape {
Arrow, Arrow,
IBeam, IBeam,
Crosshair, Crosshair,
Hand, Hand,
HResize, HResize,
VResize, VResize,
ResizeAll, ResizeAll,
ResizeNWSE, ResizeNWSE,
ResizeNESW ResizeNESW
}; };
// ============================================================================ // ============================================================================
@ -51,103 +53,107 @@ enum class CursorShape {
// ============================================================================ // ============================================================================
class Window { class Window {
public: public:
Window(); Window();
~Window(); ~Window();
// 创建窗口 // 创建窗口
bool create(const WindowConfig& config); bool create(const WindowConfig &config);
void destroy(); void destroy();
// 窗口操作 // 窗口操作
void pollEvents(); void pollEvents();
void swapBuffers(); void swapBuffers();
bool shouldClose() const; bool shouldClose() const;
void setShouldClose(bool close); void setShouldClose(bool close);
// 窗口属性 // 窗口属性
void setTitle(const std::string& title); void setTitle(const std::string &title);
void setSize(int width, int height); void setSize(int width, int height);
void setPosition(int x, int y); void setPosition(int x, int y);
void setFullscreen(bool fullscreen); void setFullscreen(bool fullscreen);
void setVSync(bool enabled); void setVSync(bool enabled);
void setResizable(bool resizable); void setResizable(bool resizable);
// 获取窗口属性 // 获取窗口属性
int getWidth() const { return width_; } int getWidth() const { return width_; }
int getHeight() const { return height_; } int getHeight() const { return height_; }
Size getSize() const { return Size(static_cast<float>(width_), static_cast<float>(height_)); } Size size() const {
Vec2 pos() const; return Size(static_cast<float>(width_), static_cast<float>(height_));
bool isFullscreen() const { return fullscreen_; } }
bool isVSync() const { return vsync_; } Vec2 pos() const;
bool isFullscreen() const { return fullscreen_; }
bool isVSync() const { return vsync_; }
// DPI 缩放 (PC 端自动检测Switch 固定 1.0) // DPI 缩放 (PC 端自动检测Switch 固定 1.0)
float getContentScaleX() const; float getContentScaleX() const;
float getContentScaleY() const; float getContentScaleY() const;
Vec2 getContentScale() const; Vec2 getContentScale() const;
// 窗口状态 // 窗口状态
bool isFocused() const { return focused_; } bool isFocused() const { return focused_; }
bool isMinimized() const; bool isMinimized() const;
bool isMaximized() const; bool isMaximized() const;
// 获取 SDL2 窗口和 GL 上下文 // 获取 SDL2 窗口和 GL 上下文
SDL_Window* getSDLWindow() const { return sdlWindow_; } SDL_Window *getSDLWindow() const { return sdlWindow_; }
SDL_GLContext getGLContext() const { return glContext_; } SDL_GLContext getGLContext() const { return glContext_; }
// 设置/获取用户数据 // 设置/获取用户数据
void setUserData(void* data) { userData_ = data; } void setUserData(void *data) { userData_ = data; }
void* getUserData() const { return userData_; } void *getUserData() const { return userData_; }
// 事件队列 // 事件队列
void setEventQueue(EventQueue* queue) { eventQueue_ = queue; } void setEventQueue(EventQueue *queue) { eventQueue_ = queue; }
EventQueue* getEventQueue() const { return eventQueue_; } EventQueue *getEventQueue() const { return eventQueue_; }
// 获取输入管理器 // 获取输入管理器
Input* getInput() const { return input_.get(); } Input *getInput() const { return input_.get(); }
// 光标操作 (PC 端有效Switch 上为空操作) // 光标操作 (PC 端有效Switch 上为空操作)
void setCursor(CursorShape shape); void setCursor(CursorShape shape);
void resetCursor(); void resetCursor();
void setMouseVisible(bool visible); void setMouseVisible(bool visible);
// 窗口回调 // 窗口回调
using ResizeCallback = std::function<void(int width, int height)>; using ResizeCallback = std::function<void(int width, int height)>;
using FocusCallback = std::function<void(bool focused)>; using FocusCallback = std::function<void(bool focused)>;
using CloseCallback = std::function<void()>; using CloseCallback = std::function<void()>;
void setResizeCallback(ResizeCallback callback) { resizeCallback_ = callback; } void setResizeCallback(ResizeCallback callback) {
void setFocusCallback(FocusCallback callback) { focusCallback_ = callback; } resizeCallback_ = callback;
void setCloseCallback(CloseCallback callback) { closeCallback_ = callback; } }
void setFocusCallback(FocusCallback callback) { focusCallback_ = callback; }
void setCloseCallback(CloseCallback callback) { closeCallback_ = callback; }
private: private:
// SDL2 状态 // SDL2 状态
SDL_Window* sdlWindow_; SDL_Window *sdlWindow_;
SDL_GLContext glContext_; SDL_GLContext glContext_;
SDL_Cursor* sdlCursors_[9]; // 光标缓存 SDL_Cursor *sdlCursors_[9]; // 光标缓存
SDL_Cursor* currentCursor_; SDL_Cursor *currentCursor_;
int width_; int width_;
int height_; int height_;
bool vsync_; bool vsync_;
bool shouldClose_; bool shouldClose_;
bool fullscreen_; bool fullscreen_;
bool focused_; bool focused_;
float contentScaleX_; float contentScaleX_;
float contentScaleY_; float contentScaleY_;
bool enableDpiScale_; bool enableDpiScale_;
void* userData_; void *userData_;
EventQueue* eventQueue_; EventQueue *eventQueue_;
UniquePtr<Input> input_; UniquePtr<Input> input_;
ResizeCallback resizeCallback_; ResizeCallback resizeCallback_;
FocusCallback focusCallback_; FocusCallback focusCallback_;
CloseCallback closeCallback_; CloseCallback closeCallback_;
bool initSDL(const WindowConfig& config); bool initSDL(const WindowConfig &config);
void deinitSDL(); void deinitSDL();
void initCursors(); void initCursors();
void deinitCursors(); void deinitCursors();
void updateContentScale(); void updateContentScale();
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -5,12 +5,13 @@
#include <core/math_types.h> #include <core/math_types.h>
#include <core/types.h> #include <core/types.h>
#include <event/event_dispatcher.h> #include <event/event_dispatcher.h>
#include <graphics/render_backend.h>
#include <functional> #include <functional>
#include <graphics/render_backend.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
namespace extra2d { namespace extra2d {
// 前向声明 // 前向声明
@ -82,7 +83,7 @@ public:
* @brief * @brief
* @param color RGB颜色 * @param color RGB颜色
*/ */
void setColor(const Color3B& color); void setColor(const Color3B &color);
Color3B getColor() const { return color_; } Color3B getColor() const { return color_; }
/** /**
@ -148,7 +149,7 @@ public:
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 边界框(用于空间索引) // 边界框(用于空间索引)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
virtual Rect getBoundingBox() const; virtual Rect boundingBox() const;
// 是否需要参与空间索引(默认 true // 是否需要参与空间索引(默认 true
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; } void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
@ -165,17 +166,17 @@ public:
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Tween 动画系统 // Tween 动画系统
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** /**
* @brief Tween * @brief Tween
* @return Tween * @return Tween
*/ */
Tween& tween(); Tween &tween();
/** /**
* @brief Tween * @brief Tween
*/ */
Tween& tween(const std::string &name); Tween &tween(const std::string &name);
/** /**
* @brief * @brief
@ -216,12 +217,14 @@ public:
/** /**
* @brief 1 * @brief 1
*/ */
Tween &fadeIn(float duration, TweenEasing easing = static_cast<TweenEasing>(0)); Tween &fadeIn(float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/** /**
* @brief 0 * @brief 0
*/ */
Tween &fadeOut(float duration, TweenEasing easing = static_cast<TweenEasing>(0)); Tween &fadeOut(float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/** /**
* @brief * @brief
@ -270,62 +273,62 @@ protected:
private: private:
// ========================================================================== // ==========================================================================
// 成员变量按类型大小降序排列,减少内存对齐填充 // 成员变量按类型大小降序排列,减少内存对齐填充
// 64位系统对齐std::string(32) > glm::mat4(64) > std::vector(24) > // 64位系统对齐std::string(32) > glm::mat4(64) > std::vector(24) >
// double(8) > float(4) > int(4) > bool(1) // double(8) > float(4) > int(4) > bool(1)
// ========================================================================== // ==========================================================================
// 1. 大块内存64字节 // 1. 大块内存64字节
mutable glm::mat4 localTransform_; // 64 bytes mutable glm::mat4 localTransform_; // 64 bytes
mutable glm::mat4 worldTransform_; // 64 bytes mutable glm::mat4 worldTransform_; // 64 bytes
// 2. 字符串和容器24-32字节 // 2. 字符串和容器24-32字节
std::string name_; // 32 bytes std::string name_; // 32 bytes
std::vector<Ptr<Node>> children_; // 24 bytes std::vector<Ptr<Node>> children_; // 24 bytes
// 3. 子节点索引(加速查找) // 3. 子节点索引(加速查找)
std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes
std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes
// 4. 事件分发器 // 4. 事件分发器
EventDispatcher eventDispatcher_; // 大小取决于实现 EventDispatcher eventDispatcher_; // 大小取决于实现
// 5. 父节点引用 // 5. 父节点引用
WeakPtr<Node> parent_; // 16 bytes WeakPtr<Node> parent_; // 16 bytes
// 7. 变换属性(按访问频率分组) // 7. 变换属性(按访问频率分组)
Vec2 position_ = Vec2::Zero(); // 8 bytes Vec2 position_ = Vec2::Zero(); // 8 bytes
Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes
Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes
Vec2 skew_ = Vec2::Zero(); // 8 bytes Vec2 skew_ = Vec2::Zero(); // 8 bytes
// 8. 边界框(用于空间索引) // 8. 边界框(用于空间索引)
Rect lastSpatialBounds_; // 16 bytes Rect lastSpatialBounds_; // 16 bytes
// 9. 浮点属性 // 9. 浮点属性
float rotation_ = 0.0f; // 4 bytes float rotation_ = 0.0f; // 4 bytes
float opacity_ = 1.0f; // 4 bytes float opacity_ = 1.0f; // 4 bytes
// 10. 颜色属性 // 10. 颜色属性
Color3B color_ = Color3B(255, 255, 255); // 3 bytes Color3B color_ = Color3B(255, 255, 255); // 3 bytes
// 11. 整数属性 // 11. 整数属性
int zOrder_ = 0; // 4 bytes int zOrder_ = 0; // 4 bytes
int tag_ = -1; // 4 bytes int tag_ = -1; // 4 bytes
// 12. 布尔属性 // 12. 布尔属性
bool flipX_ = false; // 1 byte bool flipX_ = false; // 1 byte
bool flipY_ = false; // 1 byte bool flipY_ = false; // 1 byte
// 11. 场景指针 // 11. 场景指针
Scene *scene_ = nullptr; // 8 bytes Scene *scene_ = nullptr; // 8 bytes
// 12. 布尔标志(打包在一起) // 12. 布尔标志(打包在一起)
mutable bool transformDirty_ = true; // 1 byte mutable bool transformDirty_ = true; // 1 byte
mutable bool worldTransformDirty_ = true; // 1 byte mutable bool worldTransformDirty_ = true; // 1 byte
bool childrenOrderDirty_ = false; // 1 byte bool childrenOrderDirty_ = false; // 1 byte
bool visible_ = true; // 1 byte bool visible_ = true; // 1 byte
bool running_ = false; // 1 byte bool running_ = false; // 1 byte
bool spatialIndexed_ = true; // 1 byte bool spatialIndexed_ = true; // 1 byte
// 13. Tween 动画列表 // 13. Tween 动画列表
std::vector<std::shared_ptr<Tween>> tweens_; std::vector<std::shared_ptr<Tween>> tweens_;

View File

@ -85,7 +85,7 @@ public:
void addPoint(const Vec2 &point); void addPoint(const Vec2 &point);
void clearPoints(); void clearPoints();
Rect getBoundingBox() const override; Rect boundingBox() const override;
protected: protected:
void onDraw(RenderBackend &renderer) override; void onDraw(RenderBackend &renderer) override;

View File

@ -37,7 +37,7 @@ public:
static Ptr<Sprite> create(Ptr<Texture> texture); static Ptr<Sprite> create(Ptr<Texture> texture);
static Ptr<Sprite> create(Ptr<Texture> texture, const Rect &rect); static Ptr<Sprite> create(Ptr<Texture> texture, const Rect &rect);
Rect getBoundingBox() const override; Rect boundingBox() const override;
protected: protected:
void onDraw(RenderBackend &renderer) override; void onDraw(RenderBackend &renderer) override;

View File

@ -178,7 +178,7 @@ public:
*/ */
void setStateTextColor(const Color &colorOff, const Color &colorOn); void setStateTextColor(const Color &colorOff, const Color &colorOn);
Rect getBoundingBox() const override; Rect boundingBox() const override;
protected: protected:
void onDrawWidget(RenderBackend &renderer) override; void onDrawWidget(RenderBackend &renderer) override;

View File

@ -11,88 +11,88 @@ namespace extra2d {
// ============================================================================ // ============================================================================
class CheckBox : public Widget { class CheckBox : public Widget {
public: public:
CheckBox(); CheckBox();
~CheckBox() override = default; ~CheckBox() override = default;
static Ptr<CheckBox> create(); static Ptr<CheckBox> create();
static Ptr<CheckBox> create(const std::string &label); static Ptr<CheckBox> create(const std::string &label);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 选中状态 // 选中状态
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setChecked(bool checked); void setChecked(bool checked);
bool isChecked() const { return checked_; } bool isChecked() const { return checked_; }
void toggle(); void toggle();
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 标签设置 // 标签设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setLabel(const std::string &label); void setLabel(const std::string &label);
const std::string &getLabel() const { return label_; } const std::string &getLabel() const { return label_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 字体设置 // 字体设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setFont(Ptr<FontAtlas> font); void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; } Ptr<FontAtlas> getFont() const { return font_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 文字颜色 // 文字颜色
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setTextColor(const Color &color); void setTextColor(const Color &color);
Color getTextColor() const { return textColor_; } Color getTextColor() const { return textColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 复选框尺寸 // 复选框尺寸
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setBoxSize(float size); void setBoxSize(float size);
float getBoxSize() const { return boxSize_; } float getBoxSize() const { return boxSize_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 间距 // 间距
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setSpacing(float spacing); void setSpacing(float spacing);
float getSpacing() const { return spacing_; } float getSpacing() const { return spacing_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 颜色设置 // 颜色设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setCheckedColor(const Color &color); void setCheckedColor(const Color &color);
Color getCheckedColor() const { return checkedColor_; } Color getCheckedColor() const { return checkedColor_; }
void setUncheckedColor(const Color &color); void setUncheckedColor(const Color &color);
Color getUncheckedColor() const { return uncheckedColor_; } Color getUncheckedColor() const { return uncheckedColor_; }
void setCheckMarkColor(const Color &color); void setCheckMarkColor(const Color &color);
Color getCheckMarkColor() const { return checkMarkColor_; } Color getCheckMarkColor() const { return checkMarkColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 回调设置 // 回调设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setOnStateChange(Function<void(bool)> callback); void setOnStateChange(Function<void(bool)> callback);
Rect getBoundingBox() const override; Rect boundingBox() const override;
protected: protected:
void onDrawWidget(RenderBackend &renderer) override; void onDrawWidget(RenderBackend &renderer) override;
bool onMousePress(const MouseEvent &event) override; bool onMousePress(const MouseEvent &event) override;
bool onMouseRelease(const MouseEvent &event) override; bool onMouseRelease(const MouseEvent &event) override;
private: private:
bool checked_ = false; bool checked_ = false;
std::string label_; std::string label_;
Ptr<FontAtlas> font_; Ptr<FontAtlas> font_;
Color textColor_ = Colors::White; Color textColor_ = Colors::White;
float boxSize_ = 20.0f; float boxSize_ = 20.0f;
float spacing_ = 8.0f; float spacing_ = 8.0f;
Color checkedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f); Color checkedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
Color uncheckedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f); Color uncheckedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
Color checkMarkColor_ = Colors::White; Color checkMarkColor_ = Colors::White;
bool pressed_ = false; bool pressed_ = false;
Function<void(bool)> onStateChange_; Function<void(bool)> onStateChange_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -12,135 +12,136 @@ namespace extra2d {
// ============================================================================ // ============================================================================
class Label : public Widget { class Label : public Widget {
public: public:
Label(); Label();
explicit Label(const std::string &text); explicit Label(const std::string &text);
~Label() override = default; ~Label() override = default;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 静态创建方法 // 静态创建方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
static Ptr<Label> create(); static Ptr<Label> create();
static Ptr<Label> create(const std::string &text); static Ptr<Label> create(const std::string &text);
static Ptr<Label> create(const std::string &text, Ptr<FontAtlas> font); static Ptr<Label> create(const std::string &text, Ptr<FontAtlas> font);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 文本内容 // 文本内容
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setText(const std::string &text); void setText(const std::string &text);
const std::string &getText() const { return text_; } const std::string &getText() const { return text_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 字体设置 // 字体设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setFont(Ptr<FontAtlas> font); void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; } Ptr<FontAtlas> getFont() const { return font_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 文字颜色 // 文字颜色
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setTextColor(const Color &color); void setTextColor(const Color &color);
Color getTextColor() const { return textColor_; } Color getTextColor() const { return textColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 字体大小 // 字体大小
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setFontSize(int size); void setFontSize(int size);
int getFontSize() const { return fontSize_; } int getFontSize() const { return fontSize_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 水平对齐方式 // 水平对齐方式
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
enum class HorizontalAlign { Left, Center, Right }; enum class HorizontalAlign { Left, Center, Right };
void setHorizontalAlign(HorizontalAlign align);
HorizontalAlign getHorizontalAlign() const { return hAlign_; }
// ------------------------------------------------------------------------ void setHorizontalAlign(HorizontalAlign align);
// 垂直对齐方式 HorizontalAlign getHorizontalAlign() const { return hAlign_; }
// ------------------------------------------------------------------------
enum class VerticalAlign { Top, Middle, Bottom };
void setVerticalAlign(VerticalAlign align);
VerticalAlign getVerticalAlign() const { return vAlign_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 阴影效果 // 垂直对齐方式
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setShadowEnabled(bool enabled); enum class VerticalAlign { Top, Middle, Bottom };
bool isShadowEnabled() const { return shadowEnabled_; }
void setShadowColor(const Color &color);
Color getShadowColor() const { return shadowColor_; }
void setShadowOffset(const Vec2 &offset);
Vec2 getShadowOffset() const { return shadowOffset_; }
// ------------------------------------------------------------------------ void setVerticalAlign(VerticalAlign align);
// 描边效果 VerticalAlign getVerticalAlign() const { return vAlign_; }
// ------------------------------------------------------------------------
void setOutlineEnabled(bool enabled);
bool isOutlineEnabled() const { return outlineEnabled_; }
void setOutlineColor(const Color &color);
Color getOutlineColor() const { return outlineColor_; }
void setOutlineWidth(float width);
float getOutlineWidth() const { return outlineWidth_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 多行文本 // 阴影效果
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setMultiLine(bool multiLine); void setShadowEnabled(bool enabled);
bool isMultiLine() const { return multiLine_; } bool isShadowEnabled() const { return shadowEnabled_; }
void setLineSpacing(float spacing);
float getLineSpacing() const { return lineSpacing_; }
// ------------------------------------------------------------------------ void setShadowColor(const Color &color);
// 最大宽度(用于自动换行) Color getShadowColor() const { return shadowColor_; }
// ------------------------------------------------------------------------
void setMaxWidth(float maxWidth);
float getMaxWidth() const { return maxWidth_; }
// ------------------------------------------------------------------------ void setShadowOffset(const Vec2 &offset);
// 尺寸计算 Vec2 getShadowOffset() const { return shadowOffset_; }
// ------------------------------------------------------------------------
Vec2 getTextSize() const;
float getLineHeight() const;
Rect getBoundingBox() const override; // ------------------------------------------------------------------------
// 描边效果
// ------------------------------------------------------------------------
void setOutlineEnabled(bool enabled);
bool isOutlineEnabled() const { return outlineEnabled_; }
void setOutlineColor(const Color &color);
Color getOutlineColor() const { return outlineColor_; }
void setOutlineWidth(float width);
float getOutlineWidth() const { return outlineWidth_; }
// ------------------------------------------------------------------------
// 多行文本
// ------------------------------------------------------------------------
void setMultiLine(bool multiLine);
bool isMultiLine() const { return multiLine_; }
void setLineSpacing(float spacing);
float getLineSpacing() const { return lineSpacing_; }
// ------------------------------------------------------------------------
// 最大宽度(用于自动换行)
// ------------------------------------------------------------------------
void setMaxWidth(float maxWidth);
float getMaxWidth() const { return maxWidth_; }
// ------------------------------------------------------------------------
// 尺寸计算
// ------------------------------------------------------------------------
Vec2 getTextSize() const;
float getLineHeight() const;
Rect boundingBox() const override;
protected: protected:
void onDrawWidget(RenderBackend &renderer) override; void onDrawWidget(RenderBackend &renderer) override;
private: private:
std::string text_; std::string text_;
Ptr<FontAtlas> font_; Ptr<FontAtlas> font_;
Color textColor_ = Colors::White; Color textColor_ = Colors::White;
int fontSize_ = 16; int fontSize_ = 16;
HorizontalAlign hAlign_ = HorizontalAlign::Left;
VerticalAlign vAlign_ = VerticalAlign::Top;
bool shadowEnabled_ = false;
Color shadowColor_ = Color(0.0f, 0.0f, 0.0f, 0.5f);
Vec2 shadowOffset_ = Vec2(2.0f, 2.0f);
bool outlineEnabled_ = false;
Color outlineColor_ = Colors::Black;
float outlineWidth_ = 1.0f;
bool multiLine_ = false;
float lineSpacing_ = 1.0f;
float maxWidth_ = 0.0f;
mutable Vec2 cachedSize_ = Vec2::Zero();
mutable bool sizeDirty_ = true;
void updateCache() const; HorizontalAlign hAlign_ = HorizontalAlign::Left;
void drawText(RenderBackend &renderer, const Vec2 &position, const Color &color); VerticalAlign vAlign_ = VerticalAlign::Top;
Vec2 calculateDrawPosition() const;
std::vector<std::string> splitLines() const; bool shadowEnabled_ = false;
Color shadowColor_ = Color(0.0f, 0.0f, 0.0f, 0.5f);
Vec2 shadowOffset_ = Vec2(2.0f, 2.0f);
bool outlineEnabled_ = false;
Color outlineColor_ = Colors::Black;
float outlineWidth_ = 1.0f;
bool multiLine_ = false;
float lineSpacing_ = 1.0f;
float maxWidth_ = 0.0f;
mutable Vec2 cachedSize_ = Vec2::Zero();
mutable bool sizeDirty_ = true;
void updateCache() const;
void drawText(RenderBackend &renderer, const Vec2 &position,
const Color &color);
Vec2 calculateDrawPosition() const;
std::vector<std::string> splitLines() const;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -12,207 +12,209 @@ namespace extra2d {
// ============================================================================ // ============================================================================
class ProgressBar : public Widget { class ProgressBar : public Widget {
public: public:
ProgressBar(); ProgressBar();
~ProgressBar() override = default; ~ProgressBar() override = default;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 静态创建方法 // 静态创建方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
static Ptr<ProgressBar> create(); static Ptr<ProgressBar> create();
static Ptr<ProgressBar> create(float min, float max, float value); static Ptr<ProgressBar> create(float min, float max, float value);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 数值范围 // 数值范围
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setRange(float min, float max); void setRange(float min, float max);
float getMin() const { return min_; } float getMin() const { return min_; }
float getMax() const { return max_; } float getMax() const { return max_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 当前值 // 当前值
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setValue(float value); void setValue(float value);
float getValue() const { return value_; } float getValue() const { return value_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 获取百分比 (0.0 - 1.0) // 获取百分比 (0.0 - 1.0)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
float getPercent() const; float getPercent() const;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 进度条方向 // 进度条方向
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
enum class Direction { LeftToRight, RightToLeft, BottomToTop, TopToBottom }; enum class Direction { LeftToRight, RightToLeft, BottomToTop, TopToBottom };
void setDirection(Direction dir);
Direction getDirection() const { return direction_; }
// ------------------------------------------------------------------------ void setDirection(Direction dir);
// 颜色设置 Direction getDirection() const { return direction_; }
// ------------------------------------------------------------------------
void setBackgroundColor(const Color &color);
Color getBackgroundColor() const { return bgColor_; }
void setFillColor(const Color &color);
Color getFillColor() const { return fillColor_; }
// 渐变填充色(从 fillColor_ 到 fillColorEnd_
void setGradientFillEnabled(bool enabled);
bool isGradientFillEnabled() const { return gradientEnabled_; }
void setFillColorEnd(const Color &color);
Color getFillColorEnd() const { return fillColorEnd_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 分段颜色(根据百分比自动切换颜色) // 颜色设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setSegmentedColorsEnabled(bool enabled); void setBackgroundColor(const Color &color);
bool isSegmentedColorsEnabled() const { return segmentedColorsEnabled_; } Color getBackgroundColor() const { return bgColor_; }
// 设置分段阈值和颜色,例如:>70%绿色, >30%黄色, 其他红色
void addColorSegment(float percentThreshold, const Color &color);
void clearColorSegments();
// ------------------------------------------------------------------------ void setFillColor(const Color &color);
// 圆角设置 Color getFillColor() const { return fillColor_; }
// ------------------------------------------------------------------------
void setCornerRadius(float radius);
float getCornerRadius() const { return cornerRadius_; }
void setRoundedCornersEnabled(bool enabled);
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
// ------------------------------------------------------------------------ // 渐变填充色(从 fillColor_ 到 fillColorEnd_
// 边框设置 void setGradientFillEnabled(bool enabled);
// ------------------------------------------------------------------------ bool isGradientFillEnabled() const { return gradientEnabled_; }
void setBorderEnabled(bool enabled);
bool isBorderEnabled() const { return borderEnabled_; }
void setBorderColor(const Color &color);
Color getBorderColor() const { return borderColor_; }
void setBorderWidth(float width);
float getBorderWidth() const { return borderWidth_; }
// ------------------------------------------------------------------------ void setFillColorEnd(const Color &color);
// 内边距(填充与边框的距离) Color getFillColorEnd() const { return fillColorEnd_; }
// ------------------------------------------------------------------------
void setPadding(float padding);
float getPadding() const { return padding_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 文本显示 // 分段颜色(根据百分比自动切换颜色)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setTextEnabled(bool enabled); void setSegmentedColorsEnabled(bool enabled);
bool isTextEnabled() const { return textEnabled_; } bool isSegmentedColorsEnabled() const { return segmentedColorsEnabled_; }
void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; }
void setTextColor(const Color &color);
Color getTextColor() const { return textColor_; }
// 文本格式:"{value}/{max}", "{percent}%", "{value:.1f}" 等
void setTextFormat(const std::string &format);
const std::string &getTextFormat() const { return textFormat_; }
// ------------------------------------------------------------------------ // 设置分段阈值和颜色,例如:>70%绿色, >30%黄色, 其他红色
// 动画效果 void addColorSegment(float percentThreshold, const Color &color);
// ------------------------------------------------------------------------ void clearColorSegments();
void setAnimatedChangeEnabled(bool enabled);
bool isAnimatedChangeEnabled() const { return animatedChangeEnabled_; }
void setAnimationSpeed(float speed); // 每秒变化量
float getAnimationSpeed() const { return animationSpeed_; }
// 延迟显示效果如LOL血条
void setDelayedDisplayEnabled(bool enabled);
bool isDelayedDisplayEnabled() const { return delayedDisplayEnabled_; }
void setDelayTime(float seconds);
float getDelayTime() const { return delayTime_; }
void setDelayedFillColor(const Color &color);
Color getDelayedFillColor() const { return delayedFillColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 条纹效果 // 圆角设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setStripedEnabled(bool enabled); void setCornerRadius(float radius);
bool isStripedEnabled() const { return stripedEnabled_; } float getCornerRadius() const { return cornerRadius_; }
void setStripeColor(const Color &color);
Color getStripeColor() const { return stripeColor_; }
void setStripeSpeed(float speed); // 条纹移动速度
float getStripeSpeed() const { return stripeSpeed_; }
Rect getBoundingBox() const override; void setRoundedCornersEnabled(bool enabled);
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
// ------------------------------------------------------------------------
// 边框设置
// ------------------------------------------------------------------------
void setBorderEnabled(bool enabled);
bool isBorderEnabled() const { return borderEnabled_; }
void setBorderColor(const Color &color);
Color getBorderColor() const { return borderColor_; }
void setBorderWidth(float width);
float getBorderWidth() const { return borderWidth_; }
// ------------------------------------------------------------------------
// 内边距(填充与边框的距离)
// ------------------------------------------------------------------------
void setPadding(float padding);
float getPadding() const { return padding_; }
// ------------------------------------------------------------------------
// 文本显示
// ------------------------------------------------------------------------
void setTextEnabled(bool enabled);
bool isTextEnabled() const { return textEnabled_; }
void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; }
void setTextColor(const Color &color);
Color getTextColor() const { return textColor_; }
// 文本格式:"{value}/{max}", "{percent}%", "{value:.1f}" 等
void setTextFormat(const std::string &format);
const std::string &getTextFormat() const { return textFormat_; }
// ------------------------------------------------------------------------
// 动画效果
// ------------------------------------------------------------------------
void setAnimatedChangeEnabled(bool enabled);
bool isAnimatedChangeEnabled() const { return animatedChangeEnabled_; }
void setAnimationSpeed(float speed); // 每秒变化量
float getAnimationSpeed() const { return animationSpeed_; }
// 延迟显示效果如LOL血条
void setDelayedDisplayEnabled(bool enabled);
bool isDelayedDisplayEnabled() const { return delayedDisplayEnabled_; }
void setDelayTime(float seconds);
float getDelayTime() const { return delayTime_; }
void setDelayedFillColor(const Color &color);
Color getDelayedFillColor() const { return delayedFillColor_; }
// ------------------------------------------------------------------------
// 条纹效果
// ------------------------------------------------------------------------
void setStripedEnabled(bool enabled);
bool isStripedEnabled() const { return stripedEnabled_; }
void setStripeColor(const Color &color);
Color getStripeColor() const { return stripeColor_; }
void setStripeSpeed(float speed); // 条纹移动速度
float getStripeSpeed() const { return stripeSpeed_; }
Rect boundingBox() const override;
protected: protected:
void onUpdate(float deltaTime) override; void onUpdate(float deltaTime) override;
void onDrawWidget(RenderBackend &renderer) override; void onDrawWidget(RenderBackend &renderer) override;
private: private:
// 数值 // 数值
float min_ = 0.0f; float min_ = 0.0f;
float max_ = 100.0f; float max_ = 100.0f;
float value_ = 50.0f; float value_ = 50.0f;
// 方向 // 方向
Direction direction_ = Direction::LeftToRight; Direction direction_ = Direction::LeftToRight;
// 颜色 // 颜色
Color bgColor_ = Color(0.2f, 0.2f, 0.2f, 1.0f); Color bgColor_ = Color(0.2f, 0.2f, 0.2f, 1.0f);
Color fillColor_ = Color(0.0f, 0.8f, 0.2f, 1.0f); Color fillColor_ = Color(0.0f, 0.8f, 0.2f, 1.0f);
Color fillColorEnd_ = Color(0.0f, 0.6f, 0.1f, 1.0f); Color fillColorEnd_ = Color(0.0f, 0.6f, 0.1f, 1.0f);
bool gradientEnabled_ = false; bool gradientEnabled_ = false;
// 分段颜色 // 分段颜色
bool segmentedColorsEnabled_ = false; bool segmentedColorsEnabled_ = false;
std::vector<std::pair<float, Color>> colorSegments_; std::vector<std::pair<float, Color>> colorSegments_;
// 圆角 // 圆角
float cornerRadius_ = 4.0f; float cornerRadius_ = 4.0f;
bool roundedCornersEnabled_ = true; bool roundedCornersEnabled_ = true;
// 边框 // 边框
bool borderEnabled_ = false; bool borderEnabled_ = false;
Color borderColor_ = Colors::White; Color borderColor_ = Colors::White;
float borderWidth_ = 1.0f; float borderWidth_ = 1.0f;
// 内边距 // 内边距
float padding_ = 2.0f; float padding_ = 2.0f;
// 文本 // 文本
bool textEnabled_ = false; bool textEnabled_ = false;
Ptr<FontAtlas> font_; Ptr<FontAtlas> font_;
Color textColor_ = Colors::White; Color textColor_ = Colors::White;
std::string textFormat_ = "{percent:.0f}%"; std::string textFormat_ = "{percent:.0f}%";
// 动画 // 动画
bool animatedChangeEnabled_ = false; bool animatedChangeEnabled_ = false;
float animationSpeed_ = 100.0f; float animationSpeed_ = 100.0f;
float displayValue_ = 50.0f; // 用于动画的显示值 float displayValue_ = 50.0f; // 用于动画的显示值
// 延迟显示 // 延迟显示
bool delayedDisplayEnabled_ = false; bool delayedDisplayEnabled_ = false;
float delayTime_ = 0.3f; float delayTime_ = 0.3f;
float delayTimer_ = 0.0f; float delayTimer_ = 0.0f;
float delayedValue_ = 50.0f; float delayedValue_ = 50.0f;
Color delayedFillColor_ = Color(1.0f, 0.0f, 0.0f, 0.5f); Color delayedFillColor_ = Color(1.0f, 0.0f, 0.0f, 0.5f);
// 条纹 // 条纹
bool stripedEnabled_ = false; bool stripedEnabled_ = false;
Color stripeColor_ = Color(1.0f, 1.0f, 1.0f, 0.2f); Color stripeColor_ = Color(1.0f, 1.0f, 1.0f, 0.2f);
float stripeSpeed_ = 50.0f; float stripeSpeed_ = 50.0f;
float stripeOffset_ = 0.0f; float stripeOffset_ = 0.0f;
Color getCurrentFillColor() const; Color getCurrentFillColor() const;
std::string formatText() const; std::string formatText() const;
void drawRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius); void drawRoundedRect(RenderBackend &renderer, const Rect &rect,
void fillRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius); const Color &color, float radius);
void drawStripes(RenderBackend &renderer, const Rect &rect); void fillRoundedRect(RenderBackend &renderer, const Rect &rect,
const Color &color, float radius);
void drawStripes(RenderBackend &renderer, const Rect &rect);
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -11,95 +11,95 @@ namespace extra2d {
// ============================================================================ // ============================================================================
class RadioButton : public Widget { class RadioButton : public Widget {
public: public:
RadioButton(); RadioButton();
~RadioButton() override = default; ~RadioButton() override = default;
static Ptr<RadioButton> create(); static Ptr<RadioButton> create();
static Ptr<RadioButton> create(const std::string &label); static Ptr<RadioButton> create(const std::string &label);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 选择状态 // 选择状态
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setSelected(bool selected); void setSelected(bool selected);
bool isSelected() const { return selected_; } bool isSelected() const { return selected_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 标签设置 // 标签设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setLabel(const std::string &label); void setLabel(const std::string &label);
const std::string &getLabel() const { return label_; } const std::string &getLabel() const { return label_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 字体设置 // 字体设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setFont(Ptr<FontAtlas> font); void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; } Ptr<FontAtlas> getFont() const { return font_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 文字颜色 // 文字颜色
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setTextColor(const Color &color); void setTextColor(const Color &color);
Color getTextColor() const { return textColor_; } Color getTextColor() const { return textColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 圆形尺寸 // 圆形尺寸
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setCircleSize(float size); void setCircleSize(float size);
float getCircleSize() const { return circleSize_; } float getCircleSize() const { return circleSize_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 间距 // 间距
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setSpacing(float spacing); void setSpacing(float spacing);
float getSpacing() const { return spacing_; } float getSpacing() const { return spacing_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 颜色设置 // 颜色设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setSelectedColor(const Color &color); void setSelectedColor(const Color &color);
Color getSelectedColor() const { return selectedColor_; } Color getSelectedColor() const { return selectedColor_; }
void setUnselectedColor(const Color &color); void setUnselectedColor(const Color &color);
Color getUnselectedColor() const { return unselectedColor_; } Color getUnselectedColor() const { return unselectedColor_; }
void setDotColor(const Color &color); void setDotColor(const Color &color);
Color getDotColor() const { return dotColor_; } Color getDotColor() const { return dotColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 分组 // 分组
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setGroupId(int groupId); void setGroupId(int groupId);
int getGroupId() const { return groupId_; } int getGroupId() const { return groupId_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 回调设置 // 回调设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setOnStateChange(Function<void(bool)> callback); void setOnStateChange(Function<void(bool)> callback);
Rect getBoundingBox() const override; Rect boundingBox() const override;
protected: protected:
void onDrawWidget(RenderBackend &renderer) override; void onDrawWidget(RenderBackend &renderer) override;
bool onMousePress(const MouseEvent &event) override; bool onMousePress(const MouseEvent &event) override;
bool onMouseRelease(const MouseEvent &event) override; bool onMouseRelease(const MouseEvent &event) override;
private: private:
bool selected_ = false; bool selected_ = false;
std::string label_; std::string label_;
Ptr<FontAtlas> font_; Ptr<FontAtlas> font_;
Color textColor_ = Colors::White; Color textColor_ = Colors::White;
float circleSize_ = 20.0f; float circleSize_ = 20.0f;
float spacing_ = 8.0f; float spacing_ = 8.0f;
Color selectedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f); Color selectedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
Color unselectedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f); Color unselectedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
Color dotColor_ = Colors::White; Color dotColor_ = Colors::White;
int groupId_ = 0; int groupId_ = 0;
bool pressed_ = false; bool pressed_ = false;
Function<void(bool)> onStateChange_; Function<void(bool)> onStateChange_;
}; };
// ============================================================================ // ============================================================================
@ -107,17 +107,17 @@ private:
// ============================================================================ // ============================================================================
class RadioButtonGroup { class RadioButtonGroup {
public: public:
void addButton(RadioButton *button); void addButton(RadioButton *button);
void removeButton(RadioButton *button); void removeButton(RadioButton *button);
void selectButton(RadioButton *button); void selectButton(RadioButton *button);
RadioButton *getSelectedButton() const { return selectedButton_; } RadioButton *getSelectedButton() const { return selectedButton_; }
void setOnSelectionChange(Function<void(RadioButton*)> callback); void setOnSelectionChange(Function<void(RadioButton *)> callback);
private: private:
std::vector<RadioButton*> buttons_; std::vector<RadioButton *> buttons_;
RadioButton *selectedButton_ = nullptr; RadioButton *selectedButton_ = nullptr;
Function<void(RadioButton*)> onSelectionChange_; Function<void(RadioButton *)> onSelectionChange_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -11,145 +11,145 @@ namespace extra2d {
// ============================================================================ // ============================================================================
class Slider : public Widget { class Slider : public Widget {
public: public:
Slider(); Slider();
~Slider() override = default; ~Slider() override = default;
static Ptr<Slider> create(); static Ptr<Slider> create();
static Ptr<Slider> create(float min, float max, float value); static Ptr<Slider> create(float min, float max, float value);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 数值范围 // 数值范围
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setRange(float min, float max); void setRange(float min, float max);
float getMin() const { return min_; } float getMin() const { return min_; }
float getMax() const { return max_; } float getMax() const { return max_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 当前值 // 当前值
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setValue(float value); void setValue(float value);
float getValue() const { return value_; } float getValue() const { return value_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 步进值 // 步进值
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setStep(float step); void setStep(float step);
float getStep() const { return step_; } float getStep() const { return step_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 方向 // 方向
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setVertical(bool vertical); void setVertical(bool vertical);
bool isVertical() const { return vertical_; } bool isVertical() const { return vertical_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 轨道尺寸 // 轨道尺寸
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setTrackSize(float size); void setTrackSize(float size);
float getTrackSize() const { return trackSize_; } float getTrackSize() const { return trackSize_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 滑块尺寸 // 滑块尺寸
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setThumbSize(float size); void setThumbSize(float size);
float getThumbSize() const { return thumbSize_; } float getThumbSize() const { return thumbSize_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 颜色设置 // 颜色设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setTrackColor(const Color &color); void setTrackColor(const Color &color);
Color getTrackColor() const { return trackColor_; } Color getTrackColor() const { return trackColor_; }
void setFillColor(const Color &color); void setFillColor(const Color &color);
Color getFillColor() const { return fillColor_; } Color getFillColor() const { return fillColor_; }
void setThumbColor(const Color &color); void setThumbColor(const Color &color);
Color getThumbColor() const { return thumbColor_; } Color getThumbColor() const { return thumbColor_; }
void setThumbHoverColor(const Color &color); void setThumbHoverColor(const Color &color);
Color getThumbHoverColor() const { return thumbHoverColor_; } Color getThumbHoverColor() const { return thumbHoverColor_; }
void setThumbPressedColor(const Color &color); void setThumbPressedColor(const Color &color);
Color getThumbPressedColor() const { return thumbPressedColor_; } Color getThumbPressedColor() const { return thumbPressedColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 显示设置 // 显示设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setShowThumb(bool show); void setShowThumb(bool show);
bool isShowThumb() const { return showThumb_; } bool isShowThumb() const { return showThumb_; }
void setShowFill(bool show); void setShowFill(bool show);
bool isShowFill() const { return showFill_; } bool isShowFill() const { return showFill_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 文本显示 // 文本显示
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setTextEnabled(bool enabled); void setTextEnabled(bool enabled);
bool isTextEnabled() const { return textEnabled_; } bool isTextEnabled() const { return textEnabled_; }
void setFont(Ptr<FontAtlas> font); void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; } Ptr<FontAtlas> getFont() const { return font_; }
void setTextColor(const Color &color); void setTextColor(const Color &color);
Color getTextColor() const { return textColor_; } Color getTextColor() const { return textColor_; }
void setTextFormat(const std::string &format); void setTextFormat(const std::string &format);
const std::string &getTextFormat() const { return textFormat_; } const std::string &getTextFormat() const { return textFormat_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 回调设置 // 回调设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setOnValueChange(Function<void(float)> callback); void setOnValueChange(Function<void(float)> callback);
void setOnDragStart(Function<void()> callback); void setOnDragStart(Function<void()> callback);
void setOnDragEnd(Function<void()> callback); void setOnDragEnd(Function<void()> callback);
Rect getBoundingBox() const override; Rect boundingBox() const override;
protected: protected:
void onDrawWidget(RenderBackend &renderer) override; void onDrawWidget(RenderBackend &renderer) override;
bool onMousePress(const MouseEvent &event) override; bool onMousePress(const MouseEvent &event) override;
bool onMouseRelease(const MouseEvent &event) override; bool onMouseRelease(const MouseEvent &event) override;
bool onMouseMove(const MouseEvent &event) override; bool onMouseMove(const MouseEvent &event) override;
void onMouseEnter() override; void onMouseEnter() override;
void onMouseLeave() override; void onMouseLeave() override;
private: private:
float min_ = 0.0f; float min_ = 0.0f;
float max_ = 100.0f; float max_ = 100.0f;
float value_ = 50.0f; float value_ = 50.0f;
float step_ = 0.0f; float step_ = 0.0f;
bool vertical_ = false;
float trackSize_ = 6.0f;
float thumbSize_ = 16.0f;
Color trackColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
Color fillColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
Color thumbColor_ = Color(0.8f, 0.8f, 0.8f, 1.0f);
Color thumbHoverColor_ = Color(1.0f, 1.0f, 1.0f, 1.0f);
Color thumbPressedColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
bool showThumb_ = true;
bool showFill_ = true;
bool textEnabled_ = false;
Ptr<FontAtlas> font_;
Color textColor_ = Colors::White;
std::string textFormat_ = "{value:.0f}";
bool dragging_ = false;
bool hovered_ = false;
Function<void(float)> onValueChange_;
Function<void()> onDragStart_;
Function<void()> onDragEnd_;
float valueToPosition(float value) const; bool vertical_ = false;
float positionToValue(float pos) const; float trackSize_ = 6.0f;
Rect getThumbRect() const; float thumbSize_ = 16.0f;
Rect getTrackRect() const;
std::string formatText() const; Color trackColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
float snapToStep(float value) const; Color fillColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
Color thumbColor_ = Color(0.8f, 0.8f, 0.8f, 1.0f);
Color thumbHoverColor_ = Color(1.0f, 1.0f, 1.0f, 1.0f);
Color thumbPressedColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
bool showThumb_ = true;
bool showFill_ = true;
bool textEnabled_ = false;
Ptr<FontAtlas> font_;
Color textColor_ = Colors::White;
std::string textFormat_ = "{value:.0f}";
bool dragging_ = false;
bool hovered_ = false;
Function<void(float)> onValueChange_;
Function<void()> onDragStart_;
Function<void()> onDragEnd_;
float valueToPosition(float value) const;
float positionToValue(float pos) const;
Rect getThumbRect() const;
Rect getTrackRect() const;
std::string formatText() const;
float snapToStep(float value) const;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -2,11 +2,12 @@
#include <core/color.h> #include <core/color.h>
#include <core/types.h> #include <core/types.h>
#include <graphics/font.h>
#include <ui/widget.h>
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <graphics/font.h>
#include <string> #include <string>
#include <ui/widget.h>
namespace extra2d { namespace extra2d {
@ -98,7 +99,7 @@ public:
Vec2 getTextSize() const; Vec2 getTextSize() const;
float getLineHeight() const; float getLineHeight() const;
Rect getBoundingBox() const override; Rect boundingBox() const override;
protected: protected:
void onDrawWidget(RenderBackend &renderer) override; void onDrawWidget(RenderBackend &renderer) override;

View File

@ -27,9 +27,9 @@ public:
void setSize(const Size &size); void setSize(const Size &size);
void setSize(float width, float height); void setSize(float width, float height);
Size getSize() const { return size_; } Size size() const { return size_; }
Rect getBoundingBox() const override; Rect boundingBox() const override;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 鼠标事件处理(子类可重写) // 鼠标事件处理(子类可重写)
@ -55,7 +55,7 @@ public:
protected: protected:
// 供子类使用的辅助方法 // 供子类使用的辅助方法
bool isPointInside(float x, float y) const { bool isPointInside(float x, float y) const {
return getBoundingBox().containsPoint(Point(x, y)); return boundingBox().containsPoint(Point(x, y));
} }
// 子类重写此方法以支持自定义渲染 // 子类重写此方法以支持自定义渲染

View File

@ -1,18 +1,17 @@
#include <algorithm> #include <algorithm>
#include <cmath>
#include <animation/tween.h> #include <animation/tween.h>
#include <cmath>
#include <graphics/render_command.h> #include <graphics/render_command.h>
#include <scene/node.h> #include <scene/node.h>
#include <scene/scene.h> #include <scene/scene.h>
#include <utils/logger.h> #include <utils/logger.h>
namespace extra2d { namespace extra2d {
Node::Node() = default; Node::Node() = default;
Node::~Node() { Node::~Node() { removeAllChildren(); }
removeAllChildren();
}
void Node::addChild(Ptr<Node> child) { void Node::addChild(Ptr<Node> child) {
if (!child || child.get() == this) { if (!child || child.get() == this) {
@ -200,7 +199,7 @@ void Node::setOpacity(float opacity) {
void Node::setVisible(bool visible) { visible_ = visible; } void Node::setVisible(bool visible) { visible_ = visible; }
void Node::setColor(const Color3B& color) { color_ = color; } void Node::setColor(const Color3B &color) { color_ = color; }
void Node::setFlipX(bool flipX) { flipX_ = flipX; } void Node::setFlipX(bool flipX) { flipX_ = flipX; }
@ -260,9 +259,9 @@ glm::mat4 Node::getWorldTransform() const {
if (worldTransformDirty_) { if (worldTransformDirty_) {
// 使用线程局部存储的固定数组,避免每帧内存分配 // 使用线程局部存储的固定数组,避免每帧内存分配
// 限制最大深度为 256 层,足以覆盖绝大多数场景 // 限制最大深度为 256 层,足以覆盖绝大多数场景
thread_local std::array<const Node*, 256> nodeChainCache; thread_local std::array<const Node *, 256> nodeChainCache;
thread_local size_t chainCount = 0; thread_local size_t chainCount = 0;
chainCount = 0; chainCount = 0;
const Node *current = this; const Node *current = this;
while (current && chainCount < nodeChainCache.size()) { while (current && chainCount < nodeChainCache.size()) {
@ -300,7 +299,7 @@ void Node::batchUpdateTransforms() {
if (transformDirty_) { if (transformDirty_) {
(void)getLocalTransform(); // 这会计算并缓存本地变换 (void)getLocalTransform(); // 这会计算并缓存本地变换
} }
// 如果世界变换脏了,需要重新计算 // 如果世界变换脏了,需要重新计算
if (worldTransformDirty_) { if (worldTransformDirty_) {
auto parent = parent_.lock(); auto parent = parent_.lock();
@ -313,7 +312,7 @@ void Node::batchUpdateTransforms() {
} }
worldTransformDirty_ = false; worldTransformDirty_ = false;
} }
// 递归更新子节点 // 递归更新子节点
for (auto &child : children_) { for (auto &child : children_) {
child->batchUpdateTransforms(); child->batchUpdateTransforms();
@ -384,7 +383,7 @@ void Node::onDetachFromScene() {
} }
} }
Rect Node::getBoundingBox() const { Rect Node::boundingBox() const {
// 默认返回一个以位置为中心的点矩形 // 默认返回一个以位置为中心的点矩形
return Rect(position_.x, position_.y, 0, 0); return Rect(position_.x, position_.y, 0, 0);
} }
@ -394,7 +393,7 @@ void Node::updateSpatialIndex() {
return; return;
} }
Rect newBounds = getBoundingBox(); Rect newBounds = boundingBox();
if (newBounds != lastSpatialBounds_) { if (newBounds != lastSpatialBounds_) {
scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds); scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds);
lastSpatialBounds_ = newBounds; lastSpatialBounds_ = newBounds;
@ -462,13 +461,13 @@ void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
} }
} }
Tween& Node::tween() { Tween &Node::tween() {
auto tw = std::make_shared<Tween>(this); auto tw = std::make_shared<Tween>(this);
tweens_.push_back(tw); tweens_.push_back(tw);
return *tweens_.back(); return *tweens_.back();
} }
Tween& Node::tween(const std::string &name) { Tween &Node::tween(const std::string &name) {
auto tw = std::make_shared<Tween>(this, name); auto tw = std::make_shared<Tween>(this, name);
tweens_.push_back(tw); tweens_.push_back(tw);
return *tweens_.back(); return *tweens_.back();

View File

@ -37,7 +37,7 @@ Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
return nullptr; return nullptr;
} }
Rect bounds = node->getBoundingBox(); Rect bounds = node->boundingBox();
if (!bounds.empty() && bounds.containsPoint(worldPos)) { if (!bounds.empty() && bounds.containsPoint(worldPos)) {
return node.get(); return node.get();
} }

View File

@ -2,8 +2,9 @@
#include <cmath> #include <cmath>
#include <graphics/render_backend.h> #include <graphics/render_backend.h>
#include <graphics/render_command.h> #include <graphics/render_command.h>
#include <scene/shape_node.h>
#include <limits> #include <limits>
#include <scene/shape_node.h>
namespace extra2d { namespace extra2d {
@ -124,7 +125,7 @@ void ShapeNode::clearPoints() {
updateSpatialIndex(); updateSpatialIndex();
} }
Rect ShapeNode::getBoundingBox() const { Rect ShapeNode::boundingBox() const {
if (points_.empty()) { if (points_.empty()) {
return Rect(); return Rect();
} }
@ -262,16 +263,16 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
case ShapeType::Point: case ShapeType::Point:
if (!points_.empty()) { if (!points_.empty()) {
cmd.type = RenderCommandType::FilledCircle; cmd.type = RenderCommandType::FilledCircle;
cmd.data = cmd.data = CircleCommandData{
CircleCommandData{points_[0] + offset, lineWidth_ * 0.5f, color_, 8, 0.0f, true}; points_[0] + offset, lineWidth_ * 0.5f, color_, 8, 0.0f, true};
} }
break; break;
case ShapeType::Line: case ShapeType::Line:
if (points_.size() >= 2) { if (points_.size() >= 2) {
cmd.type = RenderCommandType::Line; cmd.type = RenderCommandType::Line;
cmd.data = LineCommandData{points_[0] + offset, points_[1] + offset, color_, cmd.data = LineCommandData{points_[0] + offset, points_[1] + offset,
lineWidth_}; color_, lineWidth_};
} }
break; break;
@ -281,14 +282,14 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
cmd.type = RenderCommandType::FilledRect; cmd.type = RenderCommandType::FilledRect;
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x, Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
points_[2].y - points_[0].y); points_[2].y - points_[0].y);
cmd.data = cmd.data = RectCommandData{Rect(rect.origin + offset, rect.size),
RectCommandData{Rect(rect.origin + offset, rect.size), color_, 0.0f, true}; color_, 0.0f, true};
} else { } else {
cmd.type = RenderCommandType::Rect; cmd.type = RenderCommandType::Rect;
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x, Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
points_[2].y - points_[0].y); points_[2].y - points_[0].y);
cmd.data = cmd.data = RectCommandData{Rect(rect.origin + offset, rect.size),
RectCommandData{Rect(rect.origin + offset, rect.size), color_, lineWidth_, false}; color_, lineWidth_, false};
} }
} }
break; break;
@ -298,12 +299,12 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
float radius = points_[1].x; float radius = points_[1].x;
if (filled_) { if (filled_) {
cmd.type = RenderCommandType::FilledCircle; cmd.type = RenderCommandType::FilledCircle;
cmd.data = cmd.data = CircleCommandData{points_[0] + offset, radius, color_,
CircleCommandData{points_[0] + offset, radius, color_, segments_, 0.0f, true}; segments_, 0.0f, true};
} else { } else {
cmd.type = RenderCommandType::Circle; cmd.type = RenderCommandType::Circle;
cmd.data = CircleCommandData{points_[0] + offset, radius, color_, segments_, cmd.data = CircleCommandData{points_[0] + offset, radius, color_,
lineWidth_, false}; segments_, lineWidth_, false};
} }
} }
break; break;
@ -336,7 +337,8 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
cmd.data = PolygonCommandData{transformedPoints, color_, 0.0f, true}; cmd.data = PolygonCommandData{transformedPoints, color_, 0.0f, true};
} else { } else {
cmd.type = RenderCommandType::Polygon; cmd.type = RenderCommandType::Polygon;
cmd.data = PolygonCommandData{transformedPoints, color_, lineWidth_, false}; cmd.data =
PolygonCommandData{transformedPoints, color_, lineWidth_, false};
} }
} }
break; break;

View File

@ -43,7 +43,7 @@ Ptr<Sprite> Sprite::create(Ptr<Texture> texture, const Rect &rect) {
return sprite; return sprite;
} }
Rect Sprite::getBoundingBox() const { Rect Sprite::boundingBox() const {
if (!texture_ || !texture_->isValid()) { if (!texture_ || !texture_->isValid()) {
return Rect(); return Rect();
} }
@ -159,7 +159,7 @@ void Sprite::generateRenderCommand(std::vector<RenderCommand> &commands,
cmd.type = RenderCommandType::Sprite; cmd.type = RenderCommandType::Sprite;
cmd.layer = zOrder; cmd.layer = zOrder;
cmd.data = SpriteCommandData{texture_.get(), destRect, srcRect, color_, cmd.data = SpriteCommandData{texture_.get(), destRect, srcRect, color_,
worldRotation, anchorPt, 0}; worldRotation, anchorPt, 0};
commands.push_back(std::move(cmd)); commands.push_back(std::move(cmd));
} }

View File

@ -130,7 +130,7 @@ void SpatialManager::rebuild() {
auto objects = oldIndex->query(bounds); auto objects = oldIndex->query(bounds);
for (Node *node : objects) { for (Node *node : objects) {
if (node) { if (node) {
auto nodeBounds = node->getBoundingBox(); auto nodeBounds = node->boundingBox();
index_->insert(node, nodeBounds); index_->insert(node, nodeBounds);
} }
} }

View File

@ -1,6 +1,6 @@
#include <algorithm> #include <algorithm>
#include <cmath>
#include <app/application.h> #include <app/application.h>
#include <cmath>
#include <core/string.h> #include <core/string.h>
#include <graphics/render_backend.h> #include <graphics/render_backend.h>
#include <ui/button.h> #include <ui/button.h>
@ -93,7 +93,7 @@ Ptr<Button> Button::create(const std::string &text, Ptr<FontAtlas> font) {
*/ */
void Button::setText(const std::string &text) { void Button::setText(const std::string &text) {
text_ = text; text_ = text;
if (font_ && getSize().empty()) { if (font_ && size().empty()) {
Vec2 textSize = font_->measureText(text_); Vec2 textSize = font_->measureText(text_);
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f); setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
} }
@ -105,7 +105,7 @@ void Button::setText(const std::string &text) {
*/ */
void Button::setFont(Ptr<FontAtlas> font) { void Button::setFont(Ptr<FontAtlas> font) {
font_ = font; font_ = font;
if (font_ && getSize().empty() && !text_.empty()) { if (font_ && size().empty() && !text_.empty()) {
Vec2 textSize = font_->measureText(text_); Vec2 textSize = font_->measureText(text_);
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f); setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
} }
@ -117,7 +117,7 @@ void Button::setFont(Ptr<FontAtlas> font) {
*/ */
void Button::setPadding(const Vec2 &padding) { void Button::setPadding(const Vec2 &padding) {
padding_ = padding; padding_ = padding;
if (font_ && getSize().empty() && !text_.empty()) { if (font_ && size().empty() && !text_.empty()) {
Vec2 textSize = font_->measureText(text_); Vec2 textSize = font_->measureText(text_);
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f); setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
} }
@ -128,9 +128,7 @@ void Button::setPadding(const Vec2 &padding) {
* @param x X方向内边距 * @param x X方向内边距
* @param y Y方向内边距 * @param y Y方向内边距
*/ */
void Button::setPadding(float x, float y) { void Button::setPadding(float x, float y) { setPadding(Vec2(x, y)); }
setPadding(Vec2(x, y));
}
/** /**
* @brief * @brief
@ -356,20 +354,20 @@ void Button::setCustomSize(float width, float height) {
* @brief * @brief
* @return * @return
*/ */
Rect Button::getBoundingBox() const { Rect Button::boundingBox() const {
auto position = convertToWorldSpace(extra2d::Vec2::Zero()); auto position = convertToWorldSpace(extra2d::Vec2::Zero());
auto anchorPt = anchor(); auto anchorPt = anchor();
auto scaleVal = scale(); auto scaleVal = scale();
auto size = getSize(); auto widgetSize = size();
if (size.empty()) { if (widgetSize.empty()) {
return Rect(); return Rect();
} }
float w = size.width * scaleVal.x; float w = widgetSize.width * scaleVal.x;
float h = size.height * scaleVal.y; float h = widgetSize.height * scaleVal.y;
float x0 = position.x - size.width * anchorPt.x * scaleVal.x; float x0 = position.x - widgetSize.width * anchorPt.x * scaleVal.x;
float y0 = position.y - size.height * anchorPt.y * scaleVal.y; float y0 = position.y - widgetSize.height * anchorPt.y * scaleVal.y;
return Rect(x0, y0, w, h); return Rect(x0, y0, w, h);
} }
@ -616,7 +614,7 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
* @param renderer * @param renderer
*/ */
void Button::onDrawWidget(RenderBackend &renderer) { void Button::onDrawWidget(RenderBackend &renderer) {
Rect rect = getBoundingBox(); Rect rect = boundingBox();
if (rect.empty()) { if (rect.empty()) {
return; return;
} }

View File

@ -1,6 +1,6 @@
#include <ui/check_box.h>
#include <graphics/render_backend.h>
#include <core/string.h> #include <core/string.h>
#include <graphics/render_backend.h>
#include <ui/check_box.h>
namespace extra2d { namespace extra2d {
@ -8,17 +8,15 @@ namespace extra2d {
* @brief * @brief
*/ */
CheckBox::CheckBox() { CheckBox::CheckBox() {
setAnchor(0.0f, 0.0f); setAnchor(0.0f, 0.0f);
setSize(boxSize_, boxSize_); setSize(boxSize_, boxSize_);
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Ptr<CheckBox> CheckBox::create() { Ptr<CheckBox> CheckBox::create() { return makePtr<CheckBox>(); }
return makePtr<CheckBox>();
}
/** /**
* @brief * @brief
@ -26,9 +24,9 @@ Ptr<CheckBox> CheckBox::create() {
* @return * @return
*/ */
Ptr<CheckBox> CheckBox::create(const std::string &label) { Ptr<CheckBox> CheckBox::create(const std::string &label) {
auto cb = makePtr<CheckBox>(); auto cb = makePtr<CheckBox>();
cb->setLabel(label); cb->setLabel(label);
return cb; return cb;
} }
/** /**
@ -36,75 +34,61 @@ Ptr<CheckBox> CheckBox::create(const std::string &label) {
* @param checked * @param checked
*/ */
void CheckBox::setChecked(bool checked) { void CheckBox::setChecked(bool checked) {
if (checked_ != checked) { if (checked_ != checked) {
checked_ = checked; checked_ = checked;
if (onStateChange_) { if (onStateChange_) {
onStateChange_(checked_); onStateChange_(checked_);
}
} }
}
} }
/** /**
* @brief * @brief
*/ */
void CheckBox::toggle() { void CheckBox::toggle() { setChecked(!checked_); }
setChecked(!checked_);
}
/** /**
* @brief * @brief
* @param label * @param label
*/ */
void CheckBox::setLabel(const std::string &label) { void CheckBox::setLabel(const std::string &label) { label_ = label; }
label_ = label;
}
/** /**
* @brief * @brief
* @param font * @param font
*/ */
void CheckBox::setFont(Ptr<FontAtlas> font) { void CheckBox::setFont(Ptr<FontAtlas> font) { font_ = font; }
font_ = font;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void CheckBox::setTextColor(const Color &color) { void CheckBox::setTextColor(const Color &color) { textColor_ = color; }
textColor_ = color;
}
/** /**
* @brief * @brief
* @param size * @param size
*/ */
void CheckBox::setBoxSize(float size) { void CheckBox::setBoxSize(float size) { boxSize_ = size; }
boxSize_ = size;
}
/** /**
* @brief * @brief
* @param spacing * @param spacing
*/ */
void CheckBox::setSpacing(float spacing) { void CheckBox::setSpacing(float spacing) { spacing_ = spacing; }
spacing_ = spacing;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void CheckBox::setCheckedColor(const Color &color) { void CheckBox::setCheckedColor(const Color &color) { checkedColor_ = color; }
checkedColor_ = color;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void CheckBox::setUncheckedColor(const Color &color) { void CheckBox::setUncheckedColor(const Color &color) {
uncheckedColor_ = color; uncheckedColor_ = color;
} }
/** /**
@ -112,7 +96,7 @@ void CheckBox::setUncheckedColor(const Color &color) {
* @param color * @param color
*/ */
void CheckBox::setCheckMarkColor(const Color &color) { void CheckBox::setCheckMarkColor(const Color &color) {
checkMarkColor_ = color; checkMarkColor_ = color;
} }
/** /**
@ -120,23 +104,23 @@ void CheckBox::setCheckMarkColor(const Color &color) {
* @param callback * @param callback
*/ */
void CheckBox::setOnStateChange(Function<void(bool)> callback) { void CheckBox::setOnStateChange(Function<void(bool)> callback) {
onStateChange_ = callback; onStateChange_ = callback;
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Rect CheckBox::getBoundingBox() const { Rect CheckBox::boundingBox() const {
Vec2 position = pos(); Vec2 position = pos();
float width = boxSize_; float width = boxSize_;
if (!label_.empty() && font_) { if (!label_.empty() && font_) {
Vec2 textSize = font_->measureText(label_); Vec2 textSize = font_->measureText(label_);
width += spacing_ + textSize.x; width += spacing_ + textSize.x;
} }
return Rect(position.x, position.y, width, boxSize_); return Rect(position.x, position.y, width, boxSize_);
} }
/** /**
@ -144,30 +128,31 @@ Rect CheckBox::getBoundingBox() const {
* @param renderer * @param renderer
*/ */
void CheckBox::onDrawWidget(RenderBackend &renderer) { void CheckBox::onDrawWidget(RenderBackend &renderer) {
Vec2 position = pos(); Vec2 position = pos();
Rect boxRect(position.x, position.y + (getSize().height - boxSize_) * 0.5f, boxSize_, boxSize_); Rect boxRect(position.x, position.y + (size().height - boxSize_) * 0.5f,
Color boxColor = checked_ ? checkedColor_ : uncheckedColor_; boxSize_, boxSize_);
renderer.fillRect(boxRect, boxColor); Color boxColor = checked_ ? checkedColor_ : uncheckedColor_;
renderer.drawRect(boxRect, Colors::White, 1.0f); renderer.fillRect(boxRect, boxColor);
renderer.drawRect(boxRect, Colors::White, 1.0f);
if (checked_) {
float padding = boxSize_ * 0.2f; if (checked_) {
float x1 = boxRect.origin.x + padding; float padding = boxSize_ * 0.2f;
float y1 = boxRect.origin.y + boxSize_ * 0.5f; float x1 = boxRect.origin.x + padding;
float x2 = boxRect.origin.x + boxSize_ * 0.4f; float y1 = boxRect.origin.y + boxSize_ * 0.5f;
float y2 = boxRect.origin.y + boxSize_ - padding; float x2 = boxRect.origin.x + boxSize_ * 0.4f;
float x3 = boxRect.origin.x + boxSize_ - padding; float y2 = boxRect.origin.y + boxSize_ - padding;
float y3 = boxRect.origin.y + padding; float x3 = boxRect.origin.x + boxSize_ - padding;
float y3 = boxRect.origin.y + padding;
renderer.drawLine(Vec2(x1, y1), Vec2(x2, y2), checkMarkColor_, 2.0f);
renderer.drawLine(Vec2(x2, y2), Vec2(x3, y3), checkMarkColor_, 2.0f); renderer.drawLine(Vec2(x1, y1), Vec2(x2, y2), checkMarkColor_, 2.0f);
} renderer.drawLine(Vec2(x2, y2), Vec2(x3, y3), checkMarkColor_, 2.0f);
}
if (!label_.empty() && font_) {
Vec2 textPos(position.x + boxSize_ + spacing_, position.y); if (!label_.empty() && font_) {
renderer.drawText(*font_, label_, textPos, textColor_); Vec2 textPos(position.x + boxSize_ + spacing_, position.y);
} renderer.drawText(*font_, label_, textPos, textColor_);
}
} }
/** /**
@ -176,11 +161,11 @@ void CheckBox::onDrawWidget(RenderBackend &renderer) {
* @return * @return
*/ */
bool CheckBox::onMousePress(const MouseEvent &event) { bool CheckBox::onMousePress(const MouseEvent &event) {
if (event.button == MouseButton::Left) { if (event.button == MouseButton::Left) {
pressed_ = true; pressed_ = true;
return true; return true;
} }
return false; return false;
} }
/** /**
@ -189,16 +174,16 @@ bool CheckBox::onMousePress(const MouseEvent &event) {
* @return * @return
*/ */
bool CheckBox::onMouseRelease(const MouseEvent &event) { bool CheckBox::onMouseRelease(const MouseEvent &event) {
if (event.button == MouseButton::Left && pressed_) { if (event.button == MouseButton::Left && pressed_) {
pressed_ = false; pressed_ = false;
Vec2 position = pos(); Vec2 position = pos();
Rect boxRect(position.x, position.y, boxSize_, boxSize_); Rect boxRect(position.x, position.y, boxSize_, boxSize_);
if (boxRect.containsPoint(Point(event.x, event.y))) { if (boxRect.containsPoint(Point(event.x, event.y))) {
toggle(); toggle();
}
return true;
} }
return false; return true;
}
return false;
} }
} // namespace extra2d } // namespace extra2d

View File

@ -1,32 +1,28 @@
#include <ui/label.h>
#include <graphics/render_backend.h>
#include <core/string.h> #include <core/string.h>
#include <graphics/render_backend.h>
#include <ui/label.h>
namespace extra2d { namespace extra2d {
/** /**
* @brief * @brief
*/ */
Label::Label() { Label::Label() { setAnchor(0.0f, 0.0f); }
setAnchor(0.0f, 0.0f);
}
/** /**
* @brief * @brief
* @param text * @param text
*/ */
Label::Label(const std::string &text) : text_(text) { Label::Label(const std::string &text) : text_(text) {
setAnchor(0.0f, 0.0f); setAnchor(0.0f, 0.0f);
sizeDirty_ = true; sizeDirty_ = true;
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Ptr<Label> Label::create() { Ptr<Label> Label::create() { return makePtr<Label>(); }
return makePtr<Label>();
}
/** /**
* @brief * @brief
@ -34,7 +30,7 @@ Ptr<Label> Label::create() {
* @return * @return
*/ */
Ptr<Label> Label::create(const std::string &text) { Ptr<Label> Label::create(const std::string &text) {
return makePtr<Label>(text); return makePtr<Label>(text);
} }
/** /**
@ -44,9 +40,9 @@ Ptr<Label> Label::create(const std::string &text) {
* @return * @return
*/ */
Ptr<Label> Label::create(const std::string &text, Ptr<FontAtlas> font) { Ptr<Label> Label::create(const std::string &text, Ptr<FontAtlas> font) {
auto label = makePtr<Label>(text); auto label = makePtr<Label>(text);
label->setFont(font); label->setFont(font);
return label; return label;
} }
/** /**
@ -54,9 +50,9 @@ Ptr<Label> Label::create(const std::string &text, Ptr<FontAtlas> font) {
* @param text * @param text
*/ */
void Label::setText(const std::string &text) { void Label::setText(const std::string &text) {
text_ = text; text_ = text;
sizeDirty_ = true; sizeDirty_ = true;
updateSpatialIndex(); updateSpatialIndex();
} }
/** /**
@ -64,101 +60,83 @@ void Label::setText(const std::string &text) {
* @param font * @param font
*/ */
void Label::setFont(Ptr<FontAtlas> font) { void Label::setFont(Ptr<FontAtlas> font) {
font_ = font; font_ = font;
sizeDirty_ = true; sizeDirty_ = true;
updateSpatialIndex(); updateSpatialIndex();
} }
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Label::setTextColor(const Color &color) { void Label::setTextColor(const Color &color) { textColor_ = color; }
textColor_ = color;
}
/** /**
* @brief * @brief
* @param size * @param size
*/ */
void Label::setFontSize(int size) { void Label::setFontSize(int size) {
fontSize_ = size; fontSize_ = size;
sizeDirty_ = true; sizeDirty_ = true;
updateSpatialIndex(); updateSpatialIndex();
} }
/** /**
* @brief * @brief
* @param align * @param align
*/ */
void Label::setHorizontalAlign(HorizontalAlign align) { void Label::setHorizontalAlign(HorizontalAlign align) { hAlign_ = align; }
hAlign_ = align;
}
/** /**
* @brief * @brief
* @param align * @param align
*/ */
void Label::setVerticalAlign(VerticalAlign align) { void Label::setVerticalAlign(VerticalAlign align) { vAlign_ = align; }
vAlign_ = align;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void Label::setShadowEnabled(bool enabled) { void Label::setShadowEnabled(bool enabled) { shadowEnabled_ = enabled; }
shadowEnabled_ = enabled;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Label::setShadowColor(const Color &color) { void Label::setShadowColor(const Color &color) { shadowColor_ = color; }
shadowColor_ = color;
}
/** /**
* @brief * @brief
* @param offset * @param offset
*/ */
void Label::setShadowOffset(const Vec2 &offset) { void Label::setShadowOffset(const Vec2 &offset) { shadowOffset_ = offset; }
shadowOffset_ = offset;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void Label::setOutlineEnabled(bool enabled) { void Label::setOutlineEnabled(bool enabled) { outlineEnabled_ = enabled; }
outlineEnabled_ = enabled;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Label::setOutlineColor(const Color &color) { void Label::setOutlineColor(const Color &color) { outlineColor_ = color; }
outlineColor_ = color;
}
/** /**
* @brief * @brief
* @param width * @param width
*/ */
void Label::setOutlineWidth(float width) { void Label::setOutlineWidth(float width) { outlineWidth_ = width; }
outlineWidth_ = width;
}
/** /**
* @brief * @brief
* @param multiLine * @param multiLine
*/ */
void Label::setMultiLine(bool multiLine) { void Label::setMultiLine(bool multiLine) {
multiLine_ = multiLine; multiLine_ = multiLine;
sizeDirty_ = true; sizeDirty_ = true;
updateSpatialIndex(); updateSpatialIndex();
} }
/** /**
@ -166,9 +144,9 @@ void Label::setMultiLine(bool multiLine) {
* @param spacing * @param spacing
*/ */
void Label::setLineSpacing(float spacing) { void Label::setLineSpacing(float spacing) {
lineSpacing_ = spacing; lineSpacing_ = spacing;
sizeDirty_ = true; sizeDirty_ = true;
updateSpatialIndex(); updateSpatialIndex();
} }
/** /**
@ -176,9 +154,9 @@ void Label::setLineSpacing(float spacing) {
* @param maxWidth * @param maxWidth
*/ */
void Label::setMaxWidth(float maxWidth) { void Label::setMaxWidth(float maxWidth) {
maxWidth_ = maxWidth; maxWidth_ = maxWidth;
sizeDirty_ = true; sizeDirty_ = true;
updateSpatialIndex(); updateSpatialIndex();
} }
/** /**
@ -186,8 +164,8 @@ void Label::setMaxWidth(float maxWidth) {
* @return * @return
*/ */
Vec2 Label::getTextSize() const { Vec2 Label::getTextSize() const {
updateCache(); updateCache();
return cachedSize_; return cachedSize_;
} }
/** /**
@ -195,38 +173,38 @@ Vec2 Label::getTextSize() const {
* @return * @return
*/ */
float Label::getLineHeight() const { float Label::getLineHeight() const {
if (font_) { if (font_) {
return font_->getLineHeight() * lineSpacing_; return font_->getLineHeight() * lineSpacing_;
} }
return static_cast<float>(fontSize_) * lineSpacing_; return static_cast<float>(fontSize_) * lineSpacing_;
} }
/** /**
* @brief * @brief
*/ */
void Label::updateCache() const { void Label::updateCache() const {
if (!sizeDirty_ || !font_) { if (!sizeDirty_ || !font_) {
return; return;
}
if (multiLine_) {
auto lines = splitLines();
float maxWidth = 0.0f;
float totalHeight = 0.0f;
float lineHeight = getLineHeight();
for (size_t i = 0; i < lines.size(); ++i) {
Vec2 lineSize = font_->measureText(lines[i]);
maxWidth = std::max(maxWidth, lineSize.x);
totalHeight += lineHeight;
} }
if (multiLine_) { cachedSize_ = Vec2(maxWidth, totalHeight);
auto lines = splitLines(); } else {
float maxWidth = 0.0f; cachedSize_ = font_->measureText(text_);
float totalHeight = 0.0f; }
float lineHeight = getLineHeight();
sizeDirty_ = false;
for (size_t i = 0; i < lines.size(); ++i) {
Vec2 lineSize = font_->measureText(lines[i]);
maxWidth = std::max(maxWidth, lineSize.x);
totalHeight += lineHeight;
}
cachedSize_ = Vec2(maxWidth, totalHeight);
} else {
cachedSize_ = font_->measureText(text_);
}
sizeDirty_ = false;
} }
/** /**
@ -234,70 +212,70 @@ void Label::updateCache() const {
* @return * @return
*/ */
std::vector<std::string> Label::splitLines() const { std::vector<std::string> Label::splitLines() const {
std::vector<std::string> lines; std::vector<std::string> lines;
if (text_.empty()) { if (text_.empty()) {
return lines;
}
if (maxWidth_ <= 0.0f || !font_) {
lines.push_back(text_);
return lines;
}
size_t start = 0;
size_t end = text_.find('\n');
while (end != std::string::npos) {
std::string line = text_.substr(start, end - start);
Vec2 lineSize = font_->measureText(line);
if (lineSize.x > maxWidth_) {
std::string currentLine;
for (size_t i = 0; i < line.length(); ++i) {
std::string testLine = currentLine + line[i];
Vec2 testSize = font_->measureText(testLine);
if (testSize.x > maxWidth_ && !currentLine.empty()) {
lines.push_back(currentLine);
currentLine = line[i];
} else {
currentLine = testLine;
}
}
if (!currentLine.empty()) {
lines.push_back(currentLine);
}
} else {
lines.push_back(line);
}
start = end + 1;
end = text_.find('\n', start);
}
if (start < text_.length()) {
std::string line = text_.substr(start);
Vec2 lineSize = font_->measureText(line);
if (lineSize.x > maxWidth_) {
std::string currentLine;
for (size_t i = 0; i < line.length(); ++i) {
std::string testLine = currentLine + line[i];
Vec2 testSize = font_->measureText(testLine);
if (testSize.x > maxWidth_ && !currentLine.empty()) {
lines.push_back(currentLine);
currentLine = line[i];
} else {
currentLine = testLine;
}
}
if (!currentLine.empty()) {
lines.push_back(currentLine);
}
} else {
lines.push_back(line);
}
}
return lines; return lines;
}
if (maxWidth_ <= 0.0f || !font_) {
lines.push_back(text_);
return lines;
}
size_t start = 0;
size_t end = text_.find('\n');
while (end != std::string::npos) {
std::string line = text_.substr(start, end - start);
Vec2 lineSize = font_->measureText(line);
if (lineSize.x > maxWidth_) {
std::string currentLine;
for (size_t i = 0; i < line.length(); ++i) {
std::string testLine = currentLine + line[i];
Vec2 testSize = font_->measureText(testLine);
if (testSize.x > maxWidth_ && !currentLine.empty()) {
lines.push_back(currentLine);
currentLine = line[i];
} else {
currentLine = testLine;
}
}
if (!currentLine.empty()) {
lines.push_back(currentLine);
}
} else {
lines.push_back(line);
}
start = end + 1;
end = text_.find('\n', start);
}
if (start < text_.length()) {
std::string line = text_.substr(start);
Vec2 lineSize = font_->measureText(line);
if (lineSize.x > maxWidth_) {
std::string currentLine;
for (size_t i = 0; i < line.length(); ++i) {
std::string testLine = currentLine + line[i];
Vec2 testSize = font_->measureText(testLine);
if (testSize.x > maxWidth_ && !currentLine.empty()) {
lines.push_back(currentLine);
currentLine = line[i];
} else {
currentLine = testLine;
}
}
if (!currentLine.empty()) {
lines.push_back(currentLine);
}
} else {
lines.push_back(line);
}
}
return lines;
} }
/** /**
@ -305,38 +283,38 @@ std::vector<std::string> Label::splitLines() const {
* @return * @return
*/ */
Vec2 Label::calculateDrawPosition() const { Vec2 Label::calculateDrawPosition() const {
Vec2 position = pos(); Vec2 position = pos();
Vec2 size = getTextSize(); Vec2 textSize = getTextSize();
Size widgetSize = getSize(); Size widgetSize = size();
float refWidth = widgetSize.empty() ? size.x : widgetSize.width; float refWidth = widgetSize.empty() ? textSize.x : widgetSize.width;
float refHeight = widgetSize.empty() ? size.y : widgetSize.height; float refHeight = widgetSize.empty() ? textSize.y : widgetSize.height;
switch (hAlign_) { switch (hAlign_) {
case HorizontalAlign::Center: case HorizontalAlign::Center:
position.x += (refWidth - size.x) * 0.5f; position.x += (refWidth - textSize.x) * 0.5f;
break; break;
case HorizontalAlign::Right: case HorizontalAlign::Right:
position.x += refWidth - size.x; position.x += refWidth - textSize.x;
break; break;
case HorizontalAlign::Left: case HorizontalAlign::Left:
default: default:
break; break;
} }
switch (vAlign_) { switch (vAlign_) {
case VerticalAlign::Middle: case VerticalAlign::Middle:
position.y += (refHeight - size.y) * 0.5f; position.y += (refHeight - textSize.y) * 0.5f;
break; break;
case VerticalAlign::Bottom: case VerticalAlign::Bottom:
position.y += refHeight - size.y; position.y += refHeight - textSize.y;
break; break;
case VerticalAlign::Top: case VerticalAlign::Top:
default: default:
break; break;
} }
return position; return position;
} }
/** /**
@ -345,42 +323,43 @@ Vec2 Label::calculateDrawPosition() const {
* @param position * @param position
* @param color * @param color
*/ */
void Label::drawText(RenderBackend &renderer, const Vec2 &position, const Color &color) { void Label::drawText(RenderBackend &renderer, const Vec2 &position,
if (!font_ || text_.empty()) { const Color &color) {
return; if (!font_ || text_.empty()) {
} return;
}
if (multiLine_) { if (multiLine_) {
auto lines = splitLines(); auto lines = splitLines();
float lineHeight = getLineHeight(); float lineHeight = getLineHeight();
Vec2 drawPos = position; Vec2 drawPos = position;
for (const auto &line : lines) { for (const auto &line : lines) {
renderer.drawText(*font_, line, drawPos, color); renderer.drawText(*font_, line, drawPos, color);
drawPos.y += lineHeight; drawPos.y += lineHeight;
}
} else {
renderer.drawText(*font_, text_, position, color);
} }
} else {
renderer.drawText(*font_, text_, position, color);
}
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Rect Label::getBoundingBox() const { Rect Label::boundingBox() const {
if (!font_ || text_.empty()) { if (!font_ || text_.empty()) {
return Rect(); return Rect();
} }
updateCache(); updateCache();
Vec2 size = cachedSize_; Vec2 size = cachedSize_;
if (size.x <= 0.0f || size.y <= 0.0f) { if (size.x <= 0.0f || size.y <= 0.0f) {
return Rect(); return Rect();
} }
Vec2 drawPos = calculateDrawPosition(); Vec2 drawPos = calculateDrawPosition();
return Rect(drawPos.x, drawPos.y, size.x, size.y); return Rect(drawPos.x, drawPos.y, size.x, size.y);
} }
/** /**
@ -388,30 +367,30 @@ Rect Label::getBoundingBox() const {
* @param renderer * @param renderer
*/ */
void Label::onDrawWidget(RenderBackend &renderer) { void Label::onDrawWidget(RenderBackend &renderer) {
if (!font_ || text_.empty()) { if (!font_ || text_.empty()) {
return; return;
} }
Vec2 pos = calculateDrawPosition(); Vec2 pos = calculateDrawPosition();
if (shadowEnabled_) { if (shadowEnabled_) {
Vec2 shadowPos = pos + shadowOffset_; Vec2 shadowPos = pos + shadowOffset_;
drawText(renderer, shadowPos, shadowColor_); drawText(renderer, shadowPos, shadowColor_);
} }
if (outlineEnabled_) { if (outlineEnabled_) {
float w = outlineWidth_; float w = outlineWidth_;
drawText(renderer, pos + Vec2(-w, -w), outlineColor_); drawText(renderer, pos + Vec2(-w, -w), outlineColor_);
drawText(renderer, pos + Vec2(0, -w), outlineColor_); drawText(renderer, pos + Vec2(0, -w), outlineColor_);
drawText(renderer, pos + Vec2(w, -w), outlineColor_); drawText(renderer, pos + Vec2(w, -w), outlineColor_);
drawText(renderer, pos + Vec2(-w, 0), outlineColor_); drawText(renderer, pos + Vec2(-w, 0), outlineColor_);
drawText(renderer, pos + Vec2(w, 0), outlineColor_); drawText(renderer, pos + Vec2(w, 0), outlineColor_);
drawText(renderer, pos + Vec2(-w, w), outlineColor_); drawText(renderer, pos + Vec2(-w, w), outlineColor_);
drawText(renderer, pos + Vec2(0, w), outlineColor_); drawText(renderer, pos + Vec2(0, w), outlineColor_);
drawText(renderer, pos + Vec2(w, w), outlineColor_); drawText(renderer, pos + Vec2(w, w), outlineColor_);
} }
drawText(renderer, pos, textColor_); drawText(renderer, pos, textColor_);
} }
} // namespace extra2d } // namespace extra2d

View File

@ -1,7 +1,7 @@
#include <ui/progress_bar.h>
#include <graphics/render_backend.h>
#include <core/string.h>
#include <cmath> #include <cmath>
#include <core/string.h>
#include <graphics/render_backend.h>
#include <ui/progress_bar.h>
namespace extra2d { namespace extra2d {
@ -9,17 +9,15 @@ namespace extra2d {
* @brief * @brief
*/ */
ProgressBar::ProgressBar() { ProgressBar::ProgressBar() {
setAnchor(0.0f, 0.0f); setAnchor(0.0f, 0.0f);
setSize(200.0f, 20.0f); setSize(200.0f, 20.0f);
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Ptr<ProgressBar> ProgressBar::create() { Ptr<ProgressBar> ProgressBar::create() { return makePtr<ProgressBar>(); }
return makePtr<ProgressBar>();
}
/** /**
* @brief * @brief
@ -29,10 +27,10 @@ Ptr<ProgressBar> ProgressBar::create() {
* @return * @return
*/ */
Ptr<ProgressBar> ProgressBar::create(float min, float max, float value) { Ptr<ProgressBar> ProgressBar::create(float min, float max, float value) {
auto bar = makePtr<ProgressBar>(); auto bar = makePtr<ProgressBar>();
bar->setRange(min, max); bar->setRange(min, max);
bar->setValue(value); bar->setValue(value);
return bar; return bar;
} }
/** /**
@ -41,9 +39,9 @@ Ptr<ProgressBar> ProgressBar::create(float min, float max, float value) {
* @param max * @param max
*/ */
void ProgressBar::setRange(float min, float max) { void ProgressBar::setRange(float min, float max) {
min_ = min; min_ = min;
max_ = max; max_ = max;
setValue(value_); setValue(value_);
} }
/** /**
@ -51,15 +49,15 @@ void ProgressBar::setRange(float min, float max) {
* @param value * @param value
*/ */
void ProgressBar::setValue(float value) { void ProgressBar::setValue(float value) {
value_ = std::clamp(value, min_, max_); value_ = std::clamp(value, min_, max_);
if (!animatedChangeEnabled_) { if (!animatedChangeEnabled_) {
displayValue_ = value_; displayValue_ = value_;
} }
if (delayedDisplayEnabled_) { if (delayedDisplayEnabled_) {
delayTimer_ = delayTime_; delayTimer_ = delayTime_;
} }
} }
/** /**
@ -67,56 +65,49 @@ void ProgressBar::setValue(float value) {
* @return 0.0-1.0 * @return 0.0-1.0
*/ */
float ProgressBar::getPercent() const { float ProgressBar::getPercent() const {
if (max_ <= min_) return 0.0f; if (max_ <= min_)
return (displayValue_ - min_) / (max_ - min_); return 0.0f;
return (displayValue_ - min_) / (max_ - min_);
} }
/** /**
* @brief * @brief
* @param dir * @param dir
*/ */
void ProgressBar::setDirection(Direction dir) { void ProgressBar::setDirection(Direction dir) { direction_ = dir; }
direction_ = dir;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void ProgressBar::setBackgroundColor(const Color &color) { void ProgressBar::setBackgroundColor(const Color &color) { bgColor_ = color; }
bgColor_ = color;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void ProgressBar::setFillColor(const Color &color) { void ProgressBar::setFillColor(const Color &color) { fillColor_ = color; }
fillColor_ = color;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void ProgressBar::setGradientFillEnabled(bool enabled) { void ProgressBar::setGradientFillEnabled(bool enabled) {
gradientEnabled_ = enabled; gradientEnabled_ = enabled;
} }
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void ProgressBar::setFillColorEnd(const Color &color) { void ProgressBar::setFillColorEnd(const Color &color) { fillColorEnd_ = color; }
fillColorEnd_ = color;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void ProgressBar::setSegmentedColorsEnabled(bool enabled) { void ProgressBar::setSegmentedColorsEnabled(bool enabled) {
segmentedColorsEnabled_ = enabled; segmentedColorsEnabled_ = enabled;
} }
/** /**
@ -125,96 +116,78 @@ void ProgressBar::setSegmentedColorsEnabled(bool enabled) {
* @param color * @param color
*/ */
void ProgressBar::addColorSegment(float percentThreshold, const Color &color) { void ProgressBar::addColorSegment(float percentThreshold, const Color &color) {
colorSegments_.push_back({percentThreshold, color}); colorSegments_.push_back({percentThreshold, color});
std::sort(colorSegments_.begin(), colorSegments_.end(), std::sort(colorSegments_.begin(), colorSegments_.end(),
[](const auto &a, const auto &b) { return a.first > b.first; }); [](const auto &a, const auto &b) { return a.first > b.first; });
} }
/** /**
* @brief * @brief
*/ */
void ProgressBar::clearColorSegments() { void ProgressBar::clearColorSegments() { colorSegments_.clear(); }
colorSegments_.clear();
}
/** /**
* @brief * @brief
* @param radius * @param radius
*/ */
void ProgressBar::setCornerRadius(float radius) { void ProgressBar::setCornerRadius(float radius) { cornerRadius_ = radius; }
cornerRadius_ = radius;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void ProgressBar::setRoundedCornersEnabled(bool enabled) { void ProgressBar::setRoundedCornersEnabled(bool enabled) {
roundedCornersEnabled_ = enabled; roundedCornersEnabled_ = enabled;
} }
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void ProgressBar::setBorderEnabled(bool enabled) { void ProgressBar::setBorderEnabled(bool enabled) { borderEnabled_ = enabled; }
borderEnabled_ = enabled;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void ProgressBar::setBorderColor(const Color &color) { void ProgressBar::setBorderColor(const Color &color) { borderColor_ = color; }
borderColor_ = color;
}
/** /**
* @brief * @brief
* @param width * @param width
*/ */
void ProgressBar::setBorderWidth(float width) { void ProgressBar::setBorderWidth(float width) { borderWidth_ = width; }
borderWidth_ = width;
}
/** /**
* @brief * @brief
* @param padding * @param padding
*/ */
void ProgressBar::setPadding(float padding) { void ProgressBar::setPadding(float padding) { padding_ = padding; }
padding_ = padding;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void ProgressBar::setTextEnabled(bool enabled) { void ProgressBar::setTextEnabled(bool enabled) { textEnabled_ = enabled; }
textEnabled_ = enabled;
}
/** /**
* @brief * @brief
* @param font * @param font
*/ */
void ProgressBar::setFont(Ptr<FontAtlas> font) { void ProgressBar::setFont(Ptr<FontAtlas> font) { font_ = font; }
font_ = font;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void ProgressBar::setTextColor(const Color &color) { void ProgressBar::setTextColor(const Color &color) { textColor_ = color; }
textColor_ = color;
}
/** /**
* @brief * @brief
* @param format * @param format
*/ */
void ProgressBar::setTextFormat(const std::string &format) { void ProgressBar::setTextFormat(const std::string &format) {
textFormat_ = format; textFormat_ = format;
} }
/** /**
@ -222,82 +195,72 @@ void ProgressBar::setTextFormat(const std::string &format) {
* @param enabled * @param enabled
*/ */
void ProgressBar::setAnimatedChangeEnabled(bool enabled) { void ProgressBar::setAnimatedChangeEnabled(bool enabled) {
animatedChangeEnabled_ = enabled; animatedChangeEnabled_ = enabled;
if (!enabled) { if (!enabled) {
displayValue_ = value_; displayValue_ = value_;
} }
} }
/** /**
* @brief * @brief
* @param speed * @param speed
*/ */
void ProgressBar::setAnimationSpeed(float speed) { void ProgressBar::setAnimationSpeed(float speed) { animationSpeed_ = speed; }
animationSpeed_ = speed;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void ProgressBar::setDelayedDisplayEnabled(bool enabled) { void ProgressBar::setDelayedDisplayEnabled(bool enabled) {
delayedDisplayEnabled_ = enabled; delayedDisplayEnabled_ = enabled;
} }
/** /**
* @brief * @brief
* @param seconds * @param seconds
*/ */
void ProgressBar::setDelayTime(float seconds) { void ProgressBar::setDelayTime(float seconds) { delayTime_ = seconds; }
delayTime_ = seconds;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void ProgressBar::setDelayedFillColor(const Color &color) { void ProgressBar::setDelayedFillColor(const Color &color) {
delayedFillColor_ = color; delayedFillColor_ = color;
} }
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void ProgressBar::setStripedEnabled(bool enabled) { void ProgressBar::setStripedEnabled(bool enabled) { stripedEnabled_ = enabled; }
stripedEnabled_ = enabled;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void ProgressBar::setStripeColor(const Color &color) { void ProgressBar::setStripeColor(const Color &color) { stripeColor_ = color; }
stripeColor_ = color;
}
/** /**
* @brief * @brief
* @param speed * @param speed
*/ */
void ProgressBar::setStripeSpeed(float speed) { void ProgressBar::setStripeSpeed(float speed) { stripeSpeed_ = speed; }
stripeSpeed_ = speed;
}
/** /**
* @brief * @brief
* @return * @return
*/ */
Color ProgressBar::getCurrentFillColor() const { Color ProgressBar::getCurrentFillColor() const {
if (segmentedColorsEnabled_ && !colorSegments_.empty()) { if (segmentedColorsEnabled_ && !colorSegments_.empty()) {
float percent = getPercent(); float percent = getPercent();
for (const auto &segment : colorSegments_) { for (const auto &segment : colorSegments_) {
if (percent >= segment.first) { if (percent >= segment.first) {
return segment.second; return segment.second;
} }
}
} }
return fillColor_; }
return fillColor_;
} }
/** /**
@ -305,33 +268,33 @@ Color ProgressBar::getCurrentFillColor() const {
* @return * @return
*/ */
std::string ProgressBar::formatText() const { std::string ProgressBar::formatText() const {
std::string result = textFormat_; std::string result = textFormat_;
size_t pos = result.find("{value}"); size_t pos = result.find("{value}");
if (pos != std::string::npos) { if (pos != std::string::npos) {
result.replace(pos, 7, std::to_string(static_cast<int>(displayValue_))); result.replace(pos, 7, std::to_string(static_cast<int>(displayValue_)));
} }
pos = result.find("{max}"); pos = result.find("{max}");
if (pos != std::string::npos) { if (pos != std::string::npos) {
result.replace(pos, 5, std::to_string(static_cast<int>(max_))); result.replace(pos, 5, std::to_string(static_cast<int>(max_)));
} }
pos = result.find("{percent}"); pos = result.find("{percent}");
if (pos != std::string::npos) { if (pos != std::string::npos) {
int percent = static_cast<int>(getPercent() * 100); int percent = static_cast<int>(getPercent() * 100);
result.replace(pos, 9, std::to_string(percent)); result.replace(pos, 9, std::to_string(percent));
} }
return result; return result;
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Rect ProgressBar::getBoundingBox() const { Rect ProgressBar::boundingBox() const {
return Rect(pos().x, pos().y, getSize().width, getSize().height); return Rect(pos().x, pos().y, size().width, size().height);
} }
/** /**
@ -339,38 +302,38 @@ Rect ProgressBar::getBoundingBox() const {
* @param deltaTime * @param deltaTime
*/ */
void ProgressBar::onUpdate(float deltaTime) { void ProgressBar::onUpdate(float deltaTime) {
if (animatedChangeEnabled_ && displayValue_ != value_) { if (animatedChangeEnabled_ && displayValue_ != value_) {
float diff = value_ - displayValue_; float diff = value_ - displayValue_;
float change = animationSpeed_ * deltaTime; float change = animationSpeed_ * deltaTime;
if (std::abs(diff) <= change) { if (std::abs(diff) <= change) {
displayValue_ = value_; displayValue_ = value_;
} else { } else {
displayValue_ += (diff > 0 ? change : -change); displayValue_ += (diff > 0 ? change : -change);
}
} }
}
if (delayedDisplayEnabled_) {
if (delayTimer_ > 0.0f) { if (delayedDisplayEnabled_) {
delayTimer_ -= deltaTime; if (delayTimer_ > 0.0f) {
} else { delayTimer_ -= deltaTime;
float diff = displayValue_ - delayedValue_; } else {
float change = animationSpeed_ * deltaTime; float diff = displayValue_ - delayedValue_;
float change = animationSpeed_ * deltaTime;
if (std::abs(diff) <= change) {
delayedValue_ = displayValue_; if (std::abs(diff) <= change) {
} else { delayedValue_ = displayValue_;
delayedValue_ += (diff > 0 ? change : -change); } else {
} delayedValue_ += (diff > 0 ? change : -change);
} }
} }
}
if (stripedEnabled_) {
stripeOffset_ += stripeSpeed_ * deltaTime; if (stripedEnabled_) {
if (stripeOffset_ > 20.0f) { stripeOffset_ += stripeSpeed_ * deltaTime;
stripeOffset_ -= 20.0f; if (stripeOffset_ > 20.0f) {
} stripeOffset_ -= 20.0f;
} }
}
} }
/** /**
@ -378,104 +341,102 @@ void ProgressBar::onUpdate(float deltaTime) {
* @param renderer * @param renderer
*/ */
void ProgressBar::onDrawWidget(RenderBackend &renderer) { void ProgressBar::onDrawWidget(RenderBackend &renderer) {
Vec2 position = pos(); Vec2 position = pos();
Size size = getSize(); Size widgetSize = size();
float bgX = position.x + padding_; float bgX = position.x + padding_;
float bgY = position.y + padding_; float bgY = position.y + padding_;
float bgW = size.width - padding_ * 2; float bgW = widgetSize.width - padding_ * 2;
float bgH = size.height - padding_ * 2; float bgH = widgetSize.height - padding_ * 2;
Rect bgRect(bgX, bgY, bgW, bgH); Rect bgRect(bgX, bgY, bgW, bgH);
if (roundedCornersEnabled_) { if (roundedCornersEnabled_) {
fillRoundedRect(renderer, bgRect, bgColor_, cornerRadius_); fillRoundedRect(renderer, bgRect, bgColor_, cornerRadius_);
} else { } else {
renderer.fillRect(bgRect, bgColor_); renderer.fillRect(bgRect, bgColor_);
} }
float percent = getPercent(); float percent = getPercent();
float fillX = bgX, fillY = bgY, fillW = bgW, fillH = bgH; float fillX = bgX, fillY = bgY, fillW = bgW, fillH = bgH;
switch (direction_) {
case Direction::LeftToRight:
fillW = bgW * percent;
break;
case Direction::RightToLeft:
fillW = bgW * percent;
fillX = bgX + bgW - fillW;
break;
case Direction::BottomToTop:
fillH = bgH * percent;
fillY = bgY + bgH - fillH;
break;
case Direction::TopToBottom:
fillH = bgH * percent;
break;
}
Rect fillRect(fillX, fillY, fillW, fillH);
if (delayedDisplayEnabled_ && delayedValue_ > displayValue_) {
float delayedPercent = (delayedValue_ - min_) / (max_ - min_);
float delayedX = bgX, delayedY = bgY, delayedW = bgW, delayedH = bgH;
switch (direction_) { switch (direction_) {
case Direction::LeftToRight: case Direction::LeftToRight:
fillW = bgW * percent; delayedW = bgW * delayedPercent;
break; break;
case Direction::RightToLeft: case Direction::RightToLeft:
fillW = bgW * percent; delayedW = bgW * delayedPercent;
fillX = bgX + bgW - fillW; delayedX = bgX + bgW - delayedW;
break; break;
case Direction::BottomToTop: case Direction::BottomToTop:
fillH = bgH * percent; delayedH = bgH * delayedPercent;
fillY = bgY + bgH - fillH; delayedY = bgY + bgH - delayedH;
break; break;
case Direction::TopToBottom: case Direction::TopToBottom:
fillH = bgH * percent; delayedH = bgH * delayedPercent;
break; break;
} }
Rect fillRect(fillX, fillY, fillW, fillH); Rect delayedRect(delayedX, delayedY, delayedW, delayedH);
if (delayedDisplayEnabled_ && delayedValue_ > displayValue_) { if (roundedCornersEnabled_) {
float delayedPercent = (delayedValue_ - min_) / (max_ - min_); fillRoundedRect(renderer, delayedRect, delayedFillColor_, cornerRadius_);
float delayedX = bgX, delayedY = bgY, delayedW = bgW, delayedH = bgH; } else {
renderer.fillRect(delayedRect, delayedFillColor_);
switch (direction_) {
case Direction::LeftToRight:
delayedW = bgW * delayedPercent;
break;
case Direction::RightToLeft:
delayedW = bgW * delayedPercent;
delayedX = bgX + bgW - delayedW;
break;
case Direction::BottomToTop:
delayedH = bgH * delayedPercent;
delayedY = bgY + bgH - delayedH;
break;
case Direction::TopToBottom:
delayedH = bgH * delayedPercent;
break;
}
Rect delayedRect(delayedX, delayedY, delayedW, delayedH);
if (roundedCornersEnabled_) {
fillRoundedRect(renderer, delayedRect, delayedFillColor_, cornerRadius_);
} else {
renderer.fillRect(delayedRect, delayedFillColor_);
}
} }
}
if (fillW > 0 && fillH > 0) {
Color fillColor = getCurrentFillColor(); if (fillW > 0 && fillH > 0) {
Color fillColor = getCurrentFillColor();
if (roundedCornersEnabled_) {
fillRoundedRect(renderer, fillRect, fillColor, cornerRadius_); if (roundedCornersEnabled_) {
} else { fillRoundedRect(renderer, fillRect, fillColor, cornerRadius_);
renderer.fillRect(fillRect, fillColor); } else {
} renderer.fillRect(fillRect, fillColor);
if (stripedEnabled_) {
drawStripes(renderer, fillRect);
}
} }
if (borderEnabled_) { if (stripedEnabled_) {
if (roundedCornersEnabled_) { drawStripes(renderer, fillRect);
drawRoundedRect(renderer, bgRect, borderColor_, cornerRadius_);
} else {
renderer.drawRect(bgRect, borderColor_, borderWidth_);
}
} }
}
if (textEnabled_ && font_) {
std::string text = formatText(); if (borderEnabled_) {
Vec2 textSize = font_->measureText(text); if (roundedCornersEnabled_) {
drawRoundedRect(renderer, bgRect, borderColor_, cornerRadius_);
Vec2 textPos( } else {
position.x + (size.width - textSize.x) * 0.5f, renderer.drawRect(bgRect, borderColor_, borderWidth_);
position.y + (size.height - textSize.y) * 0.5f
);
renderer.drawText(*font_, text, textPos, textColor_);
} }
}
if (textEnabled_ && font_) {
std::string text = formatText();
Vec2 textSize = font_->measureText(text);
Vec2 textPos(position.x + (widgetSize.width - textSize.x) * 0.5f,
position.y + (widgetSize.height - textSize.y) * 0.5f);
renderer.drawText(*font_, text, textPos, textColor_);
}
} }
/** /**
@ -485,8 +446,9 @@ void ProgressBar::onDrawWidget(RenderBackend &renderer) {
* @param color * @param color
* @param radius * @param radius
*/ */
void ProgressBar::drawRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius) { void ProgressBar::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
renderer.drawRect(rect, color, borderWidth_); const Color &color, float radius) {
renderer.drawRect(rect, color, borderWidth_);
} }
/** /**
@ -496,8 +458,9 @@ void ProgressBar::drawRoundedRect(RenderBackend &renderer, const Rect &rect, con
* @param color * @param color
* @param radius * @param radius
*/ */
void ProgressBar::fillRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius) { void ProgressBar::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
renderer.fillRect(rect, color); const Color &color, float radius) {
renderer.fillRect(rect, color);
} }
/** /**
@ -506,31 +469,34 @@ void ProgressBar::fillRoundedRect(RenderBackend &renderer, const Rect &rect, con
* @param rect * @param rect
*/ */
void ProgressBar::drawStripes(RenderBackend &renderer, const Rect &rect) { void ProgressBar::drawStripes(RenderBackend &renderer, const Rect &rect) {
const float stripeWidth = 10.0f; const float stripeWidth = 10.0f;
const float spacing = 20.0f; const float spacing = 20.0f;
float rectRight = rect.origin.x + rect.size.width; float rectRight = rect.origin.x + rect.size.width;
float rectBottom = rect.origin.y + rect.size.height; float rectBottom = rect.origin.y + rect.size.height;
for (float x = rect.origin.x - spacing + stripeOffset_; x < rectRight; x += spacing) { for (float x = rect.origin.x - spacing + stripeOffset_; x < rectRight;
float x1 = x; x += spacing) {
float y1 = rect.origin.y; float x1 = x;
float x2 = x + stripeWidth; float y1 = rect.origin.y;
float y2 = rectBottom; float x2 = x + stripeWidth;
float y2 = rectBottom;
if (x1 < rect.origin.x) x1 = rect.origin.x;
if (x2 > rectRight) x2 = rectRight; if (x1 < rect.origin.x)
x1 = rect.origin.x;
if (x2 > x1) { if (x2 > rectRight)
for (int i = 0; i < static_cast<int>(rect.size.height); i += 4) { x2 = rectRight;
float sy = rect.origin.y + i;
float sx = x1 + i * 0.5f; if (x2 > x1) {
if (sx < x2) { for (int i = 0; i < static_cast<int>(rect.size.height); i += 4) {
Rect stripeRect(sx, sy, std::min(2.0f, x2 - sx), 2.0f); float sy = rect.origin.y + i;
renderer.fillRect(stripeRect, stripeColor_); float sx = x1 + i * 0.5f;
} if (sx < x2) {
} Rect stripeRect(sx, sy, std::min(2.0f, x2 - sx), 2.0f);
renderer.fillRect(stripeRect, stripeColor_);
} }
}
} }
}
} }
} // namespace extra2d } // namespace extra2d

View File

@ -1,6 +1,6 @@
#include <ui/radio_button.h>
#include <graphics/render_backend.h>
#include <core/string.h> #include <core/string.h>
#include <graphics/render_backend.h>
#include <ui/radio_button.h>
namespace extra2d { namespace extra2d {
@ -8,17 +8,15 @@ namespace extra2d {
* @brief * @brief
*/ */
RadioButton::RadioButton() { RadioButton::RadioButton() {
setAnchor(0.0f, 0.0f); setAnchor(0.0f, 0.0f);
setSize(circleSize_, circleSize_); setSize(circleSize_, circleSize_);
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Ptr<RadioButton> RadioButton::create() { Ptr<RadioButton> RadioButton::create() { return makePtr<RadioButton>(); }
return makePtr<RadioButton>();
}
/** /**
* @brief * @brief
@ -26,9 +24,9 @@ Ptr<RadioButton> RadioButton::create() {
* @return * @return
*/ */
Ptr<RadioButton> RadioButton::create(const std::string &label) { Ptr<RadioButton> RadioButton::create(const std::string &label) {
auto rb = makePtr<RadioButton>(); auto rb = makePtr<RadioButton>();
rb->setLabel(label); rb->setLabel(label);
return rb; return rb;
} }
/** /**
@ -36,60 +34,50 @@ Ptr<RadioButton> RadioButton::create(const std::string &label) {
* @param selected * @param selected
*/ */
void RadioButton::setSelected(bool selected) { void RadioButton::setSelected(bool selected) {
if (selected_ != selected) { if (selected_ != selected) {
selected_ = selected; selected_ = selected;
if (onStateChange_) { if (onStateChange_) {
onStateChange_(selected_); onStateChange_(selected_);
}
} }
}
} }
/** /**
* @brief * @brief
* @param label * @param label
*/ */
void RadioButton::setLabel(const std::string &label) { void RadioButton::setLabel(const std::string &label) { label_ = label; }
label_ = label;
}
/** /**
* @brief * @brief
* @param font * @param font
*/ */
void RadioButton::setFont(Ptr<FontAtlas> font) { void RadioButton::setFont(Ptr<FontAtlas> font) { font_ = font; }
font_ = font;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void RadioButton::setTextColor(const Color &color) { void RadioButton::setTextColor(const Color &color) { textColor_ = color; }
textColor_ = color;
}
/** /**
* @brief * @brief
* @param size * @param size
*/ */
void RadioButton::setCircleSize(float size) { void RadioButton::setCircleSize(float size) { circleSize_ = size; }
circleSize_ = size;
}
/** /**
* @brief * @brief
* @param spacing * @param spacing
*/ */
void RadioButton::setSpacing(float spacing) { void RadioButton::setSpacing(float spacing) { spacing_ = spacing; }
spacing_ = spacing;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void RadioButton::setSelectedColor(const Color &color) { void RadioButton::setSelectedColor(const Color &color) {
selectedColor_ = color; selectedColor_ = color;
} }
/** /**
@ -97,47 +85,43 @@ void RadioButton::setSelectedColor(const Color &color) {
* @param color * @param color
*/ */
void RadioButton::setUnselectedColor(const Color &color) { void RadioButton::setUnselectedColor(const Color &color) {
unselectedColor_ = color; unselectedColor_ = color;
} }
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void RadioButton::setDotColor(const Color &color) { void RadioButton::setDotColor(const Color &color) { dotColor_ = color; }
dotColor_ = color;
}
/** /**
* @brief ID * @brief ID
* @param groupId ID * @param groupId ID
*/ */
void RadioButton::setGroupId(int groupId) { void RadioButton::setGroupId(int groupId) { groupId_ = groupId; }
groupId_ = groupId;
}
/** /**
* @brief * @brief
* @param callback * @param callback
*/ */
void RadioButton::setOnStateChange(Function<void(bool)> callback) { void RadioButton::setOnStateChange(Function<void(bool)> callback) {
onStateChange_ = callback; onStateChange_ = callback;
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Rect RadioButton::getBoundingBox() const { Rect RadioButton::boundingBox() const {
Vec2 position = pos(); Vec2 position = pos();
float width = circleSize_; float width = circleSize_;
if (!label_.empty() && font_) { if (!label_.empty() && font_) {
Vec2 textSize = font_->measureText(label_); Vec2 textSize = font_->measureText(label_);
width += spacing_ + textSize.x; width += spacing_ + textSize.x;
} }
return Rect(position.x, position.y, width, circleSize_); return Rect(position.x, position.y, width, circleSize_);
} }
/** /**
@ -145,24 +129,25 @@ Rect RadioButton::getBoundingBox() const {
* @param renderer * @param renderer
*/ */
void RadioButton::onDrawWidget(RenderBackend &renderer) { void RadioButton::onDrawWidget(RenderBackend &renderer) {
Vec2 position = pos(); Vec2 position = pos();
float centerX = position.x + circleSize_ * 0.5f; float centerX = position.x + circleSize_ * 0.5f;
float centerY = position.y + getSize().height * 0.5f; float centerY = position.y + size().height * 0.5f;
float radius = circleSize_ * 0.5f; float radius = circleSize_ * 0.5f;
Color circleColor = selected_ ? selectedColor_ : unselectedColor_; Color circleColor = selected_ ? selectedColor_ : unselectedColor_;
renderer.drawCircle(Vec2(centerX, centerY), radius, circleColor, true); renderer.drawCircle(Vec2(centerX, centerY), radius, circleColor, true);
renderer.drawCircle(Vec2(centerX, centerY), radius, Colors::White, false, 1.0f); renderer.drawCircle(Vec2(centerX, centerY), radius, Colors::White, false,
1.0f);
if (selected_) {
float dotRadius = radius * 0.4f; if (selected_) {
renderer.drawCircle(Vec2(centerX, centerY), dotRadius, dotColor_, true); float dotRadius = radius * 0.4f;
} renderer.drawCircle(Vec2(centerX, centerY), dotRadius, dotColor_, true);
}
if (!label_.empty() && font_) {
Vec2 textPos(position.x + circleSize_ + spacing_, position.y); if (!label_.empty() && font_) {
renderer.drawText(*font_, label_, textPos, textColor_); Vec2 textPos(position.x + circleSize_ + spacing_, position.y);
} renderer.drawText(*font_, label_, textPos, textColor_);
}
} }
/** /**
@ -171,11 +156,11 @@ void RadioButton::onDrawWidget(RenderBackend &renderer) {
* @return * @return
*/ */
bool RadioButton::onMousePress(const MouseEvent &event) { bool RadioButton::onMousePress(const MouseEvent &event) {
if (event.button == MouseButton::Left) { if (event.button == MouseButton::Left) {
pressed_ = true; pressed_ = true;
return true; return true;
} }
return false; return false;
} }
/** /**
@ -184,21 +169,21 @@ bool RadioButton::onMousePress(const MouseEvent &event) {
* @return * @return
*/ */
bool RadioButton::onMouseRelease(const MouseEvent &event) { bool RadioButton::onMouseRelease(const MouseEvent &event) {
if (event.button == MouseButton::Left && pressed_) { if (event.button == MouseButton::Left && pressed_) {
pressed_ = false; pressed_ = false;
Vec2 position = pos(); Vec2 position = pos();
float centerX = position.x + circleSize_ * 0.5f; float centerX = position.x + circleSize_ * 0.5f;
float centerY = position.y + getSize().height * 0.5f; float centerY = position.y + size().height * 0.5f;
float radius = circleSize_ * 0.5f; float radius = circleSize_ * 0.5f;
float dx = event.x - centerX; float dx = event.x - centerX;
float dy = event.y - centerY; float dy = event.y - centerY;
if (dx * dx + dy * dy <= radius * radius) { if (dx * dx + dy * dy <= radius * radius) {
setSelected(true); setSelected(true);
}
return true;
} }
return false; return true;
}
return false;
} }
// ============================================================================ // ============================================================================
@ -210,18 +195,19 @@ bool RadioButton::onMouseRelease(const MouseEvent &event) {
* @param button * @param button
*/ */
void RadioButtonGroup::addButton(RadioButton *button) { void RadioButtonGroup::addButton(RadioButton *button) {
if (button && std::find(buttons_.begin(), buttons_.end(), button) == buttons_.end()) { if (button &&
buttons_.push_back(button); std::find(buttons_.begin(), buttons_.end(), button) == buttons_.end()) {
button->setOnStateChange([this, button](bool selected) { buttons_.push_back(button);
if (selected) { button->setOnStateChange([this, button](bool selected) {
selectButton(button); if (selected) {
} selectButton(button);
}); }
});
if (button->isSelected() && !selectedButton_) {
selectedButton_ = button; if (button->isSelected() && !selectedButton_) {
} selectedButton_ = button;
} }
}
} }
/** /**
@ -229,13 +215,13 @@ void RadioButtonGroup::addButton(RadioButton *button) {
* @param button * @param button
*/ */
void RadioButtonGroup::removeButton(RadioButton *button) { void RadioButtonGroup::removeButton(RadioButton *button) {
auto it = std::find(buttons_.begin(), buttons_.end(), button); auto it = std::find(buttons_.begin(), buttons_.end(), button);
if (it != buttons_.end()) { if (it != buttons_.end()) {
buttons_.erase(it); buttons_.erase(it);
if (selectedButton_ == button) { if (selectedButton_ == button) {
selectedButton_ = nullptr; selectedButton_ = nullptr;
}
} }
}
} }
/** /**
@ -243,28 +229,30 @@ void RadioButtonGroup::removeButton(RadioButton *button) {
* @param button * @param button
*/ */
void RadioButtonGroup::selectButton(RadioButton *button) { void RadioButtonGroup::selectButton(RadioButton *button) {
if (selectedButton_ == button) return; if (selectedButton_ == button)
return;
if (selectedButton_) {
selectedButton_->setSelected(false); if (selectedButton_) {
} selectedButton_->setSelected(false);
}
selectedButton_ = button;
if (button) { selectedButton_ = button;
button->setSelected(true); if (button) {
} button->setSelected(true);
}
if (onSelectionChange_) {
onSelectionChange_(button); if (onSelectionChange_) {
} onSelectionChange_(button);
}
} }
/** /**
* @brief * @brief
* @param callback * @param callback
*/ */
void RadioButtonGroup::setOnSelectionChange(Function<void(RadioButton*)> callback) { void RadioButtonGroup::setOnSelectionChange(
onSelectionChange_ = callback; Function<void(RadioButton *)> callback) {
onSelectionChange_ = callback;
} }
} // namespace extra2d } // namespace extra2d

View File

@ -1,7 +1,7 @@
#include <ui/slider.h>
#include <graphics/render_backend.h>
#include <core/string.h>
#include <cmath> #include <cmath>
#include <core/string.h>
#include <graphics/render_backend.h>
#include <ui/slider.h>
namespace extra2d { namespace extra2d {
@ -9,17 +9,15 @@ namespace extra2d {
* @brief * @brief
*/ */
Slider::Slider() { Slider::Slider() {
setAnchor(0.0f, 0.0f); setAnchor(0.0f, 0.0f);
setSize(200.0f, 20.0f); setSize(200.0f, 20.0f);
} }
/** /**
* @brief * @brief
* @return * @return
*/ */
Ptr<Slider> Slider::create() { Ptr<Slider> Slider::create() { return makePtr<Slider>(); }
return makePtr<Slider>();
}
/** /**
* @brief * @brief
@ -29,10 +27,10 @@ Ptr<Slider> Slider::create() {
* @return * @return
*/ */
Ptr<Slider> Slider::create(float min, float max, float value) { Ptr<Slider> Slider::create(float min, float max, float value) {
auto slider = makePtr<Slider>(); auto slider = makePtr<Slider>();
slider->setRange(min, max); slider->setRange(min, max);
slider->setValue(value); slider->setValue(value);
return slider; return slider;
} }
/** /**
@ -41,9 +39,9 @@ Ptr<Slider> Slider::create(float min, float max, float value) {
* @param max * @param max
*/ */
void Slider::setRange(float min, float max) { void Slider::setRange(float min, float max) {
min_ = min; min_ = min;
max_ = max; max_ = max;
setValue(value_); setValue(value_);
} }
/** /**
@ -51,17 +49,17 @@ void Slider::setRange(float min, float max) {
* @param value * @param value
*/ */
void Slider::setValue(float value) { void Slider::setValue(float value) {
float newValue = std::clamp(value, min_, max_); float newValue = std::clamp(value, min_, max_);
if (step_ > 0.0f) { if (step_ > 0.0f) {
newValue = snapToStep(newValue); newValue = snapToStep(newValue);
} }
if (value_ != newValue) { if (value_ != newValue) {
value_ = newValue; value_ = newValue;
if (onValueChange_) { if (onValueChange_) {
onValueChange_(value_); onValueChange_(value_);
}
} }
}
} }
/** /**
@ -69,66 +67,54 @@ void Slider::setValue(float value) {
* @param step * @param step
*/ */
void Slider::setStep(float step) { void Slider::setStep(float step) {
step_ = step; step_ = step;
if (step_ > 0.0f) { if (step_ > 0.0f) {
setValue(value_); setValue(value_);
} }
} }
/** /**
* @brief * @brief
* @param vertical * @param vertical
*/ */
void Slider::setVertical(bool vertical) { void Slider::setVertical(bool vertical) { vertical_ = vertical; }
vertical_ = vertical;
}
/** /**
* @brief * @brief
* @param size * @param size
*/ */
void Slider::setTrackSize(float size) { void Slider::setTrackSize(float size) { trackSize_ = size; }
trackSize_ = size;
}
/** /**
* @brief * @brief
* @param size * @param size
*/ */
void Slider::setThumbSize(float size) { void Slider::setThumbSize(float size) { thumbSize_ = size; }
thumbSize_ = size;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Slider::setTrackColor(const Color &color) { void Slider::setTrackColor(const Color &color) { trackColor_ = color; }
trackColor_ = color;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Slider::setFillColor(const Color &color) { void Slider::setFillColor(const Color &color) { fillColor_ = color; }
fillColor_ = color;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Slider::setThumbColor(const Color &color) { void Slider::setThumbColor(const Color &color) { thumbColor_ = color; }
thumbColor_ = color;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Slider::setThumbHoverColor(const Color &color) { void Slider::setThumbHoverColor(const Color &color) {
thumbHoverColor_ = color; thumbHoverColor_ = color;
} }
/** /**
@ -136,63 +122,51 @@ void Slider::setThumbHoverColor(const Color &color) {
* @param color * @param color
*/ */
void Slider::setThumbPressedColor(const Color &color) { void Slider::setThumbPressedColor(const Color &color) {
thumbPressedColor_ = color; thumbPressedColor_ = color;
} }
/** /**
* @brief * @brief
* @param show * @param show
*/ */
void Slider::setShowThumb(bool show) { void Slider::setShowThumb(bool show) { showThumb_ = show; }
showThumb_ = show;
}
/** /**
* @brief * @brief
* @param show * @param show
*/ */
void Slider::setShowFill(bool show) { void Slider::setShowFill(bool show) { showFill_ = show; }
showFill_ = show;
}
/** /**
* @brief * @brief
* @param enabled * @param enabled
*/ */
void Slider::setTextEnabled(bool enabled) { void Slider::setTextEnabled(bool enabled) { textEnabled_ = enabled; }
textEnabled_ = enabled;
}
/** /**
* @brief * @brief
* @param font * @param font
*/ */
void Slider::setFont(Ptr<FontAtlas> font) { void Slider::setFont(Ptr<FontAtlas> font) { font_ = font; }
font_ = font;
}
/** /**
* @brief * @brief
* @param color * @param color
*/ */
void Slider::setTextColor(const Color &color) { void Slider::setTextColor(const Color &color) { textColor_ = color; }
textColor_ = color;
}
/** /**
* @brief * @brief
* @param format * @param format
*/ */
void Slider::setTextFormat(const std::string &format) { void Slider::setTextFormat(const std::string &format) { textFormat_ = format; }
textFormat_ = format;
}
/** /**
* @brief * @brief
* @param callback * @param callback
*/ */
void Slider::setOnValueChange(Function<void(float)> callback) { void Slider::setOnValueChange(Function<void(float)> callback) {
onValueChange_ = callback; onValueChange_ = callback;
} }
/** /**
@ -200,23 +174,21 @@ void Slider::setOnValueChange(Function<void(float)> callback) {
* @param callback * @param callback
*/ */
void Slider::setOnDragStart(Function<void()> callback) { void Slider::setOnDragStart(Function<void()> callback) {
onDragStart_ = callback; onDragStart_ = callback;
} }
/** /**
* @brief * @brief
* @param callback * @param callback
*/ */
void Slider::setOnDragEnd(Function<void()> callback) { void Slider::setOnDragEnd(Function<void()> callback) { onDragEnd_ = callback; }
onDragEnd_ = callback;
}
/** /**
* @brief * @brief
* @return * @return
*/ */
Rect Slider::getBoundingBox() const { Rect Slider::boundingBox() const {
return Rect(pos().x, pos().y, getSize().width, getSize().height); return Rect(pos().x, pos().y, size().width, size().height);
} }
/** /**
@ -225,16 +197,16 @@ Rect Slider::getBoundingBox() const {
* @return * @return
*/ */
float Slider::valueToPosition(float value) const { float Slider::valueToPosition(float value) const {
Vec2 position = pos(); Vec2 position = pos();
Size size = getSize(); Size sz = size();
float percent = (value - min_) / (max_ - min_); float percent = (value - min_) / (max_ - min_);
if (vertical_) { if (vertical_) {
return position.y + size.height - percent * size.height; return position.y + sz.height - percent * sz.height;
} else { } else {
return position.x + percent * size.width; return position.x + percent * sz.width;
} }
} }
/** /**
@ -243,18 +215,18 @@ float Slider::valueToPosition(float value) const {
* @return * @return
*/ */
float Slider::positionToValue(float position) const { float Slider::positionToValue(float position) const {
Vec2 widgetPos = pos(); Vec2 widgetPos = pos();
Size size = getSize(); Size widgetSize = size();
float percent; float percent;
if (vertical_) { if (vertical_) {
percent = (widgetPos.y + size.height - position) / size.height; percent = (widgetPos.y + widgetSize.height - position) / widgetSize.height;
} else { } else {
percent = (position - widgetPos.x) / size.width; percent = (position - widgetPos.x) / widgetSize.width;
} }
percent = std::clamp(percent, 0.0f, 1.0f); percent = std::clamp(percent, 0.0f, 1.0f);
return min_ + percent * (max_ - min_); return min_ + percent * (max_ - min_);
} }
/** /**
@ -262,26 +234,19 @@ float Slider::positionToValue(float position) const {
* @return * @return
*/ */
Rect Slider::getThumbRect() const { Rect Slider::getThumbRect() const {
Vec2 position = pos(); Vec2 widgetPos = pos();
Size size = getSize(); Size widgetSize = size();
float thumbPos = valueToPosition(value_); float thumbPos = valueToPosition(value_);
if (vertical_) { if (vertical_) {
return Rect( return Rect(widgetPos.x + (widgetSize.width - thumbSize_) * 0.5f,
position.x + (size.width - thumbSize_) * 0.5f, thumbPos - thumbSize_ * 0.5f, thumbSize_, thumbSize_);
thumbPos - thumbSize_ * 0.5f, } else {
thumbSize_, return Rect(thumbPos - thumbSize_ * 0.5f,
thumbSize_ widgetPos.y + (widgetSize.height - thumbSize_) * 0.5f,
); thumbSize_, thumbSize_);
} else { }
return Rect(
thumbPos - thumbSize_ * 0.5f,
position.y + (size.height - thumbSize_) * 0.5f,
thumbSize_,
thumbSize_
);
}
} }
/** /**
@ -289,24 +254,17 @@ Rect Slider::getThumbRect() const {
* @return * @return
*/ */
Rect Slider::getTrackRect() const { Rect Slider::getTrackRect() const {
Vec2 position = pos(); Vec2 widgetPos = pos();
Size size = getSize(); Size widgetSize = size();
if (vertical_) { if (vertical_) {
return Rect( return Rect(widgetPos.x + (widgetSize.width - trackSize_) * 0.5f,
position.x + (size.width - trackSize_) * 0.5f, widgetPos.y, trackSize_, widgetSize.height);
position.y, } else {
trackSize_, return Rect(widgetPos.x,
size.height widgetPos.y + (widgetSize.height - trackSize_) * 0.5f,
); widgetSize.width, trackSize_);
} else { }
return Rect(
position.x,
position.y + (size.height - trackSize_) * 0.5f,
size.width,
trackSize_
);
}
} }
/** /**
@ -314,23 +272,24 @@ Rect Slider::getTrackRect() const {
* @return * @return
*/ */
std::string Slider::formatText() const { std::string Slider::formatText() const {
std::string result = textFormat_; std::string result = textFormat_;
size_t pos = result.find("{value}"); size_t pos = result.find("{value}");
if (pos != std::string::npos) { if (pos != std::string::npos) {
result.replace(pos, 7, std::to_string(static_cast<int>(value_))); result.replace(pos, 7, std::to_string(static_cast<int>(value_)));
}
pos = result.find("{value:");
if (pos != std::string::npos) {
size_t endPos = result.find("}", pos);
if (endPos != std::string::npos) {
std::string format = result.substr(pos + 7, endPos - pos - 8);
result.replace(pos, endPos - pos + 1,
std::to_string(static_cast<int>(value_)));
} }
}
pos = result.find("{value:");
if (pos != std::string::npos) { return result;
size_t endPos = result.find("}", pos);
if (endPos != std::string::npos) {
std::string format = result.substr(pos + 7, endPos - pos - 8);
result.replace(pos, endPos - pos + 1, std::to_string(static_cast<int>(value_)));
}
}
return result;
} }
/** /**
@ -339,8 +298,8 @@ std::string Slider::formatText() const {
* @return * @return
*/ */
float Slider::snapToStep(float value) const { float Slider::snapToStep(float value) const {
float steps = std::round((value - min_) / step_); float steps = std::round((value - min_) / step_);
return min_ + steps * step_; return min_ + steps * step_;
} }
/** /**
@ -348,55 +307,53 @@ float Slider::snapToStep(float value) const {
* @param renderer * @param renderer
*/ */
void Slider::onDrawWidget(RenderBackend &renderer) { void Slider::onDrawWidget(RenderBackend &renderer) {
Rect trackRect = getTrackRect(); Rect trackRect = getTrackRect();
renderer.fillRect(trackRect, trackColor_); renderer.fillRect(trackRect, trackColor_);
if (showFill_) { if (showFill_) {
float percent = (value_ - min_) / (max_ - min_); float percent = (value_ - min_) / (max_ - min_);
float fillX = trackRect.origin.x; float fillX = trackRect.origin.x;
float fillY = trackRect.origin.y; float fillY = trackRect.origin.y;
float fillW = trackRect.size.width; float fillW = trackRect.size.width;
float fillH = trackRect.size.height; float fillH = trackRect.size.height;
if (vertical_) { if (vertical_) {
fillH = trackRect.size.height * percent; fillH = trackRect.size.height * percent;
fillY = trackRect.origin.y + trackRect.size.height - fillH; fillY = trackRect.origin.y + trackRect.size.height - fillH;
} else { } else {
fillW = trackRect.size.width * percent; fillW = trackRect.size.width * percent;
}
Rect fillRect(fillX, fillY, fillW, fillH);
renderer.fillRect(fillRect, fillColor_);
} }
if (showThumb_) { Rect fillRect(fillX, fillY, fillW, fillH);
Rect thumbRect = getThumbRect(); renderer.fillRect(fillRect, fillColor_);
Color thumbColor = thumbColor_; }
if (dragging_) { if (showThumb_) {
thumbColor = thumbPressedColor_; Rect thumbRect = getThumbRect();
} else if (hovered_) { Color thumbColor = thumbColor_;
thumbColor = thumbHoverColor_;
} if (dragging_) {
thumbColor = thumbPressedColor_;
renderer.fillRect(thumbRect, thumbColor); } else if (hovered_) {
renderer.drawRect(thumbRect, Colors::White, 1.0f); thumbColor = thumbHoverColor_;
}
if (textEnabled_ && font_) {
std::string text = formatText();
Vec2 textSize = font_->measureText(text);
Vec2 position = pos();
Size size = getSize();
Vec2 textPos(
position.x + size.width + 10.0f,
position.y + (size.height - textSize.y) * 0.5f
);
renderer.drawText(*font_, text, textPos, textColor_);
} }
renderer.fillRect(thumbRect, thumbColor);
renderer.drawRect(thumbRect, Colors::White, 1.0f);
}
if (textEnabled_ && font_) {
std::string text = formatText();
Vec2 textSize = font_->measureText(text);
Vec2 widgetPos = pos();
Size widgetSize = size();
Vec2 textPos(widgetPos.x + widgetSize.width + 10.0f,
widgetPos.y + (widgetSize.height - textSize.y) * 0.5f);
renderer.drawText(*font_, text, textPos, textColor_);
}
} }
/** /**
@ -405,29 +362,29 @@ void Slider::onDrawWidget(RenderBackend &renderer) {
* @return * @return
*/ */
bool Slider::onMousePress(const MouseEvent &event) { bool Slider::onMousePress(const MouseEvent &event) {
if (event.button == MouseButton::Left) { if (event.button == MouseButton::Left) {
Rect thumbRect = getThumbRect(); Rect thumbRect = getThumbRect();
if (thumbRect.containsPoint(Point(event.x, event.y))) { if (thumbRect.containsPoint(Point(event.x, event.y))) {
dragging_ = true; dragging_ = true;
if (onDragStart_) { if (onDragStart_) {
onDragStart_(); onDragStart_();
} }
return true; return true;
}
Rect trackRect = getTrackRect();
if (trackRect.containsPoint(Point(event.x, event.y))) {
float newValue = positionToValue(vertical_ ? event.y : event.x);
setValue(newValue);
dragging_ = true;
if (onDragStart_) {
onDragStart_();
}
return true;
}
} }
return false;
Rect trackRect = getTrackRect();
if (trackRect.containsPoint(Point(event.x, event.y))) {
float newValue = positionToValue(vertical_ ? event.y : event.x);
setValue(newValue);
dragging_ = true;
if (onDragStart_) {
onDragStart_();
}
return true;
}
}
return false;
} }
/** /**
@ -436,14 +393,14 @@ bool Slider::onMousePress(const MouseEvent &event) {
* @return * @return
*/ */
bool Slider::onMouseRelease(const MouseEvent &event) { bool Slider::onMouseRelease(const MouseEvent &event) {
if (event.button == MouseButton::Left && dragging_) { if (event.button == MouseButton::Left && dragging_) {
dragging_ = false; dragging_ = false;
if (onDragEnd_) { if (onDragEnd_) {
onDragEnd_(); onDragEnd_();
}
return true;
} }
return false; return true;
}
return false;
} }
/** /**
@ -452,31 +409,27 @@ bool Slider::onMouseRelease(const MouseEvent &event) {
* @return * @return
*/ */
bool Slider::onMouseMove(const MouseEvent &event) { bool Slider::onMouseMove(const MouseEvent &event) {
if (dragging_) { if (dragging_) {
float newValue = positionToValue(vertical_ ? event.y : event.x); float newValue = positionToValue(vertical_ ? event.y : event.x);
setValue(newValue); setValue(newValue);
return true; return true;
} }
Rect thumbRect = getThumbRect(); Rect thumbRect = getThumbRect();
bool wasHovered = hovered_; bool wasHovered = hovered_;
hovered_ = thumbRect.containsPoint(Point(event.x, event.y)); hovered_ = thumbRect.containsPoint(Point(event.x, event.y));
return hovered_ != wasHovered; return hovered_ != wasHovered;
} }
/** /**
* @brief * @brief
*/ */
void Slider::onMouseEnter() { void Slider::onMouseEnter() { hovered_ = true; }
hovered_ = true;
}
/** /**
* @brief * @brief
*/ */
void Slider::onMouseLeave() { void Slider::onMouseLeave() { hovered_ = false; }
hovered_ = false;
}
} // namespace extra2d } // namespace extra2d

View File

@ -1,10 +1,9 @@
#include <core/string.h>
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <core/string.h>
#include <graphics/render_backend.h> #include <graphics/render_backend.h>
#include <ui/text.h> #include <ui/text.h>
namespace extra2d { namespace extra2d {
/** /**
@ -114,7 +113,7 @@ void Text::updateCache() const {
Vec2 Text::calculateDrawPosition() const { Vec2 Text::calculateDrawPosition() const {
Vec2 position = pos(); Vec2 position = pos();
Vec2 textSize = getTextSize(); Vec2 textSize = getTextSize();
Size widgetSize = getSize(); Size widgetSize = size();
Vec2 anchorPt = anchor(); Vec2 anchorPt = anchor();
float refWidth = widgetSize.empty() ? textSize.x : widgetSize.width; float refWidth = widgetSize.empty() ? textSize.x : widgetSize.width;
@ -216,7 +215,7 @@ Ptr<Text> Text::createFormat(Ptr<FontAtlas> font, const char *fmt, ...) {
* @brief * @brief
* @return * @return
*/ */
Rect Text::getBoundingBox() const { Rect Text::boundingBox() const {
if (!font_ || text_.empty()) { if (!font_ || text_.empty()) {
return Rect(); return Rect();
} }

View File

@ -3,9 +3,7 @@
namespace extra2d { namespace extra2d {
Widget::Widget() { Widget::Widget() { setSpatialIndexed(false); }
setSpatialIndexed(false);
}
void Widget::setSize(const Size &size) { void Widget::setSize(const Size &size) {
size_ = size; size_ = size;
@ -16,7 +14,7 @@ void Widget::setSize(float width, float height) {
setSize(Size(width, height)); setSize(Size(width, height));
} }
Rect Widget::getBoundingBox() const { Rect Widget::boundingBox() const {
if (size_.empty()) { if (size_.empty()) {
return Rect(); return Rect();
} }
@ -40,8 +38,6 @@ Rect Widget::getBoundingBox() const {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 重写 onDraw // 重写 onDraw
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void Widget::onDraw(RenderBackend &renderer) { void Widget::onDraw(RenderBackend &renderer) { onDrawWidget(renderer); }
onDrawWidget(renderer);
}
} // namespace extra2d } // namespace extra2d