refactor: 移除空间索引系统和UI组件

移除不再使用的空间索引系统(包括四叉树、空间哈希和空间管理器)以及相关的UI组件(如按钮、复选框、滑动条等)。这些功能已被更现代的解决方案取代,移除它们可以简化代码库并减少维护负担。

同时更新了示例代码,移除了对已删除组件的依赖,改为使用基本的形状节点展示功能。
This commit is contained in:
ChestnutYueyue 2026-02-14 17:43:07 +08:00
parent 313a56bf72
commit 55c66e5038
31 changed files with 25 additions and 5462 deletions

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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())

View File

@ -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 {

View File

@ -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; }

View File

@ -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

View File

@ -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_backO(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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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("应用结束");