refactor: 移除空间索引系统和UI组件
移除不再使用的空间索引系统(包括四叉树、空间哈希和空间管理器)以及相关的UI组件(如按钮、复选框、滑动条等)。这些功能已被更现代的解决方案取代,移除它们可以简化代码库并减少维护负担。 同时更新了示例代码,移除了对已删除组件的依赖,改为使用基本的形状节点展示功能。
This commit is contained in:
parent
313a56bf72
commit
55c66e5038
|
|
@ -36,16 +36,6 @@
|
||||||
#include <extra2d/scene/transition_flip_scene.h>
|
#include <extra2d/scene/transition_flip_scene.h>
|
||||||
#include <extra2d/scene/transition_box_scene.h>
|
#include <extra2d/scene/transition_box_scene.h>
|
||||||
|
|
||||||
// UI
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
#include <extra2d/ui/button.h>
|
|
||||||
#include <extra2d/ui/text.h>
|
|
||||||
#include <extra2d/ui/label.h>
|
|
||||||
#include <extra2d/ui/progress_bar.h>
|
|
||||||
#include <extra2d/ui/check_box.h>
|
|
||||||
#include <extra2d/ui/radio_button.h>
|
|
||||||
#include <extra2d/ui/slider.h>
|
|
||||||
|
|
||||||
// Event
|
// Event
|
||||||
#include <extra2d/event/event.h>
|
#include <extra2d/event/event.h>
|
||||||
#include <extra2d/event/event_queue.h>
|
#include <extra2d/event/event_queue.h>
|
||||||
|
|
@ -65,12 +55,6 @@
|
||||||
#include <extra2d/utils/data.h>
|
#include <extra2d/utils/data.h>
|
||||||
#include <extra2d/utils/random.h>
|
#include <extra2d/utils/random.h>
|
||||||
|
|
||||||
// Spatial
|
|
||||||
#include <extra2d/spatial/spatial_index.h>
|
|
||||||
#include <extra2d/spatial/quadtree.h>
|
|
||||||
#include <extra2d/spatial/spatial_hash.h>
|
|
||||||
#include <extra2d/spatial/spatial_manager.h>
|
|
||||||
|
|
||||||
// Application
|
// Application
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,17 +143,10 @@ public:
|
||||||
virtual void onDetachFromScene();
|
virtual void onDetachFromScene();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 边界框(用于空间索引)
|
// 边界框
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
virtual Rect getBoundingBox() const;
|
virtual Rect getBoundingBox() const;
|
||||||
|
|
||||||
// 是否需要参与空间索引(默认 true)
|
|
||||||
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
|
|
||||||
bool isSpatialIndexed() const { return spatialIndexed_; }
|
|
||||||
|
|
||||||
// 更新空间索引(手动调用,通常在边界框变化后)
|
|
||||||
void updateSpatialIndex();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 事件系统
|
// 事件系统
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -218,10 +211,7 @@ private:
|
||||||
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
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
|
|
@ -236,17 +226,15 @@ private:
|
||||||
bool flipX_ = false; // 1 byte
|
bool flipX_ = false; // 1 byte
|
||||||
bool flipY_ = false; // 1 byte
|
bool flipY_ = false; // 1 byte
|
||||||
|
|
||||||
// 11. 场景指针
|
// 13. 场景指针
|
||||||
Scene *scene_ = nullptr; // 8 bytes
|
Scene *scene_ = nullptr; // 8 bytes
|
||||||
|
|
||||||
// 12. 布尔标志(打包在一起)
|
// 14. 布尔标志(打包在一起)
|
||||||
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
|
|
||||||
// 填充 2 bytes 到 8 字节对齐
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/graphics/camera.h>
|
#include <extra2d/graphics/camera.h>
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/scene/node.h>
|
||||||
#include <extra2d/spatial/spatial_manager.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -61,28 +60,6 @@ public:
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||||
int parentZOrder = 0) override;
|
int parentZOrder = 0) override;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 空间索引系统
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
SpatialManager &getSpatialManager() { return spatialManager_; }
|
|
||||||
const SpatialManager &getSpatialManager() const { return spatialManager_; }
|
|
||||||
|
|
||||||
// 启用/禁用空间索引
|
|
||||||
void setSpatialIndexingEnabled(bool enabled) {
|
|
||||||
spatialIndexingEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
|
|
||||||
|
|
||||||
// 节点空间索引管理(内部使用)
|
|
||||||
void updateNodeInSpatialIndex(Node *node, const Rect &oldBounds,
|
|
||||||
const Rect &newBounds);
|
|
||||||
void removeNodeFromSpatialIndex(Node *node);
|
|
||||||
|
|
||||||
// 碰撞检测查询
|
|
||||||
std::vector<Node *> queryNodesInArea(const Rect &area) const;
|
|
||||||
std::vector<Node *> queryNodesAtPoint(const Vec2 &point) const;
|
|
||||||
std::vector<std::pair<Node *, Node *>> queryCollisions() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 静态创建方法
|
// 静态创建方法
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -107,10 +84,6 @@ private:
|
||||||
Ptr<Camera> defaultCamera_;
|
Ptr<Camera> defaultCamera_;
|
||||||
|
|
||||||
bool paused_ = false;
|
bool paused_ = false;
|
||||||
|
|
||||||
// 空间索引系统
|
|
||||||
SpatialManager spatialManager_;
|
|
||||||
bool spatialIndexingEnabled_ = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <extra2d/spatial/spatial_index.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class QuadTree : public ISpatialIndex {
|
|
||||||
public:
|
|
||||||
static constexpr int MAX_OBJECTS = 10;
|
|
||||||
static constexpr int MAX_LEVELS = 5;
|
|
||||||
|
|
||||||
struct QuadTreeNode {
|
|
||||||
Rect bounds;
|
|
||||||
int level;
|
|
||||||
std::vector<std::pair<Node *, Rect>> objects;
|
|
||||||
std::array<std::unique_ptr<QuadTreeNode>, 4> children;
|
|
||||||
|
|
||||||
QuadTreeNode(const Rect &bounds, int level);
|
|
||||||
bool contains(const Rect &rect) const;
|
|
||||||
bool intersects(const Rect &rect) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit QuadTree(const Rect &worldBounds);
|
|
||||||
~QuadTree() override = default;
|
|
||||||
|
|
||||||
void insert(Node *node, const Rect &bounds) override;
|
|
||||||
void remove(Node *node) override;
|
|
||||||
void update(Node *node, const Rect &newBounds) override;
|
|
||||||
|
|
||||||
std::vector<Node *> query(const Rect &area) const override;
|
|
||||||
std::vector<Node *> query(const Vec2 &point) const override;
|
|
||||||
std::vector<std::pair<Node *, Node *>> queryCollisions() const override;
|
|
||||||
|
|
||||||
void clear() override;
|
|
||||||
size_t size() const override;
|
|
||||||
bool empty() const override;
|
|
||||||
|
|
||||||
void rebuild() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void split(QuadTreeNode *node);
|
|
||||||
void insertIntoNode(QuadTreeNode *node, Node *object, const Rect &bounds);
|
|
||||||
void queryNode(const QuadTreeNode *node, const Rect &area,
|
|
||||||
std::vector<Node *> &results) const;
|
|
||||||
void queryNode(const QuadTreeNode *node, const Vec2 &point,
|
|
||||||
std::vector<Node *> &results) const;
|
|
||||||
void
|
|
||||||
collectCollisions(const QuadTreeNode *node,
|
|
||||||
std::vector<std::pair<Node *, Node *>> &collisions) const;
|
|
||||||
bool removeFromNode(QuadTreeNode *node, Node *object);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用扫描线算法检测节点内对象的碰撞
|
|
||||||
* @param objects 对象列表
|
|
||||||
* @param collisions 输出碰撞对
|
|
||||||
*/
|
|
||||||
void detectCollisionsInNode(
|
|
||||||
const std::vector<std::pair<Node *, Rect>> &objects,
|
|
||||||
std::vector<std::pair<Node *, Node *>> &collisions) const;
|
|
||||||
|
|
||||||
std::unique_ptr<QuadTreeNode> root_;
|
|
||||||
Rect worldBounds_;
|
|
||||||
size_t objectCount_ = 0;
|
|
||||||
|
|
||||||
// 碰撞检测用的临时缓冲区,避免重复分配
|
|
||||||
mutable std::vector<std::pair<Node *, Rect>> collisionBuffer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/spatial/spatial_index.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 空间哈希实现 - 优化内存布局版本
|
|
||||||
* 使用连续内存存储单元格内容,减少内存碎片
|
|
||||||
*/
|
|
||||||
class SpatialHash : public ISpatialIndex {
|
|
||||||
public:
|
|
||||||
using CellKey = std::pair<int64_t, int64_t>;
|
|
||||||
|
|
||||||
struct CellKeyHash {
|
|
||||||
size_t operator()(const CellKey &key) const {
|
|
||||||
return std::hash<int64_t>()(key.first) ^
|
|
||||||
(std::hash<int64_t>()(key.second) << 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit SpatialHash(float cellSize = 64.0f);
|
|
||||||
~SpatialHash() override = default;
|
|
||||||
|
|
||||||
void insert(Node *node, const Rect &bounds) override;
|
|
||||||
void remove(Node *node) override;
|
|
||||||
void update(Node *node, const Rect &newBounds) override;
|
|
||||||
|
|
||||||
std::vector<Node *> query(const Rect &area) const override;
|
|
||||||
std::vector<Node *> query(const Vec2 &point) const override;
|
|
||||||
std::vector<std::pair<Node *, Node *>> queryCollisions() const override;
|
|
||||||
|
|
||||||
void clear() override;
|
|
||||||
size_t size() const override;
|
|
||||||
bool empty() const override;
|
|
||||||
|
|
||||||
void rebuild() override;
|
|
||||||
|
|
||||||
void setCellSize(float cellSize);
|
|
||||||
float getCellSize() const { return cellSize_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief 单元格数据 - 使用vector代替unordered_set减少内存开销
|
|
||||||
*/
|
|
||||||
struct Cell {
|
|
||||||
std::vector<Node *> objects;
|
|
||||||
|
|
||||||
void insert(Node *node);
|
|
||||||
void remove(Node *node);
|
|
||||||
bool contains(Node *node) const;
|
|
||||||
void clear() { objects.clear(); }
|
|
||||||
size_t size() const { return objects.size(); }
|
|
||||||
bool empty() const { return objects.empty(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
CellKey getCellKey(float x, float y) const;
|
|
||||||
void getCellsForRect(const Rect &rect, std::vector<CellKey> &cells) const;
|
|
||||||
void insertIntoCells(Node *node, const Rect &bounds);
|
|
||||||
void removeFromCells(Node *node, const Rect &bounds);
|
|
||||||
|
|
||||||
float cellSize_;
|
|
||||||
// 使用vector存储对象列表代替unordered_set,内存更紧凑
|
|
||||||
std::unordered_map<CellKey, Cell, CellKeyHash> grid_;
|
|
||||||
std::unordered_map<Node *, Rect> objectBounds_;
|
|
||||||
size_t objectCount_ = 0;
|
|
||||||
|
|
||||||
// 查询用的临时缓冲区,避免重复分配
|
|
||||||
mutable std::vector<Node *> queryBuffer_;
|
|
||||||
mutable std::vector<std::pair<Node *, Node *>> collisionBuffer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class Node;
|
|
||||||
|
|
||||||
enum class SpatialStrategy { Auto, QuadTree, SpatialHash };
|
|
||||||
|
|
||||||
struct SpatialQueryResult {
|
|
||||||
Node *node;
|
|
||||||
Rect bounds;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ISpatialIndex {
|
|
||||||
public:
|
|
||||||
virtual ~ISpatialIndex() = default;
|
|
||||||
|
|
||||||
virtual void insert(Node *node, const Rect &bounds) = 0;
|
|
||||||
virtual void remove(Node *node) = 0;
|
|
||||||
virtual void update(Node *node, const Rect &newBounds) = 0;
|
|
||||||
|
|
||||||
virtual std::vector<Node *> query(const Rect &area) const = 0;
|
|
||||||
virtual std::vector<Node *> query(const Vec2 &point) const = 0;
|
|
||||||
virtual std::vector<std::pair<Node *, Node *>> queryCollisions() const = 0;
|
|
||||||
|
|
||||||
virtual void clear() = 0;
|
|
||||||
virtual size_t size() const = 0;
|
|
||||||
virtual bool empty() const = 0;
|
|
||||||
|
|
||||||
virtual void rebuild() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
using SpatialIndexPtr = std::unique_ptr<ISpatialIndex>;
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/spatial/spatial_index.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class SpatialManager {
|
|
||||||
public:
|
|
||||||
using QueryCallback = std::function<bool(Node *)>;
|
|
||||||
|
|
||||||
SpatialManager();
|
|
||||||
explicit SpatialManager(const Rect &worldBounds);
|
|
||||||
~SpatialManager() = default;
|
|
||||||
|
|
||||||
void setStrategy(SpatialStrategy strategy);
|
|
||||||
void setAutoThresholds(size_t quadTreeThreshold, size_t hashThreshold);
|
|
||||||
|
|
||||||
void setWorldBounds(const Rect &bounds);
|
|
||||||
Rect getWorldBounds() const { return worldBounds_; }
|
|
||||||
|
|
||||||
void insert(Node *node, const Rect &bounds);
|
|
||||||
void remove(Node *node);
|
|
||||||
void update(Node *node, const Rect &newBounds);
|
|
||||||
|
|
||||||
std::vector<Node *> query(const Rect &area) const;
|
|
||||||
std::vector<Node *> query(const Vec2 &point) const;
|
|
||||||
std::vector<std::pair<Node *, Node *>> queryCollisions() const;
|
|
||||||
|
|
||||||
void query(const Rect &area, const QueryCallback &callback) const;
|
|
||||||
void query(const Vec2 &point, const QueryCallback &callback) const;
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
size_t size() const;
|
|
||||||
bool empty() const;
|
|
||||||
|
|
||||||
void rebuild();
|
|
||||||
void optimize();
|
|
||||||
|
|
||||||
SpatialStrategy getCurrentStrategy() const;
|
|
||||||
const char *getStrategyName() const;
|
|
||||||
|
|
||||||
static std::unique_ptr<ISpatialIndex> createIndex(SpatialStrategy strategy,
|
|
||||||
const Rect &bounds);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void selectOptimalStrategy();
|
|
||||||
|
|
||||||
SpatialStrategy currentStrategy_ = SpatialStrategy::Auto;
|
|
||||||
SpatialStrategy activeStrategy_ = SpatialStrategy::QuadTree;
|
|
||||||
std::unique_ptr<ISpatialIndex> index_;
|
|
||||||
Rect worldBounds_;
|
|
||||||
|
|
||||||
size_t quadTreeThreshold_ = 1000;
|
|
||||||
size_t hashThreshold_ = 5000;
|
|
||||||
|
|
||||||
mutable size_t queryCount_ = 0;
|
|
||||||
mutable size_t totalQueryTime_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,261 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/graphics/texture.h>
|
|
||||||
#include <extra2d/platform/window.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// 图片缩放模式
|
|
||||||
enum class ImageScaleMode {
|
|
||||||
Original, // 使用原图大小
|
|
||||||
Stretch, // 拉伸填充
|
|
||||||
ScaleFit, // 等比缩放,保持完整显示
|
|
||||||
ScaleFill // 等比缩放,填充整个区域(可能裁剪)
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 基础按钮类
|
|
||||||
// ============================================================================
|
|
||||||
class Button : public Widget {
|
|
||||||
public:
|
|
||||||
Button();
|
|
||||||
explicit Button(const std::string &text);
|
|
||||||
~Button() override = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static Ptr<Button> create();
|
|
||||||
static Ptr<Button> create(const std::string &text);
|
|
||||||
static Ptr<Button> create(const std::string &text, Ptr<FontAtlas> font);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文字内容
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setText(const std::string &text);
|
|
||||||
const std::string &getText() const { return text_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 字体
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFont(Ptr<FontAtlas> font);
|
|
||||||
Ptr<FontAtlas> getFont() const { return font_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 内边距
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setPadding(const Vec2 &padding);
|
|
||||||
void setPadding(float x, float y);
|
|
||||||
Vec2 getPadding() const { return padding_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文字颜色
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setTextColor(const Color &color);
|
|
||||||
Color getTextColor() const { return textColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 纯色背景设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBackgroundColor(const Color &normal, const Color &hover,
|
|
||||||
const Color &pressed);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 边框设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBorder(const Color &color, float width);
|
|
||||||
float getBorderWidth() const { return borderWidth_; }
|
|
||||||
Color getBorderColor() const { return borderColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 图片背景设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
|
|
||||||
Ptr<Texture> pressed = nullptr);
|
|
||||||
void setBackgroundImage(Ptr<Texture> texture, const Rect &rect);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 为切换按钮的两种状态设置图片背景
|
|
||||||
* @param offNormal 关闭状态的普通图片
|
|
||||||
* @param onNormal 开启状态的普通图片
|
|
||||||
* @param offHover 关闭状态的悬停图片(可选)
|
|
||||||
* @param onHover 开启状态的悬停图片(可选)
|
|
||||||
* @param offPressed 关闭状态的按下图片(可选)
|
|
||||||
* @param onPressed 开启状态的按下图片(可选)
|
|
||||||
*/
|
|
||||||
void setStateBackgroundImage(Ptr<Texture> offNormal, Ptr<Texture> onNormal,
|
|
||||||
Ptr<Texture> offHover = nullptr,
|
|
||||||
Ptr<Texture> onHover = nullptr,
|
|
||||||
Ptr<Texture> offPressed = nullptr,
|
|
||||||
Ptr<Texture> onPressed = nullptr);
|
|
||||||
|
|
||||||
void setBackgroundImageScaleMode(ImageScaleMode mode);
|
|
||||||
void setCustomSize(const Vec2 &size);
|
|
||||||
void setCustomSize(float width, float height);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 圆角矩形设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setCornerRadius(float radius);
|
|
||||||
float getCornerRadius() const { return cornerRadius_; }
|
|
||||||
void setRoundedCornersEnabled(bool enabled);
|
|
||||||
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 鼠标光标设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setHoverCursor(CursorShape cursor);
|
|
||||||
CursorShape getHoverCursor() const { return hoverCursor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// Alpha遮罩点击检测
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setUseAlphaMaskForHitTest(bool enabled);
|
|
||||||
bool isUseAlphaMaskForHitTest() const { return useAlphaMaskForHitTest_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 点击回调
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setOnClick(Function<void()> callback);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 切换模式支持(Toggle Button)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否为切换模式
|
|
||||||
* @param enabled true 表示启用切换模式,点击时自动切换 on/off 状态
|
|
||||||
*/
|
|
||||||
void setToggleMode(bool enabled);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前是否为切换模式
|
|
||||||
* @return true 表示处于切换模式
|
|
||||||
*/
|
|
||||||
bool isToggleMode() const { return toggleMode_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置当前状态(仅切换模式有效)
|
|
||||||
* @param on true 表示开启状态,false 表示关闭状态
|
|
||||||
*/
|
|
||||||
void setOn(bool on);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前状态
|
|
||||||
* @return true 表示开启状态,false 表示关闭状态
|
|
||||||
*/
|
|
||||||
bool isOn() const { return isOn_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 切换当前状态
|
|
||||||
*/
|
|
||||||
void toggle();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态改变回调
|
|
||||||
* @param callback 状态改变时调用的回调函数,参数为新状态
|
|
||||||
*/
|
|
||||||
void setOnStateChange(Function<void(bool)> callback);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 状态文字设置(用于切换按钮)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 为两种状态设置不同的文字
|
|
||||||
* @param textOff 关闭状态显示的文字
|
|
||||||
* @param textOn 开启状态显示的文字
|
|
||||||
*/
|
|
||||||
void setStateText(const std::string &textOff, const std::string &textOn);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 为两种状态设置不同的文字颜色
|
|
||||||
* @param colorOff 关闭状态的文字颜色
|
|
||||||
* @param colorOn 开启状态的文字颜色
|
|
||||||
*/
|
|
||||||
void setStateTextColor(const Color &colorOff, const Color &colorOn);
|
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
void drawBackgroundImage(RenderBackend &renderer, const Rect &rect);
|
|
||||||
void drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
|
||||||
const Color &color, float radius);
|
|
||||||
void fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
|
||||||
const Color &color, float radius);
|
|
||||||
Vec2 calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize);
|
|
||||||
|
|
||||||
// 状态访问(供子类使用)
|
|
||||||
bool isHovered() const { return hovered_; }
|
|
||||||
bool isPressed() const { return pressed_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string text_;
|
|
||||||
Ptr<FontAtlas> font_;
|
|
||||||
Vec2 padding_ = Vec2(10.0f, 6.0f);
|
|
||||||
|
|
||||||
// 文字颜色
|
|
||||||
Color textColor_ = Colors::White;
|
|
||||||
|
|
||||||
// 纯色背景
|
|
||||||
Color bgNormal_ = Color(0.2f, 0.2f, 0.2f, 1.0f);
|
|
||||||
Color bgHover_ = Color(0.28f, 0.28f, 0.28f, 1.0f);
|
|
||||||
Color bgPressed_ = Color(0.15f, 0.15f, 0.15f, 1.0f);
|
|
||||||
|
|
||||||
// 图片背景
|
|
||||||
Ptr<Texture> imgNormal_;
|
|
||||||
Ptr<Texture> imgHover_;
|
|
||||||
Ptr<Texture> imgPressed_;
|
|
||||||
Rect imgNormalRect_;
|
|
||||||
Rect imgHoverRect_;
|
|
||||||
Rect imgPressedRect_;
|
|
||||||
ImageScaleMode scaleMode_ = ImageScaleMode::Original;
|
|
||||||
bool useImageBackground_ = false;
|
|
||||||
bool useTextureRect_ = false;
|
|
||||||
|
|
||||||
// 切换按钮状态图片
|
|
||||||
Ptr<Texture> imgOffNormal_, imgOnNormal_;
|
|
||||||
Ptr<Texture> imgOffHover_, imgOnHover_;
|
|
||||||
Ptr<Texture> imgOffPressed_, imgOnPressed_;
|
|
||||||
bool useStateImages_ = false;
|
|
||||||
|
|
||||||
// 边框
|
|
||||||
Color borderColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
|
|
||||||
float borderWidth_ = 1.0f;
|
|
||||||
|
|
||||||
// 圆角矩形
|
|
||||||
float cornerRadius_ = 8.0f;
|
|
||||||
bool roundedCornersEnabled_ = false;
|
|
||||||
|
|
||||||
// 鼠标光标
|
|
||||||
CursorShape hoverCursor_ = CursorShape::Hand;
|
|
||||||
bool cursorChanged_ = false;
|
|
||||||
|
|
||||||
// Alpha遮罩点击检测
|
|
||||||
bool useAlphaMaskForHitTest_ = false;
|
|
||||||
|
|
||||||
bool hovered_ = false;
|
|
||||||
bool pressed_ = false;
|
|
||||||
|
|
||||||
// 切换模式相关
|
|
||||||
bool toggleMode_ = false;
|
|
||||||
bool isOn_ = false;
|
|
||||||
Function<void(bool)> onStateChange_;
|
|
||||||
|
|
||||||
// 状态文字
|
|
||||||
std::string textOff_, textOn_;
|
|
||||||
bool useStateText_ = false;
|
|
||||||
|
|
||||||
// 状态文字颜色
|
|
||||||
Color textColorOff_ = Colors::White;
|
|
||||||
Color textColorOn_ = Colors::White;
|
|
||||||
bool useStateTextColor_ = false;
|
|
||||||
|
|
||||||
Function<void()> onClick_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 复选框组件
|
|
||||||
// ============================================================================
|
|
||||||
class CheckBox : public Widget {
|
|
||||||
public:
|
|
||||||
CheckBox();
|
|
||||||
~CheckBox() override = default;
|
|
||||||
|
|
||||||
static Ptr<CheckBox> create();
|
|
||||||
static Ptr<CheckBox> create(const std::string &label);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 选中状态
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setChecked(bool checked);
|
|
||||||
bool isChecked() const { return checked_; }
|
|
||||||
void toggle();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 标签设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setLabel(const std::string &label);
|
|
||||||
const std::string &getLabel() const { return label_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 字体设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFont(Ptr<FontAtlas> font);
|
|
||||||
Ptr<FontAtlas> getFont() const { return font_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文字颜色
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setTextColor(const Color &color);
|
|
||||||
Color getTextColor() const { return textColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 复选框尺寸
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBoxSize(float size);
|
|
||||||
float getBoxSize() const { return boxSize_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 间距
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setSpacing(float spacing);
|
|
||||||
float getSpacing() const { return spacing_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 颜色设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setCheckedColor(const Color &color);
|
|
||||||
Color getCheckedColor() const { return checkedColor_; }
|
|
||||||
|
|
||||||
void setUncheckedColor(const Color &color);
|
|
||||||
Color getUncheckedColor() const { return uncheckedColor_; }
|
|
||||||
|
|
||||||
void setCheckMarkColor(const Color &color);
|
|
||||||
Color getCheckMarkColor() const { return checkMarkColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 回调设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setOnStateChange(Function<void(bool)> callback);
|
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
bool onMousePress(const MouseEvent &event) override;
|
|
||||||
bool onMouseRelease(const MouseEvent &event) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool checked_ = false;
|
|
||||||
std::string label_;
|
|
||||||
Ptr<FontAtlas> font_;
|
|
||||||
Color textColor_ = Colors::White;
|
|
||||||
|
|
||||||
float boxSize_ = 20.0f;
|
|
||||||
float spacing_ = 8.0f;
|
|
||||||
|
|
||||||
Color checkedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
|
|
||||||
Color uncheckedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
|
|
||||||
Color checkMarkColor_ = Colors::White;
|
|
||||||
|
|
||||||
bool pressed_ = false;
|
|
||||||
Function<void(bool)> onStateChange_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 文本标签组件 - 用于显示静态文本
|
|
||||||
// 支持多行、对齐、阴影、描边等游戏常用效果
|
|
||||||
// ============================================================================
|
|
||||||
class Label : public Widget {
|
|
||||||
public:
|
|
||||||
Label();
|
|
||||||
explicit Label(const std::string &text);
|
|
||||||
~Label() override = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static Ptr<Label> create();
|
|
||||||
static Ptr<Label> create(const std::string &text);
|
|
||||||
static Ptr<Label> create(const std::string &text, Ptr<FontAtlas> font);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文本内容
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setText(const std::string &text);
|
|
||||||
const std::string &getText() const { return text_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 字体设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFont(Ptr<FontAtlas> font);
|
|
||||||
Ptr<FontAtlas> getFont() const { return font_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文字颜色
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setTextColor(const Color &color);
|
|
||||||
Color getTextColor() const { return textColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 字体大小
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFontSize(int size);
|
|
||||||
int getFontSize() const { return fontSize_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 水平对齐方式
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
enum class HorizontalAlign { Left, Center, Right };
|
|
||||||
|
|
||||||
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);
|
|
||||||
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 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 getBoundingBox() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string text_;
|
|
||||||
Ptr<FontAtlas> font_;
|
|
||||||
Color textColor_ = Colors::White;
|
|
||||||
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;
|
|
||||||
void drawText(RenderBackend &renderer, const Vec2 &position, const Color &color);
|
|
||||||
Vec2 calculateDrawPosition() const;
|
|
||||||
std::vector<std::string> splitLines() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 进度条组件 - 用于显示进度/百分比
|
|
||||||
// 适用于血条、能量条、加载进度、经验条等游戏场景
|
|
||||||
// ============================================================================
|
|
||||||
class ProgressBar : public Widget {
|
|
||||||
public:
|
|
||||||
ProgressBar();
|
|
||||||
~ProgressBar() override = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static Ptr<ProgressBar> create();
|
|
||||||
static Ptr<ProgressBar> create(float min, float max, float value);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 数值范围
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setRange(float min, float max);
|
|
||||||
float getMin() const { return min_; }
|
|
||||||
float getMax() const { return max_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 当前值
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setValue(float value);
|
|
||||||
float getValue() const { return value_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 获取百分比 (0.0 - 1.0)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
float getPercent() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 进度条方向
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
enum class Direction { LeftToRight, RightToLeft, BottomToTop, TopToBottom };
|
|
||||||
|
|
||||||
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);
|
|
||||||
bool isSegmentedColorsEnabled() const { return segmentedColorsEnabled_; }
|
|
||||||
|
|
||||||
// 设置分段阈值和颜色,例如:>70%绿色, >30%黄色, 其他红色
|
|
||||||
void addColorSegment(float percentThreshold, const Color &color);
|
|
||||||
void clearColorSegments();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 圆角设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setCornerRadius(float radius);
|
|
||||||
float getCornerRadius() const { return cornerRadius_; }
|
|
||||||
|
|
||||||
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 getBoundingBox() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onUpdate(float deltaTime) override;
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 数值
|
|
||||||
float min_ = 0.0f;
|
|
||||||
float max_ = 100.0f;
|
|
||||||
float value_ = 50.0f;
|
|
||||||
|
|
||||||
// 方向
|
|
||||||
Direction direction_ = Direction::LeftToRight;
|
|
||||||
|
|
||||||
// 颜色
|
|
||||||
Color bgColor_ = Color(0.2f, 0.2f, 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);
|
|
||||||
bool gradientEnabled_ = false;
|
|
||||||
|
|
||||||
// 分段颜色
|
|
||||||
bool segmentedColorsEnabled_ = false;
|
|
||||||
std::vector<std::pair<float, Color>> colorSegments_;
|
|
||||||
|
|
||||||
// 圆角
|
|
||||||
float cornerRadius_ = 4.0f;
|
|
||||||
bool roundedCornersEnabled_ = true;
|
|
||||||
|
|
||||||
// 边框
|
|
||||||
bool borderEnabled_ = false;
|
|
||||||
Color borderColor_ = Colors::White;
|
|
||||||
float borderWidth_ = 1.0f;
|
|
||||||
|
|
||||||
// 内边距
|
|
||||||
float padding_ = 2.0f;
|
|
||||||
|
|
||||||
// 文本
|
|
||||||
bool textEnabled_ = false;
|
|
||||||
Ptr<FontAtlas> font_;
|
|
||||||
Color textColor_ = Colors::White;
|
|
||||||
std::string textFormat_ = "{percent:.0f}%";
|
|
||||||
|
|
||||||
// 动画
|
|
||||||
bool animatedChangeEnabled_ = false;
|
|
||||||
float animationSpeed_ = 100.0f;
|
|
||||||
float displayValue_ = 50.0f; // 用于动画的显示值
|
|
||||||
|
|
||||||
// 延迟显示
|
|
||||||
bool delayedDisplayEnabled_ = false;
|
|
||||||
float delayTime_ = 0.3f;
|
|
||||||
float delayTimer_ = 0.0f;
|
|
||||||
float delayedValue_ = 50.0f;
|
|
||||||
Color delayedFillColor_ = Color(1.0f, 0.0f, 0.0f, 0.5f);
|
|
||||||
|
|
||||||
// 条纹
|
|
||||||
bool stripedEnabled_ = false;
|
|
||||||
Color stripeColor_ = Color(1.0f, 1.0f, 1.0f, 0.2f);
|
|
||||||
float stripeSpeed_ = 50.0f;
|
|
||||||
float stripeOffset_ = 0.0f;
|
|
||||||
|
|
||||||
Color getCurrentFillColor() const;
|
|
||||||
std::string formatText() const;
|
|
||||||
void drawRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius);
|
|
||||||
void fillRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius);
|
|
||||||
void drawStripes(RenderBackend &renderer, const Rect &rect);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 单选按钮组件
|
|
||||||
// ============================================================================
|
|
||||||
class RadioButton : public Widget {
|
|
||||||
public:
|
|
||||||
RadioButton();
|
|
||||||
~RadioButton() override = default;
|
|
||||||
|
|
||||||
static Ptr<RadioButton> create();
|
|
||||||
static Ptr<RadioButton> create(const std::string &label);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 选择状态
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setSelected(bool selected);
|
|
||||||
bool isSelected() const { return selected_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 标签设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setLabel(const std::string &label);
|
|
||||||
const std::string &getLabel() const { return label_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 字体设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFont(Ptr<FontAtlas> font);
|
|
||||||
Ptr<FontAtlas> getFont() const { return font_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文字颜色
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setTextColor(const Color &color);
|
|
||||||
Color getTextColor() const { return textColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 圆形尺寸
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setCircleSize(float size);
|
|
||||||
float getCircleSize() const { return circleSize_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 间距
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setSpacing(float spacing);
|
|
||||||
float getSpacing() const { return spacing_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 颜色设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setSelectedColor(const Color &color);
|
|
||||||
Color getSelectedColor() const { return selectedColor_; }
|
|
||||||
|
|
||||||
void setUnselectedColor(const Color &color);
|
|
||||||
Color getUnselectedColor() const { return unselectedColor_; }
|
|
||||||
|
|
||||||
void setDotColor(const Color &color);
|
|
||||||
Color getDotColor() const { return dotColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 分组
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setGroupId(int groupId);
|
|
||||||
int getGroupId() const { return groupId_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 回调设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setOnStateChange(Function<void(bool)> callback);
|
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
bool onMousePress(const MouseEvent &event) override;
|
|
||||||
bool onMouseRelease(const MouseEvent &event) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool selected_ = false;
|
|
||||||
std::string label_;
|
|
||||||
Ptr<FontAtlas> font_;
|
|
||||||
Color textColor_ = Colors::White;
|
|
||||||
|
|
||||||
float circleSize_ = 20.0f;
|
|
||||||
float spacing_ = 8.0f;
|
|
||||||
|
|
||||||
Color selectedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
|
|
||||||
Color unselectedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
|
|
||||||
Color dotColor_ = Colors::White;
|
|
||||||
|
|
||||||
int groupId_ = 0;
|
|
||||||
|
|
||||||
bool pressed_ = false;
|
|
||||||
Function<void(bool)> onStateChange_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 单选按钮组管理器
|
|
||||||
// ============================================================================
|
|
||||||
class RadioButtonGroup {
|
|
||||||
public:
|
|
||||||
void addButton(RadioButton *button);
|
|
||||||
void removeButton(RadioButton *button);
|
|
||||||
void selectButton(RadioButton *button);
|
|
||||||
RadioButton *getSelectedButton() const { return selectedButton_; }
|
|
||||||
|
|
||||||
void setOnSelectionChange(Function<void(RadioButton*)> callback);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<RadioButton*> buttons_;
|
|
||||||
RadioButton *selectedButton_ = nullptr;
|
|
||||||
Function<void(RadioButton*)> onSelectionChange_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 滑动条组件
|
|
||||||
// ============================================================================
|
|
||||||
class Slider : public Widget {
|
|
||||||
public:
|
|
||||||
Slider();
|
|
||||||
~Slider() override = default;
|
|
||||||
|
|
||||||
static Ptr<Slider> create();
|
|
||||||
static Ptr<Slider> create(float min, float max, float value);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 数值范围
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setRange(float min, float max);
|
|
||||||
float getMin() const { return min_; }
|
|
||||||
float getMax() const { return max_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 当前值
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setValue(float value);
|
|
||||||
float getValue() const { return value_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 步进值
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setStep(float step);
|
|
||||||
float getStep() const { return step_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 方向
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setVertical(bool vertical);
|
|
||||||
bool isVertical() const { return vertical_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 轨道尺寸
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setTrackSize(float size);
|
|
||||||
float getTrackSize() const { return trackSize_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 滑块尺寸
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setThumbSize(float size);
|
|
||||||
float getThumbSize() const { return thumbSize_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 颜色设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setTrackColor(const Color &color);
|
|
||||||
Color getTrackColor() const { return trackColor_; }
|
|
||||||
|
|
||||||
void setFillColor(const Color &color);
|
|
||||||
Color getFillColor() const { return fillColor_; }
|
|
||||||
|
|
||||||
void setThumbColor(const Color &color);
|
|
||||||
Color getThumbColor() const { return thumbColor_; }
|
|
||||||
|
|
||||||
void setThumbHoverColor(const Color &color);
|
|
||||||
Color getThumbHoverColor() const { return thumbHoverColor_; }
|
|
||||||
|
|
||||||
void setThumbPressedColor(const Color &color);
|
|
||||||
Color getThumbPressedColor() const { return thumbPressedColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 显示设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setShowThumb(bool show);
|
|
||||||
bool isShowThumb() const { return showThumb_; }
|
|
||||||
|
|
||||||
void setShowFill(bool show);
|
|
||||||
bool isShowFill() const { return showFill_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文本显示
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
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_; }
|
|
||||||
|
|
||||||
void setTextFormat(const std::string &format);
|
|
||||||
const std::string &getTextFormat() const { return textFormat_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 回调设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setOnValueChange(Function<void(float)> callback);
|
|
||||||
void setOnDragStart(Function<void()> callback);
|
|
||||||
void setOnDragEnd(Function<void()> callback);
|
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
bool onMousePress(const MouseEvent &event) override;
|
|
||||||
bool onMouseRelease(const MouseEvent &event) override;
|
|
||||||
bool onMouseMove(const MouseEvent &event) override;
|
|
||||||
void onMouseEnter() override;
|
|
||||||
void onMouseLeave() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
float min_ = 0.0f;
|
|
||||||
float max_ = 100.0f;
|
|
||||||
float value_ = 50.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;
|
|
||||||
float positionToValue(float pos) const;
|
|
||||||
Rect getThumbRect() const;
|
|
||||||
Rect getTrackRect() const;
|
|
||||||
std::string formatText() const;
|
|
||||||
float snapToStep(float value) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/font.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 文本组件 - 继承自 Widget 的 UI 组件
|
|
||||||
// ============================================================================
|
|
||||||
class Text : public Widget {
|
|
||||||
public:
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 对齐方式枚举
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
enum class Alignment { Left, Center, Right };
|
|
||||||
enum class VerticalAlignment { Top, Middle, Bottom };
|
|
||||||
|
|
||||||
Text();
|
|
||||||
explicit Text(const std::string &text);
|
|
||||||
~Text() override = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static Ptr<Text> create();
|
|
||||||
static Ptr<Text> create(const std::string &text);
|
|
||||||
static Ptr<Text> create(const std::string &text, Ptr<FontAtlas> font);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 格式化创建方法(类似 printf)
|
|
||||||
// 使用示例:
|
|
||||||
// auto text = Text::createFormat("FPS: %d", 60);
|
|
||||||
// auto text = Text::createFormat(font, "得分: %d", score);
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static Ptr<Text> createFormat(const char *fmt, ...);
|
|
||||||
static Ptr<Text> createFormat(Ptr<FontAtlas> font, const char *fmt, ...);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文字内容
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setText(const std::string &text);
|
|
||||||
const std::string &getText() const { return text_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 格式化文本设置(类似 printf)
|
|
||||||
// 使用示例:
|
|
||||||
// text->setFormat("FPS: %d", 60);
|
|
||||||
// text->setFormat("位置: (%.1f, %.1f)", x, y);
|
|
||||||
// text->setFormat("生命值: %d/100", hp);
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFormat(const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
char buffer[256];
|
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
text_ = buffer;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 字体
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFont(Ptr<FontAtlas> font);
|
|
||||||
Ptr<FontAtlas> getFont() const { return font_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 文字属性
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setTextColor(const Color &color);
|
|
||||||
Color getTextColor() const { return color_; }
|
|
||||||
|
|
||||||
void setFontSize(int size);
|
|
||||||
int getFontSize() const { return fontSize_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 对齐方式
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setAlignment(Alignment align);
|
|
||||||
Alignment getAlignment() const { return alignment_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 垂直对齐方式
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setVerticalAlignment(VerticalAlignment align);
|
|
||||||
VerticalAlignment getVerticalAlignment() const { return verticalAlignment_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 尺寸计算
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Vec2 getTextSize() const;
|
|
||||||
float getLineHeight() const;
|
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string text_;
|
|
||||||
Ptr<FontAtlas> font_;
|
|
||||||
Color color_ = Colors::White;
|
|
||||||
int fontSize_ = 16;
|
|
||||||
Alignment alignment_ = Alignment::Left;
|
|
||||||
VerticalAlignment verticalAlignment_ = VerticalAlignment::Top;
|
|
||||||
|
|
||||||
mutable Vec2 cachedSize_ = Vec2::Zero();
|
|
||||||
mutable bool sizeDirty_ = true;
|
|
||||||
|
|
||||||
void updateCache() const;
|
|
||||||
Vec2 calculateDrawPosition() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/platform/input.h>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 鼠标事件结构
|
|
||||||
// ============================================================================
|
|
||||||
struct MouseEvent {
|
|
||||||
MouseButton button;
|
|
||||||
float x;
|
|
||||||
float y;
|
|
||||||
int mods; // 修饰键状态
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 坐标空间枚举 - 定义 UI 组件的渲染坐标空间
|
|
||||||
// ============================================================================
|
|
||||||
enum class CoordinateSpace {
|
|
||||||
Screen, // 屏幕空间 - 固定位置,不随相机移动
|
|
||||||
World, // 世界空间 - 随相机移动(默认行为)
|
|
||||||
Camera, // 相机空间 - 相对于相机位置的偏移
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Widget 基类 - UI 组件的基础
|
|
||||||
// ============================================================================
|
|
||||||
class Widget : public Node {
|
|
||||||
public:
|
|
||||||
Widget();
|
|
||||||
~Widget() override = default;
|
|
||||||
|
|
||||||
void setSize(const Size &size);
|
|
||||||
void setSize(float width, float height);
|
|
||||||
Size getSize() const { return size_; }
|
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 坐标空间设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setCoordinateSpace(CoordinateSpace space);
|
|
||||||
CoordinateSpace getCoordinateSpace() const { return coordinateSpace_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 屏幕空间位置设置(仅在 Screen 空间下有效)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setScreenPosition(const Vec2 &pos);
|
|
||||||
void setScreenPosition(float x, float y);
|
|
||||||
Vec2 getScreenPosition() const { return screenPosition_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 相机空间偏移设置(仅在 Camera 空间下有效)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setCameraOffset(const Vec2 &offset);
|
|
||||||
void setCameraOffset(float x, float y);
|
|
||||||
Vec2 getCameraOffset() const { return cameraOffset_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 鼠标事件处理(子类可重写)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
virtual bool onMousePress(const MouseEvent &event) { return false; }
|
|
||||||
virtual bool onMouseRelease(const MouseEvent &event) { return false; }
|
|
||||||
virtual bool onMouseMove(const MouseEvent &event) { return false; }
|
|
||||||
virtual void onMouseEnter() {}
|
|
||||||
virtual void onMouseLeave() {}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 启用/禁用状态
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setEnabled(bool enabled) { enabled_ = enabled; }
|
|
||||||
bool isEnabled() const { return enabled_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 焦点状态
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setFocused(bool focused) { focused_ = focused; }
|
|
||||||
bool isFocused() const { return focused_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// 供子类使用的辅助方法
|
|
||||||
bool isPointInside(float x, float y) const {
|
|
||||||
return getBoundingBox().containsPoint(Point(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取实际渲染位置(根据坐标空间计算)
|
|
||||||
Vec2 getRenderPosition() const;
|
|
||||||
|
|
||||||
// 子类重写此方法以支持自定义渲染
|
|
||||||
virtual void onDrawWidget(RenderBackend &renderer) {}
|
|
||||||
|
|
||||||
// 重写 Node 的 onDraw 以处理坐标空间
|
|
||||||
void onDraw(RenderBackend &renderer) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Size size_ = Size::Zero();
|
|
||||||
bool enabled_ = true;
|
|
||||||
bool focused_ = false;
|
|
||||||
bool hovered_ = false;
|
|
||||||
|
|
||||||
// 坐标空间相关
|
|
||||||
CoordinateSpace coordinateSpace_ = CoordinateSpace::World;
|
|
||||||
Vec2 screenPosition_ = Vec2::Zero(); // 屏幕空间位置
|
|
||||||
Vec2 cameraOffset_ = Vec2::Zero(); // 相机空间偏移
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -82,14 +82,11 @@ void Node::removeChild(Ptr<Node> child) {
|
||||||
|
|
||||||
auto it = std::find(children_.begin(), children_.end(), child);
|
auto it = std::find(children_.begin(), children_.end(), child);
|
||||||
if (it != children_.end()) {
|
if (it != children_.end()) {
|
||||||
// 始终从空间索引中移除(无论 running_ 状态)
|
|
||||||
// 这确保节点被正确清理
|
|
||||||
(*it)->onDetachFromScene();
|
(*it)->onDetachFromScene();
|
||||||
|
|
||||||
if (running_) {
|
if (running_) {
|
||||||
(*it)->onExit();
|
(*it)->onExit();
|
||||||
}
|
}
|
||||||
// 从索引中移除
|
|
||||||
if (!(*it)->getName().empty()) {
|
if (!(*it)->getName().empty()) {
|
||||||
nameIndex_.erase((*it)->getName());
|
nameIndex_.erase((*it)->getName());
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +155,6 @@ Ptr<Node> Node::getChildByTag(int tag) const {
|
||||||
void Node::setPosition(const Vec2 &pos) {
|
void Node::setPosition(const Vec2 &pos) {
|
||||||
position_ = pos;
|
position_ = pos;
|
||||||
markTransformDirty();
|
markTransformDirty();
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); }
|
void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); }
|
||||||
|
|
@ -166,13 +162,11 @@ void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); }
|
||||||
void Node::setRotation(float degrees) {
|
void Node::setRotation(float degrees) {
|
||||||
rotation_ = degrees;
|
rotation_ = degrees;
|
||||||
markTransformDirty();
|
markTransformDirty();
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setScale(const Vec2 &scale) {
|
void Node::setScale(const Vec2 &scale) {
|
||||||
scale_ = scale;
|
scale_ = scale;
|
||||||
markTransformDirty();
|
markTransformDirty();
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::setScale(float scale) { setScale(Vec2(scale, scale)); }
|
void Node::setScale(float scale) { setScale(Vec2(scale, scale)); }
|
||||||
|
|
@ -356,26 +350,12 @@ void Node::onRender(RenderBackend &renderer) {
|
||||||
void Node::onAttachToScene(Scene *scene) {
|
void Node::onAttachToScene(Scene *scene) {
|
||||||
scene_ = scene;
|
scene_ = scene;
|
||||||
|
|
||||||
// 添加到场景的空间索引
|
|
||||||
if (spatialIndexed_ && scene_) {
|
|
||||||
lastSpatialBounds_ = Rect();
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &child : children_) {
|
for (auto &child : children_) {
|
||||||
child->onAttachToScene(scene);
|
child->onAttachToScene(scene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::onDetachFromScene() {
|
void Node::onDetachFromScene() {
|
||||||
// 从场景的空间索引移除
|
|
||||||
// 注意:即使 lastSpatialBounds_ 为空也要尝试移除,
|
|
||||||
// 因为节点可能通过其他方式被插入到空间索引中
|
|
||||||
if (spatialIndexed_ && scene_) {
|
|
||||||
scene_->removeNodeFromSpatialIndex(this);
|
|
||||||
lastSpatialBounds_ = Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
scene_ = nullptr;
|
scene_ = nullptr;
|
||||||
for (auto &child : children_) {
|
for (auto &child : children_) {
|
||||||
child->onDetachFromScene();
|
child->onDetachFromScene();
|
||||||
|
|
@ -383,22 +363,9 @@ void Node::onDetachFromScene() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect Node::getBoundingBox() const {
|
Rect Node::getBoundingBox() const {
|
||||||
// 默认返回一个以位置为中心的点矩形
|
|
||||||
return Rect(position_.x, position_.y, 0, 0);
|
return Rect(position_.x, position_.y, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::updateSpatialIndex() {
|
|
||||||
if (!spatialIndexed_ || !scene_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect newBounds = getBoundingBox();
|
|
||||||
if (newBounds != lastSpatialBounds_) {
|
|
||||||
scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds);
|
|
||||||
lastSpatialBounds_ = newBounds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::update(float dt) { onUpdate(dt); }
|
void Node::update(float dt) { onUpdate(dt); }
|
||||||
|
|
||||||
void Node::render(RenderBackend &renderer) {
|
void Node::render(RenderBackend &renderer) {
|
||||||
|
|
|
||||||
|
|
@ -57,66 +57,12 @@ void Scene::updateScene(float dt) {
|
||||||
|
|
||||||
void Scene::onEnter() {
|
void Scene::onEnter() {
|
||||||
Node::onEnter();
|
Node::onEnter();
|
||||||
|
|
||||||
// 初始化空间索引世界边界
|
|
||||||
if (spatialIndexingEnabled_) {
|
|
||||||
spatialManager_.setWorldBounds(
|
|
||||||
Rect(0, 0, viewportSize_.width, viewportSize_.height));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::onExit() {
|
void Scene::onExit() {
|
||||||
// 清理空间索引
|
|
||||||
spatialManager_.clear();
|
|
||||||
Node::onExit();
|
Node::onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::updateNodeInSpatialIndex(Node *node, const Rect &oldBounds,
|
|
||||||
const Rect &newBounds) {
|
|
||||||
if (!spatialIndexingEnabled_ || !node || !node->isSpatialIndexed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果旧边界有效,先移除
|
|
||||||
if (!oldBounds.empty()) {
|
|
||||||
spatialManager_.remove(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果新边界有效,插入
|
|
||||||
if (!newBounds.empty()) {
|
|
||||||
spatialManager_.insert(node, newBounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scene::removeNodeFromSpatialIndex(Node *node) {
|
|
||||||
if (!spatialIndexingEnabled_ || !node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
spatialManager_.remove(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> Scene::queryNodesInArea(const Rect &area) const {
|
|
||||||
if (!spatialIndexingEnabled_) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return spatialManager_.query(area);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> Scene::queryNodesAtPoint(const Vec2 &point) const {
|
|
||||||
if (!spatialIndexingEnabled_) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return spatialManager_.query(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<Node *, Node *>> Scene::queryCollisions() const {
|
|
||||||
if (!spatialIndexingEnabled_) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return spatialManager_.queryCollisions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scene::collectRenderCommands(std::vector<RenderCommand> &commands,
|
void Scene::collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||||
int parentZOrder) {
|
int parentZOrder) {
|
||||||
if (!isVisible())
|
if (!isVisible())
|
||||||
|
|
|
||||||
|
|
@ -111,17 +111,14 @@ Ptr<ShapeNode> ShapeNode::createFilledPolygon(const std::vector<Vec2> &points,
|
||||||
|
|
||||||
void ShapeNode::setPoints(const std::vector<Vec2> &points) {
|
void ShapeNode::setPoints(const std::vector<Vec2> &points) {
|
||||||
points_ = points;
|
points_ = points;
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShapeNode::addPoint(const Vec2 &point) {
|
void ShapeNode::addPoint(const Vec2 &point) {
|
||||||
points_.push_back(point);
|
points_.push_back(point);
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShapeNode::clearPoints() {
|
void ShapeNode::clearPoints() {
|
||||||
points_.clear();
|
points_.clear();
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect ShapeNode::getBoundingBox() const {
|
Rect ShapeNode::getBoundingBox() const {
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,10 @@ void Sprite::setTexture(Ptr<Texture> texture) {
|
||||||
textureRect_ = Rect(0, 0, static_cast<float>(texture_->getWidth()),
|
textureRect_ = Rect(0, 0, static_cast<float>(texture_->getWidth()),
|
||||||
static_cast<float>(texture_->getHeight()));
|
static_cast<float>(texture_->getHeight()));
|
||||||
}
|
}
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::setTextureRect(const Rect &rect) {
|
void Sprite::setTextureRect(const Rect &rect) {
|
||||||
textureRect_ = rect;
|
textureRect_ = rect;
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::setColor(const Color &color) { color_ = color; }
|
void Sprite::setColor(const Color &color) { color_ = color; }
|
||||||
|
|
|
||||||
|
|
@ -1,329 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
#include <extra2d/spatial/quadtree.h>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
QuadTree::QuadTreeNode::QuadTreeNode(const Rect &bounds, int level)
|
|
||||||
: bounds(bounds), level(level) {}
|
|
||||||
|
|
||||||
bool QuadTree::QuadTreeNode::contains(const Rect &rect) const {
|
|
||||||
return bounds.contains(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QuadTree::QuadTreeNode::intersects(const Rect &rect) const {
|
|
||||||
return bounds.intersects(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
QuadTree::QuadTree(const Rect &worldBounds) : worldBounds_(worldBounds) {
|
|
||||||
root_ = std::make_unique<QuadTreeNode>(worldBounds, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::insert(Node *node, const Rect &bounds) {
|
|
||||||
if (!node || !root_->intersects(bounds))
|
|
||||||
return;
|
|
||||||
insertIntoNode(root_.get(), node, bounds);
|
|
||||||
objectCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::insertIntoNode(QuadTreeNode *node, Node *object,
|
|
||||||
const Rect &bounds) {
|
|
||||||
if (node->children[0]) {
|
|
||||||
int index = -1;
|
|
||||||
float midX = node->bounds.origin.x + node->bounds.size.width / 2.0f;
|
|
||||||
float midY = node->bounds.origin.y + node->bounds.size.height / 2.0f;
|
|
||||||
|
|
||||||
bool top = bounds.origin.y + bounds.size.height <= midY;
|
|
||||||
bool bottom = bounds.origin.y >= midY;
|
|
||||||
bool left = bounds.origin.x + bounds.size.width <= midX;
|
|
||||||
bool right = bounds.origin.x >= midX;
|
|
||||||
|
|
||||||
if (top && left)
|
|
||||||
index = 0;
|
|
||||||
else if (top && right)
|
|
||||||
index = 1;
|
|
||||||
else if (bottom && left)
|
|
||||||
index = 2;
|
|
||||||
else if (bottom && right)
|
|
||||||
index = 3;
|
|
||||||
|
|
||||||
if (index != -1) {
|
|
||||||
insertIntoNode(node->children[index].get(), object, bounds);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node->objects.emplace_back(object, bounds);
|
|
||||||
|
|
||||||
if (node->objects.size() > MAX_OBJECTS && node->level < MAX_LEVELS) {
|
|
||||||
if (!node->children[0]) {
|
|
||||||
split(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::split(QuadTreeNode *node) {
|
|
||||||
float midX = node->bounds.origin.x + node->bounds.size.width / 2.0f;
|
|
||||||
float midY = node->bounds.origin.y + node->bounds.size.height / 2.0f;
|
|
||||||
|
|
||||||
node->children[0] = std::make_unique<QuadTreeNode>(
|
|
||||||
Rect(node->bounds.origin.x, node->bounds.origin.y,
|
|
||||||
node->bounds.size.width / 2.0f, node->bounds.size.height / 2.0f),
|
|
||||||
node->level + 1);
|
|
||||||
node->children[1] = std::make_unique<QuadTreeNode>(
|
|
||||||
Rect(midX, node->bounds.origin.y, node->bounds.size.width / 2.0f,
|
|
||||||
node->bounds.size.height / 2.0f),
|
|
||||||
node->level + 1);
|
|
||||||
node->children[2] = std::make_unique<QuadTreeNode>(
|
|
||||||
Rect(node->bounds.origin.x, midY, node->bounds.size.width / 2.0f,
|
|
||||||
node->bounds.size.height / 2.0f),
|
|
||||||
node->level + 1);
|
|
||||||
node->children[3] = std::make_unique<QuadTreeNode>(
|
|
||||||
Rect(midX, midY, node->bounds.size.width / 2.0f,
|
|
||||||
node->bounds.size.height / 2.0f),
|
|
||||||
node->level + 1);
|
|
||||||
|
|
||||||
auto objects = std::move(node->objects);
|
|
||||||
node->objects.clear();
|
|
||||||
|
|
||||||
for (const auto &[obj, bounds] : objects) {
|
|
||||||
insertIntoNode(node, obj, bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::remove(Node *node) {
|
|
||||||
if (!node)
|
|
||||||
return;
|
|
||||||
if (removeFromNode(root_.get(), node)) {
|
|
||||||
objectCount_--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QuadTree::removeFromNode(QuadTreeNode *node, Node *object) {
|
|
||||||
auto it =
|
|
||||||
std::find_if(node->objects.begin(), node->objects.end(),
|
|
||||||
[object](const auto &pair) { return pair.first == object; });
|
|
||||||
|
|
||||||
if (it != node->objects.end()) {
|
|
||||||
node->objects.erase(it);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->children[0]) {
|
|
||||||
for (auto &child : node->children) {
|
|
||||||
if (removeFromNode(child.get(), object)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::update(Node *node, const Rect &newBounds) {
|
|
||||||
remove(node);
|
|
||||||
insert(node, newBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> QuadTree::query(const Rect &area) const {
|
|
||||||
std::vector<Node *> results;
|
|
||||||
queryNode(root_.get(), area, results);
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::queryNode(const QuadTreeNode *node, const Rect &area,
|
|
||||||
std::vector<Node *> &results) const {
|
|
||||||
if (!node || !node->intersects(area))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (const auto &[obj, bounds] : node->objects) {
|
|
||||||
if (bounds.intersects(area)) {
|
|
||||||
results.push_back(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->children[0]) {
|
|
||||||
for (const auto &child : node->children) {
|
|
||||||
queryNode(child.get(), area, results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> QuadTree::query(const Vec2 &point) const {
|
|
||||||
std::vector<Node *> results;
|
|
||||||
queryNode(root_.get(), point, results);
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::queryNode(const QuadTreeNode *node, const Vec2 &point,
|
|
||||||
std::vector<Node *> &results) const {
|
|
||||||
if (!node || !node->bounds.containsPoint(point))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (const auto &[obj, bounds] : node->objects) {
|
|
||||||
if (bounds.containsPoint(point)) {
|
|
||||||
results.push_back(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->children[0]) {
|
|
||||||
for (const auto &child : node->children) {
|
|
||||||
queryNode(child.get(), point, results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<Node *, Node *>> QuadTree::queryCollisions() const {
|
|
||||||
std::vector<std::pair<Node *, Node *>> collisions;
|
|
||||||
collectCollisions(root_.get(), collisions);
|
|
||||||
return collisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::detectCollisionsInNode(
|
|
||||||
const std::vector<std::pair<Node *, Rect>> &objects,
|
|
||||||
std::vector<std::pair<Node *, Node *>> &collisions) const {
|
|
||||||
size_t n = objects.size();
|
|
||||||
if (n < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 使用扫描线算法优化碰撞检测
|
|
||||||
// 按 x 坐标排序,只检查可能重叠的对象
|
|
||||||
collisionBuffer_.clear();
|
|
||||||
collisionBuffer_.reserve(n);
|
|
||||||
collisionBuffer_.assign(objects.begin(), objects.end());
|
|
||||||
|
|
||||||
// 按左边界排序
|
|
||||||
std::sort(collisionBuffer_.begin(), collisionBuffer_.end(),
|
|
||||||
[](const auto &a, const auto &b) {
|
|
||||||
return a.second.origin.x < b.second.origin.x;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 扫描线检测
|
|
||||||
for (size_t i = 0; i < n; ++i) {
|
|
||||||
const auto &[objA, boundsA] = collisionBuffer_[i];
|
|
||||||
float rightA = boundsA.origin.x + boundsA.size.width;
|
|
||||||
|
|
||||||
// 只检查右边界在 objA 右侧的对象
|
|
||||||
for (size_t j = i + 1; j < n; ++j) {
|
|
||||||
const auto &[objB, boundsB] = collisionBuffer_[j];
|
|
||||||
|
|
||||||
// 如果 objB 的左边界超过 objA 的右边界,后续对象都不会碰撞
|
|
||||||
if (boundsB.origin.x > rightA)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// 快速 AABB 检测
|
|
||||||
if (boundsA.intersects(boundsB)) {
|
|
||||||
collisions.emplace_back(objA, objB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::collectCollisions(
|
|
||||||
const QuadTreeNode *node,
|
|
||||||
std::vector<std::pair<Node *, Node *>> &collisions) const {
|
|
||||||
if (!node)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 使用迭代而非递归,避免深层树栈溢出
|
|
||||||
struct StackItem {
|
|
||||||
const QuadTreeNode *node;
|
|
||||||
size_t ancestorStart;
|
|
||||||
size_t ancestorEnd;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<StackItem> stack;
|
|
||||||
stack.reserve(32);
|
|
||||||
stack.push_back({node, 0, 0});
|
|
||||||
|
|
||||||
// 祖先对象列表,用于检测跨节点碰撞
|
|
||||||
collisionBuffer_.clear();
|
|
||||||
|
|
||||||
while (!stack.empty()) {
|
|
||||||
StackItem item = stack.back();
|
|
||||||
stack.pop_back();
|
|
||||||
|
|
||||||
const QuadTreeNode *current = item.node;
|
|
||||||
if (!current)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// 确保 ancestorEnd 不超过当前 buffer 大小
|
|
||||||
size_t validAncestorEnd = std::min(item.ancestorEnd, collisionBuffer_.size());
|
|
||||||
|
|
||||||
// 检测当前节点对象与祖先对象的碰撞
|
|
||||||
for (const auto &[obj, bounds] : current->objects) {
|
|
||||||
for (size_t i = item.ancestorStart; i < validAncestorEnd; ++i) {
|
|
||||||
const auto &[ancestorObj, ancestorBounds] = collisionBuffer_[i];
|
|
||||||
if (bounds.intersects(ancestorBounds)) {
|
|
||||||
collisions.emplace_back(ancestorObj, obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测当前节点内对象之间的碰撞(使用扫描线算法)
|
|
||||||
detectCollisionsInNode(current->objects, collisions);
|
|
||||||
|
|
||||||
// 记录当前节点的对象作为祖先
|
|
||||||
size_t oldSize = collisionBuffer_.size();
|
|
||||||
collisionBuffer_.insert(collisionBuffer_.end(), current->objects.begin(),
|
|
||||||
current->objects.end());
|
|
||||||
|
|
||||||
// 将子节点压入栈(逆序以保持遍历顺序)
|
|
||||||
if (current->children[0]) {
|
|
||||||
for (int i = 3; i >= 0; --i) {
|
|
||||||
if (current->children[i]) {
|
|
||||||
stack.push_back({current->children[i].get(), oldSize,
|
|
||||||
collisionBuffer_.size()});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复祖先列表(模拟递归返回)
|
|
||||||
// 只有当栈顶元素的祖先范围与当前不同时,才需要恢复
|
|
||||||
if (stack.empty()) {
|
|
||||||
collisionBuffer_.resize(oldSize);
|
|
||||||
} else {
|
|
||||||
const auto& nextItem = stack.back();
|
|
||||||
// 如果下一个节点的祖先范围与当前不同,则需要恢复到其祖先起始位置
|
|
||||||
if (nextItem.ancestorStart != oldSize) {
|
|
||||||
collisionBuffer_.resize(oldSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTree::clear() {
|
|
||||||
root_ = std::make_unique<QuadTreeNode>(worldBounds_, 0);
|
|
||||||
objectCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t QuadTree::size() const { return objectCount_; }
|
|
||||||
|
|
||||||
bool QuadTree::empty() const { return objectCount_ == 0; }
|
|
||||||
|
|
||||||
void QuadTree::rebuild() {
|
|
||||||
std::vector<std::pair<Node *, Rect>> allObjects;
|
|
||||||
|
|
||||||
std::function<void(QuadTreeNode *)> collect = [&](QuadTreeNode *node) {
|
|
||||||
if (!node)
|
|
||||||
return;
|
|
||||||
for (const auto &obj : node->objects) {
|
|
||||||
allObjects.push_back(obj);
|
|
||||||
}
|
|
||||||
if (node->children[0]) {
|
|
||||||
for (auto &child : node->children) {
|
|
||||||
collect(child.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
collect(root_.get());
|
|
||||||
clear();
|
|
||||||
|
|
||||||
for (const auto &[obj, bounds] : allObjects) {
|
|
||||||
insert(obj, bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,256 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
#include <extra2d/spatial/spatial_hash.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// Cell 实现
|
|
||||||
void SpatialHash::Cell::insert(Node *node) {
|
|
||||||
// 检查是否已存在
|
|
||||||
if (!contains(node)) {
|
|
||||||
objects.push_back(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::Cell::remove(Node *node) {
|
|
||||||
auto it = std::find(objects.begin(), objects.end(), node);
|
|
||||||
if (it != objects.end()) {
|
|
||||||
// 用最后一个元素替换,然后pop_back,O(1)操作
|
|
||||||
*it = objects.back();
|
|
||||||
objects.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SpatialHash::Cell::contains(Node *node) const {
|
|
||||||
return std::find(objects.begin(), objects.end(), node) != objects.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
SpatialHash::SpatialHash(float cellSize) : cellSize_(cellSize) {
|
|
||||||
// 预分配查询缓冲区,避免重复分配
|
|
||||||
queryBuffer_.reserve(64);
|
|
||||||
collisionBuffer_.reserve(128);
|
|
||||||
}
|
|
||||||
|
|
||||||
SpatialHash::CellKey SpatialHash::getCellKey(float x, float y) const {
|
|
||||||
int64_t cellX = static_cast<int64_t>(std::floor(x / cellSize_));
|
|
||||||
int64_t cellY = static_cast<int64_t>(std::floor(y / cellSize_));
|
|
||||||
return {cellX, cellY};
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::getCellsForRect(const Rect &rect,
|
|
||||||
std::vector<CellKey> &cells) const {
|
|
||||||
cells.clear();
|
|
||||||
|
|
||||||
CellKey minCell = getCellKey(rect.origin.x, rect.origin.y);
|
|
||||||
CellKey maxCell = getCellKey(rect.origin.x + rect.size.width,
|
|
||||||
rect.origin.y + rect.size.height);
|
|
||||||
|
|
||||||
// 预分配空间
|
|
||||||
size_t cellCount = (maxCell.first - minCell.first + 1) *
|
|
||||||
(maxCell.second - minCell.second + 1);
|
|
||||||
cells.reserve(cellCount);
|
|
||||||
|
|
||||||
for (int64_t x = minCell.first; x <= maxCell.first; ++x) {
|
|
||||||
for (int64_t y = minCell.second; y <= maxCell.second; ++y) {
|
|
||||||
cells.emplace_back(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::insertIntoCells(Node *node, const Rect &bounds) {
|
|
||||||
std::vector<CellKey> cells;
|
|
||||||
getCellsForRect(bounds, cells);
|
|
||||||
|
|
||||||
for (const auto &cell : cells) {
|
|
||||||
grid_[cell].insert(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::removeFromCells(Node *node, const Rect &bounds) {
|
|
||||||
std::vector<CellKey> cells;
|
|
||||||
getCellsForRect(bounds, cells);
|
|
||||||
|
|
||||||
for (const auto &cell : cells) {
|
|
||||||
auto it = grid_.find(cell);
|
|
||||||
if (it != grid_.end()) {
|
|
||||||
it->second.remove(node);
|
|
||||||
if (it->second.empty()) {
|
|
||||||
grid_.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::insert(Node *node, const Rect &bounds) {
|
|
||||||
if (!node)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 检查节点是否已存在,如果存在则先移除
|
|
||||||
auto it = objectBounds_.find(node);
|
|
||||||
if (it != objectBounds_.end()) {
|
|
||||||
removeFromCells(node, it->second);
|
|
||||||
it->second = bounds;
|
|
||||||
} else {
|
|
||||||
objectBounds_[node] = bounds;
|
|
||||||
objectCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertIntoCells(node, bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::remove(Node *node) {
|
|
||||||
if (!node)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto it = objectBounds_.find(node);
|
|
||||||
if (it != objectBounds_.end()) {
|
|
||||||
removeFromCells(node, it->second);
|
|
||||||
objectBounds_.erase(it);
|
|
||||||
objectCount_--;
|
|
||||||
} else {
|
|
||||||
// 节点不在 objectBounds_ 中,但可能还在 grid_ 的某些单元格中
|
|
||||||
// 需要遍历所有单元格来移除
|
|
||||||
for (auto &[cellKey, cell] : grid_) {
|
|
||||||
cell.remove(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::update(Node *node, const Rect &newBounds) {
|
|
||||||
auto it = objectBounds_.find(node);
|
|
||||||
if (it != objectBounds_.end()) {
|
|
||||||
removeFromCells(node, it->second);
|
|
||||||
insertIntoCells(node, newBounds);
|
|
||||||
it->second = newBounds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> SpatialHash::query(const Rect &area) const {
|
|
||||||
queryBuffer_.clear();
|
|
||||||
|
|
||||||
std::vector<CellKey> cells;
|
|
||||||
getCellsForRect(area, cells);
|
|
||||||
|
|
||||||
// 使用排序+去重代替unordered_set,减少内存分配
|
|
||||||
for (const auto &cell : cells) {
|
|
||||||
auto it = grid_.find(cell);
|
|
||||||
if (it != grid_.end()) {
|
|
||||||
for (Node *node : it->second.objects) {
|
|
||||||
queryBuffer_.push_back(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 排序并去重
|
|
||||||
std::sort(queryBuffer_.begin(), queryBuffer_.end());
|
|
||||||
queryBuffer_.erase(
|
|
||||||
std::unique(queryBuffer_.begin(), queryBuffer_.end()),
|
|
||||||
queryBuffer_.end());
|
|
||||||
|
|
||||||
// 过滤实际相交的对象
|
|
||||||
std::vector<Node *> results;
|
|
||||||
results.reserve(queryBuffer_.size());
|
|
||||||
|
|
||||||
for (Node *node : queryBuffer_) {
|
|
||||||
auto boundsIt = objectBounds_.find(node);
|
|
||||||
if (boundsIt != objectBounds_.end() &&
|
|
||||||
boundsIt->second.intersects(area)) {
|
|
||||||
results.push_back(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> SpatialHash::query(const Vec2 &point) const {
|
|
||||||
std::vector<Node *> results;
|
|
||||||
|
|
||||||
CellKey cell = getCellKey(point.x, point.y);
|
|
||||||
auto it = grid_.find(cell);
|
|
||||||
|
|
||||||
if (it != grid_.end()) {
|
|
||||||
for (Node *node : it->second.objects) {
|
|
||||||
auto boundsIt = objectBounds_.find(node);
|
|
||||||
if (boundsIt != objectBounds_.end() &&
|
|
||||||
boundsIt->second.containsPoint(point)) {
|
|
||||||
results.push_back(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<Node *, Node *>> SpatialHash::queryCollisions() const {
|
|
||||||
collisionBuffer_.clear();
|
|
||||||
|
|
||||||
// 使用排序+唯一性检查代替unordered_set
|
|
||||||
std::vector<std::pair<Node *, Node *>> tempCollisions;
|
|
||||||
tempCollisions.reserve(objectCount_ * 2);
|
|
||||||
|
|
||||||
for (const auto &[cell, cellData] : grid_) {
|
|
||||||
const auto &objects = cellData.objects;
|
|
||||||
size_t count = objects.size();
|
|
||||||
|
|
||||||
// 使用扫描线算法优化单元格内碰撞检测
|
|
||||||
for (size_t i = 0; i < count; ++i) {
|
|
||||||
Node *nodeA = objects[i];
|
|
||||||
auto boundsA = objectBounds_.find(nodeA);
|
|
||||||
if (boundsA == objectBounds_.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (size_t j = i + 1; j < count; ++j) {
|
|
||||||
Node *nodeB = objects[j];
|
|
||||||
auto boundsB = objectBounds_.find(nodeB);
|
|
||||||
if (boundsB == objectBounds_.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (boundsA->second.intersects(boundsB->second)) {
|
|
||||||
// 确保有序对,便于去重
|
|
||||||
if (nodeA < nodeB) {
|
|
||||||
tempCollisions.emplace_back(nodeA, nodeB);
|
|
||||||
} else {
|
|
||||||
tempCollisions.emplace_back(nodeB, nodeA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 排序并去重
|
|
||||||
std::sort(tempCollisions.begin(), tempCollisions.end());
|
|
||||||
tempCollisions.erase(
|
|
||||||
std::unique(tempCollisions.begin(), tempCollisions.end()),
|
|
||||||
tempCollisions.end());
|
|
||||||
|
|
||||||
return tempCollisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::clear() {
|
|
||||||
grid_.clear();
|
|
||||||
objectBounds_.clear();
|
|
||||||
objectCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t SpatialHash::size() const { return objectCount_; }
|
|
||||||
|
|
||||||
bool SpatialHash::empty() const { return objectCount_ == 0; }
|
|
||||||
|
|
||||||
void SpatialHash::rebuild() {
|
|
||||||
auto bounds = objectBounds_;
|
|
||||||
clear();
|
|
||||||
|
|
||||||
for (const auto &[node, bound] : bounds) {
|
|
||||||
insert(node, bound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialHash::setCellSize(float cellSize) {
|
|
||||||
if (cellSize != cellSize_ && cellSize > 0) {
|
|
||||||
cellSize_ = cellSize;
|
|
||||||
rebuild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
#include <chrono>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
#include <extra2d/spatial/quadtree.h>
|
|
||||||
#include <extra2d/spatial/spatial_hash.h>
|
|
||||||
#include <extra2d/spatial/spatial_manager.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
SpatialManager::SpatialManager() : worldBounds_(0, 0, 10000, 10000) {
|
|
||||||
selectOptimalStrategy();
|
|
||||||
}
|
|
||||||
|
|
||||||
SpatialManager::SpatialManager(const Rect &worldBounds)
|
|
||||||
: worldBounds_(worldBounds) {
|
|
||||||
selectOptimalStrategy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::setStrategy(SpatialStrategy strategy) {
|
|
||||||
if (currentStrategy_ == strategy)
|
|
||||||
return;
|
|
||||||
|
|
||||||
currentStrategy_ = strategy;
|
|
||||||
rebuild();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::setAutoThresholds(size_t quadTreeThreshold,
|
|
||||||
size_t hashThreshold) {
|
|
||||||
quadTreeThreshold_ = quadTreeThreshold;
|
|
||||||
hashThreshold_ = hashThreshold;
|
|
||||||
|
|
||||||
if (currentStrategy_ == SpatialStrategy::Auto) {
|
|
||||||
selectOptimalStrategy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::setWorldBounds(const Rect &bounds) {
|
|
||||||
worldBounds_ = bounds;
|
|
||||||
rebuild();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::insert(Node *node, const Rect &bounds) {
|
|
||||||
if (!index_) {
|
|
||||||
selectOptimalStrategy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index_) {
|
|
||||||
index_->insert(node, bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::remove(Node *node) {
|
|
||||||
if (index_) {
|
|
||||||
index_->remove(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::update(Node *node, const Rect &newBounds) {
|
|
||||||
if (index_) {
|
|
||||||
index_->update(node, newBounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> SpatialManager::query(const Rect &area) const {
|
|
||||||
if (!index_)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
|
||||||
auto results = index_->query(area);
|
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
queryCount_++;
|
|
||||||
totalQueryTime_ +=
|
|
||||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
|
||||||
.count();
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Node *> SpatialManager::query(const Vec2 &point) const {
|
|
||||||
if (!index_)
|
|
||||||
return {};
|
|
||||||
return index_->query(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<Node *, Node *>> SpatialManager::queryCollisions() const {
|
|
||||||
if (!index_)
|
|
||||||
return {};
|
|
||||||
return index_->queryCollisions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::query(const Rect &area,
|
|
||||||
const QueryCallback &callback) const {
|
|
||||||
auto results = query(area);
|
|
||||||
for (Node *node : results) {
|
|
||||||
if (!callback(node))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::query(const Vec2 &point,
|
|
||||||
const QueryCallback &callback) const {
|
|
||||||
auto results = query(point);
|
|
||||||
for (Node *node : results) {
|
|
||||||
if (!callback(node))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::clear() {
|
|
||||||
if (index_) {
|
|
||||||
index_->clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t SpatialManager::size() const { return index_ ? index_->size() : 0; }
|
|
||||||
|
|
||||||
bool SpatialManager::empty() const { return index_ ? index_->empty() : true; }
|
|
||||||
|
|
||||||
void SpatialManager::rebuild() {
|
|
||||||
if (!index_) {
|
|
||||||
selectOptimalStrategy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto oldIndex = std::move(index_);
|
|
||||||
selectOptimalStrategy();
|
|
||||||
|
|
||||||
if (index_ && oldIndex) {
|
|
||||||
auto bounds = Rect(worldBounds_);
|
|
||||||
auto objects = oldIndex->query(bounds);
|
|
||||||
for (Node *node : objects) {
|
|
||||||
if (node) {
|
|
||||||
auto nodeBounds = node->getBoundingBox();
|
|
||||||
index_->insert(node, nodeBounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::optimize() {
|
|
||||||
if (currentStrategy_ == SpatialStrategy::Auto) {
|
|
||||||
selectOptimalStrategy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index_) {
|
|
||||||
index_->rebuild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SpatialStrategy SpatialManager::getCurrentStrategy() const {
|
|
||||||
return activeStrategy_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *SpatialManager::getStrategyName() const {
|
|
||||||
switch (activeStrategy_) {
|
|
||||||
case SpatialStrategy::QuadTree:
|
|
||||||
return "QuadTree";
|
|
||||||
case SpatialStrategy::SpatialHash:
|
|
||||||
return "SpatialHash";
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<ISpatialIndex>
|
|
||||||
SpatialManager::createIndex(SpatialStrategy strategy, const Rect &bounds) {
|
|
||||||
switch (strategy) {
|
|
||||||
case SpatialStrategy::QuadTree:
|
|
||||||
return std::make_unique<QuadTree>(bounds);
|
|
||||||
case SpatialStrategy::SpatialHash:
|
|
||||||
return std::make_unique<SpatialHash>(64.0f);
|
|
||||||
default:
|
|
||||||
return std::make_unique<QuadTree>(bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpatialManager::selectOptimalStrategy() {
|
|
||||||
if (currentStrategy_ != SpatialStrategy::Auto) {
|
|
||||||
activeStrategy_ = currentStrategy_;
|
|
||||||
} else {
|
|
||||||
size_t currentSize = index_ ? index_->size() : 0;
|
|
||||||
|
|
||||||
if (currentSize < quadTreeThreshold_) {
|
|
||||||
activeStrategy_ = SpatialStrategy::QuadTree;
|
|
||||||
} else if (currentSize > hashThreshold_) {
|
|
||||||
activeStrategy_ = SpatialStrategy::SpatialHash;
|
|
||||||
} else {
|
|
||||||
// Keep current strategy in transition zone
|
|
||||||
if (!index_) {
|
|
||||||
activeStrategy_ = SpatialStrategy::QuadTree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index_ = createIndex(activeStrategy_, worldBounds_);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,693 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <extra2d/app/application.h>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/ui/button.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Button 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
Button::Button() {
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
setSpatialIndexed(false);
|
|
||||||
|
|
||||||
auto &dispatcher = getEventDispatcher();
|
|
||||||
dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) {
|
|
||||||
hovered_ = true;
|
|
||||||
auto &app = Application::instance();
|
|
||||||
app.window().setCursor(hoverCursor_);
|
|
||||||
cursorChanged_ = true;
|
|
||||||
});
|
|
||||||
dispatcher.addListener(EventType::UIHoverExit, [this](Event &) {
|
|
||||||
hovered_ = false;
|
|
||||||
pressed_ = false;
|
|
||||||
if (cursorChanged_) {
|
|
||||||
auto &app = Application::instance();
|
|
||||||
app.window().resetCursor();
|
|
||||||
cursorChanged_ = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dispatcher.addListener(EventType::UIPressed,
|
|
||||||
[this](Event &) { pressed_ = true; });
|
|
||||||
dispatcher.addListener(EventType::UIReleased,
|
|
||||||
[this](Event &) { pressed_ = false; });
|
|
||||||
dispatcher.addListener(EventType::UIClicked, [this](Event &) {
|
|
||||||
if (toggleMode_) {
|
|
||||||
toggle();
|
|
||||||
}
|
|
||||||
if (onClick_) {
|
|
||||||
onClick_();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 带文本的构造函数
|
|
||||||
* @param text 按钮文本
|
|
||||||
*/
|
|
||||||
Button::Button(const std::string &text) : Button() { text_ = text; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* @brief 创建空按钮对象
|
|
||||||
* @return 按钮对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Button> Button::create() { return makePtr<Button>(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带文本的按钮对象
|
|
||||||
* @param text 按钮文本
|
|
||||||
* @return 按钮对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Button> Button::create(const std::string &text) {
|
|
||||||
return makePtr<Button>(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带文本和字体的按钮对象
|
|
||||||
* @param text 按钮文本
|
|
||||||
* @param font 字体图集
|
|
||||||
* @return 按钮对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Button> Button::create(const std::string &text, Ptr<FontAtlas> font) {
|
|
||||||
auto btn = makePtr<Button>(text);
|
|
||||||
btn->setFont(font);
|
|
||||||
return btn;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 普通设置方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* @brief 设置按钮文本
|
|
||||||
* @param text 文本内容
|
|
||||||
*/
|
|
||||||
void Button::setText(const std::string &text) {
|
|
||||||
text_ = text;
|
|
||||||
if (font_ && getSize().empty()) {
|
|
||||||
Vec2 textSize = font_->measureText(text_);
|
|
||||||
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体
|
|
||||||
* @param font 字体图集指针
|
|
||||||
*/
|
|
||||||
void Button::setFont(Ptr<FontAtlas> font) {
|
|
||||||
font_ = font;
|
|
||||||
if (font_ && getSize().empty() && !text_.empty()) {
|
|
||||||
Vec2 textSize = font_->measureText(text_);
|
|
||||||
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置内边距(Vec2版本)
|
|
||||||
* @param padding 内边距向量
|
|
||||||
*/
|
|
||||||
void Button::setPadding(const Vec2 &padding) {
|
|
||||||
padding_ = padding;
|
|
||||||
if (font_ && getSize().empty() && !text_.empty()) {
|
|
||||||
Vec2 textSize = font_->measureText(text_);
|
|
||||||
setSize(textSize.x + padding_.x * 2.0f, textSize.y + padding_.y * 2.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置内边距(分量版本)
|
|
||||||
* @param x X方向内边距
|
|
||||||
* @param y Y方向内边距
|
|
||||||
*/
|
|
||||||
void Button::setPadding(float x, float y) {
|
|
||||||
setPadding(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本颜色
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void Button::setTextColor(const Color &color) { textColor_ = color; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置背景颜色
|
|
||||||
* @param normal 普通状态颜色
|
|
||||||
* @param hover 悬停状态颜色
|
|
||||||
* @param pressed 按下状态颜色
|
|
||||||
*/
|
|
||||||
void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
|
||||||
const Color &pressed) {
|
|
||||||
bgNormal_ = normal;
|
|
||||||
bgHover_ = hover;
|
|
||||||
bgPressed_ = pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置边框
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param width 边框宽度
|
|
||||||
*/
|
|
||||||
void Button::setBorder(const Color &color, float width) {
|
|
||||||
borderColor_ = color;
|
|
||||||
borderWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置圆角半径
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void Button::setCornerRadius(float radius) {
|
|
||||||
cornerRadius_ = std::max(0.0f, radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用圆角
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Button::setRoundedCornersEnabled(bool enabled) {
|
|
||||||
roundedCornersEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否使用Alpha遮罩进行点击检测
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Button::setUseAlphaMaskForHitTest(bool enabled) {
|
|
||||||
useAlphaMaskForHitTest_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置点击回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void Button::setOnClick(Function<void()> callback) {
|
|
||||||
onClick_ = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否为切换模式
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Button::setToggleMode(bool enabled) { toggleMode_ = enabled; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置开关状态
|
|
||||||
* @param on 是否开启
|
|
||||||
*/
|
|
||||||
void Button::setOn(bool on) {
|
|
||||||
if (isOn_ != on) {
|
|
||||||
isOn_ = on;
|
|
||||||
if (onStateChange_) {
|
|
||||||
onStateChange_(isOn_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 切换状态
|
|
||||||
*/
|
|
||||||
void Button::toggle() { setOn(!isOn_); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态改变回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void Button::setOnStateChange(Function<void(bool)> callback) {
|
|
||||||
onStateChange_ = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态文字
|
|
||||||
* @param textOff 关闭状态文字
|
|
||||||
* @param textOn 开启状态文字
|
|
||||||
*/
|
|
||||||
void Button::setStateText(const std::string &textOff,
|
|
||||||
const std::string &textOn) {
|
|
||||||
textOff_ = textOff;
|
|
||||||
textOn_ = textOn;
|
|
||||||
useStateText_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态文字颜色
|
|
||||||
* @param colorOff 关闭状态颜色
|
|
||||||
* @param colorOn 开启状态颜色
|
|
||||||
*/
|
|
||||||
void Button::setStateTextColor(const Color &colorOff, const Color &colorOn) {
|
|
||||||
textColorOff_ = colorOff;
|
|
||||||
textColorOn_ = colorOn;
|
|
||||||
useStateTextColor_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置悬停光标
|
|
||||||
* @param cursor 光标形状
|
|
||||||
*/
|
|
||||||
void Button::setHoverCursor(CursorShape cursor) { hoverCursor_ = cursor; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置背景图片
|
|
||||||
* @param normal 普通状态图片
|
|
||||||
* @param hover 悬停状态图片
|
|
||||||
* @param pressed 按下状态图片
|
|
||||||
*/
|
|
||||||
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
|
|
||||||
Ptr<Texture> pressed) {
|
|
||||||
imgNormal_ = normal;
|
|
||||||
imgHover_ = hover ? hover : normal;
|
|
||||||
imgPressed_ = pressed ? pressed : (hover ? hover : normal);
|
|
||||||
useImageBackground_ = (normal != nullptr);
|
|
||||||
useTextureRect_ = false;
|
|
||||||
|
|
||||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original && normal) {
|
|
||||||
setSize(static_cast<float>(normal->getWidth()),
|
|
||||||
static_cast<float>(normal->getHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置背景图片(带矩形区域)
|
|
||||||
* @param texture 纹理
|
|
||||||
* @param rect 矩形区域
|
|
||||||
*/
|
|
||||||
void Button::setBackgroundImage(Ptr<Texture> texture, const Rect &rect) {
|
|
||||||
imgNormal_ = texture;
|
|
||||||
imgHover_ = texture;
|
|
||||||
imgPressed_ = texture;
|
|
||||||
imgNormalRect_ = rect;
|
|
||||||
imgHoverRect_ = rect;
|
|
||||||
imgPressedRect_ = rect;
|
|
||||||
useImageBackground_ = (texture != nullptr);
|
|
||||||
useTextureRect_ = true;
|
|
||||||
useStateImages_ = false;
|
|
||||||
|
|
||||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original) {
|
|
||||||
setSize(rect.size.width, rect.size.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态背景图片
|
|
||||||
* @param offNormal 关闭状态普通图片
|
|
||||||
* @param onNormal 开启状态普通图片
|
|
||||||
* @param offHover 关闭状态悬停图片
|
|
||||||
* @param onHover 开启状态悬停图片
|
|
||||||
* @param offPressed 关闭状态按下图片
|
|
||||||
* @param onPressed 开启状态按下图片
|
|
||||||
*/
|
|
||||||
void Button::setStateBackgroundImage(
|
|
||||||
Ptr<Texture> offNormal, Ptr<Texture> onNormal, Ptr<Texture> offHover,
|
|
||||||
Ptr<Texture> onHover, Ptr<Texture> offPressed, Ptr<Texture> onPressed) {
|
|
||||||
imgOffNormal_ = offNormal;
|
|
||||||
imgOnNormal_ = onNormal;
|
|
||||||
imgOffHover_ = offHover ? offHover : offNormal;
|
|
||||||
imgOnHover_ = onHover ? onHover : onNormal;
|
|
||||||
imgOffPressed_ = offPressed ? offPressed : offNormal;
|
|
||||||
imgOnPressed_ = onPressed ? onPressed : onNormal;
|
|
||||||
|
|
||||||
useStateImages_ = (offNormal != nullptr && onNormal != nullptr);
|
|
||||||
useImageBackground_ = useStateImages_;
|
|
||||||
useTextureRect_ = false;
|
|
||||||
|
|
||||||
if (useStateImages_ && scaleMode_ == ImageScaleMode::Original && offNormal) {
|
|
||||||
setSize(static_cast<float>(offNormal->getWidth()),
|
|
||||||
static_cast<float>(offNormal->getHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置背景图片缩放模式
|
|
||||||
* @param mode 缩放模式
|
|
||||||
*/
|
|
||||||
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
|
||||||
scaleMode_ = mode;
|
|
||||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
|
|
||||||
imgNormal_) {
|
|
||||||
setSize(static_cast<float>(imgNormal_->getWidth()),
|
|
||||||
static_cast<float>(imgNormal_->getHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置自定义尺寸(Vec2版本)
|
|
||||||
* @param size 尺寸向量
|
|
||||||
*/
|
|
||||||
void Button::setCustomSize(const Vec2 &size) { setSize(size.x, size.y); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置自定义尺寸(分量版本)
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
*/
|
|
||||||
void Button::setCustomSize(float width, float height) {
|
|
||||||
setSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取边界框
|
|
||||||
* @return 边界矩形
|
|
||||||
*/
|
|
||||||
Rect Button::getBoundingBox() const {
|
|
||||||
auto pos = getRenderPosition();
|
|
||||||
auto anchor = getAnchor();
|
|
||||||
auto scale = getScale();
|
|
||||||
auto size = getSize();
|
|
||||||
|
|
||||||
if (size.empty()) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
float w = size.width * scale.x;
|
|
||||||
float h = size.height * scale.y;
|
|
||||||
float x0 = pos.x - size.width * anchor.x * scale.x;
|
|
||||||
float y0 = pos.y - size.height * anchor.y * scale.y;
|
|
||||||
|
|
||||||
return Rect(x0, y0, w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算图片尺寸
|
|
||||||
* @param buttonSize 按钮尺寸
|
|
||||||
* @param imageSize 图片尺寸
|
|
||||||
* @return 计算后的图片尺寸
|
|
||||||
*/
|
|
||||||
Vec2 Button::calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize) {
|
|
||||||
switch (scaleMode_) {
|
|
||||||
case ImageScaleMode::Original:
|
|
||||||
return imageSize;
|
|
||||||
|
|
||||||
case ImageScaleMode::Stretch:
|
|
||||||
return buttonSize;
|
|
||||||
|
|
||||||
case ImageScaleMode::ScaleFit: {
|
|
||||||
float scaleX = buttonSize.x / imageSize.x;
|
|
||||||
float scaleY = buttonSize.y / imageSize.y;
|
|
||||||
float scale = std::min(scaleX, scaleY);
|
|
||||||
return Vec2(imageSize.x * scale, imageSize.y * scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
case ImageScaleMode::ScaleFill: {
|
|
||||||
float scaleX = buttonSize.x / imageSize.x;
|
|
||||||
float scaleY = buttonSize.y / imageSize.y;
|
|
||||||
float scale = std::max(scaleX, scaleY);
|
|
||||||
return Vec2(imageSize.x * scale, imageSize.y * scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制背景图片
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 绘制区域
|
|
||||||
*/
|
|
||||||
void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
|
||||||
Texture *texture = nullptr;
|
|
||||||
Rect srcRect;
|
|
||||||
|
|
||||||
if (useStateImages_) {
|
|
||||||
if (isOn_) {
|
|
||||||
if (pressed_ && imgOnPressed_) {
|
|
||||||
texture = imgOnPressed_.get();
|
|
||||||
} else if (hovered_ && imgOnHover_) {
|
|
||||||
texture = imgOnHover_.get();
|
|
||||||
} else {
|
|
||||||
texture = imgOnNormal_.get();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (pressed_ && imgOffPressed_) {
|
|
||||||
texture = imgOffPressed_.get();
|
|
||||||
} else if (hovered_ && imgOffHover_) {
|
|
||||||
texture = imgOffHover_.get();
|
|
||||||
} else {
|
|
||||||
texture = imgOffNormal_.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (texture) {
|
|
||||||
srcRect = Rect(0, 0, static_cast<float>(texture->getWidth()),
|
|
||||||
static_cast<float>(texture->getHeight()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (pressed_ && imgPressed_) {
|
|
||||||
texture = imgPressed_.get();
|
|
||||||
srcRect = useTextureRect_
|
|
||||||
? imgPressedRect_
|
|
||||||
: Rect(0, 0, static_cast<float>(imgPressed_->getWidth()),
|
|
||||||
static_cast<float>(imgPressed_->getHeight()));
|
|
||||||
} else if (hovered_ && imgHover_) {
|
|
||||||
texture = imgHover_.get();
|
|
||||||
srcRect = useTextureRect_
|
|
||||||
? imgHoverRect_
|
|
||||||
: Rect(0, 0, static_cast<float>(imgHover_->getWidth()),
|
|
||||||
static_cast<float>(imgHover_->getHeight()));
|
|
||||||
} else if (imgNormal_) {
|
|
||||||
texture = imgNormal_.get();
|
|
||||||
srcRect = useTextureRect_
|
|
||||||
? imgNormalRect_
|
|
||||||
: Rect(0, 0, static_cast<float>(imgNormal_->getWidth()),
|
|
||||||
static_cast<float>(imgNormal_->getHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!texture)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Vec2 imageSize(srcRect.size.width, srcRect.size.height);
|
|
||||||
Vec2 buttonSize(rect.size.width, rect.size.height);
|
|
||||||
Vec2 drawSize = calculateImageSize(buttonSize, imageSize);
|
|
||||||
|
|
||||||
Vec2 drawPos(rect.origin.x + (rect.size.width - drawSize.x) * 0.5f,
|
|
||||||
rect.origin.y + (rect.size.height - drawSize.y) * 0.5f);
|
|
||||||
|
|
||||||
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
|
|
||||||
|
|
||||||
renderer.drawSprite(*texture, destRect, srcRect, Colors::White, 0.0f,
|
|
||||||
Vec2::Zero());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制圆角矩形边框
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
|
||||||
const Color &color, float radius) {
|
|
||||||
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
|
|
||||||
radius = std::min(radius, maxRadius);
|
|
||||||
|
|
||||||
if (radius <= 0.0f) {
|
|
||||||
renderer.drawRect(rect, color, borderWidth_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int segments = 8;
|
|
||||||
float x = rect.origin.x;
|
|
||||||
float y = rect.origin.y;
|
|
||||||
float w = rect.size.width;
|
|
||||||
float h = rect.size.height;
|
|
||||||
float r = radius;
|
|
||||||
|
|
||||||
renderer.drawLine(Vec2(x + r, y), Vec2(x + w - r, y), color, borderWidth_);
|
|
||||||
renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color,
|
|
||||||
borderWidth_);
|
|
||||||
renderer.drawLine(Vec2(x, y + r), Vec2(x, y + h - r), color, borderWidth_);
|
|
||||||
renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color,
|
|
||||||
borderWidth_);
|
|
||||||
|
|
||||||
for (int i = 0; i < segments; i++) {
|
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f;
|
|
||||||
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f;
|
|
||||||
Vec2 p1(x + r + r * cosf(angle1), y + r + r * sinf(angle1));
|
|
||||||
Vec2 p2(x + r + r * cosf(angle2), y + r + r * sinf(angle2));
|
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < segments; i++) {
|
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f;
|
|
||||||
float angle2 =
|
|
||||||
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
|
|
||||||
Vec2 p1(x + w - r + r * cosf(angle1), y + r + r * sinf(angle1));
|
|
||||||
Vec2 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2));
|
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < segments; i++) {
|
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments;
|
|
||||||
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments;
|
|
||||||
Vec2 p1(x + w - r + r * cosf(angle1), y + h - r + r * sinf(angle1));
|
|
||||||
Vec2 p2(x + w - r + r * cosf(angle2), y + h - r + r * sinf(angle2));
|
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < segments; i++) {
|
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f;
|
|
||||||
float angle2 =
|
|
||||||
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
|
|
||||||
Vec2 p1(x + r + r * cosf(angle1), y + h - r + r * sinf(angle1));
|
|
||||||
Vec2 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2));
|
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充圆角矩形
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
|
||||||
const Color &color, float radius) {
|
|
||||||
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
|
|
||||||
radius = std::min(radius, maxRadius);
|
|
||||||
|
|
||||||
if (radius <= 0.0f) {
|
|
||||||
renderer.fillRect(rect, color);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int segments = 8;
|
|
||||||
float x = rect.origin.x;
|
|
||||||
float y = rect.origin.y;
|
|
||||||
float w = rect.size.width;
|
|
||||||
float h = rect.size.height;
|
|
||||||
float r = radius;
|
|
||||||
|
|
||||||
std::vector<Vec2> vertices;
|
|
||||||
|
|
||||||
vertices.push_back(Vec2(x + r, y + r));
|
|
||||||
vertices.push_back(Vec2(x + w - r, y + r));
|
|
||||||
vertices.push_back(Vec2(x + w - r, y + h - r));
|
|
||||||
vertices.push_back(Vec2(x + r, y + h - r));
|
|
||||||
|
|
||||||
renderer.fillPolygon(vertices, color);
|
|
||||||
|
|
||||||
renderer.fillRect(Rect(x + r, y, w - 2 * r, r), color);
|
|
||||||
renderer.fillRect(Rect(x + r, y + h - r, w - 2 * r, r), color);
|
|
||||||
renderer.fillRect(Rect(x, y + r, r, h - 2 * r), color);
|
|
||||||
renderer.fillRect(Rect(x + w - r, y + r, r, h - 2 * r), color);
|
|
||||||
|
|
||||||
vertices.clear();
|
|
||||||
vertices.push_back(Vec2(x + r, y + r));
|
|
||||||
for (int i = 0; i <= segments; i++) {
|
|
||||||
float angle = 3.14159f + 3.14159f * 0.5f * (float)i / segments;
|
|
||||||
vertices.push_back(Vec2(x + r + r * cosf(angle), y + r + r * sinf(angle)));
|
|
||||||
}
|
|
||||||
renderer.fillPolygon(vertices, color);
|
|
||||||
|
|
||||||
vertices.clear();
|
|
||||||
vertices.push_back(Vec2(x + w - r, y + r));
|
|
||||||
for (int i = 0; i <= segments; i++) {
|
|
||||||
float angle = 3.14159f * 1.5f + 3.14159f * 0.5f * (float)i / segments;
|
|
||||||
vertices.push_back(
|
|
||||||
Vec2(x + w - r + r * cosf(angle), y + r + r * sinf(angle)));
|
|
||||||
}
|
|
||||||
renderer.fillPolygon(vertices, color);
|
|
||||||
|
|
||||||
vertices.clear();
|
|
||||||
vertices.push_back(Vec2(x + w - r, y + h - r));
|
|
||||||
for (int i = 0; i <= segments; i++) {
|
|
||||||
float angle = 0 + 3.14159f * 0.5f * (float)i / segments;
|
|
||||||
vertices.push_back(
|
|
||||||
Vec2(x + w - r + r * cosf(angle), y + h - r + r * sinf(angle)));
|
|
||||||
}
|
|
||||||
renderer.fillPolygon(vertices, color);
|
|
||||||
|
|
||||||
vertices.clear();
|
|
||||||
vertices.push_back(Vec2(x + r, y + h - r));
|
|
||||||
for (int i = 0; i <= segments; i++) {
|
|
||||||
float angle = 3.14159f * 0.5f + 3.14159f * 0.5f * (float)i / segments;
|
|
||||||
vertices.push_back(
|
|
||||||
Vec2(x + r + r * cosf(angle), y + h - r + r * sinf(angle)));
|
|
||||||
}
|
|
||||||
renderer.fillPolygon(vertices, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制组件
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
*/
|
|
||||||
void Button::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
Rect rect = getBoundingBox();
|
|
||||||
if (rect.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useImageBackground_) {
|
|
||||||
drawBackgroundImage(renderer, rect);
|
|
||||||
} else {
|
|
||||||
renderer.endSpriteBatch();
|
|
||||||
|
|
||||||
Color bg = bgNormal_;
|
|
||||||
if (pressed_) {
|
|
||||||
bg = bgPressed_;
|
|
||||||
} else if (hovered_) {
|
|
||||||
bg = bgHover_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roundedCornersEnabled_) {
|
|
||||||
fillRoundedRect(renderer, rect, bg, cornerRadius_);
|
|
||||||
} else {
|
|
||||||
renderer.fillRect(rect, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.beginSpriteBatch();
|
|
||||||
|
|
||||||
renderer.endSpriteBatch();
|
|
||||||
|
|
||||||
if (borderWidth_ > 0.0f) {
|
|
||||||
if (roundedCornersEnabled_) {
|
|
||||||
drawRoundedRect(renderer, rect, borderColor_, cornerRadius_);
|
|
||||||
} else {
|
|
||||||
renderer.drawRect(rect, borderColor_, borderWidth_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.beginSpriteBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font_) {
|
|
||||||
std::string textToDraw;
|
|
||||||
if (useStateText_) {
|
|
||||||
textToDraw = isOn_ ? textOn_ : textOff_;
|
|
||||||
} else {
|
|
||||||
textToDraw = text_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color colorToUse;
|
|
||||||
if (useStateTextColor_) {
|
|
||||||
colorToUse = isOn_ ? textColorOn_ : textColorOff_;
|
|
||||||
} else {
|
|
||||||
colorToUse = textColor_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!textToDraw.empty()) {
|
|
||||||
Vec2 textSize = font_->measureText(textToDraw);
|
|
||||||
|
|
||||||
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
|
|
||||||
rect.center().y - textSize.y * 0.5f);
|
|
||||||
|
|
||||||
float minX = rect.left() + padding_.x;
|
|
||||||
float minY = rect.top() + padding_.y;
|
|
||||||
float maxX = rect.right() - padding_.x - textSize.x;
|
|
||||||
float maxY = rect.bottom() - padding_.y - textSize.y;
|
|
||||||
|
|
||||||
textPos.x = std::max(minX, std::min(textPos.x, maxX));
|
|
||||||
textPos.y = std::max(minY, std::min(textPos.y, maxY));
|
|
||||||
|
|
||||||
colorToUse.a = 1.0f;
|
|
||||||
|
|
||||||
renderer.drawText(*font_, textToDraw, textPos, colorToUse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
#include <extra2d/ui/check_box.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
CheckBox::CheckBox() {
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
setSize(boxSize_, boxSize_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建复选框对象
|
|
||||||
* @return 复选框对象指针
|
|
||||||
*/
|
|
||||||
Ptr<CheckBox> CheckBox::create() {
|
|
||||||
return makePtr<CheckBox>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带标签的复选框对象
|
|
||||||
* @param label 标签文本
|
|
||||||
* @return 复选框对象指针
|
|
||||||
*/
|
|
||||||
Ptr<CheckBox> CheckBox::create(const std::string &label) {
|
|
||||||
auto cb = makePtr<CheckBox>();
|
|
||||||
cb->setLabel(label);
|
|
||||||
return cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置选中状态
|
|
||||||
* @param checked 是否选中
|
|
||||||
*/
|
|
||||||
void CheckBox::setChecked(bool checked) {
|
|
||||||
if (checked_ != checked) {
|
|
||||||
checked_ = checked;
|
|
||||||
if (onStateChange_) {
|
|
||||||
onStateChange_(checked_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 切换选中状态
|
|
||||||
*/
|
|
||||||
void CheckBox::toggle() {
|
|
||||||
setChecked(!checked_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置标签文本
|
|
||||||
* @param label 标签文本
|
|
||||||
*/
|
|
||||||
void CheckBox::setLabel(const std::string &label) {
|
|
||||||
label_ = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体
|
|
||||||
* @param font 字体图集指针
|
|
||||||
*/
|
|
||||||
void CheckBox::setFont(Ptr<FontAtlas> font) {
|
|
||||||
font_ = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本颜色
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void CheckBox::setTextColor(const Color &color) {
|
|
||||||
textColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置复选框尺寸
|
|
||||||
* @param size 复选框尺寸
|
|
||||||
*/
|
|
||||||
void CheckBox::setBoxSize(float size) {
|
|
||||||
boxSize_ = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置间距
|
|
||||||
* @param spacing 间距值
|
|
||||||
*/
|
|
||||||
void CheckBox::setSpacing(float spacing) {
|
|
||||||
spacing_ = spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置选中颜色
|
|
||||||
* @param color 选中颜色
|
|
||||||
*/
|
|
||||||
void CheckBox::setCheckedColor(const Color &color) {
|
|
||||||
checkedColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置未选中颜色
|
|
||||||
* @param color 未选中颜色
|
|
||||||
*/
|
|
||||||
void CheckBox::setUncheckedColor(const Color &color) {
|
|
||||||
uncheckedColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置勾选标记颜色
|
|
||||||
* @param color 勾选标记颜色
|
|
||||||
*/
|
|
||||||
void CheckBox::setCheckMarkColor(const Color &color) {
|
|
||||||
checkMarkColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态改变回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void CheckBox::setOnStateChange(Function<void(bool)> callback) {
|
|
||||||
onStateChange_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取边界框
|
|
||||||
* @return 边界矩形
|
|
||||||
*/
|
|
||||||
Rect CheckBox::getBoundingBox() const {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
float width = boxSize_;
|
|
||||||
|
|
||||||
if (!label_.empty() && font_) {
|
|
||||||
Vec2 textSize = font_->measureText(label_);
|
|
||||||
width += spacing_ + textSize.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Rect(pos.x, pos.y, width, boxSize_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制组件
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
*/
|
|
||||||
void CheckBox::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
|
|
||||||
Rect boxRect(pos.x, pos.y + (getSize().height - boxSize_) * 0.5f, boxSize_, boxSize_);
|
|
||||||
Color boxColor = checked_ ? checkedColor_ : uncheckedColor_;
|
|
||||||
renderer.fillRect(boxRect, boxColor);
|
|
||||||
renderer.drawRect(boxRect, Colors::White, 1.0f);
|
|
||||||
|
|
||||||
if (checked_) {
|
|
||||||
float padding = boxSize_ * 0.2f;
|
|
||||||
float x1 = boxRect.origin.x + padding;
|
|
||||||
float y1 = boxRect.origin.y + boxSize_ * 0.5f;
|
|
||||||
float x2 = boxRect.origin.x + boxSize_ * 0.4f;
|
|
||||||
float y2 = boxRect.origin.y + boxSize_ - 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!label_.empty() && font_) {
|
|
||||||
Vec2 textPos(pos.x + boxSize_ + spacing_, pos.y);
|
|
||||||
renderer.drawText(*font_, label_, textPos, textColor_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标按下事件处理
|
|
||||||
* @param event 鼠标事件
|
|
||||||
* @return 是否处理了事件
|
|
||||||
*/
|
|
||||||
bool CheckBox::onMousePress(const MouseEvent &event) {
|
|
||||||
if (event.button == MouseButton::Left) {
|
|
||||||
pressed_ = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标释放事件处理
|
|
||||||
* @param event 鼠标事件
|
|
||||||
* @return 是否处理了事件
|
|
||||||
*/
|
|
||||||
bool CheckBox::onMouseRelease(const MouseEvent &event) {
|
|
||||||
if (event.button == MouseButton::Left && pressed_) {
|
|
||||||
pressed_ = false;
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Rect boxRect(pos.x, pos.y, boxSize_, boxSize_);
|
|
||||||
if (boxRect.containsPoint(Point(event.x, event.y))) {
|
|
||||||
toggle();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,417 +0,0 @@
|
||||||
#include <extra2d/ui/label.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
Label::Label() {
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 带文本的构造函数
|
|
||||||
* @param text 初始文本内容
|
|
||||||
*/
|
|
||||||
Label::Label(const std::string &text) : text_(text) {
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
sizeDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建空标签对象
|
|
||||||
* @return 标签对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Label> Label::create() {
|
|
||||||
return makePtr<Label>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带文本的标签对象
|
|
||||||
* @param text 文本内容
|
|
||||||
* @return 标签对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Label> Label::create(const std::string &text) {
|
|
||||||
return makePtr<Label>(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带文本和字体的标签对象
|
|
||||||
* @param text 文本内容
|
|
||||||
* @param font 字体图集
|
|
||||||
* @return 标签对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Label> Label::create(const std::string &text, Ptr<FontAtlas> font) {
|
|
||||||
auto label = makePtr<Label>(text);
|
|
||||||
label->setFont(font);
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本内容
|
|
||||||
* @param text 新的文本内容
|
|
||||||
*/
|
|
||||||
void Label::setText(const std::string &text) {
|
|
||||||
text_ = text;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体
|
|
||||||
* @param font 字体图集指针
|
|
||||||
*/
|
|
||||||
void Label::setFont(Ptr<FontAtlas> font) {
|
|
||||||
font_ = font;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本颜色
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void Label::setTextColor(const Color &color) {
|
|
||||||
textColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体大小
|
|
||||||
* @param size 字体大小
|
|
||||||
*/
|
|
||||||
void Label::setFontSize(int size) {
|
|
||||||
fontSize_ = size;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置水平对齐方式
|
|
||||||
* @param align 对齐方式
|
|
||||||
*/
|
|
||||||
void Label::setHorizontalAlign(HorizontalAlign align) {
|
|
||||||
hAlign_ = align;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置垂直对齐方式
|
|
||||||
* @param align 垂直对齐方式
|
|
||||||
*/
|
|
||||||
void Label::setVerticalAlign(VerticalAlign align) {
|
|
||||||
vAlign_ = align;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置阴影是否启用
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Label::setShadowEnabled(bool enabled) {
|
|
||||||
shadowEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置阴影颜色
|
|
||||||
* @param color 阴影颜色
|
|
||||||
*/
|
|
||||||
void Label::setShadowColor(const Color &color) {
|
|
||||||
shadowColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置阴影偏移
|
|
||||||
* @param offset 偏移向量
|
|
||||||
*/
|
|
||||||
void Label::setShadowOffset(const Vec2 &offset) {
|
|
||||||
shadowOffset_ = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置描边是否启用
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Label::setOutlineEnabled(bool enabled) {
|
|
||||||
outlineEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置描边颜色
|
|
||||||
* @param color 描边颜色
|
|
||||||
*/
|
|
||||||
void Label::setOutlineColor(const Color &color) {
|
|
||||||
outlineColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置描边宽度
|
|
||||||
* @param width 描边宽度
|
|
||||||
*/
|
|
||||||
void Label::setOutlineWidth(float width) {
|
|
||||||
outlineWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否多行模式
|
|
||||||
* @param multiLine 是否多行
|
|
||||||
*/
|
|
||||||
void Label::setMultiLine(bool multiLine) {
|
|
||||||
multiLine_ = multiLine;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置行间距
|
|
||||||
* @param spacing 行间距倍数
|
|
||||||
*/
|
|
||||||
void Label::setLineSpacing(float spacing) {
|
|
||||||
lineSpacing_ = spacing;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置最大宽度
|
|
||||||
* @param maxWidth 最大宽度
|
|
||||||
*/
|
|
||||||
void Label::setMaxWidth(float maxWidth) {
|
|
||||||
maxWidth_ = maxWidth;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取文本尺寸
|
|
||||||
* @return 文本的宽度和高度
|
|
||||||
*/
|
|
||||||
Vec2 Label::getTextSize() const {
|
|
||||||
updateCache();
|
|
||||||
return cachedSize_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取行高
|
|
||||||
* @return 行高值
|
|
||||||
*/
|
|
||||||
float Label::getLineHeight() const {
|
|
||||||
if (font_) {
|
|
||||||
return font_->getLineHeight() * lineSpacing_;
|
|
||||||
}
|
|
||||||
return static_cast<float>(fontSize_) * lineSpacing_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新缓存
|
|
||||||
*/
|
|
||||||
void Label::updateCache() const {
|
|
||||||
if (!sizeDirty_ || !font_) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedSize_ = Vec2(maxWidth, totalHeight);
|
|
||||||
} else {
|
|
||||||
cachedSize_ = font_->measureText(text_);
|
|
||||||
}
|
|
||||||
|
|
||||||
sizeDirty_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 分割文本为多行
|
|
||||||
* @return 分割后的行列表
|
|
||||||
*/
|
|
||||||
std::vector<std::string> Label::splitLines() const {
|
|
||||||
std::vector<std::string> lines;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算绘制位置
|
|
||||||
* @return 绘制位置坐标
|
|
||||||
*/
|
|
||||||
Vec2 Label::calculateDrawPosition() const {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Vec2 size = getTextSize();
|
|
||||||
Size widgetSize = getSize();
|
|
||||||
|
|
||||||
float refWidth = widgetSize.empty() ? size.x : widgetSize.width;
|
|
||||||
float refHeight = widgetSize.empty() ? size.y : widgetSize.height;
|
|
||||||
|
|
||||||
switch (hAlign_) {
|
|
||||||
case HorizontalAlign::Center:
|
|
||||||
pos.x += (refWidth - size.x) * 0.5f;
|
|
||||||
break;
|
|
||||||
case HorizontalAlign::Right:
|
|
||||||
pos.x += refWidth - size.x;
|
|
||||||
break;
|
|
||||||
case HorizontalAlign::Left:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (vAlign_) {
|
|
||||||
case VerticalAlign::Middle:
|
|
||||||
pos.y += (refHeight - size.y) * 0.5f;
|
|
||||||
break;
|
|
||||||
case VerticalAlign::Bottom:
|
|
||||||
pos.y += refHeight - size.y;
|
|
||||||
break;
|
|
||||||
case VerticalAlign::Top:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制文本
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param position 绘制位置
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void Label::drawText(RenderBackend &renderer, const Vec2 &position, const Color &color) {
|
|
||||||
if (!font_ || text_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (multiLine_) {
|
|
||||||
auto lines = splitLines();
|
|
||||||
float lineHeight = getLineHeight();
|
|
||||||
Vec2 pos = position;
|
|
||||||
|
|
||||||
for (const auto &line : lines) {
|
|
||||||
renderer.drawText(*font_, line, pos, color);
|
|
||||||
pos.y += lineHeight;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
renderer.drawText(*font_, text_, position, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取边界框
|
|
||||||
* @return 边界矩形
|
|
||||||
*/
|
|
||||||
Rect Label::getBoundingBox() const {
|
|
||||||
if (!font_ || text_.empty()) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCache();
|
|
||||||
Vec2 size = cachedSize_;
|
|
||||||
if (size.x <= 0.0f || size.y <= 0.0f) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 pos = calculateDrawPosition();
|
|
||||||
return Rect(pos.x, pos.y, size.x, size.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制组件
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
*/
|
|
||||||
void Label::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
if (!font_ || text_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 pos = calculateDrawPosition();
|
|
||||||
|
|
||||||
if (shadowEnabled_) {
|
|
||||||
Vec2 shadowPos = pos + shadowOffset_;
|
|
||||||
drawText(renderer, shadowPos, shadowColor_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outlineEnabled_) {
|
|
||||||
float w = outlineWidth_;
|
|
||||||
drawText(renderer, pos + Vec2(-w, -w), outlineColor_);
|
|
||||||
drawText(renderer, pos + Vec2(0, -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, w), outlineColor_);
|
|
||||||
drawText(renderer, pos + Vec2(0, w), outlineColor_);
|
|
||||||
drawText(renderer, pos + Vec2(w, w), outlineColor_);
|
|
||||||
}
|
|
||||||
|
|
||||||
drawText(renderer, pos, textColor_);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,536 +0,0 @@
|
||||||
#include <extra2d/ui/progress_bar.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
ProgressBar::ProgressBar() {
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
setSize(200.0f, 20.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建进度条对象
|
|
||||||
* @return 进度条对象指针
|
|
||||||
*/
|
|
||||||
Ptr<ProgressBar> ProgressBar::create() {
|
|
||||||
return makePtr<ProgressBar>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带范围的进度条对象
|
|
||||||
* @param min 最小值
|
|
||||||
* @param max 最大值
|
|
||||||
* @param value 当前值
|
|
||||||
* @return 进度条对象指针
|
|
||||||
*/
|
|
||||||
Ptr<ProgressBar> ProgressBar::create(float min, float max, float value) {
|
|
||||||
auto bar = makePtr<ProgressBar>();
|
|
||||||
bar->setRange(min, max);
|
|
||||||
bar->setValue(value);
|
|
||||||
return bar;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置数值范围
|
|
||||||
* @param min 最小值
|
|
||||||
* @param max 最大值
|
|
||||||
*/
|
|
||||||
void ProgressBar::setRange(float min, float max) {
|
|
||||||
min_ = min;
|
|
||||||
max_ = max;
|
|
||||||
setValue(value_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置当前值
|
|
||||||
* @param value 新值
|
|
||||||
*/
|
|
||||||
void ProgressBar::setValue(float value) {
|
|
||||||
value_ = std::clamp(value, min_, max_);
|
|
||||||
|
|
||||||
if (!animatedChangeEnabled_) {
|
|
||||||
displayValue_ = value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delayedDisplayEnabled_) {
|
|
||||||
delayTimer_ = delayTime_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取百分比
|
|
||||||
* @return 百分比值(0.0-1.0)
|
|
||||||
*/
|
|
||||||
float ProgressBar::getPercent() const {
|
|
||||||
if (max_ <= min_) return 0.0f;
|
|
||||||
return (displayValue_ - min_) / (max_ - min_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置方向
|
|
||||||
* @param dir 方向枚举
|
|
||||||
*/
|
|
||||||
void ProgressBar::setDirection(Direction dir) {
|
|
||||||
direction_ = dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置背景颜色
|
|
||||||
* @param color 背景颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::setBackgroundColor(const Color &color) {
|
|
||||||
bgColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置填充颜色
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::setFillColor(const Color &color) {
|
|
||||||
fillColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用渐变填充
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setGradientFillEnabled(bool enabled) {
|
|
||||||
gradientEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置渐变结束颜色
|
|
||||||
* @param color 结束颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::setFillColorEnd(const Color &color) {
|
|
||||||
fillColorEnd_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用分段颜色
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setSegmentedColorsEnabled(bool enabled) {
|
|
||||||
segmentedColorsEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加颜色分段
|
|
||||||
* @param percentThreshold 百分比阈值
|
|
||||||
* @param color 颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::addColorSegment(float percentThreshold, const Color &color) {
|
|
||||||
colorSegments_.push_back({percentThreshold, color});
|
|
||||||
std::sort(colorSegments_.begin(), colorSegments_.end(),
|
|
||||||
[](const auto &a, const auto &b) { return a.first > b.first; });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有颜色分段
|
|
||||||
*/
|
|
||||||
void ProgressBar::clearColorSegments() {
|
|
||||||
colorSegments_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置圆角半径
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void ProgressBar::setCornerRadius(float radius) {
|
|
||||||
cornerRadius_ = radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用圆角
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setRoundedCornersEnabled(bool enabled) {
|
|
||||||
roundedCornersEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用边框
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setBorderEnabled(bool enabled) {
|
|
||||||
borderEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置边框颜色
|
|
||||||
* @param color 边框颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::setBorderColor(const Color &color) {
|
|
||||||
borderColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置边框宽度
|
|
||||||
* @param width 边框宽度
|
|
||||||
*/
|
|
||||||
void ProgressBar::setBorderWidth(float width) {
|
|
||||||
borderWidth_ = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置内边距
|
|
||||||
* @param padding 内边距值
|
|
||||||
*/
|
|
||||||
void ProgressBar::setPadding(float padding) {
|
|
||||||
padding_ = padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用文本显示
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setTextEnabled(bool enabled) {
|
|
||||||
textEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体
|
|
||||||
* @param font 字体图集指针
|
|
||||||
*/
|
|
||||||
void ProgressBar::setFont(Ptr<FontAtlas> font) {
|
|
||||||
font_ = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本颜色
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::setTextColor(const Color &color) {
|
|
||||||
textColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本格式
|
|
||||||
* @param format 格式字符串
|
|
||||||
*/
|
|
||||||
void ProgressBar::setTextFormat(const std::string &format) {
|
|
||||||
textFormat_ = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用动画变化
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setAnimatedChangeEnabled(bool enabled) {
|
|
||||||
animatedChangeEnabled_ = enabled;
|
|
||||||
if (!enabled) {
|
|
||||||
displayValue_ = value_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置动画速度
|
|
||||||
* @param speed 每秒变化量
|
|
||||||
*/
|
|
||||||
void ProgressBar::setAnimationSpeed(float speed) {
|
|
||||||
animationSpeed_ = speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用延迟显示
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setDelayedDisplayEnabled(bool enabled) {
|
|
||||||
delayedDisplayEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置延迟时间
|
|
||||||
* @param seconds 延迟秒数
|
|
||||||
*/
|
|
||||||
void ProgressBar::setDelayTime(float seconds) {
|
|
||||||
delayTime_ = seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置延迟显示填充颜色
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::setDelayedFillColor(const Color &color) {
|
|
||||||
delayedFillColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用条纹效果
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ProgressBar::setStripedEnabled(bool enabled) {
|
|
||||||
stripedEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置条纹颜色
|
|
||||||
* @param color 条纹颜色
|
|
||||||
*/
|
|
||||||
void ProgressBar::setStripeColor(const Color &color) {
|
|
||||||
stripeColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置条纹移动速度
|
|
||||||
* @param speed 移动速度
|
|
||||||
*/
|
|
||||||
void ProgressBar::setStripeSpeed(float speed) {
|
|
||||||
stripeSpeed_ = speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前填充颜色
|
|
||||||
* @return 当前填充颜色
|
|
||||||
*/
|
|
||||||
Color ProgressBar::getCurrentFillColor() const {
|
|
||||||
if (segmentedColorsEnabled_ && !colorSegments_.empty()) {
|
|
||||||
float percent = getPercent();
|
|
||||||
for (const auto &segment : colorSegments_) {
|
|
||||||
if (percent >= segment.first) {
|
|
||||||
return segment.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fillColor_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 格式化文本
|
|
||||||
* @return 格式化后的文本
|
|
||||||
*/
|
|
||||||
std::string ProgressBar::formatText() const {
|
|
||||||
std::string result = textFormat_;
|
|
||||||
|
|
||||||
size_t pos = result.find("{value}");
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
result.replace(pos, 7, std::to_string(static_cast<int>(displayValue_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = result.find("{max}");
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
result.replace(pos, 5, std::to_string(static_cast<int>(max_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = result.find("{percent}");
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
int percent = static_cast<int>(getPercent() * 100);
|
|
||||||
result.replace(pos, 9, std::to_string(percent));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取边界框
|
|
||||||
* @return 边界矩形
|
|
||||||
*/
|
|
||||||
Rect ProgressBar::getBoundingBox() const {
|
|
||||||
return Rect(getPosition().x, getPosition().y, getSize().width, getSize().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新函数
|
|
||||||
* @param deltaTime 帧间隔时间
|
|
||||||
*/
|
|
||||||
void ProgressBar::onUpdate(float deltaTime) {
|
|
||||||
if (animatedChangeEnabled_ && displayValue_ != value_) {
|
|
||||||
float diff = value_ - displayValue_;
|
|
||||||
float change = animationSpeed_ * deltaTime;
|
|
||||||
|
|
||||||
if (std::abs(diff) <= change) {
|
|
||||||
displayValue_ = value_;
|
|
||||||
} else {
|
|
||||||
displayValue_ += (diff > 0 ? change : -change);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delayedDisplayEnabled_) {
|
|
||||||
if (delayTimer_ > 0.0f) {
|
|
||||||
delayTimer_ -= deltaTime;
|
|
||||||
} else {
|
|
||||||
float diff = displayValue_ - delayedValue_;
|
|
||||||
float change = animationSpeed_ * deltaTime;
|
|
||||||
|
|
||||||
if (std::abs(diff) <= change) {
|
|
||||||
delayedValue_ = displayValue_;
|
|
||||||
} else {
|
|
||||||
delayedValue_ += (diff > 0 ? change : -change);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stripedEnabled_) {
|
|
||||||
stripeOffset_ += stripeSpeed_ * deltaTime;
|
|
||||||
if (stripeOffset_ > 20.0f) {
|
|
||||||
stripeOffset_ -= 20.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制组件
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
*/
|
|
||||||
void ProgressBar::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Size size = getSize();
|
|
||||||
|
|
||||||
float bgX = pos.x + padding_;
|
|
||||||
float bgY = pos.y + padding_;
|
|
||||||
float bgW = size.width - padding_ * 2;
|
|
||||||
float bgH = size.height - padding_ * 2;
|
|
||||||
Rect bgRect(bgX, bgY, bgW, bgH);
|
|
||||||
|
|
||||||
if (roundedCornersEnabled_) {
|
|
||||||
fillRoundedRect(renderer, bgRect, bgColor_, cornerRadius_);
|
|
||||||
} else {
|
|
||||||
renderer.fillRect(bgRect, bgColor_);
|
|
||||||
}
|
|
||||||
|
|
||||||
float percent = getPercent();
|
|
||||||
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_) {
|
|
||||||
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 (roundedCornersEnabled_) {
|
|
||||||
fillRoundedRect(renderer, fillRect, fillColor, cornerRadius_);
|
|
||||||
} else {
|
|
||||||
renderer.fillRect(fillRect, fillColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stripedEnabled_) {
|
|
||||||
drawStripes(renderer, fillRect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (borderEnabled_) {
|
|
||||||
if (roundedCornersEnabled_) {
|
|
||||||
drawRoundedRect(renderer, bgRect, borderColor_, cornerRadius_);
|
|
||||||
} else {
|
|
||||||
renderer.drawRect(bgRect, borderColor_, borderWidth_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textEnabled_ && font_) {
|
|
||||||
std::string text = formatText();
|
|
||||||
Vec2 textSize = font_->measureText(text);
|
|
||||||
|
|
||||||
Vec2 textPos(
|
|
||||||
pos.x + (size.width - textSize.x) * 0.5f,
|
|
||||||
pos.y + (size.height - textSize.y) * 0.5f
|
|
||||||
);
|
|
||||||
|
|
||||||
renderer.drawText(*font_, text, textPos, textColor_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制圆角矩形边框
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void ProgressBar::drawRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius) {
|
|
||||||
renderer.drawRect(rect, color, borderWidth_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充圆角矩形
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void ProgressBar::fillRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius) {
|
|
||||||
renderer.fillRect(rect, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制条纹效果
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 矩形区域
|
|
||||||
*/
|
|
||||||
void ProgressBar::drawStripes(RenderBackend &renderer, const Rect &rect) {
|
|
||||||
const float stripeWidth = 10.0f;
|
|
||||||
const float spacing = 20.0f;
|
|
||||||
float rectRight = rect.origin.x + rect.size.width;
|
|
||||||
float rectBottom = rect.origin.y + rect.size.height;
|
|
||||||
|
|
||||||
for (float x = rect.origin.x - spacing + stripeOffset_; x < rectRight; x += spacing) {
|
|
||||||
float x1 = x;
|
|
||||||
float y1 = rect.origin.y;
|
|
||||||
float x2 = x + stripeWidth;
|
|
||||||
float y2 = rectBottom;
|
|
||||||
|
|
||||||
if (x1 < rect.origin.x) x1 = rect.origin.x;
|
|
||||||
if (x2 > rectRight) x2 = rectRight;
|
|
||||||
|
|
||||||
if (x2 > x1) {
|
|
||||||
for (int i = 0; i < static_cast<int>(rect.size.height); i += 4) {
|
|
||||||
float sy = rect.origin.y + i;
|
|
||||||
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
|
|
||||||
|
|
@ -1,270 +0,0 @@
|
||||||
#include <extra2d/ui/radio_button.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
RadioButton::RadioButton() {
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
setSize(circleSize_, circleSize_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建单选按钮对象
|
|
||||||
* @return 单选按钮对象指针
|
|
||||||
*/
|
|
||||||
Ptr<RadioButton> RadioButton::create() {
|
|
||||||
return makePtr<RadioButton>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带标签的单选按钮对象
|
|
||||||
* @param label 标签文本
|
|
||||||
* @return 单选按钮对象指针
|
|
||||||
*/
|
|
||||||
Ptr<RadioButton> RadioButton::create(const std::string &label) {
|
|
||||||
auto rb = makePtr<RadioButton>();
|
|
||||||
rb->setLabel(label);
|
|
||||||
return rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置选中状态
|
|
||||||
* @param selected 是否选中
|
|
||||||
*/
|
|
||||||
void RadioButton::setSelected(bool selected) {
|
|
||||||
if (selected_ != selected) {
|
|
||||||
selected_ = selected;
|
|
||||||
if (onStateChange_) {
|
|
||||||
onStateChange_(selected_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置标签文本
|
|
||||||
* @param label 标签文本
|
|
||||||
*/
|
|
||||||
void RadioButton::setLabel(const std::string &label) {
|
|
||||||
label_ = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体
|
|
||||||
* @param font 字体图集指针
|
|
||||||
*/
|
|
||||||
void RadioButton::setFont(Ptr<FontAtlas> font) {
|
|
||||||
font_ = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本颜色
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void RadioButton::setTextColor(const Color &color) {
|
|
||||||
textColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置圆形尺寸
|
|
||||||
* @param size 圆形尺寸
|
|
||||||
*/
|
|
||||||
void RadioButton::setCircleSize(float size) {
|
|
||||||
circleSize_ = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置间距
|
|
||||||
* @param spacing 间距值
|
|
||||||
*/
|
|
||||||
void RadioButton::setSpacing(float spacing) {
|
|
||||||
spacing_ = spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置选中颜色
|
|
||||||
* @param color 选中颜色
|
|
||||||
*/
|
|
||||||
void RadioButton::setSelectedColor(const Color &color) {
|
|
||||||
selectedColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置未选中颜色
|
|
||||||
* @param color 未选中颜色
|
|
||||||
*/
|
|
||||||
void RadioButton::setUnselectedColor(const Color &color) {
|
|
||||||
unselectedColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置圆点颜色
|
|
||||||
* @param color 圆点颜色
|
|
||||||
*/
|
|
||||||
void RadioButton::setDotColor(const Color &color) {
|
|
||||||
dotColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置分组ID
|
|
||||||
* @param groupId 分组ID
|
|
||||||
*/
|
|
||||||
void RadioButton::setGroupId(int groupId) {
|
|
||||||
groupId_ = groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态改变回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void RadioButton::setOnStateChange(Function<void(bool)> callback) {
|
|
||||||
onStateChange_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取边界框
|
|
||||||
* @return 边界矩形
|
|
||||||
*/
|
|
||||||
Rect RadioButton::getBoundingBox() const {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
float width = circleSize_;
|
|
||||||
|
|
||||||
if (!label_.empty() && font_) {
|
|
||||||
Vec2 textSize = font_->measureText(label_);
|
|
||||||
width += spacing_ + textSize.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Rect(pos.x, pos.y, width, circleSize_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制组件
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
*/
|
|
||||||
void RadioButton::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
float centerX = pos.x + circleSize_ * 0.5f;
|
|
||||||
float centerY = pos.y + getSize().height * 0.5f;
|
|
||||||
float radius = circleSize_ * 0.5f;
|
|
||||||
|
|
||||||
Color circleColor = selected_ ? selectedColor_ : unselectedColor_;
|
|
||||||
renderer.drawCircle(Vec2(centerX, centerY), radius, circleColor, true);
|
|
||||||
renderer.drawCircle(Vec2(centerX, centerY), radius, Colors::White, false, 1.0f);
|
|
||||||
|
|
||||||
if (selected_) {
|
|
||||||
float dotRadius = radius * 0.4f;
|
|
||||||
renderer.drawCircle(Vec2(centerX, centerY), dotRadius, dotColor_, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!label_.empty() && font_) {
|
|
||||||
Vec2 textPos(pos.x + circleSize_ + spacing_, pos.y);
|
|
||||||
renderer.drawText(*font_, label_, textPos, textColor_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标按下事件处理
|
|
||||||
* @param event 鼠标事件
|
|
||||||
* @return 是否处理了事件
|
|
||||||
*/
|
|
||||||
bool RadioButton::onMousePress(const MouseEvent &event) {
|
|
||||||
if (event.button == MouseButton::Left) {
|
|
||||||
pressed_ = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标释放事件处理
|
|
||||||
* @param event 鼠标事件
|
|
||||||
* @return 是否处理了事件
|
|
||||||
*/
|
|
||||||
bool RadioButton::onMouseRelease(const MouseEvent &event) {
|
|
||||||
if (event.button == MouseButton::Left && pressed_) {
|
|
||||||
pressed_ = false;
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
float centerX = pos.x + circleSize_ * 0.5f;
|
|
||||||
float centerY = pos.y + getSize().height * 0.5f;
|
|
||||||
float radius = circleSize_ * 0.5f;
|
|
||||||
|
|
||||||
float dx = event.x - centerX;
|
|
||||||
float dy = event.y - centerY;
|
|
||||||
if (dx * dx + dy * dy <= radius * radius) {
|
|
||||||
setSelected(true);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RadioButtonGroup 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加单选按钮到组
|
|
||||||
* @param button 单选按钮指针
|
|
||||||
*/
|
|
||||||
void RadioButtonGroup::addButton(RadioButton *button) {
|
|
||||||
if (button && std::find(buttons_.begin(), buttons_.end(), button) == buttons_.end()) {
|
|
||||||
buttons_.push_back(button);
|
|
||||||
button->setOnStateChange([this, button](bool selected) {
|
|
||||||
if (selected) {
|
|
||||||
selectButton(button);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (button->isSelected() && !selectedButton_) {
|
|
||||||
selectedButton_ = button;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从组中移除单选按钮
|
|
||||||
* @param button 单选按钮指针
|
|
||||||
*/
|
|
||||||
void RadioButtonGroup::removeButton(RadioButton *button) {
|
|
||||||
auto it = std::find(buttons_.begin(), buttons_.end(), button);
|
|
||||||
if (it != buttons_.end()) {
|
|
||||||
buttons_.erase(it);
|
|
||||||
if (selectedButton_ == button) {
|
|
||||||
selectedButton_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 选择指定的单选按钮
|
|
||||||
* @param button 单选按钮指针
|
|
||||||
*/
|
|
||||||
void RadioButtonGroup::selectButton(RadioButton *button) {
|
|
||||||
if (selectedButton_ == button) return;
|
|
||||||
|
|
||||||
if (selectedButton_) {
|
|
||||||
selectedButton_->setSelected(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedButton_ = button;
|
|
||||||
if (button) {
|
|
||||||
button->setSelected(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onSelectionChange_) {
|
|
||||||
onSelectionChange_(button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置选择改变回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void RadioButtonGroup::setOnSelectionChange(Function<void(RadioButton*)> callback) {
|
|
||||||
onSelectionChange_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,482 +0,0 @@
|
||||||
#include <extra2d/ui/slider.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
Slider::Slider() {
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
setSize(200.0f, 20.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建滑动条对象
|
|
||||||
* @return 滑动条对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Slider> Slider::create() {
|
|
||||||
return makePtr<Slider>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带范围的滑动条对象
|
|
||||||
* @param min 最小值
|
|
||||||
* @param max 最大值
|
|
||||||
* @param value 当前值
|
|
||||||
* @return 滑动条对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Slider> Slider::create(float min, float max, float value) {
|
|
||||||
auto slider = makePtr<Slider>();
|
|
||||||
slider->setRange(min, max);
|
|
||||||
slider->setValue(value);
|
|
||||||
return slider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置数值范围
|
|
||||||
* @param min 最小值
|
|
||||||
* @param max 最大值
|
|
||||||
*/
|
|
||||||
void Slider::setRange(float min, float max) {
|
|
||||||
min_ = min;
|
|
||||||
max_ = max;
|
|
||||||
setValue(value_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置当前值
|
|
||||||
* @param value 新值
|
|
||||||
*/
|
|
||||||
void Slider::setValue(float value) {
|
|
||||||
float newValue = std::clamp(value, min_, max_);
|
|
||||||
if (step_ > 0.0f) {
|
|
||||||
newValue = snapToStep(newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value_ != newValue) {
|
|
||||||
value_ = newValue;
|
|
||||||
if (onValueChange_) {
|
|
||||||
onValueChange_(value_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置步进值
|
|
||||||
* @param step 步进值
|
|
||||||
*/
|
|
||||||
void Slider::setStep(float step) {
|
|
||||||
step_ = step;
|
|
||||||
if (step_ > 0.0f) {
|
|
||||||
setValue(value_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否为垂直方向
|
|
||||||
* @param vertical 是否垂直
|
|
||||||
*/
|
|
||||||
void Slider::setVertical(bool vertical) {
|
|
||||||
vertical_ = vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置轨道尺寸
|
|
||||||
* @param size 轨道尺寸
|
|
||||||
*/
|
|
||||||
void Slider::setTrackSize(float size) {
|
|
||||||
trackSize_ = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置滑块尺寸
|
|
||||||
* @param size 滑块尺寸
|
|
||||||
*/
|
|
||||||
void Slider::setThumbSize(float size) {
|
|
||||||
thumbSize_ = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置轨道颜色
|
|
||||||
* @param color 轨道颜色
|
|
||||||
*/
|
|
||||||
void Slider::setTrackColor(const Color &color) {
|
|
||||||
trackColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置填充颜色
|
|
||||||
* @param color 填充颜色
|
|
||||||
*/
|
|
||||||
void Slider::setFillColor(const Color &color) {
|
|
||||||
fillColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置滑块颜色
|
|
||||||
* @param color 滑块颜色
|
|
||||||
*/
|
|
||||||
void Slider::setThumbColor(const Color &color) {
|
|
||||||
thumbColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置滑块悬停颜色
|
|
||||||
* @param color 悬停颜色
|
|
||||||
*/
|
|
||||||
void Slider::setThumbHoverColor(const Color &color) {
|
|
||||||
thumbHoverColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置滑块按下颜色
|
|
||||||
* @param color 按下颜色
|
|
||||||
*/
|
|
||||||
void Slider::setThumbPressedColor(const Color &color) {
|
|
||||||
thumbPressedColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否显示滑块
|
|
||||||
* @param show 是否显示
|
|
||||||
*/
|
|
||||||
void Slider::setShowThumb(bool show) {
|
|
||||||
showThumb_ = show;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否显示填充
|
|
||||||
* @param show 是否显示
|
|
||||||
*/
|
|
||||||
void Slider::setShowFill(bool show) {
|
|
||||||
showFill_ = show;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用文本显示
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Slider::setTextEnabled(bool enabled) {
|
|
||||||
textEnabled_ = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体
|
|
||||||
* @param font 字体图集指针
|
|
||||||
*/
|
|
||||||
void Slider::setFont(Ptr<FontAtlas> font) {
|
|
||||||
font_ = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本颜色
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void Slider::setTextColor(const Color &color) {
|
|
||||||
textColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本格式
|
|
||||||
* @param format 格式字符串
|
|
||||||
*/
|
|
||||||
void Slider::setTextFormat(const std::string &format) {
|
|
||||||
textFormat_ = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置值改变回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void Slider::setOnValueChange(Function<void(float)> callback) {
|
|
||||||
onValueChange_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置拖拽开始回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void Slider::setOnDragStart(Function<void()> callback) {
|
|
||||||
onDragStart_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置拖拽结束回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void Slider::setOnDragEnd(Function<void()> callback) {
|
|
||||||
onDragEnd_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取边界框
|
|
||||||
* @return 边界矩形
|
|
||||||
*/
|
|
||||||
Rect Slider::getBoundingBox() const {
|
|
||||||
return Rect(getPosition().x, getPosition().y, getSize().width, getSize().height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将值转换为位置
|
|
||||||
* @param value 数值
|
|
||||||
* @return 位置坐标
|
|
||||||
*/
|
|
||||||
float Slider::valueToPosition(float value) const {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Size size = getSize();
|
|
||||||
|
|
||||||
float percent = (value - min_) / (max_ - min_);
|
|
||||||
|
|
||||||
if (vertical_) {
|
|
||||||
return pos.y + size.height - percent * size.height;
|
|
||||||
} else {
|
|
||||||
return pos.x + percent * size.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将位置转换为值
|
|
||||||
* @param pos 位置坐标
|
|
||||||
* @return 数值
|
|
||||||
*/
|
|
||||||
float Slider::positionToValue(float pos) const {
|
|
||||||
Vec2 widgetPos = getPosition();
|
|
||||||
Size size = getSize();
|
|
||||||
|
|
||||||
float percent;
|
|
||||||
if (vertical_) {
|
|
||||||
percent = (widgetPos.y + size.height - pos) / size.height;
|
|
||||||
} else {
|
|
||||||
percent = (pos - widgetPos.x) / size.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
percent = std::clamp(percent, 0.0f, 1.0f);
|
|
||||||
return min_ + percent * (max_ - min_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取滑块矩形
|
|
||||||
* @return 滑块矩形
|
|
||||||
*/
|
|
||||||
Rect Slider::getThumbRect() const {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Size size = getSize();
|
|
||||||
|
|
||||||
float thumbPos = valueToPosition(value_);
|
|
||||||
|
|
||||||
if (vertical_) {
|
|
||||||
return Rect(
|
|
||||||
pos.x + (size.width - thumbSize_) * 0.5f,
|
|
||||||
thumbPos - thumbSize_ * 0.5f,
|
|
||||||
thumbSize_,
|
|
||||||
thumbSize_
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Rect(
|
|
||||||
thumbPos - thumbSize_ * 0.5f,
|
|
||||||
pos.y + (size.height - thumbSize_) * 0.5f,
|
|
||||||
thumbSize_,
|
|
||||||
thumbSize_
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取轨道矩形
|
|
||||||
* @return 轨道矩形
|
|
||||||
*/
|
|
||||||
Rect Slider::getTrackRect() const {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Size size = getSize();
|
|
||||||
|
|
||||||
if (vertical_) {
|
|
||||||
return Rect(
|
|
||||||
pos.x + (size.width - trackSize_) * 0.5f,
|
|
||||||
pos.y,
|
|
||||||
trackSize_,
|
|
||||||
size.height
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Rect(
|
|
||||||
pos.x,
|
|
||||||
pos.y + (size.height - trackSize_) * 0.5f,
|
|
||||||
size.width,
|
|
||||||
trackSize_
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 格式化文本
|
|
||||||
* @return 格式化后的文本
|
|
||||||
*/
|
|
||||||
std::string Slider::formatText() const {
|
|
||||||
std::string result = textFormat_;
|
|
||||||
|
|
||||||
size_t pos = result.find("{value}");
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
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_)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将值对齐到步进
|
|
||||||
* @param value 原始值
|
|
||||||
* @return 对齐后的值
|
|
||||||
*/
|
|
||||||
float Slider::snapToStep(float value) const {
|
|
||||||
float steps = std::round((value - min_) / step_);
|
|
||||||
return min_ + steps * step_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制组件
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
*/
|
|
||||||
void Slider::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
Rect trackRect = getTrackRect();
|
|
||||||
|
|
||||||
renderer.fillRect(trackRect, trackColor_);
|
|
||||||
|
|
||||||
if (showFill_) {
|
|
||||||
float percent = (value_ - min_) / (max_ - min_);
|
|
||||||
float fillX = trackRect.origin.x;
|
|
||||||
float fillY = trackRect.origin.y;
|
|
||||||
float fillW = trackRect.size.width;
|
|
||||||
float fillH = trackRect.size.height;
|
|
||||||
|
|
||||||
if (vertical_) {
|
|
||||||
fillH = trackRect.size.height * percent;
|
|
||||||
fillY = trackRect.origin.y + trackRect.size.height - fillH;
|
|
||||||
} else {
|
|
||||||
fillW = trackRect.size.width * percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect fillRect(fillX, fillY, fillW, fillH);
|
|
||||||
renderer.fillRect(fillRect, fillColor_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showThumb_) {
|
|
||||||
Rect thumbRect = getThumbRect();
|
|
||||||
Color thumbColor = thumbColor_;
|
|
||||||
|
|
||||||
if (dragging_) {
|
|
||||||
thumbColor = thumbPressedColor_;
|
|
||||||
} else if (hovered_) {
|
|
||||||
thumbColor = thumbHoverColor_;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.fillRect(thumbRect, thumbColor);
|
|
||||||
renderer.drawRect(thumbRect, Colors::White, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textEnabled_ && font_) {
|
|
||||||
std::string text = formatText();
|
|
||||||
Vec2 textSize = font_->measureText(text);
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Size size = getSize();
|
|
||||||
|
|
||||||
Vec2 textPos(
|
|
||||||
pos.x + size.width + 10.0f,
|
|
||||||
pos.y + (size.height - textSize.y) * 0.5f
|
|
||||||
);
|
|
||||||
|
|
||||||
renderer.drawText(*font_, text, textPos, textColor_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标按下事件处理
|
|
||||||
* @param event 鼠标事件
|
|
||||||
* @return 是否处理了事件
|
|
||||||
*/
|
|
||||||
bool Slider::onMousePress(const MouseEvent &event) {
|
|
||||||
if (event.button == MouseButton::Left) {
|
|
||||||
Rect thumbRect = getThumbRect();
|
|
||||||
|
|
||||||
if (thumbRect.containsPoint(Point(event.x, event.y))) {
|
|
||||||
dragging_ = true;
|
|
||||||
if (onDragStart_) {
|
|
||||||
onDragStart_();
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标释放事件处理
|
|
||||||
* @param event 鼠标事件
|
|
||||||
* @return 是否处理了事件
|
|
||||||
*/
|
|
||||||
bool Slider::onMouseRelease(const MouseEvent &event) {
|
|
||||||
if (event.button == MouseButton::Left && dragging_) {
|
|
||||||
dragging_ = false;
|
|
||||||
if (onDragEnd_) {
|
|
||||||
onDragEnd_();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标移动事件处理
|
|
||||||
* @param event 鼠标事件
|
|
||||||
* @return 是否处理了事件
|
|
||||||
*/
|
|
||||||
bool Slider::onMouseMove(const MouseEvent &event) {
|
|
||||||
if (dragging_) {
|
|
||||||
float newValue = positionToValue(vertical_ ? event.y : event.x);
|
|
||||||
setValue(newValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect thumbRect = getThumbRect();
|
|
||||||
bool wasHovered = hovered_;
|
|
||||||
hovered_ = thumbRect.containsPoint(Point(event.x, event.y));
|
|
||||||
|
|
||||||
return hovered_ != wasHovered;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标进入事件处理
|
|
||||||
*/
|
|
||||||
void Slider::onMouseEnter() {
|
|
||||||
hovered_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 鼠标离开事件处理
|
|
||||||
*/
|
|
||||||
void Slider::onMouseLeave() {
|
|
||||||
hovered_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/ui/text.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
Text::Text() { setAnchor(0.0f, 0.0f); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 带文本的构造函数
|
|
||||||
* @param text 初始文本内容
|
|
||||||
*/
|
|
||||||
Text::Text(const std::string &text) : text_(text) {
|
|
||||||
sizeDirty_ = true;
|
|
||||||
setAnchor(0.0f, 0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本内容
|
|
||||||
* @param text 新的文本内容
|
|
||||||
*/
|
|
||||||
void Text::setText(const std::string &text) {
|
|
||||||
text_ = text;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体
|
|
||||||
* @param font 字体图集指针
|
|
||||||
*/
|
|
||||||
void Text::setFont(Ptr<FontAtlas> font) {
|
|
||||||
font_ = font;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置文本颜色
|
|
||||||
* @param color 文本颜色
|
|
||||||
*/
|
|
||||||
void Text::setTextColor(const Color &color) { color_ = color; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体大小
|
|
||||||
* @param size 字体大小
|
|
||||||
*/
|
|
||||||
void Text::setFontSize(int size) {
|
|
||||||
fontSize_ = size;
|
|
||||||
sizeDirty_ = true;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置水平对齐方式
|
|
||||||
* @param align 对齐方式
|
|
||||||
*/
|
|
||||||
void Text::setAlignment(Alignment align) {
|
|
||||||
alignment_ = align;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置垂直对齐方式
|
|
||||||
* @param align 垂直对齐方式
|
|
||||||
*/
|
|
||||||
void Text::setVerticalAlignment(VerticalAlignment align) {
|
|
||||||
verticalAlignment_ = align;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取文本尺寸
|
|
||||||
* @return 文本的宽度和高度
|
|
||||||
*/
|
|
||||||
Vec2 Text::getTextSize() const {
|
|
||||||
updateCache();
|
|
||||||
return cachedSize_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取行高
|
|
||||||
* @return 行高值
|
|
||||||
*/
|
|
||||||
float Text::getLineHeight() const {
|
|
||||||
if (font_) {
|
|
||||||
return font_->getLineHeight();
|
|
||||||
}
|
|
||||||
return static_cast<float>(fontSize_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新缓存
|
|
||||||
*/
|
|
||||||
void Text::updateCache() const {
|
|
||||||
if (!sizeDirty_ || !font_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedSize_ = font_->measureText(text_);
|
|
||||||
sizeDirty_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算绘制位置
|
|
||||||
* @return 绘制位置坐标
|
|
||||||
*/
|
|
||||||
Vec2 Text::calculateDrawPosition() const {
|
|
||||||
Vec2 pos = getPosition();
|
|
||||||
Vec2 textSize = getTextSize();
|
|
||||||
Size widgetSize = getSize();
|
|
||||||
Vec2 anchor = getAnchor();
|
|
||||||
|
|
||||||
float refWidth = widgetSize.empty() ? textSize.x : widgetSize.width;
|
|
||||||
float refHeight = widgetSize.empty() ? textSize.y : widgetSize.height;
|
|
||||||
|
|
||||||
pos.x -= textSize.x * anchor.x;
|
|
||||||
pos.y -= textSize.y * anchor.y;
|
|
||||||
|
|
||||||
if (!widgetSize.empty()) {
|
|
||||||
switch (alignment_) {
|
|
||||||
case Alignment::Center:
|
|
||||||
pos.x += (refWidth - textSize.x) * 0.5f;
|
|
||||||
break;
|
|
||||||
case Alignment::Right:
|
|
||||||
pos.x += refWidth - textSize.x;
|
|
||||||
break;
|
|
||||||
case Alignment::Left:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!widgetSize.empty()) {
|
|
||||||
switch (verticalAlignment_) {
|
|
||||||
case VerticalAlignment::Middle:
|
|
||||||
pos.y += (refHeight - textSize.y) * 0.5f;
|
|
||||||
break;
|
|
||||||
case VerticalAlignment::Bottom:
|
|
||||||
pos.y += refHeight - textSize.y;
|
|
||||||
break;
|
|
||||||
case VerticalAlignment::Top:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建空文本对象
|
|
||||||
* @return 文本对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Text> Text::create() { return makePtr<Text>(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带文本的对象
|
|
||||||
* @param text 文本内容
|
|
||||||
* @return 文本对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Text> Text::create(const std::string &text) { return makePtr<Text>(text); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带文本和字体的对象
|
|
||||||
* @param text 文本内容
|
|
||||||
* @param font 字体图集
|
|
||||||
* @return 文本对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Text> Text::create(const std::string &text, Ptr<FontAtlas> font) {
|
|
||||||
auto t = makePtr<Text>(text);
|
|
||||||
t->setFont(font);
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 格式化创建文本
|
|
||||||
* @param fmt 格式字符串
|
|
||||||
* @param ... 可变参数
|
|
||||||
* @return 文本对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Text> Text::createFormat(const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
char buffer[256];
|
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return makePtr<Text>(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 格式化创建文本(带字体)
|
|
||||||
* @param font 字体图集
|
|
||||||
* @param fmt 格式字符串
|
|
||||||
* @param ... 可变参数
|
|
||||||
* @return 文本对象指针
|
|
||||||
*/
|
|
||||||
Ptr<Text> Text::createFormat(Ptr<FontAtlas> font, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
char buffer[256];
|
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
auto t = makePtr<Text>(buffer);
|
|
||||||
t->setFont(font);
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取边界框
|
|
||||||
* @return 边界矩形
|
|
||||||
*/
|
|
||||||
Rect Text::getBoundingBox() const {
|
|
||||||
if (!font_ || text_.empty()) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCache();
|
|
||||||
Vec2 size = cachedSize_;
|
|
||||||
if (size.x <= 0.0f || size.y <= 0.0f) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 pos = calculateDrawPosition();
|
|
||||||
return Rect(pos.x, pos.y, size.x, size.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制组件
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
*/
|
|
||||||
void Text::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
if (!font_ || text_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 pos = calculateDrawPosition();
|
|
||||||
renderer.drawText(*font_, text_, pos, color_);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
#include <cmath>
|
|
||||||
#include <extra2d/graphics/camera.h>
|
|
||||||
#include <extra2d/scene/scene.h>
|
|
||||||
#include <extra2d/ui/widget.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
Widget::Widget() {
|
|
||||||
setSpatialIndexed(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::setSize(const Size &size) {
|
|
||||||
size_ = size;
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::setSize(float width, float height) {
|
|
||||||
setSize(Size(width, height));
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect Widget::getBoundingBox() const {
|
|
||||||
if (size_.empty()) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto pos = getRenderPosition();
|
|
||||||
auto anchor = getAnchor();
|
|
||||||
auto scale = getScale();
|
|
||||||
|
|
||||||
float w = size_.width * scale.x;
|
|
||||||
float h = size_.height * scale.y;
|
|
||||||
float x0 = pos.x - size_.width * anchor.x * scale.x;
|
|
||||||
float y0 = pos.y - size_.height * anchor.y * scale.y;
|
|
||||||
float x1 = x0 + w;
|
|
||||||
float y1 = y0 + h;
|
|
||||||
|
|
||||||
float l = std::min(x0, x1);
|
|
||||||
float t = std::min(y0, y1);
|
|
||||||
return Rect(l, t, std::abs(w), std::abs(h));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 坐标空间设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void Widget::setCoordinateSpace(CoordinateSpace space) {
|
|
||||||
coordinateSpace_ = space;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::setScreenPosition(const Vec2 &pos) {
|
|
||||||
screenPosition_ = pos;
|
|
||||||
if (coordinateSpace_ == CoordinateSpace::Screen) {
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::setScreenPosition(float x, float y) {
|
|
||||||
setScreenPosition(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::setCameraOffset(const Vec2 &offset) {
|
|
||||||
cameraOffset_ = offset;
|
|
||||||
if (coordinateSpace_ == CoordinateSpace::Camera) {
|
|
||||||
updateSpatialIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Widget::setCameraOffset(float x, float y) {
|
|
||||||
setCameraOffset(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 获取实际渲染位置(根据坐标空间计算)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Vec2 Widget::getRenderPosition() const {
|
|
||||||
switch (coordinateSpace_) {
|
|
||||||
case CoordinateSpace::Screen:
|
|
||||||
// 屏幕空间:直接使用屏幕位置
|
|
||||||
return screenPosition_;
|
|
||||||
|
|
||||||
case CoordinateSpace::Camera: {
|
|
||||||
// 相机空间:相机位置 + 偏移
|
|
||||||
Scene *scene = getScene();
|
|
||||||
if (scene) {
|
|
||||||
Camera *camera = scene->getActiveCamera();
|
|
||||||
if (camera) {
|
|
||||||
return camera->getPosition() + cameraOffset_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果没有场景或相机,使用偏移作为绝对位置
|
|
||||||
return cameraOffset_;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CoordinateSpace::World:
|
|
||||||
default:
|
|
||||||
// 世界空间:使用节点的世界位置
|
|
||||||
return convertToWorldSpace(extra2d::Vec2::Zero());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 重写 onDraw 以处理坐标空间
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void Widget::onDraw(RenderBackend &renderer) {
|
|
||||||
// 根据坐标空间调整渲染
|
|
||||||
if (coordinateSpace_ == CoordinateSpace::Screen) {
|
|
||||||
// 屏幕空间:临时修改位置为屏幕位置,保持锚点不变
|
|
||||||
Vec2 worldPos = getPosition();
|
|
||||||
const_cast<Widget*>(this)->setPosition(screenPosition_);
|
|
||||||
|
|
||||||
// 调用子类的绘制
|
|
||||||
onDrawWidget(renderer);
|
|
||||||
|
|
||||||
// 恢复原始位置
|
|
||||||
const_cast<Widget*>(this)->setPosition(worldPos);
|
|
||||||
} else if (coordinateSpace_ == CoordinateSpace::Camera) {
|
|
||||||
// 相机空间:计算相对于相机的位置
|
|
||||||
Scene *scene = getScene();
|
|
||||||
if (scene) {
|
|
||||||
Camera *camera = scene->getActiveCamera();
|
|
||||||
if (camera) {
|
|
||||||
Vec2 worldPos = getPosition();
|
|
||||||
Vec2 cameraRelativePos = camera->getPosition() + cameraOffset_;
|
|
||||||
const_cast<Widget*>(this)->setPosition(cameraRelativePos);
|
|
||||||
|
|
||||||
// 调用子类的绘制
|
|
||||||
onDrawWidget(renderer);
|
|
||||||
|
|
||||||
// 恢复原始位置
|
|
||||||
const_cast<Widget*>(this)->setPosition(worldPos);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果没有场景或相机,按世界空间处理
|
|
||||||
onDrawWidget(renderer);
|
|
||||||
} else {
|
|
||||||
// 世界空间:正常渲染
|
|
||||||
onDrawWidget(renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -2,13 +2,9 @@
|
||||||
|
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Hello World 场景
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Hello World 场景类
|
* @brief Hello World 场景类
|
||||||
* 显示简单的 "Hello World" 文字
|
* 显示简单的精灵和形状
|
||||||
*/
|
*/
|
||||||
class HelloWorldScene : public Scene {
|
class HelloWorldScene : public Scene {
|
||||||
public:
|
public:
|
||||||
|
|
@ -18,49 +14,28 @@ public:
|
||||||
void onEnter() override {
|
void onEnter() override {
|
||||||
E2D_LOG_INFO("HelloWorldScene::onEnter - 进入场景");
|
E2D_LOG_INFO("HelloWorldScene::onEnter - 进入场景");
|
||||||
|
|
||||||
// 设置背景颜色为深蓝色
|
|
||||||
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
|
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
|
||||||
|
|
||||||
// 加载字体(支持多种字体后备)
|
auto center = ShapeNode::createFilledCircle(
|
||||||
auto &resources = Application::instance().resources();
|
Vec2(640.0f, 360.0f), 50.0f, Color(1.0f, 1.0f, 1.0f, 1.0f), 32);
|
||||||
font_ = resources.loadFont("assets/font.ttf", 48, true);
|
center->setName("center_circle");
|
||||||
|
addChild(center);
|
||||||
|
|
||||||
if (!font_) {
|
auto rect = ShapeNode::createFilledRect(
|
||||||
E2D_LOG_ERROR("字体加载失败,文字渲染将不可用!");
|
Rect(100.0f, 100.0f, 200.0f, 100.0f), Color(1.0f, 0.5f, 0.5f, 1.0f));
|
||||||
return;
|
rect->setName("red_rect");
|
||||||
}
|
addChild(rect);
|
||||||
|
|
||||||
// 创建 "你好世界" 文本组件 - 使用屏幕空间(固定位置,不随相机移动)
|
auto line = ShapeNode::createLine(Vec2(500.0f, 500.0f), Vec2(780.0f, 500.0f),
|
||||||
auto text1 = Text::create("你好世界", font_);
|
Color(0.0f, 1.0f, 1.0f, 1.0f), 3.0f);
|
||||||
text1->setCoordinateSpace(CoordinateSpace::Screen);
|
line->setName("cyan_line");
|
||||||
text1->setScreenPosition(640.0f, 360.0f); // 屏幕中心
|
addChild(line);
|
||||||
text1->setAnchor(0.5f, 0.5f); // 中心锚点,让文字中心对准位置
|
|
||||||
text1->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
|
|
||||||
addChild(text1);
|
|
||||||
|
|
||||||
// 创建提示文本组件 - 使用屏幕空间,固定在屏幕底部
|
auto triangle = ShapeNode::createFilledTriangle(
|
||||||
auto text2 = Text::create("退出按键(START 按钮)", font_);
|
Vec2(900.0f, 200.0f), Vec2(1000.0f, 350.0f), Vec2(800.0f, 350.0f),
|
||||||
text2->setCoordinateSpace(CoordinateSpace::Screen);
|
Color(0.5f, 1.0f, 0.5f, 1.0f));
|
||||||
text2->setScreenPosition(640.0f, 650.0f); // 屏幕底部
|
triangle->setName("green_triangle");
|
||||||
text2->setAnchor(0.5f, 0.5f);
|
addChild(triangle);
|
||||||
text2->setTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f));
|
|
||||||
addChild(text2);
|
|
||||||
|
|
||||||
// 创建相机空间文本 - 跟随相机但保持相对偏移
|
|
||||||
auto text3 = Text::create("相机空间文本", font_);
|
|
||||||
text3->setCoordinateSpace(CoordinateSpace::Camera);
|
|
||||||
text3->setCameraOffset(50.0f, 50.0f); // 相机左上角偏移(屏幕坐标系Y向下)
|
|
||||||
text3->setAnchor(0.0f, 0.0f); // 左上角锚点,文字从指定位置开始显示
|
|
||||||
text3->setTextColor(Color(0.0f, 1.0f, 1.0f, 1.0f));
|
|
||||||
addChild(text3);
|
|
||||||
|
|
||||||
// 创建世界空间文本 - 随相机移动(默认行为)
|
|
||||||
auto text4 = Text::create("世界空间文本", font_);
|
|
||||||
text4->setCoordinateSpace(CoordinateSpace::World);
|
|
||||||
text4->setPosition(100.0f, 100.0f); // 世界坐标
|
|
||||||
text4->setAnchor(0.0f, 0.0f); // 左上角锚点,文字从指定位置开始显示
|
|
||||||
text4->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f));
|
|
||||||
addChild(text4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -70,26 +45,19 @@ public:
|
||||||
void onUpdate(float dt) override {
|
void onUpdate(float dt) override {
|
||||||
Scene::onUpdate(dt);
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
// 检查退出按键
|
|
||||||
auto &input = Application::instance().input();
|
auto &input = Application::instance().input();
|
||||||
|
|
||||||
// 使用手柄 START 按钮退出 (GamepadButton::Start)
|
|
||||||
if (input.isButtonPressed(GamepadButton::Start)) {
|
if (input.isButtonPressed(GamepadButton::Start)) {
|
||||||
E2D_LOG_INFO("退出应用 (START 按钮)");
|
E2D_LOG_INFO("退出应用 (START 按钮)");
|
||||||
Application::instance().quit();
|
Application::instance().quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
Ptr<FontAtlas> font_; // 字体图集
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 程序入口
|
* @brief 程序入口
|
||||||
// ============================================================================
|
*/
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
// 初始化日志系统
|
|
||||||
Logger::init();
|
Logger::init();
|
||||||
Logger::setLevel(LogLevel::Debug);
|
Logger::setLevel(LogLevel::Debug);
|
||||||
|
|
||||||
|
|
@ -97,10 +65,8 @@ int main(int argc, char **argv) {
|
||||||
E2D_LOG_INFO("Easy2D Hello World Demo");
|
E2D_LOG_INFO("Easy2D Hello World Demo");
|
||||||
E2D_LOG_INFO("========================");
|
E2D_LOG_INFO("========================");
|
||||||
|
|
||||||
// 获取应用实例
|
|
||||||
auto &app = Application::instance();
|
auto &app = Application::instance();
|
||||||
|
|
||||||
// 配置应用
|
|
||||||
AppConfig config;
|
AppConfig config;
|
||||||
config.title = "Easy2D - Hello World";
|
config.title = "Easy2D - Hello World";
|
||||||
config.width = 1280;
|
config.width = 1280;
|
||||||
|
|
@ -108,18 +74,15 @@ int main(int argc, char **argv) {
|
||||||
config.vsync = true;
|
config.vsync = true;
|
||||||
config.fpsLimit = 60;
|
config.fpsLimit = 60;
|
||||||
|
|
||||||
// 初始化应用
|
|
||||||
if (!app.init(config)) {
|
if (!app.init(config)) {
|
||||||
E2D_LOG_ERROR("应用初始化失败!");
|
E2D_LOG_ERROR("应用初始化失败!");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 进入 Hello World 场景
|
|
||||||
app.enterScene(makePtr<HelloWorldScene>());
|
app.enterScene(makePtr<HelloWorldScene>());
|
||||||
|
|
||||||
E2D_LOG_INFO("开始主循环...");
|
E2D_LOG_INFO("开始主循环...");
|
||||||
|
|
||||||
// 运行应用
|
|
||||||
app.run();
|
app.run();
|
||||||
|
|
||||||
E2D_LOG_INFO("应用结束");
|
E2D_LOG_INFO("应用结束");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue